From 3c438e20eddf5e6fd992a9eacb95678dff7e5a3d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 28 Apr 2009 08:52:21 +0000 Subject: [PATCH 001/970] Main trunk (i.e. latest development version) of itop SVN:trunk[53] From b756db09925f2d10b790a9da4b67a5d6bdac2e3e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 28 Apr 2009 09:03:12 +0000 Subject: [PATCH 002/970] Moved under "trunk" to be able to track releases under "tags" SVN:trunk[55] --- .../userrights/userrightsmatrix.class.inc.php | 385 ++ .../userrights/userrightsnull.class.inc.php | 64 + application/ajaxwebpage.class.inc.php | 145 + application/application.inc.php | 12 + application/applicationcontext.class.inc.php | 81 + application/audit.category.class.inc.php | 45 + application/audit.rule.class.inc.php | 52 + application/cmdbabstract.class.inc.php | 647 +++ application/csvpage.class.inc.php | 35 + application/dialogstack.class.inc.php | 252 + application/displayblock.class.inc.php | 686 +++ application/iotask.class.inc.php | 54 + application/itopwebpage.class.inc.php | 414 ++ application/itopwizardwebpage.class.inc.php | 32 + application/loginwebpage.class.inc.php | 117 + application/menunode.class.inc.php | 219 + application/nicewebpage.class.inc.php | 76 + application/startup.inc.php | 7 + application/template.class.inc.php | 224 + application/templates/audit_category.html | 12 + application/ui.linkswidget.class.inc.php | 301 + application/uiwizard.class.inc.php | 257 + application/usercontext.class.inc.php | 106 + application/utils.inc.php | 80 + application/webpage.class.inc.php | 289 + application/wizardhelper.class.inc.php | 204 + application/xmlpage.class.inc.php | 36 + business/ChangeMgmt.php | 284 + business/Changes-04-Sep-2007.php | 32 + business/KEDB.php | 165 + business/ServiceMgmt.business.php | 261 + business/business_itopbegins.class.inc.php | 261 + business/business_test.class.inc.php | 366 ++ business/data.samples.inc.php | 4845 +++++++++++++++++ business/incident.business.php | 367 ++ business/itop.business.class.inc.php | 1629 ++++++ business/templates/Circuits.html | 12 + business/templates/application.html | 19 + business/templates/change.html | 17 + business/templates/contract.html | 18 + business/templates/default.html | 8 + business/templates/document.html | 13 + business/templates/group.html | 16 + business/templates/interface.html | 14 + business/templates/knownError.html | 11 + business/templates/location.html | 25 + business/templates/network.device.html | 25 + business/templates/pc.html | 27 + business/templates/person.html | 18 + business/templates/server.html | 30 + business/templates/service.html | 13 + business/templates/software.html | 13 + business/templates/team.html | 21 + business/templates/ticket.html | 21 + business/test_farm.class.inc.php | 272 + config-dist.php | 38 + config-test-farm.php | 35 + config-test-mymodel.php | 36 + core/MyHelpers.class.inc.php | 491 ++ core/archive.class.inc.php | 323 ++ core/attributedef.class.inc.php | 989 ++++ core/bulkchange.class.inc.php | 439 ++ core/cmdbchange.class.inc.php | 44 + core/cmdbchangeop.class.inc.php | 167 + core/cmdbobject.class.inc.php | 432 ++ core/cmdbsource.class.inc.php | 420 ++ core/config.class.inc.php | 272 + core/coreexception.class.inc.php | 63 + core/csvparser.class.inc.php | 192 + core/data.generator.class.inc.php | 362 ++ core/dbobject.class.php | 831 +++ core/dbobjectsearch.class.php | 973 ++++ core/dbobjectset.class.php | 250 + core/expression.class.inc.php | 485 ++ core/filterdef.class.inc.php | 296 + core/metamodel.class.php | 2643 +++++++++ core/oql/build.cmd | 3 + core/oql/oql-lexer.php | 522 ++ core/oql/oql-lexer.plex | 305 ++ core/oql/oql-parser.php | 1669 ++++++ core/oql/oql-parser.y | 250 + core/oql/oqlexception.class.inc.php | 78 + core/oql/oqlinterpreter.class.inc.php | 59 + core/oql/oqlquery.class.inc.php | 168 + core/sqlquery.class.inc.php | 413 ++ core/stimulus.class.inc.php | 59 + core/test.class.inc.php | 504 ++ core/userrights.class.inc.php | 211 + core/valuesetdef.class.inc.php | 238 + css/blue_green.css | 222 + css/date.picker.css | 117 + css/default.css | 165 + css/jqModal.css | 40 + css/jquery.autocomplete.css | 46 + css/jquery.tabs-ie.css | 15 + css/jquery.tabs.css | 76 + css/jquery.treeview.css | 47 + css/light-grey.css | 633 +++ images/Resize of Resize of erwanIncidents.jpg | Bin 0 -> 559 bytes images/Resize of Resize of erwanNetwork.jpg | Bin 0 -> 764 bytes images/Resize of Resize of erwanPC.jpg | Bin 0 -> 866 bytes images/Resize of erwanBuidling.jpg | Bin 0 -> 976 bytes images/Resize of erwanBuilding.jpg | Bin 0 -> 853 bytes images/Resize of erwanDocument.jpg | Bin 0 -> 883 bytes images/Resize of erwanIncidents.jpg | Bin 0 -> 859 bytes images/Resize of erwanNetwork.jpg | Bin 0 -> 1317 bytes images/Resize of erwanPC.jpg | Bin 0 -> 823 bytes images/Thumbs.db | Bin 0 -> 187392 bytes images/WanLinks.jpg | Bin 0 -> 3908 bytes images/actions_left.png | Bin 0 -> 205 bytes images/actions_right.png | Bin 0 -> 283 bytes images/bandeau2.gif | Bin 0 -> 1222 bytes images/bandeau3.gif | Bin 0 -> 132 bytes images/blue-corner.gif | Bin 0 -> 309 bytes images/bomb.jpg | Bin 0 -> 4145 bytes images/bomb.png | Bin 0 -> 8543 bytes images/calendar.png | Bin 0 -> 1091 bytes images/charts.swf | Bin 0 -> 29627 bytes images/charts_library/arno.swf | Bin 0 -> 6541 bytes images/charts_library/arst.swf | Bin 0 -> 6799 bytes images/charts_library/brfl.swf | Bin 0 -> 6848 bytes images/charts_library/brno.swf | Bin 0 -> 7004 bytes images/charts_library/brst.swf | Bin 0 -> 6854 bytes images/charts_library/cl3d.swf | Bin 0 -> 10125 bytes images/charts_library/clfl.swf | Bin 0 -> 6613 bytes images/charts_library/clno.swf | Bin 0 -> 6632 bytes images/charts_library/clp3.swf | Bin 0 -> 10106 bytes images/charts_library/cls3.swf | Bin 0 -> 10165 bytes images/charts_library/clst.swf | Bin 0 -> 6655 bytes images/charts_library/cnno.swf | Bin 0 -> 6801 bytes images/charts_library/lnno.swf | Bin 0 -> 7239 bytes images/charts_library/mxno.swf | Bin 0 -> 8736 bytes images/charts_library/pi3d.swf | Bin 0 -> 8592 bytes images/charts_library/pino.swf | Bin 0 -> 6152 bytes images/charts_library/pono.swf | Bin 0 -> 8178 bytes images/charts_library/scno.swf | Bin 0 -> 7209 bytes images/clean-mid.png | Bin 0 -> 2406 bytes images/clean.png | Bin 0 -> 8714 bytes images/connect_to_network.png | Bin 0 -> 15254 bytes images/contacts.gif | Bin 0 -> 1057 bytes images/contacts_big.gif | Bin 0 -> 4586 bytes images/database exi.png | Bin 0 -> 14728 bytes images/database.png | Bin 0 -> 13514 bytes images/device.gif | Bin 0 -> 1070 bytes images/devices_big.gif | Bin 0 -> 4142 bytes images/documents.jpg | Bin 0 -> 7871 bytes images/drawer-handle.gif | Bin 0 -> 318 bytes images/erwanBuilding.jpg | Bin 0 -> 848 bytes images/erwanContracts.jpg | Bin 0 -> 1830 bytes images/erwanContracts2.jpg | Bin 0 -> 3298 bytes images/erwanContracts3.jpg | Bin 0 -> 2609 bytes images/erwanDocument.jpg | Bin 0 -> 887 bytes images/erwanIncidents.jpg | Bin 0 -> 861 bytes images/erwanMobilePhones.jpg | Bin 0 -> 2569 bytes images/erwanNetwork.jpg | Bin 0 -> 766 bytes images/erwanPC.jpg | Bin 0 -> 828 bytes images/erwanServices.jpg | Bin 0 -> 2839 bytes images/erwanTask.jpg | Bin 0 -> 2914 bytes images/erwanTools.jpg | Bin 0 -> 2247 bytes images/folder_documents.png | Bin 0 -> 11783 bytes images/green-corner.png | Bin 0 -> 287 bytes images/green-header.gif | Bin 0 -> 120 bytes images/green-square.gif | Bin 0 -> 56 bytes images/grey-header.gif | Bin 0 -> 122 bytes images/hgrabber2.gif | Bin 0 -> 217 bytes images/hgrabber2_active.gif | Bin 0 -> 220 bytes images/home.gif | Bin 0 -> 988 bytes images/iTop-icon.ico | Bin 0 -> 1150 bytes images/iTop-icon.png | Bin 0 -> 608 bytes images/iTop.gif | Bin 0 -> 3681 bytes images/iTop2.gif | Bin 0 -> 3765 bytes images/imageChange.gif | Bin 0 -> 4227 bytes images/imageChange.png | Bin 0 -> 2107 bytes images/indicator.gif | Bin 0 -> 1553 bytes images/indicator_arrows.gif | Bin 0 -> 729 bytes images/info-mid.png | Bin 0 -> 3165 bytes images/info.png | Bin 0 -> 13918 bytes images/kservices-big.png | Bin 0 -> 19890 bytes images/laptop_pcmcia.png | Bin 0 -> 15250 bytes images/left-border.gif | Bin 0 -> 83 bytes images/lifecycle/bizChangeTicket.png | Bin 0 -> 39688 bytes images/lifecycle/bizContract.png | Bin 0 -> 36691 bytes images/lifecycle/bizIncidentTicket.png | Bin 0 -> 36813 bytes images/lifecycle/bizServer.png | Bin 0 -> 78782 bytes images/location.gif | Bin 0 -> 998 bytes images/magnifier.gif | Bin 0 -> 534 bytes images/messagebox_warning-mid.png | Bin 0 -> 3168 bytes images/messagebox_warning.png | Bin 0 -> 7104 bytes images/mini-arrow-green-open.gif | Bin 0 -> 53 bytes images/mini-arrow-green.gif | Bin 0 -> 54 bytes images/minus.gif | Bin 0 -> 139 bytes images/network-server.png | Bin 0 -> 9234 bytes images/open-flash-chart.swf | Bin 0 -> 263109 bytes images/orange-header.gif | Bin 0 -> 122 bytes images/plus.gif | Bin 0 -> 142 bytes images/red-arrow.gif | Bin 0 -> 56 bytes images/red-header.gif | Bin 0 -> 122 bytes images/settings.gif | Bin 0 -> 256 bytes images/software.jpg | Bin 0 -> 2186 bytes images/starthere.png | Bin 0 -> 14272 bytes images/std_view.gif | Bin 0 -> 571 bytes images/stop-mid.png | Bin 0 -> 2616 bytes images/stop.png | Bin 0 -> 9678 bytes images/tab-topleft.gif | Bin 0 -> 46 bytes images/tab.png | Bin 0 -> 593 bytes images/tar.png | Bin 0 -> 20427 bytes images/tv-collapsable-last.gif | Bin 0 -> 85 bytes images/tv-collapsable.gif | Bin 0 -> 781 bytes images/tv-expandable-last.gif | Bin 0 -> 89 bytes images/tv-expandable.gif | Bin 0 -> 787 bytes images/tv-item-last.gif | Bin 0 -> 65 bytes images/tv-item.gif | Bin 0 -> 750 bytes images/users2-big.png | Bin 0 -> 11703 bytes images/vgrabber2.gif | Bin 0 -> 381 bytes images/vgrabber2_active.gif | Bin 0 -> 258 bytes images/vsplitter-grey.gif | Bin 0 -> 59 bytes images/wan-mid.gif | Bin 0 -> 547 bytes images/wan.gif | Bin 0 -> 262 bytes images/wizActiveStepLeft.gif | Bin 0 -> 362 bytes images/wizActiveStepRight.gif | Bin 0 -> 501 bytes images/wizArrow.gif | Bin 0 -> 155 bytes images/wizStepLeft.gif | Bin 0 -> 217 bytes images/wizStepRight.gif | Bin 0 -> 425 bytes images/zoom.gif | Bin 0 -> 275 bytes index.php | 32 + js/LinksWidget.js | 112 + js/date.js | 467 ++ js/dimensions.js | 317 ++ js/forms-json-utils.js | 106 + js/hovertip.js | 458 ++ js/jqModal.js | 67 + js/jquery.autocomplete.js | 513 ++ js/jquery.bgiframe.js | 49 + js/jquery.blockUI.js | 312 ++ js/jquery.date.picker.js | 1056 ++++ js/jquery.dimensions.js | 308 ++ js/jquery.history_remote.pack.js | 1 + js/jquery.jdMenu.js | 435 ++ js/jquery.latest.js | 3549 ++++++++++++ js/jquery.pack.js | 1 + js/jquery.tablehover.js | 426 ++ js/jquery.tablesorter.min.js | 2 + js/jquery.tabs-ie.css | 15 + js/jquery.tabs.pack.js | 1 + js/jquery.treeview.js | 223 + js/json.js | 473 ++ js/json/json2.js | 461 ++ js/splitter.js | 180 + js/swfobject.js | 5 + js/themes/dark/dark.css | 11 + js/themes/dark/dark.form.css | 192 + js/themes/dark/dark.form.png | Bin 0 -> 12308 bytes js/themes/dark/dark.menu.css | 9 + js/themes/dark/dark.modal.css | 93 + js/themes/dark/dark.tabs.css | 73 + js/themes/dark/dark.tree.css | 61 + js/themes/flora/flora.accordion.css | 12 + js/themes/flora/flora.all.css | 9 + js/themes/flora/flora.calendar.css | 167 + js/themes/flora/flora.css | 2 + js/themes/flora/flora.dialog.css | 86 + js/themes/flora/flora.menu.css | 8 + js/themes/flora/flora.resizable.css | 20 + js/themes/flora/flora.shadow.css | 33 + js/themes/flora/flora.slider.css | 8 + js/themes/flora/flora.tablesorter.css | 40 + js/themes/flora/flora.tabs.css | 80 + js/themes/flora/i/Thumbs.db | Bin 0 -> 39424 bytes js/themes/flora/i/accordion-left-act.png | Bin 0 -> 249 bytes js/themes/flora/i/accordion-left-over.png | Bin 0 -> 174 bytes js/themes/flora/i/accordion-left.png | Bin 0 -> 174 bytes js/themes/flora/i/accordion-middle-act.png | Bin 0 -> 148 bytes js/themes/flora/i/accordion-middle-over.png | Bin 0 -> 122 bytes js/themes/flora/i/accordion-middle.png | Bin 0 -> 122 bytes js/themes/flora/i/accordion-right-act.png | Bin 0 -> 245 bytes js/themes/flora/i/accordion-right-over.png | Bin 0 -> 177 bytes js/themes/flora/i/accordion-right.png | Bin 0 -> 177 bytes js/themes/flora/i/asc.gif | Bin 0 -> 54 bytes js/themes/flora/i/bg.gif | Bin 0 -> 64 bytes js/themes/flora/i/desc.gif | Bin 0 -> 54 bytes js/themes/flora/i/dialog-e.gif | Bin 0 -> 440 bytes js/themes/flora/i/dialog-n.gif | Bin 0 -> 569 bytes js/themes/flora/i/dialog-ne.gif | Bin 0 -> 353 bytes js/themes/flora/i/dialog-nw.gif | Bin 0 -> 353 bytes js/themes/flora/i/dialog-s.gif | Bin 0 -> 434 bytes js/themes/flora/i/dialog-se.gif | Bin 0 -> 175 bytes js/themes/flora/i/dialog-sw.gif | Bin 0 -> 175 bytes js/themes/flora/i/dialog-title.gif | Bin 0 -> 238 bytes .../flora/i/dialog-titlebar-close-hover.png | Bin 0 -> 2927 bytes js/themes/flora/i/dialog-titlebar-close.png | Bin 0 -> 2880 bytes js/themes/flora/i/dialog-w.gif | Bin 0 -> 437 bytes js/themes/flora/i/menu-submenu.gif | Bin 0 -> 93 bytes js/themes/flora/i/resizable-e.gif | Bin 0 -> 338 bytes js/themes/flora/i/resizable-n.gif | Bin 0 -> 341 bytes js/themes/flora/i/resizable-ne.gif | Bin 0 -> 124 bytes js/themes/flora/i/resizable-nw.gif | Bin 0 -> 91 bytes js/themes/flora/i/resizable-s.gif | Bin 0 -> 341 bytes js/themes/flora/i/resizable-se.gif | Bin 0 -> 120 bytes js/themes/flora/i/resizable-sw.gif | Bin 0 -> 175 bytes js/themes/flora/i/resizable-w.gif | Bin 0 -> 339 bytes js/themes/flora/i/shadow.png | Bin 0 -> 3977 bytes js/themes/flora/i/slider-bg-1.png | Bin 0 -> 204 bytes js/themes/flora/i/slider-bg-2.png | Bin 0 -> 326 bytes js/themes/flora/i/slider-handle.gif | Bin 0 -> 176 bytes js/themes/flora/i/tabs.gif | Bin 0 -> 377 bytes js/themes/light/light.css | 11 + js/themes/light/light.form.css | 45 + js/themes/light/light.menu.css | 8 + js/themes/light/light.modal.css | 91 + js/themes/light/light.tabs.css | 71 + js/themes/light/light.tree.css | 61 + js/ui.accordion.js | 246 + js/ui.calendar.js | 871 +++ js/ui.dialog.js | 133 + js/ui.draggable.ext.js | 259 + js/ui.draggable.js | 182 + js/ui.droppable.ext.js | 25 + js/ui.droppable.js | 201 + js/ui.magnifier.js | 191 + js/ui.mouse.js | 253 + js/ui.resizable.js | 304 ++ js/ui.selectable.js | 116 + js/ui.shadow.js | 87 + js/ui.slider.js | 294 + js/ui.sortable.js | 262 + js/ui.tablesorter.js | 808 +++ js/ui.tabs.js | 445 ++ js/wizard.utils.js | 163 + js/wizardhelper.js | 108 + pages/ITopConsultant.php | 467 ++ pages/UI.php | 660 +++ pages/UniversalSearch.php | 142 + pages/advanced_search.php | 310 ++ pages/ajax.php | 40 + pages/ajax.render.php | 218 + pages/audit.php | 149 + pages/csvimport.php | 527 ++ pages/data_generator.php | 321 ++ pages/db_importer.php | 171 + pages/graphviz.php | 82 + pages/incident.php | 757 +++ pages/index.php | 704 +++ pages/opensearch.xml | 8 + pages/opensearch.xml.php | 16 + pages/php-ofc-library/JSON.php | 806 +++ pages/php-ofc-library/README.txt | 16 + pages/php-ofc-library/json_format.php | 86 + pages/php-ofc-library/ofc_area_base.php | 66 + pages/php-ofc-library/ofc_area_hollow.php | 10 + pages/php-ofc-library/ofc_area_line.php | 10 + pages/php-ofc-library/ofc_bar.php | 34 + pages/php-ofc-library/ofc_bar_3d.php | 31 + pages/php-ofc-library/ofc_bar_base.php | 41 + pages/php-ofc-library/ofc_bar_filled.php | 39 + pages/php-ofc-library/ofc_bar_glass.php | 33 + pages/php-ofc-library/ofc_bar_sketch.php | 23 + pages/php-ofc-library/ofc_bar_stack.php | 50 + pages/php-ofc-library/ofc_hbar.php | 64 + pages/php-ofc-library/ofc_line.php | 9 + pages/php-ofc-library/ofc_line_base.php | 70 + pages/php-ofc-library/ofc_line_dot.php | 33 + pages/php-ofc-library/ofc_line_hollow.php | 9 + pages/php-ofc-library/ofc_line_style.php | 11 + pages/php-ofc-library/ofc_pie.php | 109 + pages/php-ofc-library/ofc_radar_axis.php | 47 + .../php-ofc-library/ofc_radar_axis_labels.php | 15 + .../ofc_radar_spoke_labels.php | 15 + pages/php-ofc-library/ofc_scatter.php | 42 + pages/php-ofc-library/ofc_scatter_line.php | 27 + pages/php-ofc-library/ofc_shape.php | 25 + pages/php-ofc-library/ofc_title.php | 15 + pages/php-ofc-library/ofc_tooltip.php | 51 + pages/php-ofc-library/ofc_upload_image.php | 61 + pages/php-ofc-library/ofc_x_axis.php | 77 + pages/php-ofc-library/ofc_x_axis_label.php | 42 + pages/php-ofc-library/ofc_x_axis_labels.php | 34 + pages/php-ofc-library/ofc_x_legend.php | 15 + pages/php-ofc-library/ofc_y_axis.php | 17 + pages/php-ofc-library/ofc_y_axis_base.php | 56 + pages/php-ofc-library/ofc_y_axis_right.php | 6 + pages/php-ofc-library/ofc_y_legend.php | 15 + .../open-flash-chart-object.php | 109 + pages/php-ofc-library/open-flash-chart.php | 138 + pages/schema.php | 475 ++ pages/sibusql.php | 57 + pages/test.php | 126 + pages/testlist.inc.php | 1132 ++++ setup/ajax.dataloader.php | 64 + setup/data/01.organizations.xml | 15 + setup/data/02.locations.xml | 22 + setup/data/03.persons.xml | 33 + setup/data/04.teams.xml | 19 + setup/data/05.pcs.xml | 1865 +++++++ setup/data/06.servers.xml | 4063 ++++++++++++++ setup/data/07.applications.xml | 33 + setup/data/08.nw-devices.xml | 71 + setup/data/09.links_contacts.xml | 23 + setup/data/10.workgroups.xml | 17 + setup/data/11.incidents.xml | 132 + setup/data/12.relatedtickets.xml | 8 + setup/data/13.infratickets.xml | 33 + setup/data/14.contacttickets.xml | 13 + setup/data/15.changetickets.xml | 85 + setup/data/16.infrachangetickets.xml | 23 + setup/data/17.contactchangetickets.xml | 23 + setup/data/18.contracts.xml | 23 + setup/data/19.infracontracts.xml | 9 + setup/data/20.contactcontracts.xml | 13 + setup/data/21.auditcategories.xml | 8 + setup/data/22.auditrules.xml | 17 + setup/data/export.cmd | 27 + setup/export_menus.cmd | 6 + setup/index.php | 536 ++ setup/jquery.progression.js | 193 + setup/menus.xml | 610 +++ setup/orange-progress.gif | Bin 0 -> 40302 bytes setup/setup.js | 138 + setup/setuppage.class.inc.php | 176 + setup/xmldataloader.class.inc.php | 282 + toolkit.php | 53 + webservices/export.php | 73 + webservices/import.php | 168 + 422 files changed, 62557 insertions(+) create mode 100644 addons/userrights/userrightsmatrix.class.inc.php create mode 100644 addons/userrights/userrightsnull.class.inc.php create mode 100644 application/ajaxwebpage.class.inc.php create mode 100644 application/application.inc.php create mode 100644 application/applicationcontext.class.inc.php create mode 100644 application/audit.category.class.inc.php create mode 100644 application/audit.rule.class.inc.php create mode 100644 application/cmdbabstract.class.inc.php create mode 100644 application/csvpage.class.inc.php create mode 100644 application/dialogstack.class.inc.php create mode 100644 application/displayblock.class.inc.php create mode 100644 application/iotask.class.inc.php create mode 100644 application/itopwebpage.class.inc.php create mode 100644 application/itopwizardwebpage.class.inc.php create mode 100644 application/loginwebpage.class.inc.php create mode 100644 application/menunode.class.inc.php create mode 100644 application/nicewebpage.class.inc.php create mode 100644 application/startup.inc.php create mode 100644 application/template.class.inc.php create mode 100644 application/templates/audit_category.html create mode 100644 application/ui.linkswidget.class.inc.php create mode 100644 application/uiwizard.class.inc.php create mode 100644 application/usercontext.class.inc.php create mode 100644 application/utils.inc.php create mode 100644 application/webpage.class.inc.php create mode 100644 application/wizardhelper.class.inc.php create mode 100644 application/xmlpage.class.inc.php create mode 100644 business/ChangeMgmt.php create mode 100644 business/Changes-04-Sep-2007.php create mode 100644 business/KEDB.php create mode 100644 business/ServiceMgmt.business.php create mode 100644 business/business_itopbegins.class.inc.php create mode 100644 business/business_test.class.inc.php create mode 100644 business/data.samples.inc.php create mode 100644 business/incident.business.php create mode 100644 business/itop.business.class.inc.php create mode 100644 business/templates/Circuits.html create mode 100644 business/templates/application.html create mode 100644 business/templates/change.html create mode 100644 business/templates/contract.html create mode 100644 business/templates/default.html create mode 100644 business/templates/document.html create mode 100644 business/templates/group.html create mode 100644 business/templates/interface.html create mode 100644 business/templates/knownError.html create mode 100644 business/templates/location.html create mode 100644 business/templates/network.device.html create mode 100644 business/templates/pc.html create mode 100644 business/templates/person.html create mode 100644 business/templates/server.html create mode 100644 business/templates/service.html create mode 100644 business/templates/software.html create mode 100644 business/templates/team.html create mode 100644 business/templates/ticket.html create mode 100644 business/test_farm.class.inc.php create mode 100644 config-dist.php create mode 100644 config-test-farm.php create mode 100644 config-test-mymodel.php create mode 100644 core/MyHelpers.class.inc.php create mode 100644 core/archive.class.inc.php create mode 100644 core/attributedef.class.inc.php create mode 100644 core/bulkchange.class.inc.php create mode 100644 core/cmdbchange.class.inc.php create mode 100644 core/cmdbchangeop.class.inc.php create mode 100644 core/cmdbobject.class.inc.php create mode 100644 core/cmdbsource.class.inc.php create mode 100644 core/config.class.inc.php create mode 100644 core/coreexception.class.inc.php create mode 100644 core/csvparser.class.inc.php create mode 100644 core/data.generator.class.inc.php create mode 100644 core/dbobject.class.php create mode 100644 core/dbobjectsearch.class.php create mode 100644 core/dbobjectset.class.php create mode 100644 core/expression.class.inc.php create mode 100644 core/filterdef.class.inc.php create mode 100644 core/metamodel.class.php create mode 100644 core/oql/build.cmd create mode 100644 core/oql/oql-lexer.php create mode 100644 core/oql/oql-lexer.plex create mode 100644 core/oql/oql-parser.php create mode 100644 core/oql/oql-parser.y create mode 100644 core/oql/oqlexception.class.inc.php create mode 100644 core/oql/oqlinterpreter.class.inc.php create mode 100644 core/oql/oqlquery.class.inc.php create mode 100644 core/sqlquery.class.inc.php create mode 100644 core/stimulus.class.inc.php create mode 100644 core/test.class.inc.php create mode 100644 core/userrights.class.inc.php create mode 100644 core/valuesetdef.class.inc.php create mode 100644 css/blue_green.css create mode 100644 css/date.picker.css create mode 100644 css/default.css create mode 100644 css/jqModal.css create mode 100644 css/jquery.autocomplete.css create mode 100644 css/jquery.tabs-ie.css create mode 100644 css/jquery.tabs.css create mode 100644 css/jquery.treeview.css create mode 100644 css/light-grey.css create mode 100644 images/Resize of Resize of erwanIncidents.jpg create mode 100644 images/Resize of Resize of erwanNetwork.jpg create mode 100644 images/Resize of Resize of erwanPC.jpg create mode 100644 images/Resize of erwanBuidling.jpg create mode 100644 images/Resize of erwanBuilding.jpg create mode 100644 images/Resize of erwanDocument.jpg create mode 100644 images/Resize of erwanIncidents.jpg create mode 100644 images/Resize of erwanNetwork.jpg create mode 100644 images/Resize of erwanPC.jpg create mode 100644 images/Thumbs.db create mode 100644 images/WanLinks.jpg create mode 100644 images/actions_left.png create mode 100644 images/actions_right.png create mode 100644 images/bandeau2.gif create mode 100644 images/bandeau3.gif create mode 100644 images/blue-corner.gif create mode 100644 images/bomb.jpg create mode 100644 images/bomb.png create mode 100644 images/calendar.png create mode 100644 images/charts.swf create mode 100644 images/charts_library/arno.swf create mode 100644 images/charts_library/arst.swf create mode 100644 images/charts_library/brfl.swf create mode 100644 images/charts_library/brno.swf create mode 100644 images/charts_library/brst.swf create mode 100644 images/charts_library/cl3d.swf create mode 100644 images/charts_library/clfl.swf create mode 100644 images/charts_library/clno.swf create mode 100644 images/charts_library/clp3.swf create mode 100644 images/charts_library/cls3.swf create mode 100644 images/charts_library/clst.swf create mode 100644 images/charts_library/cnno.swf create mode 100644 images/charts_library/lnno.swf create mode 100644 images/charts_library/mxno.swf create mode 100644 images/charts_library/pi3d.swf create mode 100644 images/charts_library/pino.swf create mode 100644 images/charts_library/pono.swf create mode 100644 images/charts_library/scno.swf create mode 100644 images/clean-mid.png create mode 100644 images/clean.png create mode 100644 images/connect_to_network.png create mode 100644 images/contacts.gif create mode 100644 images/contacts_big.gif create mode 100644 images/database exi.png create mode 100644 images/database.png create mode 100644 images/device.gif create mode 100644 images/devices_big.gif create mode 100644 images/documents.jpg create mode 100644 images/drawer-handle.gif create mode 100644 images/erwanBuilding.jpg create mode 100644 images/erwanContracts.jpg create mode 100644 images/erwanContracts2.jpg create mode 100644 images/erwanContracts3.jpg create mode 100644 images/erwanDocument.jpg create mode 100644 images/erwanIncidents.jpg create mode 100644 images/erwanMobilePhones.jpg create mode 100644 images/erwanNetwork.jpg create mode 100644 images/erwanPC.jpg create mode 100644 images/erwanServices.jpg create mode 100644 images/erwanTask.jpg create mode 100644 images/erwanTools.jpg create mode 100644 images/folder_documents.png create mode 100644 images/green-corner.png create mode 100644 images/green-header.gif create mode 100644 images/green-square.gif create mode 100644 images/grey-header.gif create mode 100644 images/hgrabber2.gif create mode 100644 images/hgrabber2_active.gif create mode 100644 images/home.gif create mode 100644 images/iTop-icon.ico create mode 100644 images/iTop-icon.png create mode 100644 images/iTop.gif create mode 100644 images/iTop2.gif create mode 100644 images/imageChange.gif create mode 100644 images/imageChange.png create mode 100644 images/indicator.gif create mode 100644 images/indicator_arrows.gif create mode 100644 images/info-mid.png create mode 100644 images/info.png create mode 100644 images/kservices-big.png create mode 100644 images/laptop_pcmcia.png create mode 100644 images/left-border.gif create mode 100644 images/lifecycle/bizChangeTicket.png create mode 100644 images/lifecycle/bizContract.png create mode 100644 images/lifecycle/bizIncidentTicket.png create mode 100644 images/lifecycle/bizServer.png create mode 100644 images/location.gif create mode 100644 images/magnifier.gif create mode 100644 images/messagebox_warning-mid.png create mode 100644 images/messagebox_warning.png create mode 100644 images/mini-arrow-green-open.gif create mode 100644 images/mini-arrow-green.gif create mode 100644 images/minus.gif create mode 100644 images/network-server.png create mode 100644 images/open-flash-chart.swf create mode 100644 images/orange-header.gif create mode 100644 images/plus.gif create mode 100644 images/red-arrow.gif create mode 100644 images/red-header.gif create mode 100644 images/settings.gif create mode 100644 images/software.jpg create mode 100644 images/starthere.png create mode 100644 images/std_view.gif create mode 100644 images/stop-mid.png create mode 100644 images/stop.png create mode 100644 images/tab-topleft.gif create mode 100644 images/tab.png create mode 100644 images/tar.png create mode 100644 images/tv-collapsable-last.gif create mode 100644 images/tv-collapsable.gif create mode 100644 images/tv-expandable-last.gif create mode 100644 images/tv-expandable.gif create mode 100644 images/tv-item-last.gif create mode 100644 images/tv-item.gif create mode 100644 images/users2-big.png create mode 100644 images/vgrabber2.gif create mode 100644 images/vgrabber2_active.gif create mode 100644 images/vsplitter-grey.gif create mode 100644 images/wan-mid.gif create mode 100644 images/wan.gif create mode 100644 images/wizActiveStepLeft.gif create mode 100644 images/wizActiveStepRight.gif create mode 100644 images/wizArrow.gif create mode 100644 images/wizStepLeft.gif create mode 100644 images/wizStepRight.gif create mode 100644 images/zoom.gif create mode 100644 index.php create mode 100644 js/LinksWidget.js create mode 100644 js/date.js create mode 100644 js/dimensions.js create mode 100644 js/forms-json-utils.js create mode 100644 js/hovertip.js create mode 100644 js/jqModal.js create mode 100644 js/jquery.autocomplete.js create mode 100644 js/jquery.bgiframe.js create mode 100644 js/jquery.blockUI.js create mode 100644 js/jquery.date.picker.js create mode 100644 js/jquery.dimensions.js create mode 100644 js/jquery.history_remote.pack.js create mode 100644 js/jquery.jdMenu.js create mode 100644 js/jquery.latest.js create mode 100644 js/jquery.pack.js create mode 100644 js/jquery.tablehover.js create mode 100644 js/jquery.tablesorter.min.js create mode 100644 js/jquery.tabs-ie.css create mode 100644 js/jquery.tabs.pack.js create mode 100644 js/jquery.treeview.js create mode 100644 js/json.js create mode 100644 js/json/json2.js create mode 100644 js/splitter.js create mode 100644 js/swfobject.js create mode 100644 js/themes/dark/dark.css create mode 100644 js/themes/dark/dark.form.css create mode 100644 js/themes/dark/dark.form.png create mode 100644 js/themes/dark/dark.menu.css create mode 100644 js/themes/dark/dark.modal.css create mode 100644 js/themes/dark/dark.tabs.css create mode 100644 js/themes/dark/dark.tree.css create mode 100644 js/themes/flora/flora.accordion.css create mode 100644 js/themes/flora/flora.all.css create mode 100644 js/themes/flora/flora.calendar.css create mode 100644 js/themes/flora/flora.css create mode 100644 js/themes/flora/flora.dialog.css create mode 100644 js/themes/flora/flora.menu.css create mode 100644 js/themes/flora/flora.resizable.css create mode 100644 js/themes/flora/flora.shadow.css create mode 100644 js/themes/flora/flora.slider.css create mode 100644 js/themes/flora/flora.tablesorter.css create mode 100644 js/themes/flora/flora.tabs.css create mode 100644 js/themes/flora/i/Thumbs.db create mode 100644 js/themes/flora/i/accordion-left-act.png create mode 100644 js/themes/flora/i/accordion-left-over.png create mode 100644 js/themes/flora/i/accordion-left.png create mode 100644 js/themes/flora/i/accordion-middle-act.png create mode 100644 js/themes/flora/i/accordion-middle-over.png create mode 100644 js/themes/flora/i/accordion-middle.png create mode 100644 js/themes/flora/i/accordion-right-act.png create mode 100644 js/themes/flora/i/accordion-right-over.png create mode 100644 js/themes/flora/i/accordion-right.png create mode 100644 js/themes/flora/i/asc.gif create mode 100644 js/themes/flora/i/bg.gif create mode 100644 js/themes/flora/i/desc.gif create mode 100644 js/themes/flora/i/dialog-e.gif create mode 100644 js/themes/flora/i/dialog-n.gif create mode 100644 js/themes/flora/i/dialog-ne.gif create mode 100644 js/themes/flora/i/dialog-nw.gif create mode 100644 js/themes/flora/i/dialog-s.gif create mode 100644 js/themes/flora/i/dialog-se.gif create mode 100644 js/themes/flora/i/dialog-sw.gif create mode 100644 js/themes/flora/i/dialog-title.gif create mode 100644 js/themes/flora/i/dialog-titlebar-close-hover.png create mode 100644 js/themes/flora/i/dialog-titlebar-close.png create mode 100644 js/themes/flora/i/dialog-w.gif create mode 100644 js/themes/flora/i/menu-submenu.gif create mode 100644 js/themes/flora/i/resizable-e.gif create mode 100644 js/themes/flora/i/resizable-n.gif create mode 100644 js/themes/flora/i/resizable-ne.gif create mode 100644 js/themes/flora/i/resizable-nw.gif create mode 100644 js/themes/flora/i/resizable-s.gif create mode 100644 js/themes/flora/i/resizable-se.gif create mode 100644 js/themes/flora/i/resizable-sw.gif create mode 100644 js/themes/flora/i/resizable-w.gif create mode 100644 js/themes/flora/i/shadow.png create mode 100644 js/themes/flora/i/slider-bg-1.png create mode 100644 js/themes/flora/i/slider-bg-2.png create mode 100644 js/themes/flora/i/slider-handle.gif create mode 100644 js/themes/flora/i/tabs.gif create mode 100644 js/themes/light/light.css create mode 100644 js/themes/light/light.form.css create mode 100644 js/themes/light/light.menu.css create mode 100644 js/themes/light/light.modal.css create mode 100644 js/themes/light/light.tabs.css create mode 100644 js/themes/light/light.tree.css create mode 100644 js/ui.accordion.js create mode 100644 js/ui.calendar.js create mode 100644 js/ui.dialog.js create mode 100644 js/ui.draggable.ext.js create mode 100644 js/ui.draggable.js create mode 100644 js/ui.droppable.ext.js create mode 100644 js/ui.droppable.js create mode 100644 js/ui.magnifier.js create mode 100644 js/ui.mouse.js create mode 100644 js/ui.resizable.js create mode 100644 js/ui.selectable.js create mode 100644 js/ui.shadow.js create mode 100644 js/ui.slider.js create mode 100644 js/ui.sortable.js create mode 100644 js/ui.tablesorter.js create mode 100644 js/ui.tabs.js create mode 100644 js/wizard.utils.js create mode 100644 js/wizardhelper.js create mode 100644 pages/ITopConsultant.php create mode 100644 pages/UI.php create mode 100644 pages/UniversalSearch.php create mode 100644 pages/advanced_search.php create mode 100644 pages/ajax.php create mode 100644 pages/ajax.render.php create mode 100644 pages/audit.php create mode 100644 pages/csvimport.php create mode 100644 pages/data_generator.php create mode 100644 pages/db_importer.php create mode 100644 pages/graphviz.php create mode 100644 pages/incident.php create mode 100644 pages/index.php create mode 100644 pages/opensearch.xml create mode 100644 pages/opensearch.xml.php create mode 100644 pages/php-ofc-library/JSON.php create mode 100644 pages/php-ofc-library/README.txt create mode 100644 pages/php-ofc-library/json_format.php create mode 100644 pages/php-ofc-library/ofc_area_base.php create mode 100644 pages/php-ofc-library/ofc_area_hollow.php create mode 100644 pages/php-ofc-library/ofc_area_line.php create mode 100644 pages/php-ofc-library/ofc_bar.php create mode 100644 pages/php-ofc-library/ofc_bar_3d.php create mode 100644 pages/php-ofc-library/ofc_bar_base.php create mode 100644 pages/php-ofc-library/ofc_bar_filled.php create mode 100644 pages/php-ofc-library/ofc_bar_glass.php create mode 100644 pages/php-ofc-library/ofc_bar_sketch.php create mode 100644 pages/php-ofc-library/ofc_bar_stack.php create mode 100644 pages/php-ofc-library/ofc_hbar.php create mode 100644 pages/php-ofc-library/ofc_line.php create mode 100644 pages/php-ofc-library/ofc_line_base.php create mode 100644 pages/php-ofc-library/ofc_line_dot.php create mode 100644 pages/php-ofc-library/ofc_line_hollow.php create mode 100644 pages/php-ofc-library/ofc_line_style.php create mode 100644 pages/php-ofc-library/ofc_pie.php create mode 100644 pages/php-ofc-library/ofc_radar_axis.php create mode 100644 pages/php-ofc-library/ofc_radar_axis_labels.php create mode 100644 pages/php-ofc-library/ofc_radar_spoke_labels.php create mode 100644 pages/php-ofc-library/ofc_scatter.php create mode 100644 pages/php-ofc-library/ofc_scatter_line.php create mode 100644 pages/php-ofc-library/ofc_shape.php create mode 100644 pages/php-ofc-library/ofc_title.php create mode 100644 pages/php-ofc-library/ofc_tooltip.php create mode 100644 pages/php-ofc-library/ofc_upload_image.php create mode 100644 pages/php-ofc-library/ofc_x_axis.php create mode 100644 pages/php-ofc-library/ofc_x_axis_label.php create mode 100644 pages/php-ofc-library/ofc_x_axis_labels.php create mode 100644 pages/php-ofc-library/ofc_x_legend.php create mode 100644 pages/php-ofc-library/ofc_y_axis.php create mode 100644 pages/php-ofc-library/ofc_y_axis_base.php create mode 100644 pages/php-ofc-library/ofc_y_axis_right.php create mode 100644 pages/php-ofc-library/ofc_y_legend.php create mode 100644 pages/php-ofc-library/open-flash-chart-object.php create mode 100644 pages/php-ofc-library/open-flash-chart.php create mode 100644 pages/schema.php create mode 100644 pages/sibusql.php create mode 100644 pages/test.php create mode 100644 pages/testlist.inc.php create mode 100644 setup/ajax.dataloader.php create mode 100644 setup/data/01.organizations.xml create mode 100644 setup/data/02.locations.xml create mode 100644 setup/data/03.persons.xml create mode 100644 setup/data/04.teams.xml create mode 100644 setup/data/05.pcs.xml create mode 100644 setup/data/06.servers.xml create mode 100644 setup/data/07.applications.xml create mode 100644 setup/data/08.nw-devices.xml create mode 100644 setup/data/09.links_contacts.xml create mode 100644 setup/data/10.workgroups.xml create mode 100644 setup/data/11.incidents.xml create mode 100644 setup/data/12.relatedtickets.xml create mode 100644 setup/data/13.infratickets.xml create mode 100644 setup/data/14.contacttickets.xml create mode 100644 setup/data/15.changetickets.xml create mode 100644 setup/data/16.infrachangetickets.xml create mode 100644 setup/data/17.contactchangetickets.xml create mode 100644 setup/data/18.contracts.xml create mode 100644 setup/data/19.infracontracts.xml create mode 100644 setup/data/20.contactcontracts.xml create mode 100644 setup/data/21.auditcategories.xml create mode 100644 setup/data/22.auditrules.xml create mode 100644 setup/data/export.cmd create mode 100644 setup/export_menus.cmd create mode 100644 setup/index.php create mode 100644 setup/jquery.progression.js create mode 100644 setup/menus.xml create mode 100644 setup/orange-progress.gif create mode 100644 setup/setup.js create mode 100644 setup/setuppage.class.inc.php create mode 100644 setup/xmldataloader.class.inc.php create mode 100644 toolkit.php create mode 100644 webservices/export.php create mode 100644 webservices/import.php diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php new file mode 100644 index 0000000000..de90baa473 --- /dev/null +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -0,0 +1,385 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + + +class UserRightsMatrixUsers extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "user", + "description" => "users and credentials", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "login", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_ur_matrixusers", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeInteger("userid", array("label"=>"User id", "description"=>"User identifier (depends on the business model)", "allowed_values"=>null, "sql"=>"userid", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("password", array("label"=>"password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("userid"); + MetaModel::Init_AddFilterFromAttribute("login"); + } +} + +class UserRightsMatrixClassGrant extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "class_permission", + "description" => "permissions on classes", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_ur_matrixclasses", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("userid"); + MetaModel::Init_AddFilterFromAttribute("login"); + MetaModel::Init_AddFilterFromAttribute("class"); + MetaModel::Init_AddFilterFromAttribute("action"); + } +} + +class UserRightsMatrixClassStimulusGrant extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "stimulus_permission", + "description" => "permissions on stimilus in the life cycle of the object", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_ur_matrixclassesstimulus", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("userid"); + MetaModel::Init_AddFilterFromAttribute("login"); + MetaModel::Init_AddFilterFromAttribute("class"); + MetaModel::Init_AddFilterFromAttribute("stimulus"); + } +} + +class UserRightsMatrixAttributeGrant extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "attribute_permission", + "description" => "permissions at the attributes level", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_ur_matrixattributes", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("userid"); + MetaModel::Init_AddFilterFromAttribute("login"); + MetaModel::Init_AddFilterFromAttribute("class"); + MetaModel::Init_AddFilterFromAttribute("attcode"); + MetaModel::Init_AddFilterFromAttribute("action"); + } +} + + + + +class UserRightsMatrix extends UserRightsAddOnAPI +{ + static public $m_aActionCodes = array( + UR_ACTION_READ => 'read', + UR_ACTION_MODIFY => 'modify', + UR_ACTION_DELETE => 'delete', + UR_ACTION_BULK_READ => 'bulk read', + UR_ACTION_BULK_MODIFY => 'bulk modify', + UR_ACTION_BULK_DELETE => 'bulk delete', + ); + + // Installation: create the very first user + public function CreateAdministrator($sAdminUser, $sAdminPwd) + { + // Maybe we should check that no other user with userid == 0 exists + $oUser = new UserRightsMatrixUsers(); + $oUser->Set('login', $sAdminUser); + $oUser->Set('password', $sAdminPwd); + $oUser->Set('userid', 1); // one is for root ! + $iUserId = $oUser->DBInsertNoReload(); + $this->SetupUser($iUserId, true); + return true; + } + + public function Setup() + { + // Users must be added manually + // This procedure will then update the matrix when a new user is found or a new class/attribute appears + $oUserSet = new DBObjectSet(DBObjectSearch::FromSibuSQL("UserRightsMatrixUsers")); + while ($oUser = $oUserSet->Fetch()) + { + $this->SetupUser($oUser->GetKey()); + } + return true; + } + + protected function SetupUser($iUserId, $bNewUser = false) + { + foreach(array('bizmodel', 'application', 'gui', 'core/cmdb') as $sCategory) + { + foreach (MetaModel::GetClasses($sCategory) as $sClass) + { + foreach (self::$m_aActionCodes as $iActionCode => $sAction) + { + if ($bNewUser) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassGrant WHERE class = '$sClass' AND action = '$sAction' AND userid = $iUserId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + // Create a new entry + $oMyClassGrant = MetaModel::NewObject("UserRightsMatrixClassGrant"); + $oMyClassGrant->Set("userid", $iUserId); + $oMyClassGrant->Set("class", $sClass); + $oMyClassGrant->Set("action", $sAction); + $oMyClassGrant->Set("permission", "yes"); + $iId = $oMyClassGrant->DBInsertNoReload(); + } + } + foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + if ($bNewUser) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND userid = $iUserId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + // Create a new entry + $oMyClassGrant = MetaModel::NewObject("UserRightsMatrixClassStimulusGrant"); + $oMyClassGrant->Set("userid", $iUserId); + $oMyClassGrant->Set("class", $sClass); + $oMyClassGrant->Set("stimulus", $sStimulusCode); + $oMyClassGrant->Set("permission", "yes"); + $iId = $oMyClassGrant->DBInsertNoReload(); + } + } + foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) + { + if ($bNewUser) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixAttributeGrant WHERE class = '$sClass' AND attcode = '$sAttCode' AND userid = $iUserId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + foreach (array('read', 'modify') as $sAction) + { + // Create a new entry + $oMyAttGrant = MetaModel::NewObject("UserRightsMatrixAttributeGrant"); + $oMyAttGrant->Set("userid", $iUserId); + $oMyAttGrant->Set("class", $sClass); + $oMyAttGrant->Set("attcode", $sAttCode); + $oMyAttGrant->Set("action", $sAction); + $oMyAttGrant->Set("permission", "yes"); + $iId = $oMyAttGrant->DBInsertNoReload(); + } + } + } + } + } + } + + + public function Init() + { + // Could be loaded in a shared memory (?) + return true; + } + + public function CheckCredentials($sUserName, $sPassword) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixUsers WHERE login = '$sUserName'")); + if ($oSet->Count() < 1) + { + // todo: throw an exception? + return false; + } + + $oLogin = $oSet->Fetch(); + if ($oLogin->Get('password') == $sPassword) + { + return true; + } + // todo: throw an exception? + return false; + } + + public function GetFilter($sUserName, $sClass) + { + $oNullFilter = new DBObjectSearch($sClass); + return $oNullFilter; + } + + public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) + { + if (!array_key_exists($iActionCode, self::$m_aActionCodes)) + { + return UR_ALLOWED_NO; + } + $sAction = self::$m_aActionCodes[$iActionCode]; + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'")); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + switch ($oGrantRecord->Get('permission')) + { + case 'yes': + $iRetCode = UR_ALLOWED_YES; + break; + case 'no': + default: + $iRetCode = UR_ALLOWED_NO; + break; + } + return $iRetCode; + } + + public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + { + if (!array_key_exists($iActionCode, self::$m_aActionCodes)) + { + return UR_ALLOWED_NO; + } + $sAction = self::$m_aActionCodes[$iActionCode]; + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixAttributeGrant WHERE UserRightsMatrixAttributeGrant.class = '$sClass' AND UserRightsMatrixAttributeGrant.attcode = '$sAttCode' AND UserRightsMatrixAttributeGrant.action = '$sAction' AND UserRightsMatrixAttributeGrant.login = '$sUserName'")); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + switch ($oGrantRecord->Get('permission')) + { + case 'yes': + $iRetCode = UR_ALLOWED_YES; + break; + case 'no': + default: + $iRetCode = UR_ALLOWED_NO; + break; + } + return $iRetCode; + } + + public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'")); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + switch ($oGrantRecord->Get('permission')) + { + case 'yes': + $iRetCode = UR_ALLOWED_YES; + break; + case 'no': + default: + $iRetCode = UR_ALLOWED_NO; + break; + } + return $iRetCode; + } +} + +UserRights::SelectModule('UserRightsMatrix'); + +?> diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php new file mode 100644 index 0000000000..c5480f7eac --- /dev/null +++ b/addons/userrights/userrightsnull.class.inc.php @@ -0,0 +1,64 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + + +class UserRightsNull extends UserRightsAddOnAPI +{ + // Installation: create the very first user + public function CreateAdministrator($sAdminUser, $sAdminPwd) + { + return true; + } + + public function Setup() + { + return true; + } + + public function Init() + { + return true; + } + + public function CheckCredentials($sUserName, $sPassword) + { + return true; + } + + public function GetFilter($sUserName, $sClass) + { + $oNullFilter = new DBObjectSearch($sClass); + return $oNullFilter; + } + + public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) + { + return UR_ALLOWED_YES; + } + + public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) + { + return UR_ALLOWED_YES; + } + + public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + { + return UR_ALLOWED_YES; + } +} + +UserRights::SelectModule('UserRightsNull'); + +?> diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php new file mode 100644 index 0000000000..3d061f69a5 --- /dev/null +++ b/application/ajaxwebpage.class.inc.php @@ -0,0 +1,145 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + */ + +class ajax_page extends web_page +{ + /** + * Jquery style ready script + * @var Hash + */ + protected $m_sReadyScript; + + /** + * constructor for the web page + * @param string $s_title Not used + */ + function __construct($s_title) + { + parent::__construct($s_title); + $this->m_sReadyScript = ""; + } + + + /** + * Echoes the content of the whole page + * @return void + */ + public function output() + { + foreach($this->a_headers as $s_header) + { + header($s_header); + } + $s_captured_output = ob_get_contents(); + ob_end_clean(); + echo trim($this->s_content); + if (!empty($this->m_sReadyScript)) + { + echo "\n"; + } + if (trim($s_captured_output) != "") + { + echo $s_captured_output; + } + } + + /** + * Adds a paragraph with a smaller font into the page + * NOT implemented (i.e does nothing) + * @param string $sText Content of the (small) paragraph + * @return void + */ + public function small_p($sText) + { + } + + /** + * Adds a tabular content to the web page + * @param Hash $aConfig Configuration of the table: hash array of 'column_id' => 'Column Label' + * @param Hash $aData Hash array. Data to display in the table: each row is made of 'column_id' => Data. A column 'pkey' is expected for each row + * @param Hash $aParams Hash array. Extra parameters for the table. Entry 'class' holds the class of the objects listed in the table + * @return void + */ + public function table($aConfig, $aData, $aParams = array()) + { + // WARNING WARNING WARNING + // This whole function is actually a copy paste from iTopWebPage::table + $oAppContext = new ApplicationContext(); + + static $iNbTables = 0; + $iNbTables++; + $sHtml = ""; + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aConfig as $sName=>$aDef) + { + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aData as $aRow) + { + if (false) //(isset($aParams['preview']) && $aParams['preview']) + { + $sHtml .= "\n"; + } + else if (isset($aRow['key'])) + { + $sHtml .= "\n"; + } + else + { + $sHtml .= "\n"; + } + foreach($aConfig as $sName=>$aVoid) + { + if ($sName != 'key') + { + $sValue = empty($aRow[$sName]) ? ' ' : $aRow[$sName]; + $sHtml .= "\n"; + } + else + { + $sUIPage = cmdbAbstractObject::ComputeUIPage($aParams['class']); + $sHtml .= "\n"; + } + } + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "
".$aDef['label']."
$sValueGetForLink()."\">
\n"; + if (isset($aParams['preview']) && $aParams['preview']) + { + $sHtml .= "
Preview Pane
"; + } + $this->add($sHtml); + } + /** + * Adds a script to be executed when the DOM is ready (typical JQuery use) + * NOT implemented in this version of the class. + * @return void + */ + public function add_ready_script($sScript) + { + // Does nothing in ajax rendered content.. for now... + // Maybe we should add this as a simple '); // TO DO: add support for $aExtraParams in asynchronous/Ajax mode + } + } + + public function GetDisplay(web_page $oPage, $sId, $aExtraParams = array()) + { + $sHtml = ''; + $aExtraParams = array_merge($aExtraParams, $this->m_aParams); + if (!$this->m_bAsynchronous) + { + // render now + $sHtml .= "
\n"; + $sHtml .= $this->GetRenderContent($oPage, $aExtraParams); + $sHtml .= "
\n"; + } + else + { + // render it as an Ajax (asynchronous) call + $sFilter = $this->m_oFilter->serialize(); + $sHtml .= "
\n"; + $sHtml .= $oPage->GetP(" Loading..."); + $sHtml .= "
\n"; + $sHtml .= ' + '; // TO DO: add support for $aExtraParams in asynchronous/Ajax mode + } + return $sHtml; + } + + public function RenderContent(web_page $oPage, $aExtraParams = array()) + { + $oPage->add($this->GetRenderContent($oPage, $aExtraParams)); + } + + public function GetRenderContent(web_page $oPage, $aExtraParams = array()) + { + $sHtml = ''; + // Add the extra params into the filter if they make sense for such a filter + $bDoSearch = utils::ReadParam('dosearch', false); + if ($this->m_oSet == null) + { + $aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass())); + foreach($aFilterCodes as $sFilterCode) + { + $sExternalFilterValue = utils::ReadParam($sFilterCode, ''); + if (isset($aExtraParams[$sFilterCode])) + { + $this->m_oFilter->AddCondition($sFilterCode, $aExtraParams[$sFilterCode]); // Use the default 'loose' operator + } + else if ($bDoSearch && $sExternalFilterValue != "") + { + $this->m_oFilter->AddCondition($sFilterCode, $sExternalFilterValue); // Use the default 'loose' operator + } + } + $this->m_oSet = new CMDBObjectSet($this->m_oFilter); + } + switch($this->m_sStyle) + { + case 'count': + if (isset($aExtraParams['group_by'])) + { + $sGroupByField = $aExtraParams['group_by']; + $aGroupBy = array(); + while($oObj = $this->m_oSet->Fetch()) + { + $sValue = $oObj->Get($sGroupByField); + $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; + } + $sFilter = urlencode($this->m_oFilter->serialize()); + $aData = array(); + foreach($aGroupBy as $sValue => $iCount) + { + $aData[] = array ( 'group' => $sValue, + 'value' => "$iCount"); // TO DO: add the context information + } + $sHtml .= $oPage->GetTable(array('group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''), 'value' => array('label'=>'Count', 'description' => 'Number of elements')), $aData); + } + else + { + // Simply count the number of elements in the set + $iCount = $oSet->Count(); + $sHtml .= $oPage->GetP("$iCount objects matching the criteria."); + } + + break; + + case 'list': + $bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false; + if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) + { + if (!$bDashboardMode) + { + $sHtml .= $oPage->GetP($this->m_oSet->Count()." object(s)."); + } + $sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : ''; + $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $sLinkage, !$bDashboardMode /* bDisplayMenu */); + } + else + { + $sHtml .= $oPage->GetP("No object to display."); + $sClass = $this->m_oFilter->GetClass(); + if (!$bDashboardMode) + { + if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES) + { + $sHtml .= $oPage->GetP("Click here to create a new ".Metamodel::GetName($sClass)."\n"); + } + } + } + break; + + case 'details': + if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + { + while($oObj = $this->m_oSet->Fetch()) + { + $sHtml .= $oObj->GetDetails($oPage); + } + } + break; + + case 'bare_details': + if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + { + while($oObj = $this->m_oSet->Fetch()) + { + $sHtml .= $oObj->GetBareDetails($oPage); + } + } + break; + + case 'csv': + if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + { + $sHtml .= "\n"; + } + break; + + case 'modify': + if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES) + { + while($oObj = $this->m_oSet->Fetch()) + { + $sHtml .= $oObj->GetModifyForm($oPage); + } + } + break; + + case 'search': + $iSearchSectionId = 1; + $sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; + $sHtml .= "
\n"; + $sHtml .= "

Search form for ".Metamodel::GetName($this->m_oSet->GetClass())."

\n"; + $oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); + $sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams); + $sHtml .= "
\n"; + $sHtml .= "
\n"; + $sHtml .= "
Search
\n"; + break; + + case 'pie_chart': + $sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : ''; + $sFilter = $this->m_oFilter->ToOQL(); + $sHtml .= " + + + + + + + + + "; + break; + + case 'pie_chart_ajax': + if (isset($aExtraParams['group_by'])) + { + $sGroupByField = $aExtraParams['group_by']; + $aGroupBy = array(); + while($oObj = $this->m_oSet->Fetch()) + { + $sValue = $oObj->Get($sGroupByField); + $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; + } + $sFilter = urlencode($this->m_oFilter->serialize()); + $aData = array(); + $sHtml .= "\n"; + $sHtml .= "3d pie\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aGroupBy as $sValue => $void) + { + $sHtml .= "$sValue\n"; + } + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aGroupBy as $void => $iCount) + { + $sHtml .= "$iCount\n"; + } + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= " + + + + ||||||||||||||||||||||||||||||||||||||||||||||| + + + + + + ddaa41 + 88dd11 + 4e62dd + ff8811 + 4d4d4d + 5a4b6e + 1188ff + + "; + $sHtml .= "\n"; + } + else + { + // Simply count the number of elements in the set + $iCount = $oSet->Count(); + $sHtml .= "\n\n"; + } + break; + + case 'open_flash_chart': + static $iChartCounter = 0; + $sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie'; + $sTitle = isset($aExtraParams['chart_title']) ? $aExtraParams['chart_title'] : ''; + $sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : ''; + $sFilter = $this->m_oFilter->ToOQL(); + $sHtml .= "\n"; + $sHtml .= "
Here goes the chart
\n"; + $iChartCounter++; + break; + + case 'open_flash_chart_ajax': + include './php-ofc-library/open-flash-chart.php'; + $sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie'; + + $oChart = new open_flash_chart(); + switch($sChartType) + { + case 'bars': + $oChartElement = new bar_glass(); + + if (isset($aExtraParams['group_by'])) + { + $sGroupByField = $aExtraParams['group_by']; + $aGroupBy = array(); + while($oObj = $this->m_oSet->Fetch()) + { + $sValue = $oObj->Get($sGroupByField); + $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; + } + $sFilter = urlencode($this->m_oFilter->serialize()); + $aData = array(); + $aLabels = array(); + foreach($aGroupBy as $sValue => $iValue) + { + $aData[] = $iValue; + $aLabels[] = $sValue; + } + $maxValue = max($aData); + $oYAxis = new y_axis(); + $aMagicValues = array(1,2,5,10); + $iMultiplier = 1; + $index = 0; + $iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier; + while($maxValue > $iTop) + { + $index++; + $iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier; + if (($index % count($aMagicValues)) == 0) + { + $iMultiplier = $iMultiplier * 10; + } + } + //echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n"; + $oYAxis->set_range(0, $iTop, $iMultiplier); + $oChart->set_y_axis( $oYAxis ); + + $oChartElement->set_values( $aData ); + $oXAxis = new x_axis(); + $oXLabels = new x_axis_labels(); + // set them vertical + $oXLabels->set_vertical(); + // set the label text + $oXLabels->set_labels($aLabels); + // Add the X Axis Labels to the X Axis + $oXAxis->set_labels( $oXLabels ); + $oChart->set_x_axis( $oXAxis ); + } + break; + + case 'pie': + default: + $oChartElement = new pie(); + $oChartElement->set_start_angle( 35 ); + $oChartElement->set_animate( true ); + $oChartElement->set_tooltip( '#label# - #val# (#percent#)' ); + if (isset($aExtraParams['group_by'])) + { + $sGroupByField = $aExtraParams['group_by']; + $aGroupBy = array(); + while($oObj = $this->m_oSet->Fetch()) + { + $sValue = $oObj->Get($sGroupByField); + $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; + } + $sFilter = urlencode($this->m_oFilter->serialize()); + $aData = array(); + foreach($aGroupBy as $sValue => $iValue) + { + $aData[] = new pie_value($iValue, $sValue); + } + + + $oChartElement->set_values( $aData ); + $oChart->x_axis = null; + } + } + if (isset($aExtraParams['chart_title'])) //@@ BUG: not passed via ajax !!! + { + $oTitle = new title( $aExtraParams['chart_title'] ); + $oChart->set_title( $oTitle ); + } + $oChart->set_bg_colour('#FFFFFF'); + $oChart->add_element( $oChartElement ); + + $sHtml = $oChart->toPrettyString(); + break; + + default: + // Unsupported style, do nothing. + $sHtml .= "Error: unsupported style of block: ".$this->m_sStyle; + } + return $sHtml; + } +} + +/** + * Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects + * + * Each block is actually rendered as a
tag that can be rendered synchronously + * or as a piece of Javascript/JQuery/Ajax that will get its content from another page (ajax.render.php). + * The list of cmdbObjects to be displayed into the block is defined by a filter + * Right now the type of display is either: list, count or details + * - list produces a table listing the objects + * - count produces a paragraphs with a sentence saying 'cont' objects found + * - details display (as table) the details of each object found (best if only one) + */ +class HistoryBlock extends DisplayBlock +{ + public function GetRenderContent(web_page $oPage, $aExtraParams = array()) + { + $sHtml = ''; + // Add the extra params into the filter if they make sense for such a filter + $aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass())); + foreach($aFilterCodes as $sFilterCode) + { + if (isset($aExtraParams[$sFilterCode])) + { + $this->m_oFilter->AddCondition($sFilterCode, $aExtraParams[$sFilterCode]); // Use the default 'loose' operator + } + } + $oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false)); + $sHtml .= "\n"; + switch($this->m_sStyle) + { + case 'toggle': + $oLatestChangeOp = $oSet->Fetch(); + if (is_object($oLatestChangeOp)) + { + global $oContext; // User Context.. should be statis instead of global... + // There is one change in the list... only when the object has been created ! + $sDate = $oLatestChangeOp->GetAsHTML('date'); + $oChange = $oContext->GetObject('CMDBChange', $oLatestChangeOp->Get('change')); + $sUserInfo = $oChange->GetAsHTML('userinfo'); + $oSet->Load(); // Reset the pointer to the beginning of the set: there should be a better way to do this... + $sHtml .= $oPage->GetStartCollapsibleSection("Last modified on $sDate by $sUserInfo."); + $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $oSet); + $sHtml .= $oPage->GetEndCollapsibleSection(); + } + break; + + default: + $sHtml .= parent::GetRenderContent($oPage, $aExtraParams); + } + return $sHtml; + } +} + +class MenuBlock extends DisplayBlock +{ + public function GetRenderContent(web_page $oPage, $aExtraParams = array()) + { + $sHtml = ''; + $oAppContext = new ApplicationContext(); + $sContext = $oAppContext->GetForLink(); + $sClass = $this->m_oFilter->GetClass(); + $oSet = new CMDBObjectSet($this->m_oFilter); + $sFilter = $this->m_oFilter->serialize(); + $aActions = array(); + $sUIPage = cmdbAbstractObject::ComputeUIPage($sClass); + switch($oSet->Count()) + { + case 0: + // No object in the set, the only possible action is "new" + $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New', 'url' => "../page/$sUIPage?operation=new&class=$sClass&$sContext"); } + break; + + case 1: + $oObj = $oSet->Fetch(); + $id = $oObj->GetKey(); + $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); + $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); + $bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); + $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); + // Just one object in the set, possible actions are "new / clone / modify and delete" + if (isset($aExtraParams['linkage'])) + { + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "#"); } + if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "#"); } + if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } + if ($bIsModifyAllowed | $bIsDeleteAllowed) { $aActions[] = array ('label' => 'Manage Links...', 'url' => "#"); } + } + else + { + $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("http://localhost:81/pages/UI.php?operation=search&filter=$sFilter&$sContext")); + $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); + $aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } + if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Delete', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } + } + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = Metamodel::EnumStimuli($sClass); + foreach($aTransitions as $sStimulusCode => $aTransitionDef) + { + $iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet); + switch($iActionAllowed) + { + case UR_ALLOWED_YES: + $aActions[] = array('label' => $aStimuli[$sStimulusCode]->Get('label'), 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext"); + break; + + case UR_ALLOWED_DEPENDS: + $aActions[] = array('label' => $aStimuli[$sStimulusCode]->Get('label').' (*)', 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext"); + break; + + default: + // Do nothing + } + } + //print_r($aTransitions); + break; + + default: + // Check rights + // New / Modify + $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); + $bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); + $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); + if (isset($aExtraParams['linkage'])) + { + $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "#"); } + if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "#"); } + if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } + if ($bIsModifyAllowed | $bIsDeleteAllowed) { $aActions[] = array ('label' => 'Manage Links...', 'url' => "#"); } + } + else + { + // many objects in the set, possible actions are: new / modify all / delete all + $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("http://localhost:81/pages/UI.php?operation=search&filter=$sFilter&$sContext")); + $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); + $aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } + if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "../pages/$sUIPage?operation=modify_all&filter=$sFilter&$sContext"); } + if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Delete All', 'url' => "../pages/$sUIPage?operation=delete_all&filter=$sFilter&$sContext"); } + } + } + $sHtml .= "
    \n
  • Actions\n
      \n"; + foreach ($aActions as $aAction) + { + $sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : ""; + $sHtml .= "
    • {$aAction['label']}
    • \n
    • \n"; + } + $sHtml .= "
    \n
  • \n
\n"; + $oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n"); + return $sHtml; + } + +} +?> diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php new file mode 100644 index 0000000000..32eb33400f --- /dev/null +++ b/application/iotask.class.inc.php @@ -0,0 +1,54 @@ + "application", + "name" => "IOTask", + "description" => "Input / Output Task for synchronizing information with external data sources", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_iotask", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Task Name", "description"=>"Short name for this task", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Task Description", "description"=>"Long description for this task", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("category", array("label"=>"Category", "description"=>"Type of task", "allowed_values"=>new ValueSetEnum('Input, Ouput'), "sql"=>"category", "default_value"=>"Input", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("label"=>"Source Type", "description"=>"Type of data source", "allowed_values"=>new ValueSetEnum('File, Database, Web Service'), "sql"=>"source_type", "default_value"=>"File", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype", array("label"=>"Source Subtype", "description"=>"Subtype of Data Source", "allowed_values"=>new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql"=>"source_subtype", "default_value"=>"CSV", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("source_path", array("label"=>"Source Path", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"source_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "allowed_values"=>new ValueSetEnum('bizOrganization, bizContact, bizTeam, bizPerson, bizLocation, bizServer, bizPC, bizNetworkDevice, bizInterface, bizService, bizContract, bizInfraGroup, bizIncidentTicket, bizSoftware, bizApplication, bizPatch, bizWorkgroup, lnkContactRealObject, lnkInterfaces, bizInfraGrouping' ), "sql"=>"objects_class", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("label"=>"Test Mode", "description"=>"If set to 'Yes' the modifications are not applied", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"test_mode", "default_value"=>'No', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("label"=>"Verbose Mode", "description"=>"If set to 'Yes' extra debug information is added to the log", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"verbose_mode", "default_value" => 'No', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("options", array("label"=>"Options", "description"=>"Reconciliation options", "allowed_values"=>new ValueSetEnum('Full, Update Only, Creation Only'), "sql"=>"options", "default_value"=> 'Full', "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("description"); + MetaModel::Init_AddFilterFromAttribute("category"); + MetaModel::Init_AddFilterFromAttribute("source_type"); + MetaModel::Init_AddFilterFromAttribute("source_subtype"); + MetaModel::Init_AddFilterFromAttribute("objects_class"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype', 'source_path' , 'options', 'test_mode', 'verbose_mode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype', 'options')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'category', 'objects_class', 'source_type', 'source_subtype')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype')); // Criteria of the advanced search form + } +} +?> diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php new file mode 100644 index 0000000000..de1cd14ca5 --- /dev/null +++ b/application/itopwebpage.class.inc.php @@ -0,0 +1,414 @@ +m_sCurrentTabContainer = ''; + $this->m_sCurrentTab = ''; + $this->m_aTabs = array(); + $this->m_sMenu = ""; + $oAppContext = new ApplicationContext(); + $sExtraParams = $oAppContext->GetForLink(); + $this->add_header("Content-type: text/html; charset=utf-8"); + $this->add_header("Cache-control: no-cache"); + $this->m_currentOrganization = $currentOrganization; + $this->add_linked_script("../js/jquery.dimensions.js"); + $this->add_linked_script("../js/splitter.js"); + $this->add_linked_script("../js/jquery.tablehover.js"); + $this->add_linked_script("../js/jquery.treeview.js"); + $this->add_linked_script("../js/jquery.autocomplete.js"); + $this->add_linked_script("../js/jquery.bgiframe.js"); + $this->add_linked_script("../js/jquery.jdMenu.js"); + $this->add_linked_script("../js/date.js"); + $this->add_linked_script("../js/jquery.date.picker.js"); + $this->add_linked_script("../js/jquery.tablesorter.min.js"); + //$this->add_linked_script("../js/jquery-ui-personalized-1.5.3.js"); + $this->add_linked_script("../js/swfobject.js"); + $this->add_linked_stylesheet("../css/jquery.treeview.css"); + $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); + $this->add_linked_stylesheet("../css/date.picker.css"); + $this->add_ready_script( +<< 0) + { + $("#RightPane").splitter({ + type: "h" //, + //minA: 100, initA: 150, maxA: 500, + //accessKey: "_" + }); + } + + // Manually set the outer splitter's height to fill the browser window. + // This must be re-done any time the browser window is resized. + $(window).bind("resize", function(){ + var ms = $("#MySplitter"); + var top = ms.offset().top; // from dimensions.js + var wh = $(window).height(); + // Account for margin or border on the splitter container + var mrg = parseInt(ms.css("marginBottom")) || 0; + var brd = parseInt(ms.css("borderBottomWidth")) || 0; + ms.css("height", (wh-top-mrg-brd)+"px"); + + // IE fires resize for splitter; others don't so do it here + if ( !jQuery.browser.msie ) + ms.trigger("resize"); + + + }).trigger("resize"); + + var ms = $("#MySplitter"); + ms.trigger("resize"); + + if ( $("#TopPane").length > 0) + { + $("#RightPane").trigger("resize"); + } + + $("#tabbedContent > ul").tabs( 1, { fxFade: true, fxSpeed: 'fast' } ); // tabs + $("table.listResults").tableHover(); // hover tables + $(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $(".date-pick").datePicker( {clickInput: false, createButton: true, startDate: '2000-01-01'} ); // Date picker + $('#ModalDlg').jqm({ajax: '@href', trigger: 'a.jqmTrigger', overlay:70, modal:true, toTop:true}); // jqModal Window + //$('.display_block').draggable(); // make the blocks draggable +EOF +); + $this->add_script(" + // For automplete + function findValue(li) { + if( li == null ) return alert(\"No match!\"); + + // if coming from an AJAX call, let's use the CityId as the value + if( !!li.extra ) var sValue = li.extra[0]; + + // otherwise, let's just display the value in the text box + else var sValue = li.selectValue; + + //alert(\"The value you selected was: \" + sValue); + } + + function selectItem(li) { + findValue(li); + } + + function formatItem(row) { + return row[0]; + } + + function goBack() + { + window.history.back(); + } + "); + $this->DisplayMenu(); + } + + public function AddToMenu($sHtml) + { + $this->m_sMenu .= $sHtml; + } + + public function DisplayMenu() + { + // Combo box to select the organization + $this->AddToMenu("
+
\n"); + $this->AddToMenu("
\n"); + $this->AddToMenu("
    \n"); + $oAppContext = new ApplicationContext(); + // Display the menu + // 1) Application defined menus + $oSearchFilter = $oContext->NewFilter("menuNode"); + $oSearchFilter->AddCondition('parent_id', 0, '='); + $oSearchFilter->AddCondition('type', 'application', '='); + // There may be more criteria added later to have a specific menu based on the user's profile + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + while ($oRootMenuNode = $oSet->Fetch()) + { + $oRootMenuNode->DisplayMenu($this, 'application', $oAppContext->GetAsHash()); + } + // 2) User defined menus (Bookmarks) + $oSearchFilter = $oContext->NewFilter("menuNode"); + $oSearchFilter->AddCondition('parent_id', 0, '='); + $oSearchFilter->AddCondition('type', 'user', '='); + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + // There may be more criteria added later to have a specific menu based on the user's profile + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + while ($oRootMenuNode = $oSet->Fetch()) + { + $oRootMenuNode->DisplayMenu($this, 'user', $oAppContext->GetAsHash()); + } + $this->AddToMenu("
\n"); + } + + /** + * Outputs (via some echo) the complete HTML page by assembling all its elements + */ + public function output() + { + foreach($this->a_headers as $s_header) + { + header($s_header); + } + $s_captured_output = ob_get_contents(); + ob_end_clean(); + echo "\n"; + echo "\n"; + echo "\n"; + echo "{$this->s_title}\n"; + foreach($this->a_linked_scripts as $s_script) + { + echo "\n"; + } + if (count($this->m_aReadyScripts)>0) + { + $this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});"); + } + if (count($this->a_scripts)>0) + { + echo "\n"; + } + foreach($this->a_linked_stylesheets as $a_stylesheet) + { + if ($a_stylesheet['condition'] != "") + { + echo "\n"; + } + } + + if (count($this->a_styles)>0) + { + echo "\n"; + } + echo "\n"; + echo "\n"; + echo "\n"; + + // Display the header + echo "
\n"; + echo "
iTop
\n"; + //echo "
\n"; + $sText = Utils::ReadParam('text', ''); + $sOnClick = ""; + if (empty($sText)) + { + // if no search text is supplied then + // 1) the search text is filled with "your search" + // 2) clicking on it will erase it + $sText = "Your search"; + $sOnClick = " onclick=\"this.value='';this.onclick=null;\""; + } + echo "
"; + echo "
+
\n"; + echo "
\n"; + + echo "
\n"; + + // Display the menu + echo "
\n"; + echo "
\n"; + echo $this->m_sMenu; + echo "
\n"; + + echo "
\n"; + + // Render the tabs in the page (if any) + foreach($this->m_aTabs as $sTabContainerName => $m_aTabs) + { + $sTabs = ''; + if (count($m_aTabs) > 0) + { + $sTabs = "\n
\n"; + $sTabs .= "
    \n"; + // Display the unordered list that will be rendered as the tabs + $i = 0; + foreach($m_aTabs as $sTabName => $sTabContent) + { + $sTabs .= "
  • ".htmlentities($sTabName)."
  • \n"; + $i++; + } + $sTabs .= "
\n"; + // Now add the content of the tabs themselves + $i = 0; + foreach($m_aTabs as $sTabName => $sTabContent) + { + $sTabs .= "
".$sTabContent."
\n"; + $i++; + } + $sTabs .= "
\n\n"; + } + $this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content); + } + + // Display the page's content + echo $this->s_content; + + // Add the captured output + if (trim($s_captured_output) != "") + { + echo "
$s_captured_output
\n"; + } + echo "
Please wait...
\n"; // jqModal Window + echo "
\n"; + echo "
\n"; + echo "
"; + echo "\n"; + echo "\n"; + } + + public function AddTabContainer($sTabContainer) + { + $this->m_aTabs[$sTabContainer] = array(); + $this->add("\$Tabs:$sTabContainer\$"); + } + + public function AddToTab($sTabContainer, $sTabLabel, $sHtml) + { + if (!isset($this->m_aTabs[$sTabContainer][$sTabLabel])) + { + // Set the content of the tab + $this->m_aTabs[$sTabContainer][$sTabLabel] = $sHtml; + } + else + { + // Append to the content of the tab + $this->m_aTabs[$sTabContainer][$sTabLabel] .= $sHtml; + } + } + + public function SetCurrentTabContainer($sTabContainer = '') + { + $sPreviousTabContainer = $this->m_sCurrentTabContainer; + $this->m_sCurrentTabContainer = $sTabContainer; + return $sPreviousTabContainer; + } + + public function SetCurrentTab($sTabLabel = '') + { + $sPreviousTab = $this->m_sCurrentTab; + $this->m_sCurrentTab = $sTabLabel; + return $sPreviousTab; + } + + public function StartCollapsibleSection($sSectionLabel, $bOpen = false) + { + $this->add($this->GetStartCollapsibleSection($sSectionLabel, $bOpen)); + } + + public function GetStartCollapsibleSection($sSectionLabel, $bOpen = false) + { + $sHtml = ''; + static $iSectionId = 0; + $sHtml .= "$sSectionLabel
\n"; + $sStyle = $bOpen ? '' : 'style="display:none" '; + $sHtml .= "
"; + $this->add_ready_script("\$(\"#LnkCollapse_$iSectionId\").click(function() {\$(\"#Collapse_$iSectionId\").slideToggle('normal'); $(\"#LnkCollapse_$iSectionId\").toggleClass('open');});"); + //$this->add_ready_script("$('#LnkCollapse_$iSectionId').hide();"); + $iSectionId++; + return $sHtml; + } + + public function EndCollapsibleSection() + { + $this->add($this->GetEndCollapsibleSection()); + } + + public function GetEndCollapsibleSection() + { + return "
"; + } + + public function add($sHtml) + { + if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab)) + { + $this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml); + } + else + { + parent::add($sHtml); + } + } + + /* + public function AddSearchForm($sClassName, $bOpen = false) + { + $iSearchSectionId = 0; + + $sStyle = $bOpen ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; + $this->add("
\n"); + $this->add("

Search form for ".Metamodel::GetName($sClassName)."

\n"); + $this->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); + $oFilter = new DBObjectSearch($sClassName); + $sFilter = $oFilter->serialize(); + $oSet = new CMDBObjectSet($oFilter); + cmdbAbstractObject::DisplaySearchForm($this, $oSet, array('operation' => 'search', 'filter' => $sFilter, 'search_form' => true)); + $this->add("
\n"); + $this->add("
\n"); + $this->add("
Search
\n"); + + + $iSearchSectionId++; + } + */ +} + +?> diff --git a/application/itopwizardwebpage.class.inc.php b/application/itopwizardwebpage.class.inc.php new file mode 100644 index 0000000000..aadaf30c7d --- /dev/null +++ b/application/itopwizardwebpage.class.inc.php @@ -0,0 +1,32 @@ +m_iCurrentStep = $iCurrentStep; + $this->m_aSteps = $aSteps; + } + + public function output() + { + $aSteps = array(); + $iIndex = 0; + foreach($this->m_aSteps as $sStepTitle) + { + $iIndex++; + $sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep'; + $aSteps[] = "
$sStepTitle
"; + } + $sWizardHeader = "

{$this->s_title}

\n".implode("
", $aSteps)."
\n"; + $this->s_content = "$sWizardHeader
".$this->s_content."
"; + parent::output(); + } +} +?> diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php new file mode 100644 index 0000000000..32e7ad3fbd --- /dev/null +++ b/application/loginwebpage.class.inc.php @@ -0,0 +1,117 @@ +add_style(" +body { + background-color: #eee; + margin: 0; + padding: 0; +} +#login { + width: 230px; + margin-left: auto; + margin-right: auto; + margin-top: 150px; + padding: 20px; + background-color: #fff; + border: 1px solid #000; +} +.center { + text-align: center; +} + +h1 { + color: #83b217; + font-size: 16pt; +} +.v-spacer { + padding-top: 1em; +} + "); + } + + public function DisplayLoginForm($bFailedLogin = false) + { + $sAuthUser = utils::ReadParam('auth_user', ''); + $sAuthPwd = utils::ReadParam('suggest_pwd', ''); + + $this->add("
\n"); + $this->add("

Welcome to iTop!

\n"); + if ($bFailedLogin) + { + $this->add("

Incorrect login/password, please try again.

\n"); + } + else + { + $this->add("

Please identify yourself before continuing.

\n"); + } + $this->add("
\n"); + $this->add("\n"); + $this->add("\n"); + $this->add("\n"); + $this->add("\n"); + $this->add("
\n"); + $this->add("\n"); + $this->add("
\n"); + $this->add("
\n"); + } + + static function DoLogin() + { + $operation = utils::ReadParam('operation', ''); + session_start(); + + if (!session_is_registered('auth_user') || !session_is_registered('auth_pwd')) + { + if ($operation == 'login') + { + $sAuthUser = utils::ReadParam('auth_user', '', 'post'); + $sAuthPwd = utils::ReadParam('auth_pwd', '', 'post'); + } + else + { + $oPage = new login_web_page(); + $oPage->DisplayLoginForm(); + $oPage->output(); + exit; + } + } + else + { + $sAuthUser = $_SESSION['auth_user']; + $sAuthPwd = $_SESSION['auth_pwd']; + } + if (!UserRights::Login($sAuthUser, $sAuthPwd)) + { + // Unset all of the session variables. + $_SESSION = array(); + // If it's desired to kill the session, also delete the session cookie. + // Note: This will destroy the session, and not just the session data! + if (isset($_COOKIE[session_name()])) + { + setcookie(session_name(), '', time()-3600, '/'); + } + // Finally, destroy the session. + session_destroy(); + + $oPage = new login_web_page(); + $oPage->DisplayLoginForm( true /* failed attempt */); + $oPage->output(); + exit; + } + else + { + $_SESSION['auth_user'] = $sAuthUser ; + $_SESSION['auth_pwd'] = $sAuthPwd; + + } + } +} // End of class +?> diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php new file mode 100644 index 0000000000..f571f40f30 --- /dev/null +++ b/application/menunode.class.inc.php @@ -0,0 +1,219 @@ + "gui", + "name" => "menuNode", + "description" => "Main menu configuration elements", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_menunode", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); +// MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("label"=>"change", "description"=>"change", "allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "jointype"=>"closed"))); +// MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("label"=>"date", "description"=>"date and time of the change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Menu Name", "description"=>"Short name for this menu", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("label", array("label"=>"Menu Description", "description"=>"Long description for this menu", "allowed_values"=>null, "sql"=>"label", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hyperlink", array("label"=>"Hyperlink", "description"=>"Hyperlink to the page", "allowed_values"=>null, "sql"=>"hyperlink", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("icon_path", array("label"=>"Menu Icon", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"icon_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("template", array("label"=>"Template", "description"=>"HTML template for the view", "allowed_values"=>null, "sql"=>"template", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of menu", "allowed_values"=>new ValueSetEnum('application,user'), "sql"=>"type", "default_value"=>"application", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("rank", array("label"=>"Display rank", "description"=>"Sort order for displaying the menu", "allowed_values"=>null, "sql"=>"rank", "default_value" => 999, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "sql"=>"parent_id", "targetclass"=>"menuNode", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("label"=>"Owner of the menu", "description"=>"User who owns this menu (for user defined menus)", "allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"UserRightsMatrixUsers", "is_null_allowed"=>true, "depends_on"=>array('type')))); + + MetaModel::Init_AddFilterFromAttribute("label"); + MetaModel::Init_AddFilterFromAttribute("parent_id"); + MetaModel::Init_AddFilterFromAttribute("rank"); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("user_id"); + + MetaModel::Init_SetZListItems('details', array('parent_id', 'name', 'label', 'hyperlink', 'template', 'rank', 'type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('parent_id', 'name', 'label', 'rank', 'type')); // Attributes to be displayed for a list + } + + public function IsVisible() + { + return true; + } + + public function GetMenuName() + { + return $this->Get('name'); + } + + public function GetMenuIcon() + { + return $this->Get('icon_path'); + } + + public function GetMenuLabel() + { + return $this->Get('label'); + } + + public function GetMenuLink($aExtraParams) + { + $aExtraParams['menu'] = $this->GetKey(); // Make sure we overwrite the current menu id (if any) + $aParams = array(); + foreach($aExtraParams as $sName => $sValue) + { + $aParams[] = urlencode($sName)."=".urlencode($sValue); + } + return $this->Get('hyperlink')."?".implode("&", $aParams); + } + + public function GetChildNodesSet($sType) + { + $oSearchFilter = new DBObjectSearch("menuNode"); + $oSearchFilter->AddCondition('parent_id', $this->GetKey(), '='); + $oSearchFilter->AddCondition('type', $sType, '='); + if ($sType == 'user') + { + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + } + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + return $oSet; + } + + public function RenderContent(web_page $oPage, $aExtraParams = array()) + { + $sTemplate = $this->Get('template'); + $this->ProcessTemplate($sTemplate, $oPage, $aExtraParams); + } + + protected function ProcessTemplate($sTemplate, web_page $oPage, $aExtraParams = array()) + { + $iStartPos = stripos($sTemplate, '<'.DisplayBlock::TAG_BLOCK.' ',0); + $index = 0; + while(($iStartPos = stripos($sTemplate, '<'.DisplayBlock::TAG_BLOCK.' ',0)) !== false) + { + $iEndPos = stripos($sTemplate, '', $iStartPos); + + $sBlockDefinition = substr($sTemplate, $iStartPos, $iEndPos - $iStartPos + strlen('')); + $oBlock = DisplayBlock::FromTemplate($sBlockDefinition); + + $oPage->add(substr($sTemplate, 0, $iStartPos)); + if ($oBlock) // Protects agains invalid XML templates + { + $oBlock->Display($oPage, "block{$index}", $aExtraParams); // Values from $aExtraParams have precedence over $aParams + } + $index++; + $sTemplate = substr($sTemplate, $iEndPos + strlen('')); + } + // What remains is purely static (without any block inside), just output as it is + $oPage->add($sTemplate); + } + + public function DisplayMenu(iTopWebPage $oP, $sType, $aExtraParams) + { + $oP->AddToMenu("
  • GetMenuLink($aExtraParams)."\" title=\"".$this->GetMenuLabel()."\">".$this->GetMenuName().""); + $oSet = $this->GetChildNodesSet($sType); + if ($oSet->Count() > 0) + { + $oP->AddToMenu("\n
      \n"); + while($oChildNode = $oSet->Fetch()) + { + $oChildNode->DisplayMenu($oP, $sType, $aExtraParams); + } + $oP->AddToMenu("
    \n"); + } + $oP->AddToMenu("
  • \n"); + } + static public function DisplayCreationForm(web_page $oP, $sClass, $sFilter, $aExtraParams = array()) + { + $oFilter = DBObjectSearch::unserialize($sFilter); + $oP->p('Create a new menu item for: '.$oFilter->__DescribeHTML()); + $oP->add('
    '); + $oP->add(''); + $oP->add(''); + $oP->add(''); + $oP->p('Menu Label: '); + $oP->p('Description: '); + $oP->add('

    Insert after:

    '); + $oP->p(' Create as a child menu item'); + $oP->p('      '); + $oP->add('
    '); + } + + static public function GetMenuAsArray($oRootNode = null, $sType = 'application', $iDepth = 0) + { + $aNodes = array(); + if (is_object($oRootNode)) + { + $oChildSet = $oRootNode->GetChildNodesSet($sType); + while($oNode = $oChildSet->Fetch()) + { + $aNodes[] = array('depth' => $iDepth, 'id' => $oNode->GetKey(), 'label' => $oNode->GetName()); + $aNodes = array_merge($aNodes, self::GetMenuAsArray($oNode, $sType, $iDepth+1)); + } + } + else + { + $oSearchFilter = new DbObjectSearch("menuNode"); + $oSearchFilter->AddCondition('parent_id', 0, '='); + $oSearchFilter->AddCondition('type', $sType, '='); + if ($sType == 'user') + { + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + } + $oRootSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + while($oNode = $oRootSet->Fetch()) + { + $aNodes[] = array('depth' => $iDepth, 'id' => $oNode->GetKey(), 'label' => $oNode->GetName()); + $aNodes = array_merge($aNodes, self::GetMenuAsArray($oNode, $sType, $iDepth+1)); + } + } + return $aNodes; + } + /** + * Returns a set of all the nodes following the current node in the tree + * (i.e. nodes with the same parent but with a greater rank) + */ + public function GetNextNodesSet($sType = 'application') + { + $oSearchFilter = new DBObjectSearch("menuNode"); + $oSearchFilter->AddCondition('parent_id', $this->Get('parent_id')); + $oSearchFilter->AddCondition('rank', $this->Get('rank'), '>'); + $oSearchFilter->AddCondition('type', $sType, '='); + if ($sType == 'user') + { + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + } + $oSet = new DBObjectSet($oSearchFilter, array('rank'=> true)); // Order by rank (true means ascending) + return $oSet; + } +} +?> diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php new file mode 100644 index 0000000000..bd29966ed5 --- /dev/null +++ b/application/nicewebpage.class.inc.php @@ -0,0 +1,76 @@ +m_aReadyScripts = array(); + $this->add_linked_script("../js/jquery.latest.js"); + $this->add_linked_script("../js/jquery.history_remote.pack.js"); + //$this->add_linked_script("../js/ui.resizable.js"); + $this->add_linked_script("../js/ui.tabs.js"); + $this->add_linked_script("../js/hovertip.js"); + $this->add_linked_script("../js/jqModal.js"); + $this->add_linked_stylesheet("../css/light-grey.css"); + $this->add_linked_stylesheet("../js/themes/light/light.tabs.css"); + //$this->add_linked_stylesheet("../css/jquery.tabs-ie.css", "lte IE 7"); + $this->add_linked_stylesheet("../css/jqModal.css"); + $this->add_ready_script(' window.setTimeout(hovertipInit, 1);'); + } + + public function small_p($sText) + { + $this->add("

    $sText

    \n"); + } + + // By Rom, used by CSVImport and Advanced search + public function MakeClassesSelect($sName, $sDefaultValue, $iWidthPx) + { + // $aTopLevelClasses = array('bizService', 'bizContact', 'logInfra', 'bizDocument'); + // These are classes wich root class is cmdbAbstractObject ! + $this->add(""); + } + + // By Rom, used by Advanced search + public function add_select($aChoices, $sName, $sDefaultValue, $iWidthPx) + { + $this->add(""); + } + + public function add_ready_script($sScript) + { + $this->m_aReadyScripts[] = $sScript; + } + + /** + * Outputs (via some echo) the complete HTML page by assembling all its elements + */ + public function output() + { + if (count($this->m_aReadyScripts)>0) + { + $this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});"); + } + parent::output(); + } +} + +?> diff --git a/application/startup.inc.php b/application/startup.inc.php new file mode 100644 index 0000000000..83ea90c2a8 --- /dev/null +++ b/application/startup.inc.php @@ -0,0 +1,7 @@ + diff --git a/application/template.class.inc.php b/application/template.class.inc.php new file mode 100644 index 0000000000..b6bdd8a619 --- /dev/null +++ b/application/template.class.inc.php @@ -0,0 +1,224 @@ +m_aTags = array('itopblock', 'itoptabs', 'itoptab', 'itoptoggle'); + $this->m_sTemplate = $sTemplate; + } + + public function Render(web_page $oPage, $aParams = array()) + { + $this->ApplyParams($aParams); + $iStart = 0; + $iEnd = strlen($this->m_sTemplate); + $iCount = 0; + $iBeforeTagPos = $iStart; + $iAfterTagPos = $iStart; + while($sTag = $this->GetNextTag($iStart, $iEnd)) + { + $sContent = $this->GetTagContent($sTag, $iStart, $iEnd); + $aAttributes = $this->GetTagAttributes($sTag, $iStart, $iEnd); + //$oPage->p("Tag: $sTag - ($iStart, $iEnd)"); + $oPage->add(substr($this->m_sTemplate, $iBeforeTagPos, $iStart - $iBeforeTagPos)); + $this->RenderTag($oPage, $sTag, $aAttributes, $sContent); + + $iAfterTagPos = $iEnd + strlen(''); + $iBeforeTagPos = $iAfterTagPos; + $iStart = $iEnd; + $iEnd = strlen($this->m_sTemplate); + $iCount++; + if ($iCount > 10) break; + } + $oPage->add(substr($this->m_sTemplate, $iAfterTagPos)); + } + + /** + * Replaces all the parameters by the values passed in the hash array + */ + public function ApplyParams($aParams) + { + $aSearches = array(); + $aReplacements = array(); + foreach($aParams as $sSearch => $sReplace) + { + $aSearches[] = '$'.$sSearch.'$'; + $aReplacements[] = $sReplace; + } + $this->m_sTemplate = str_replace($aSearches, $aReplacements, $this->m_sTemplate); + } + + public function GetNextTag(&$iStartPos, &$iEndPos) + { + $iChunkStartPos = $iStartPos; + $sNextTag = null; + $iStartPos = $iEndPos; + foreach($this->m_aTags as $sTag) + { + // Search for the opening tag + $iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.' ', $iChunkStartPos); + if ($iOpeningPos === false) + { + $iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.'>', $iChunkStartPos); + } + if ($iOpeningPos !== false) + { + $iClosingPos = stripos($this->m_sTemplate, '', $iOpeningPos); + } + if ( ($iOpeningPos !== false) && ($iClosingPos !== false)) + { + if ($iOpeningPos < $iStartPos) + { + // This is the next tag + $iStartPos = $iOpeningPos; + $iEndPos = $iClosingPos; + $sNextTag = $sTag; + } + } + } + return $sNextTag; + } + + public function GetTagContent($sTag, $iStartPos, $iEndPos) + { + $sContent = ""; + $iContentStart = strpos($this->m_sTemplate, '>', $iStartPos); // Content of tag start immediatly after the first closing bracket + if ($iContentStart !== false) + { + $sContent = substr($this->m_sTemplate, 1+$iContentStart, $iEndPos - $iContentStart - 1); + } + return $sContent; + } + + public function GetTagAttributes($sTag, $iStartPos, $iEndPos) + { + $aAttr = array(); + $iAttrStart = strpos($this->m_sTemplate, ' ', $iStartPos); // Attributes start just after the first space + $iAttrEnd = strpos($this->m_sTemplate, '>', $iStartPos); // Attributes end just before the first closing bracket + if ( ($iAttrStart !== false) && ($iAttrEnd !== false) && ($iAttrEnd > $iAttrStart)) + { + $sAttributes = substr($this->m_sTemplate, 1+$iAttrStart, $iAttrEnd - $iAttrStart - 1); + $aAttributes = explode(' ', $sAttributes); + foreach($aAttributes as $sAttr) + { + if ( preg_match('/(.+) *= *"(.+)"$/', $sAttr, $aMatches) ) + { + $aAttr[strtolower($aMatches[1])] = $aMatches[2]; + } + } + } + return $aAttr; + } + + protected function RenderTag($oPage, $sTag, $aAttributes, $sContent) + { + static $iTabContainerCount = 0; + static $iBlockCount = 0; + switch($sTag) + { + case 'itoptabs': + $oPage->AddTabContainer('Tabs_'.$iTabContainerCount); + $oPage->SetCurrentTabContainer('Tabs_'.$iTabContainerCount); + $iTabContainerCount++; + //$oPage->p('Content:
    '.htmlentities($sContent).'
    '); + $oTemplate = new DisplayTemplate($sContent); + $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied + $oPage->SetCurrentTabContainer(''); + break; + + case 'itoptab': + $oPage->SetCurrentTab($aAttributes['name']); + $oTemplate = new DisplayTemplate($sContent); + $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied + //$oPage->p('iTop Tab Content:
    '.htmlentities($sContent).'
    '); + $oPage->SetCurrentTab(''); + break; + + case 'itoptoggle': + $oPage->StartCollapsibleSection($aAttributes['name']); + $oTemplate = new DisplayTemplate($sContent); + $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied + //$oPage->p('iTop Tab Content:
    '.htmlentities($sContent).'
    '); + $oPage->EndCollapsibleSection(); + break; + + case 'itopblock': // TO DO: Use DisplayBlock::FromTemplate here + $sBlockClass = $aAttributes['blockclass']; + $sBlockType = $aAttributes['type']; + $aExtraParams = array(); + if (isset($aAttributes['linkage'])) + { + $aExtraParams['linkage'] = $aAttributes['linkage']; + } + + switch($aAttributes['encoding']) + { + case 'text/sibusql': + $oFilter = CMDBSearchFilter::FromSibusQL($sContent); + break; + + case 'text/oql': + $oFilter = CMDBSearchFilter::FromOQL($sContent); + break; + + case 'text/serialize': + default: + $oFilter = CMDBSearchFilter::unserialize($sContent); + break; + } + $oBlock = new $sBlockClass($oFilter, $sBlockType, false, $aExtraParams); + $oBlock->Display($oPage, 'block_'.$iBlockCount); + $iBlockCount++; + break; + + default: + // Unknown tag, just ignore it or now -- output an HTML comment + $oPage->add(""); + } + } + + /** + * Unit test + */ + static public function UnitTest() + { + require_once('../application/startup.inc.php'); + require_once("../application/itopwebpage.class.inc.php"); + + $sTemplate = ' + + bizNetworkDevice: pkey = $pkey$ + + + bizInterface: device_id = $pkey$ + + + bizContact: PKEY IS contact_id IN (ContactsLinks: object_id = $pkey$) + + + bizDocument: PKEY IS doc_id IN (lnkDocumentRealObject: object_id = $pkey$) + + '; + + $oPage = new iTopWebPage('Unit Test', 3); + //$oPage->add("Template content:
    ".htmlentities($sTemplate)."
    \n"); + $oTemplate = new DisplayTemplate($sTemplate); + $oTemplate->Render($oPage, array('class'=>'Network device','pkey'=> 271, 'name' => 'deliversw01.mecanorama.fr', 'org_id' => 3)); + $oPage->output(); + } +} + +//DisplayTemplate::UnitTest(); + +?> diff --git a/application/templates/audit_category.html b/application/templates/audit_category.html new file mode 100644 index 0000000000..7a25e87911 --- /dev/null +++ b/application/templates/audit_category.html @@ -0,0 +1,12 @@ + + +$class$: pkey = $pkey$ + + + AuditRule: category_id = $pkey$ + + diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php new file mode 100644 index 0000000000..f980adec10 --- /dev/null +++ b/application/ui.linkswidget.class.inc.php @@ -0,0 +1,301 @@ +m_sClass = $sClass; + $this->m_sAttCode = $sAttCode; + $this->m_iInputId = $iInputId; + } + + public function Display(web_page $oPage, $oCurrentValuesSet = null) + { + $sHTMLValue = ''; + $sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode); + $aAllowedValues = MetaModel::GetAllowedValues_att($this->m_sClass, $this->m_sAttCode, array(), ''); + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); + $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); + $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); + $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); + $sDefaultState = MetaModel::GetDefaultState($this->m_sClass); + + $sLinkedClass = $oAttDef->GetLinkedClass(); + foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef) + { + if ($sStateAttCode == $sAttCode) + { + // State attribute is always hidden from the UI + } + else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote)) + { + $iFlags = MetaModel::GetAttributeFlags($this->m_sClass, $sDefaultState, $sAttCode); + if ( !($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY) ) + { + $aAttributes[] = $sAttCode; + } + } + } + $sAttributes = "['".implode("','", $aAttributes)."']"; + if ($oCurrentValuesSet != null) + { + // Serialize the link set into a JSon object + $aCurrentValues = array(); + $sRow = '{'; + while($oLinkObj = $oCurrentValuesSet->Fetch()) + { + foreach($aAttributes as $sLinkAttCode) + { + $sRow.= "\"$sLinkAttCode\": \"".addslashes($oLinkObj->Get($sLinkAttCode))."\", "; + } + $sRow .= "\"$sExtKeyToRemote\": ".$oLinkObj->Get($sExtKeyToRemote).'}'; + $aCurrentValues[] = $sRow; + } + $sJSON = '['.implode(',', $aCurrentValues).']'; + } + + // Many values (or even a unknown list) display an autocomplete + if ( (count($aAllowedValues) == 0) || (count($aAllowedValues) > 20) ) + { + // too many choices, use an autocomplete + // The input for the auto complete + $sTitle = $oAttDef->GetDescription(); + $sHTMLValue .= "\n"; + $sHTMLValue .= $this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk'); + $sHTMLValue .= $this->GetLinkObjectDialog($oPage, $this->m_iInputId); + $sHTMLValue .= "m_iInputId}\" size=\"35\" name=\"\" value=\"\" style=\"border: 1px solid red;\" />"; + $sHTMLValue .= "m_iInputId}.AddObject();\"/>"; + $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; + // another hidden input to store & pass the object's Id + $sHTMLValue .= "m_iInputId}\"/>\n"; + $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}\" value=\"\"/>\n"; + $oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});"); + } + else + { + // Few choices, use a normal 'select' + $sHTMLValue = "\n"; + } + $sHTMLValue .= "
    m_iInputId}_values\">\n"; + if ($oCurrentValuesSet != null) + { + // transform the DBObjectSet into a CMDBObjectSet !!! + $aLinkedObjects = $oCurrentValuesSet->ToArray(false); + if (count($aLinkedObjects) > 0) + { + $oSet = CMDBObjectSet::FromArray($sLinkedClass, $aLinkedObjects); + $oDisplayBlock = DisplayBlock::FromObjectSet($oSet, 'list'); + $sHTMLValue .= $oDisplayBlock->GetDisplay($oPage, $this->m_iInputId.'_current', array('linkage' => $sExtKeyToMe)); + } + } + $sHTMLValue .= "
    \n"; + return $sHTMLValue; + } + /** + * This static function is called by the Ajax Page when there is a need to fill an autocomplete combo + * @param $oPage web_page The ajax page used for the put^put (sent back to the browser + * @param $oContext UserContext The context of the user (for limiting the search) + * @param $sClass string The name of the class of the current object being edited + * @param $sAttCode string The name of the attribute being edited + * @param $sName string The partial name that was typed by the user + * @param $iMaxCount integer The maximum number of items to return + * @return void + */ + static public function Autocomplete(web_page $oPage, UserContext $oContext, $sClass, $sAttCode, $sName, $iMaxCount) + { + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array() /* $aArgs */, $sName); + if ($aAllowedValues != null) + { + $iCount = $iMaxCount; + foreach($aAllowedValues as $key => $value) + { + $oPage->add($value."|".$key."\n"); + $iCount--; + if ($iCount == 0) break; + } + } + else // No limitation to the allowed values + { + // Search for all the object of the linked class + $oAttDef = $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sLinkedClass = $oAttDef->GetLinkedClass(); + $sSearchClass = self::GetTargetClass($sClass, $sAttCode); + $oFilter = $oContext->NewFilter($sSearchClass); + $sSearchAttCode = MetaModel::GetNameAttributeCode($sSearchClass); + $oFilter->AddCondition($sSearchAttCode, $sName, 'Begins with'); + $oSet = new CMDBObjectSet($oFilter, array($sSearchAttCode => true)); + $iCount = 0; + while( ($iCount < $iMaxCount) && ($oObj = $oSet->fetch()) ) + { + $oPage->add($oObj->GetName()."|".$oObj->GetKey()."\n"); + $iCount++; + } + } + } + + /** + * This static function is called by the Ajax Page display a set of objects being linked + * to the object being created + * @param $oPage web_page The ajax page used for the put^put (sent back to the browser + * @param $sClass string The name of the class 'linking class' which is the class of the objects to display + * @param $sAttCode string The name of the attribute is the main object being created + * @param $sSet JSON serialized set of objects + * @return void + */ + static public function RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe) + { + $aSet = json_decode($sJSONSet, true); // true means hash array instead of object + $oSet = CMDBObjectSet::FromScratch($sClass); + foreach($aSet as $aObject) + { + $oObj = MetaModel::NewObject($sClass); + foreach($aObject as $sAttCode => $value) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value); // @@ optimization, don't do & query per object in the set ! + $oObj->Set($sAttCode, $oTargetObj); + } + else + { + $oObj->Set($sAttCode, $value); + } + + } + $oSet->AddObject($oObj); + } + cmdbAbstractObject::DisplaySet($oPage, $oSet, $sExtKeyToMe); + } + + + protected static function GetTargetClass($sClass, $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sLinkedClass = $oAttDef->GetLinkedClass(); + switch(get_class($oAttDef)) + { + case 'AttributeLinkedSetIndirect': + $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); + $sTargetClass = $oLinkingAttDef->GetTargetClass(); + break; + + case 'AttributeLinkedSet': + $sTargetClass = $sLinkedClass; + break; + } + + return $sTargetClass; + } + + protected function GetObjectPickerDialog($oPage, $sTargetClass, $sOkFunction) + { + $sHTML = <<< EOF +
    + + + + + + + + + + +
    +

    Selected objects:

    + +

    +
    +

    +

    +
    +

    Available objects:

    + +

    +
    +       +
    +
    +EOF; + $oPage->add_ready_script("$('#ManageObjectsDlg_$this->m_iInputId').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window + //$oPage->add_ready_script("UpdateObjectList('$sClass');"); + return $sHTML; + } + + protected function GetLinkObjectDialog($oPage, $sId) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); + $sLinkedClass = $oAttDef->GetLinkedClass(); + $sStateAttCode = MetaModel::GetStateAttributeCode($sLinkedClass); + $sDefaultState = MetaModel::GetDefaultState($sLinkedClass); + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); + $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); + $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); + + $sHTML = "
    \n"; + $sHTML .= "

    $sLinkedClass attributes

    \n"; + $sHTML .= "
    \n"; + $index = 0; + $aAttrsMap = array(); + foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef) + { + if ($sStateAttCode == $sAttCode) + { + // State attribute is always hidden from the UI + //$sHTMLValue = $this->GetState(); + //$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote)) + { + $iFlags = MetaModel::GetAttributeFlags($sLinkedClass, $sDefaultState, $sAttCode); + if ($iFlags & OPT_ATT_HIDDEN) + { + // Attribute is hidden, do nothing + } + else + { + if ($iFlags & OPT_ATT_READONLY) + { + // Attribute is read-only + $sHTMLValue = $this->GetAsHTML($sAttCode); + } + else + { + $sValue = ""; //$this->Get($sAttCode); + $sDisplayValue = ""; //$this->GetDisplayValue($sAttCode); + $sSubId = $sId.'_'.$index; + $aAttrsMap[$sAttCode] = $sSubId; + $index++; + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sLinkedClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sSubId); + } + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + } + } + $sHTML .= $oPage->GetDetails($aDetails); + $sHTML .= "     \n"; + $sHTML .= "
    \n"; + $sHTML .= "
    \n"; + $oPage->add_ready_script("$('#LinkDlg_$sId').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window + //$oPage->add_ready_script("UpdateObjectList('$sClass');"); + return $sHTML; + } +} +?> diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php new file mode 100644 index 0000000000..3c8a61192d --- /dev/null +++ b/application/uiwizard.class.inc.php @@ -0,0 +1,257 @@ +m_oPage = $oPage; + $this->m_sClass = $sClass; + if (empty($sTargetState)) + { + $sTargetState = MetaModel::GetDefaultState($sClass); + } + $this->m_sTargetState = $sTargetState; + $this->m_aWizardSteps = $this->ComputeWizardStructure(); + } + + public function GetObjectClass() { return $this->m_sClass; } + public function GetTargetState() { return $this->m_sTargetState; } + public function GetWizardStructure() { return $this->m_aWizardSteps; } + + /** + * Displays one step of the wizard + */ + public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false) + { + $this->m_oPage->add("
    \n"); + $this->m_oPage->add("\n"); + $aStates = MetaModel::EnumStates($this->m_sClass); + $aDetails = array(); + $sJSHandlerCode = ''; // Javascript code to be executed each time this step of the wizard is entered + foreach($aStep as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + $sAttLabel = $oAttDef->GetLabel(); + $iOptions = isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0; + + $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); + if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) + { + $aFields[$sAttCode] = array(); + foreach($aPrerequisites as $sCode) + { + $aFields[$sAttCode][$sCode] = ''; + } + } + if (count($aPrerequisites) > 0) + { + $aOptions[] = 'Prerequisites: '.implode(', ', $aPrerequisites); + } + + $sFieldFlag = ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) ? ' *' : ''; + $oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId"); + $aFieldsMap[$iMaxInputId] = $sAttCode; + $aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "
    $sHTMLValue
    "); + if ($oAttDef->GetValuesDef() != null) + { + $sJSHandlerCode .= "\toWizardHelper.RequestAllowedValues('$sAttCode');\n"; + } + if ($oAttDef->GetDefaultValue() != null) + { + $sJSHandlerCode .= "\toWizardHelper.RequestDefaultValue('$sAttCode');\n"; + } + if ($oAttDef->IsLinkSet()) + { + $sJSHandlerCode .= "\toLinkWidgetatt_$iMaxInputId.Init();"; + } + $iMaxInputId++; + } + //$aDetails[] = array('label' => '', 'value' => ''); + $this->m_oPage->details($aDetails); + $sBackButtonDisabled = ($iStepIndex <= 1) ? 'disabled' : ''; + $sDisabled = $bFinishEnabled ? '' : 'disabled'; + $nbSteps = count($this->m_aWizardSteps['mandatory']) + count($this->m_aWizardSteps['optional']); + $this->m_oPage->add("
    + + + +
    \n"); + $this->m_oPage->add(" +\n"); + $this->m_oPage->add("
    \n\n"); + } + + /** + * Display the final step of the wizard: a confirmation screen + */ + public function DisplayFinalStep($iStepIndex, $aFieldsMap) + { + $this->m_oPage->add("\n"); + } + /** + * Compute the order of the fields & pages in the wizard + * @param $oPage iTopWebPage The current page (used to display error messages) + * @param $sClass string Name of the class + * @param $sStateCode string Code of the target state of the object + * @return hash Two dimensional array: each element represents the list of fields for a given page + */ + protected function ComputeWizardStructure() + { + $aWizardSteps = array( 'mandatory' => array(), 'optional' => array()); + $aFieldsDone = array(); // Store all the fields that are already covered by a previous step of the wizard + + $aStates = MetaModel::EnumStates($this->m_sClass); + + if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) ) + { + // Check all the fields that *must* be included in the wizard for this + // particular target state + $aFields = array(); + foreach($aStates[$this->m_sTargetState]['attribute_list'] as $sAttCode => $iOptions) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + $sAttLabel = $oAttDef->GetLabel(); + + if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) + { + $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); + $aFields[$sAttCode] = array(); + foreach($aPrerequisites as $sCode) + { + $aFields[$sAttCode][$sCode] = ''; + } + } + } + + // Now use the dependencies between the fields to order them + while(count($aFields) > 0) + { + $aCurrentStep = array(); + foreach($aFields as $sAttCode => $aDependencies) + { + // All fields with no remaining dependencies can be entered at this + // step of the wizard + if (count($aDependencies) == 0) + { + $aCurrentStep[] = $sAttCode; + $aFieldsDone[$sAttCode] = ''; + unset($aFields[$sAttCode]); + // Remove this field from the dependencies of the other fields + foreach($aFields as $sUpdatedCode => $aDummy) + { + // remove the dependency + unset($aFields[$sUpdatedCode][$sAttCode]); + } + } + } + if (count($aCurrentStep) == 0) + { + // This step of the wizard would contain NO field ! + echo "Error: Circular reference in the dependencies between the fields."; + print_r($aFields); + break; + } + $aWizardSteps['mandatory'][] = $aCurrentStep; + } + } + + // Now computes the steps to fill the optional fields + $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); + $aFields = array(); // reset + foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAttDef) + { + $iOptions = (isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode])) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0; + if ( ($sStateAttCode != $sAttCode) && + (!$oAttDef->IsExternalField()) && + (($iOptions & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) == 0) && + (!isset($aFieldsDone[$sAttCode])) ) + + { + // 'State', external fields, read-only and hidden fields + // and fields that are already listed in the wizard + // are removed from the 'optional' part of the wizard + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); + $aFields[$sAttCode] = array(); + foreach($aPrerequisites as $sCode) + { + if (!isset($aFieldsDone[$sCode])) + { + // retain only the dependencies that were not covered + // in the 'mandatory' part of the wizard + $aFields[$sAttCode][$sCode] = ''; + } + } + } + } + // Now use the dependencies between the fields to order them + while(count($aFields) > 0) + { + $aCurrentStep = array(); + foreach($aFields as $sAttCode => $aDependencies) + { + // All fields with no remaining dependencies can be entered at this + // step of the wizard + if (count($aDependencies) == 0) + { + $aCurrentStep[] = $sAttCode; + $aFieldsDone[$sAttCode] = ''; + unset($aFields[$sAttCode]); + // Remove this field from the dependencies of the other fields + foreach($aFields as $sUpdatedCode => $aDummy) + { + // remove the dependency + unset($aFields[$sUpdatedCode][$sAttCode]); + } + } + } + if (count($aCurrentStep) == 0) + { + // This step of the wizard would contain NO field ! + $oPage->add("Error: Circular reference in the dependencies between the fields."); + print_r($aFields); + break; + } + $aWizardSteps['optional'][] = $aCurrentStep; + } + + return $aWizardSteps; + + } +} +?> diff --git a/application/usercontext.class.inc.php b/application/usercontext.class.inc.php new file mode 100644 index 0000000000..21451feeec --- /dev/null +++ b/application/usercontext.class.inc.php @@ -0,0 +1,106 @@ +AddCondition('SomeClass', 'someFilter', 'SomeValue', '='); + * ... + * + * 2) Use the restrictions contained in the context when retrieving objects either when: + * getting directly an instance of an object + * $oObj = $oContext->GetObject('myClass', 'someKey'); // Instead of $oObj = MetaModel::GetObject('Klass', 'someKey'); + * or when building a new search filter + * $oFilter = $oContext->NewFilter('myClass'); // Instead of $oFilter = new CMDBSearchFilter('Klass'); + */ +class UserContext +{ + /** + * Hash array to store the restricting conditions by myClass + */ + protected $m_aConditions; + + /** + * Constructor + */ + public function __construct() + { + $this->m_aConditions = array(); + } + /** + * Create a new search filter for the given class of objects that already contains the context's restrictions + */ + public function NewFilter($sClass) + { + return UserRights::GetFilter($sClass); + /* + $oFilter = new CMDBSearchFilter($sClass); + foreach($this->m_aConditions as $sConditionClass => $aConditionList) + { + // Add to the filter all the conditions of the parent classes of this class + if ($this->IsSubclass($sConditionClass,$sClass)) + { + foreach($aConditionList as $sFilterCode => $aCondition) + { + $oFilter->AddCondition($sFilterCode, $aCondition['value'], $aCondition['operator']); + } + } + } + return $oFilter; + */ + } + /** + * Retrieve an instance of an object (if allowed by the context) + */ + public function GetObject($sClass, $sKey) + { + $oObject = null; + $oFilter = $this->NewFilter($sClass); + $oFilter->AddCondition('pkey', $sKey, '='); + $oSet = new CMDBObjectSet($oFilter); + if ($oSet->Count() > 0) + { + $oObject = $oSet->Fetch(); + } + return $oObject; + } + + /** + * Add a restriction to the context for a given class of objects (and all its persistent subclasses) + */ + public function AddCondition($sClass, $sFilterCode, $value, $sOperator) + { + if(!isset($this->m_aConditions[$sClass])) + { + $this->m_aConditions[$sClass] = array(); + } + $this->m_aConditions[$sClass][$sFilterCode] = array('value'=>$value, 'operator'=>$sOperator); + } + + /** + * Check if a given class is a subclass of (or same as) another one + */ + protected function IsSubclass($sParentClass, $sSubclass) + { + $bResult = false; + if ($sParentClass == $sSubclass) + { + $bResult = true; + } + else + { + $aParentList = MetaModel::EnumParentClasses($sSubclass); + $bResult = in_array($sParentClass, $aParentList); + } + return $bResult; + } +} +?> diff --git a/application/utils.inc.php b/application/utils.inc.php new file mode 100644 index 0000000000..00aa4c8d3a --- /dev/null +++ b/application/utils.inc.php @@ -0,0 +1,80 @@ + diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php new file mode 100644 index 0000000000..0775869830 --- /dev/null +++ b/application/webpage.class.inc.php @@ -0,0 +1,289 @@ +p("Hello World !"); + * $oPage->output(); + */ +class web_page +{ + protected $s_title; + protected $s_content; + protected $a_scripts; + protected $a_styles; + protected $a_include_scripts; + protected $a_include_stylesheets; + protected $a_headers; + + public function __construct($s_title) + { + $this->s_title = $s_title; + $this->s_content = ""; + $this->a_scripts = array(); + $this->a_styles = array(); + $this->a_linked_scripts = array(); + $this->a_linked_stylesheets = array(); + $this->a_headers = array(); + ob_start(); // Start capturing the output + } + + /** + * Change the title of the page after its creation + */ + public function set_title($s_title) + { + $this->s_title = $s_title; + } + + /** + * Add any text or HTML fragment to the body of the page + */ + public function add($s_html) + { + $this->s_content .= $s_html; + } + + /** + * Add a paragraph to the body of the page + */ + public function p($s_html) + { + $this->add($this->GetP($s_html)); + } + + /** + * Add a paragraph to the body of the page + */ + public function GetP($s_html) + { + return "

    $s_html

    \n"; + } + + public function table($aConfig, $aData, $aParams = array()) + { + $this->add($this->GetTable($aConfig, $aData, $aParams)); + } + + public function GetTable($aConfig, $aData, $aParams = array()) + { + $oAppContext = new ApplicationContext(); + + static $iNbTables = 0; + $iNbTables++; + $sHtml = ""; + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aConfig as $sName=>$aDef) + { + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aData as $aRow) + { + if (false) //(isset($aParams['preview']) && $aParams['preview']) + { + $sHtml .= "\n"; + } + else if (isset($aRow['key'])) + { + $sHtml .= "\n"; + } + else + { + $sHtml .= "\n"; + } + foreach($aConfig as $sName=>$aAttribs) + { + $sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : ''; + if ($sName != 'key') + { + $sValue = ($aRow[$sName] === '') ? ' ' : $aRow[$sName]; + $sHtml .= "\n"; + } + else + { + $sUIPage = cmdbAbstractObject::ComputeUIPage($aParams['class']); + $sHtml .= "\n"; + } + } + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "
    ".$aDef['label']."
    $sValueGetForLink()."\">
    \n"; + return $sHtml; + } + + /** + * Add some Javascript to the header of the page + */ + public function add_script($s_script) + { + $this->a_scripts[] = $s_script; + } + + /** + * Add some Javascript to the header of the page + */ + public function add_ready_script($s_script) + { + // Do nothing silently... this is not supported by this type of page... + } + /** + * Add some CSS definitions to the header of the page + */ + public function add_style($s_style) + { + $this->a_styles[] = $s_style; + } + + /** + * Add a script (as an include, i.e. link) to the header of the page + */ + public function add_linked_script($s_linked_script) + { + $this->a_linked_scripts[] = $s_linked_script; + } + + /** + * Add a CSS stylesheet (as an include, i.e. link) to the header of the page + */ + public function add_linked_stylesheet($s_linked_stylesheet, $s_condition = "") + { + $this->a_linked_stylesheets[] = array( 'link' => $s_linked_stylesheet, 'condition' => $s_condition); + } + + /** + * Add some custom header to the page + */ + public function add_header($s_header) + { + $this->a_headers[] = $s_header; + } + + /** + * Add needed headers to the page so that it will no be cached + */ + public function no_cache() + { + $this->add_header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 + $this->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past + } + + /** + * Build a special kind of TABLE useful for displaying the details of an object from a hash array of data + */ + public function details($aFields) + { + + $this->add($this->GetDetails($aFields)); + } + + /** + * Build a special kind of TABLE useful for displaying the details of an object from a hash array of data + */ + public function GetDetails($aFields) + { + $sHtml = "\n"; + $sHtml .= "\n"; + foreach($aFields as $aAttrib) + { + $sHtml .= "\n"; + // By Rom, for csv import, proposed to show several values for column selection + if (is_array($aAttrib['value'])) + { + $sHtml .= "\n"; + } + else + { + $sHtml .= "\n"; + } + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "
    ".$aAttrib['label']."".implode("", $aAttrib['value'])."".$aAttrib['label']."".$aAttrib['value']."
    \n"; + return $sHtml; + } + + /** + * Outputs (via some echo) the complete HTML page by assembling all its elements + */ + public function output() + { + foreach($this->a_headers as $s_header) + { + header($s_header); + } + $s_captured_output = ob_get_contents(); + ob_end_clean(); + echo "\n"; + echo "\n"; + echo "\n"; + echo "{$this->s_title}\n"; + foreach($this->a_linked_scripts as $s_script) + { + echo "\n"; + } + if (count($this->a_scripts)>0) + { + echo "\n"; + } + foreach($this->a_linked_stylesheets as $a_stylesheet) + { + if ($a_stylesheet['condition'] != "") + { + echo "\n"; + } + } + + if (count($this->a_styles)>0) + { + echo "\n"; + } + echo "\n"; + echo "\n"; + echo $this->s_content; + if (trim($s_captured_output) != "") + { + echo "
    $s_captured_output
    \n"; + } + echo "\n"; + echo "\n"; + } + + /** + * Build a series of hidden field[s] from an array + */ + // By Rom - je verrais bien une serie d'outils pour gerer des parametres que l'on retransmet entre pages d'un wizard... + // ptet deriver webpage en webwizard + public function add_input_hidden($sLabel, $aData) + { + foreach($aData as $sKey=>$sValue) + { + $this->add(""); + } + } +} +?> diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php new file mode 100644 index 0000000000..3ab7b5c1cd --- /dev/null +++ b/application/wizardhelper.class.inc.php @@ -0,0 +1,204 @@ +m_aData['m_sClass']); + foreach($this->m_aData['m_aCurrentValues'] as $iIndex => $value) + { + $sAttCode = array_search($iIndex, $this->m_aData['m_oFieldsMap']); + // Because this is stored in a Javascript array, unused indexes + // are filled with null values + if ( ($sAttCode !== false) && ($value !== null)) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode); + if (($oAttDef->IsLinkSet()) && ($value != '') ) + { + // special handling for lists + // assumes this is handled as an array of objects + // thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...] + $aData = json_decode($value, true); // true means decode as a hash array (not an object) + // Check what are the meaningful attributes + $aFields = $this->GetLinkedWizardStructure($oAttDef); + $sLinkedClass = $oAttDef->GetLinkedClass(); + $aLinkedObjectsArray = array(); + if (!is_array($aData)) + { + echo ("aData: '$aData' (value: '$value')\n"); + } + foreach($aData as $aLinkedObject) + { + $oLinkedObj = MetaModel::NewObject($sLinkedClass); + foreach($aFields as $sLinkedAttCode) + { + if ( isset($aLinkedObject[$sLinkedAttCode]) && ($aLinkedObject[$sLinkedAttCode] !== null) ) + { + $sLinkedAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sLinkedAttCode); + if (($sLinkedAttDef->IsExternalKey()) && ($aLinkedObject[$sLinkedAttCode] != '') ) + { + // For external keys: load the target object so that external fields + // get filled too + $oTargetObj = MetaModel::GetObject($sLinkedAttDef->GetTargetClass(), $aLinkedObject[$sLinkedAttCode]); + $oLinkedObj->Set($sLinkedAttCode, $oTargetObj); + } + else + { + $oLinkedObj->Set($sLinkedAttCode, $aLinkedObject[$sLinkedAttCode]); + } + } + } + $aLinkedObjectsArray[] = $oLinkedObj; + } + $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); + $oObj->Set($sAttCode, $oSet); + } + else if (($oAttDef->IsExternalKey()) && ($value != '') ) + { + // For external keys: load the target object so that external fields + // get filled too + $oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value); + $oObj->Set($sAttCode, $oTargetObj); + } + else + { + $oObj->Set($sAttCode, $value); + } + } + } + return $oObj; + } + + public function GetFieldsForDefaultValue() + { + return $this->m_aData['m_aDefaultValueRequested']; + } + + public function SetDefaultValue($sAttCode, $value) + { + // Protect against a request for a non existing field + if (isset($this->m_aData['m_oFieldsMap'][$sAttCode])) + { + $iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode]; + $oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode); + if ($oAttDef->GetEditClass() == 'List') + { + // special handling for lists + // this as to be handled as an array of objects + // thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...] + // NOT YET IMPLEMENTED !! + $sLinkedClass = $oAttDef->GetLinkedClass(); + $oSet = $value; + $aData = array(); + $aFields = $this->GetLinkedWizardStructure($oAttDef); + while($oSet->fetch()) + { + foreach($aFields as $sLinkedAttCode) + { + $aRow[$sAttCode] = $oLinkedObj->Get($sLinkedAttCode); + } + $aData[] = $aRow; + } + $this->m_aData['m_aDefaultValue'][$iIndex] = json_encode($aData); + + } + else + { + // Normal handling for all other scalar attributes + $this->m_aData['m_aDefaultValue'][$iIndex] = $value; + } + } + } + + public function GetFieldsForAllowedValues() + { + return $this->m_aData['m_aAllowedValuesRequested']; + } + + public function SetAllowedValuesHtml($sAttCode, $sHtml) + { + // Protect against a request for a non existing field + if (isset($this->m_aData['m_oFieldsMap'][$sAttCode])) + { + $iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode]; + $this->m_aData['m_aAllowedValues'][$iIndex] = $sHtml; + } + } + + public function ToJSON() + { + return json_encode($this->m_aData); + } + + static public function FromJSON($sJSON) + { + $oWizHelper = new WizardHelper(); + if (get_magic_quotes_gpc()) + { + $sJSON = stripslashes($sJSON); + } + $aData = json_decode($sJSON, true); // true means hash array instead of object + $oWizHelper->m_aData = $aData; + return $oWizHelper; + } + + protected function GetLinkedWizardStructure($oAttDef) + { + $oWizard = new UIWizard(null, $oAttDef->GetLinkedClass()); + $aWizardSteps = $oWizard->GetWizardStructure(); + $aFields = array(); + $sExtKeyToMeCode = $oAttDef->GetExtKeyToMe(); + // Retrieve as a flat list, all the attributes that are needed to create + // an object of the linked class and put them into a flat array, except + // the attribute 'ext_key_to_me' which is a constant in our case + foreach($aWizardSteps as $sDummy => $aMainSteps) + { + // 2 entries: 'mandatory' and 'optional' + foreach($aMainSteps as $aSteps) + { + // One entry for each step of the wizard + foreach($aSteps as $sAttCode) + { + if ($sAttCode != $sExtKeyToMeCode) + { + $aFields[] = $sAttCode; + } + } + } + } + return $aFields; + } + + static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet) + { + $aSet = json_decode($sJsonSet, true); // true means hash array instead of object + $oSet = CMDBObjectSet::FromScratch($sLinkClass); + foreach($aSet as $aLinkObj) + { + $oLink = MetaModel::NewObject($sLinkClass); + foreach($aLinkObj as $sAttCode => $value) + { + $oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode); + if (($oAttDef->IsExternalKey()) && ($value != '') ) + { + // For external keys: load the target object so that external fields + // get filled too + $oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value); + $oLink->Set($sAttCode, $oTargetObj); + } + $oLink->Set($sAttCode, $value); + } + $oLink->Set($sExtKeyToMe, $oMe->GetKey()); + $oSet->AddObject($oLink); + } + return $oSet; + } +} +?> diff --git a/application/xmlpage.class.inc.php b/application/xmlpage.class.inc.php new file mode 100644 index 0000000000..d3a62adfe3 --- /dev/null +++ b/application/xmlpage.class.inc.php @@ -0,0 +1,36 @@ +add_header("Content-type: text/xml; charset=utf-8"); + $this->add_header("Cache-control: no-cache"); + $this->add_header("Content-location: export.xml"); + $this->add("\n"); + } + + public function output() + { + $this->add_header("Content-Length: ".strlen(trim($this->s_content))); + foreach($this->a_headers as $s_header) + { + header($s_header); + } + echo trim($this->s_content); + } + + public function small_p($sText) + { + } + + public function table($aConfig, $aData, $aParams = array()) + { + } +} +?> diff --git a/business/ChangeMgmt.php b/business/ChangeMgmt.php new file mode 100644 index 0000000000..891f42e280 --- /dev/null +++ b/business/ChangeMgmt.php @@ -0,0 +1,284 @@ + "bizmodel,searchable", + "name" => "Change", + "description" => "Change ticket", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "ticket_status", + "reconc_keys" => array("title"), + "db_table" => "change_ticket", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/change.html", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"TicketID", "description"=>"Refence number ofr this change", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Change", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Change Type", "description"=>"Type of the Change", "allowed_values"=>new ValueSetEnum("Routine, Normal, Emergency"), "sql"=>"type", "default_value"=>"Routine", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("domain", array("label"=>"Domain", "description"=>"Domain for the Change", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"domain", "default_value"=>"Desktop", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason for change", "description"=>"Reason for the Change", "allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("requestor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Requestor", "description"=>"who is requesting this change", "allowed_values"=>null, "sql"=>"requestor_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("requestor_mail", array("label"=>"Requested by", "description"=>"mail of user requesting this change", "allowed_values"=>null, "extkey_attcode"=> 'requestor_id', "target_attcode"=>"email"))); + + + MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'customer_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Validated,Rejected,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + // SetPossibleValues("status",array("Open","Monitored","Closed")); + + MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("label"=>"Creation date", "description"=>"Change creation date", "allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + // dfinir une date de dfaut maintenant, alias creation ou modification du ticket + MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Start date", "description"=>"Time the change is expected to start", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"End Date", "description"=>"Date when the change is supposed to end", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("close_date", array("label"=>"Closed Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Risk Assessment", "description"=>"Impact of the change", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Managed by Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "depends_on"=>array('workgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_name", array("label"=>"Managed by Agent", "description"=>"name of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisorgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Supervisor group", "description"=>"which workgroup is supervising ticket", "allowed_values"=>null, "sql"=>"supervisorgroup_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("supervisorgroup_name", array("label"=>"Supervise by Workgroup", "description"=>"name of the group supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisorgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Supervisor", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"supervisor_id", "is_null_allowed"=>true, "depends_on"=>array('supervisorgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("supervisor_name", array("label"=>"Managed by Supervisor", "description"=>"name of agent supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisor_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("managergroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Manager group", "description"=>"which workgroup is approving ticket", "allowed_values"=>null, "sql"=>"managergroup_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("managergroup_name", array("label"=>"Approved by group", "description"=>"name of workgroup approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'managergroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Manager", "description"=>"who is approving the ticket", "allowed_values"=>null, "sql"=>"manager_id", "is_null_allowed"=>true, "depends_on"=>array('managergroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("manager_name", array("label"=>"Approved by Agent", "description"=>"name of agent approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'manager_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("label"=>"Planned Outage", "description"=>"Flag to define if there is a planned outage", "allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>false, "depends_on"=>array()))); + + + MetaModel::Init_AddAttribute(new AttributeText("change_request", array("label"=>"Change Request", "description"=>"Description of Change required", "allowed_values"=>null, "sql"=>"change_req", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("change_log", array("label"=>"Implementation log", "description"=>"List all action performed during the change", "allowed_values"=>null, "sql"=>"change_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("fallback", array("label"=>"Fallback plan", "description"=>"Instruction to come back to former situation", "allowed_values"=>null, "sql"=>"fallback", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("label"=>"Assignment Count", "description"=>"Number of times this ticket was assigned or reassigned", "allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are impacted by this change", "linked_class"=>"lnkInfraChangeTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("title"); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("domain"); + MetaModel::Init_AddFilterFromAttribute("customer_id"); + MetaModel::Init_AddFilterFromAttribute("requestor_id"); + MetaModel::Init_AddFilterFromAttribute("ticket_status"); + MetaModel::Init_AddFilterFromAttribute("creation_date"); + MetaModel::Init_AddFilterFromAttribute("start_date"); + MetaModel::Init_AddFilterFromAttribute("last_update"); + MetaModel::Init_AddFilterFromAttribute("end_date"); + MetaModel::Init_AddFilterFromAttribute("close_date"); + MetaModel::Init_AddFilterFromAttribute("workgroup_id"); + MetaModel::Init_AddFilterFromAttribute("workgroup_name"); + MetaModel::Init_AddFilterFromAttribute("supervisorgroup_id"); + MetaModel::Init_AddFilterFromAttribute("managergroup_id"); + MetaModel::Init_AddFilterFromAttribute("supervisor_id"); + MetaModel::Init_AddFilterFromAttribute("manager_id"); + MetaModel::Init_AddFilterFromAttribute("agent_id"); + MetaModel::Init_AddFilterFromAttribute("impact"); + MetaModel::Init_AddFilterFromAttribute("assignment_count"); + MetaModel::Init_AddFilterFromAttribute("outage"); + + // doit-on aussi ajouter un filtre sur les extfields li une extkey ? ici le name de l'agent? + + // Display lists + MetaModel::Init_SetZListItems('details', array('name','title', 'customer_id','type','domain','requestor_id','change_request','ticket_status', 'outage','impact', 'last_update', 'start_date','end_date', 'assignment_count', 'workgroup_id','agent_id','supervisorgroup_id','supervisor_id','managergroup_id','manager_id','change_log','fallback')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('name', 'title', 'customer_id', 'ticket_status','outage','start_date','type')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'title', 'customer_id', 'ticket_status','type', 'outage','requestor_id','workgroup_id','agent_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'customer_id', 'ticket_status','type', 'outage','workgroup_id','agent_id')); // Criteria of the advanced search form + + // State machine + MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_MANDATORY, 'title' => OPT_ATT_MANDATORY, 'reason' => OPT_ATT_MANDATORY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, + 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("Validated", array("label"=>"Validated", "description"=>"Ticket is approved", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'managergroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Rejected", array("label"=>"Rejected", "description"=>"This ticket is not approved", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("PlannedScheduled", array("label"=>"Planned&Scheduled", "description"=>"Evaluation is done for this change", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_MANDATORY, 'workgroup_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MUSTCHANGE,'fallback' => OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Approved", array("label"=>"Approved", "description"=>"Ticket is approved by CAB", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("NotApproved", array("label"=>"Not Approved", "description"=>"Ticket has not been approved by CAB", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("Implemented", array("label"=>"Implementation", "description"=>"Work is in progress for this ticket", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("Monitored", array("label"=>"Monitored", "description"=>"Change performed is now monitored", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('customer_id' => OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY))); + + MetaModel::Init_DefineStimulus("ev_validate", new StimulusUserAction(array("label"=>"Validate this change", "description"=>"Make sure it is a valid change request"))); + MetaModel::Init_DefineStimulus("ev_reject", new StimulusUserAction(array("label"=>"Reject this change", "description"=>"This change request is rejected because it is a non valid one"))); + MetaModel::Init_DefineStimulus("ev_reopen", new StimulusUserAction(array("label"=>"Modify this change", "description"=>"Update change request to make it valid"))); + MetaModel::Init_DefineStimulus("ev_plan", new StimulusUserAction(array("label"=>"Plan this change", "description"=>"Plan and Schedule this change for validation"))); + MetaModel::Init_DefineStimulus("ev_approve", new StimulusUserAction(array("label"=>"Approve this change", "description"=>"This change is approved by CAB"))); + MetaModel::Init_DefineStimulus("ev_replan", new StimulusUserAction(array("label"=>"Update planning and schedule", "description"=>"Modify Plan and Schedule in order to have this change re-validated"))); + MetaModel::Init_DefineStimulus("ev_notapprove", new StimulusUserAction(array("label"=>"Not approve this change", "description"=>"This change is not approved by CAB"))); + MetaModel::Init_DefineStimulus("ev_implement", new StimulusUserAction(array("label"=>"Implement this change", "description"=>"Implementation pahse for current change"))); + MetaModel::Init_DefineStimulus("ev_monitor", new StimulusUserAction(array("label"=>"Monitor this change", "description"=>"Starting monitoring period for this change"))); + MetaModel::Init_DefineStimulus("ev_finish", new StimulusUserAction(array("label"=>"Close change", "description"=>"Change is done, and can be closed"))); + + MetaModel::Init_DefineTransition("New", "ev_validate", array("target_state"=>"Validated", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("New", "ev_reject", array("target_state"=>"Rejected", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Rejected", "ev_reopen", array("target_state"=>"New", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Validated", "ev_plan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("PlannedScheduled", "ev_approve", array("target_state"=>"Approved", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("PlannedScheduled", "ev_notapprove", array("target_state"=>"NotApproved", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("NotApproved", "ev_replan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Approved", "ev_implement", array("target_state"=>"Implemented", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Implemented", "ev_monitor", array("target_state"=>"Monitored", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Implemented", "ev_finish", array("target_state"=>"Closed", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Monitored", "ev_finish", array("target_state"=>"Closed", "actions"=>array(), "user_restriction"=>null)); + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('customer_id', $oGenerator->GetOrganizationId()); + $this->Set('title', $oGenerator->GenerateString("enum(Site,Server,Line)| |enum(is down,is flip-flopping,is not responding)")); + $this->Set('agent_id', $oGenerator->GenerateKey("bizPerson", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('ticket_status', $oGenerator->GenerateString("enum(Open,Closed,Closed,Monitored)")); + $this->Set('start_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); + $this->Set('last_update', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); + $this->Set('end_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); + } + + + + // State machine actions + public function IncrementAssignmentCount($sStimulusCode) + { + $this->Set('assignment_count', $this->Get('assignment_count') + 1); + return true; + } + + public function SetClosureDate($sStimulusCode) + { + $this->Set('end_date', time()); + return true; + } + public function SetLastUpDate($sStimulusCode) + { + $this->Set('last_update', time()); + return true; + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Change Ticket +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInfraChangeTicket extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Infra Change Ticket", + "description" => "Infra impacted by a Change ticket", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "impact", // ???? + "state_attcode" => "", + "reconc_keys" => array("impact"), // ???? + "db_table" => "infra_changeticket", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("infra_id"); + MetaModel::Init_AddFilterFromAttribute("ticket_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'ticket_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'ticket_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } +} +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any contact and a Contract +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkContactChange extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "ContactChangeLink", + "description" => "Contact associated to a change", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "role", // ???? + "state_attcode" => "", + "reconc_keys" => array("role"), // ???? + "db_table" => "contact_change", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"The contact linked to contract", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("label"=>"Contact E-mail", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("change_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "label"=>"Change Ticket", "description"=>"Change ticket ID", "allowed_values"=>null, "sql"=>"change_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("change_number", array("label"=>"change number", "description"=>"Ticket number for this change", "allowed_values"=>null, "extkey_attcode"=> 'change_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this contact for this change", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("change_id"); + MetaModel::Init_AddFilterFromAttribute("contact_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('change_id', 'contact_id', 'role')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('change_id', 'contact_id', 'role')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('change_id', 'contact_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('change_id', 'contact_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('contract_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('contact_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('role', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } + +} + +?> diff --git a/business/Changes-04-Sep-2007.php b/business/Changes-04-Sep-2007.php new file mode 100644 index 0000000000..e8097d75e0 --- /dev/null +++ b/business/Changes-04-Sep-2007.php @@ -0,0 +1,32 @@ +Changements principaux: +- la classe AbstractObject est sortie du biz model +- join_type remplac par is_null_allowed (plac la fin pour tre + facile retrouver) +- j'ai enlev toute la classe logLocatedObject qui tait en commentaire +- Enlev 'address' de l'advanced search sur une location car ce n'est plus un critre de recherche possible (remplac par country) +- Ajout des critres de recherche sur bizCircuit +- Ajout les ZList sur bizCircuit +- Ajout les Zlist pour bizInterface +- Ajout les Zlist pour lnkInfraInfra +- Ajout les Zlist pour lnkInfraTicket + +Dans AbstractObject: dsactiv l'affichage des contacts lis qui ne marche pas pour les tickets. + +Bug fix ? +- J'ai rajout un blindage if (is_object($proposedValue) &&... dans AttributeDate::MakeRealValue mais je ne comprends pas d'o sort la classe DateTime... et pourtant il y en a... + +Amliorations: +- Ajouter une vrification des ZList (les attributs/critresde recherche dclars dans la liste existent-ils pour cet objet) + +Ne marche pas: +- Objets avec des clefs externes vides +- Enums !!!! + +Data Generator: +Organization '1' updated. +5 Location objects created. +19 PC objects created. +19 Network Device objects created. +42 Person objects created. +6 Incident objects created. +17 Infra Group objects created. +34 Infra Infra objects created. diff --git a/business/KEDB.php b/business/KEDB.php new file mode 100644 index 0000000000..ec8057f32c --- /dev/null +++ b/business/KEDB.php @@ -0,0 +1,165 @@ + "bizmodel,searchable", + "name" => "Known Error", + "description" => "Error documented for a known issue", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("cust_id", "name"), // inherited attributes + "db_table" => "known_error", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/knownError.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name to identify this error", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("cust_id", array("targetclass"=>"bizOrganization", "label"=>"Organization", "description"=>"Organization for this known error", "allowed_values"=>null, "sql"=>"cust_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("cust_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'cust_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeText("symptom", array("label"=>"Symptom", "description"=>"Description of this error", "allowed_values"=>null, "sql"=>"symptom", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("root_cause", array("label"=>"Root cause", "description"=>"Original cause for this known error", "allowed_values"=>null, "sql"=>"rootcause", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("workaround", array("label"=>"Work around", "description"=>"Work around to fix this error", "allowed_values"=>null, "sql"=>"workaround", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("solution", array("label"=>"Solution", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"solution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("error_code", array("label"=>"Error Code", "description"=>"Key word to identify error", "allowed_values"=>null, "sql"=>"error_code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("domain", array("label"=>"Domain", "description"=>"Domain for this known error, network, desktop, ...", "allowed_values"=>new ValueSetEnum("Network, Server, Application, Desktop"), "sql"=>"domain", "default_value"=>"Application", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("vendor", array("label"=>"Vendor", "description"=>"Vendor concerned by this known error", "allowed_values"=>null, "sql"=>"vendor", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"Model concerned by this known error, it may be an application, a device ...", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("version", array("label"=>"Version", "description"=>"Version related to model impacted by known error", "allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("cust_id"); + MetaModel::Init_AddFilterFromAttribute("cust_name"); + MetaModel::Init_AddFilterFromAttribute("error_code"); + MetaModel::Init_AddFilterFromAttribute("domain"); + + + + + + MetaModel::Init_SetZListItems('details', array('name', 'cust_id','error_code','domain','vendor','model','version', 'symptom','root_cause','workaround','solution')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'cust_id','error_code', 'symptom')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'error_code','domain')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'cust_id','error_code', 'error_code','symptom')); // Criteria of the advanced search form + + } + + // State machine actions + public function IncrementVersion($sStimulusCode) + { + $this->Set('version_number', $this->Get('version_number') + 1); + return true; + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Known Error +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInfraError extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "InfraErrorLinks", + "description" => "Infra related to a known error", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "", // ???? + "state_attcode" => "", + "reconc_keys" => array("infra_id","error_id"), // ???? + "db_table" => "infra_error_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "jointype"=> '', "label"=>"Error name", "description"=>"Error id", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("label"=>"Error name", "description"=>"Name of the error", "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddFilterFromAttribute("infra_id"); + MetaModel::Init_AddFilterFromAttribute("error_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('infra_id', 'error_id')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('infra_id', 'infra_status','error_id')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'error_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'error_id')); // Criteria of the advanced search form + } + + +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Contract and a Document +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkDocumentError extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "DocumentsErrorLinks", + "description" => "A link between a document and a known error", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "link_type", + "state_attcode" => "", + "reconc_keys" => array("doc_name", "error_name"), + "db_table" => "documents_error_link", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document Name", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "label"=>"Error", "description"=>"Error linked to this document", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("label"=>"Error name", "description"=>"name of the linked error", "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link_type", "description"=>"Type of the link", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("doc_id"); + MetaModel::Init_AddFilterFromAttribute("doc_name"); + MetaModel::Init_AddFilterFromAttribute("error_id"); + MetaModel::Init_AddFilterFromAttribute("error_name"); + MetaModel::Init_AddFilterFromAttribute("link_type"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for a list + } +} + + +?> diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php new file mode 100644 index 0000000000..cbb67ca5fe --- /dev/null +++ b/business/ServiceMgmt.business.php @@ -0,0 +1,261 @@ + "bizmodel,searchable", + "name" => "Contract", + "description" => "Contract signed by an organization", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "status", + "reconc_keys" => array("customer_id", "name"), // inherited attributes + "db_table" => "contracts", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/contract.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name of the contract", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"Customer for this contract", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"name of the Customer", "allowed_values"=>null, "extkey_attcode"=> 'customer_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_id", array("targetclass"=>"bizOrganization", "label"=>"Provider", "description"=>"Provider for this contract", "allowed_values"=>null, "sql"=>"provider_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("label"=>"Provider", "description"=>"name of the service provider", "allowed_values"=>null, "extkey_attcode"=> 'provider_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("service_name", array("label"=>"Service Name", "description"=>"Name of service for this contract", "allowed_values"=>null, "sql"=>"service_name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team managing this contract", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team", "description"=>"name of the team managing this contract", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("service_level", array("label"=>"Service Level", "description"=>"Level of service for this contract", "allowed_values"=>new ValueSetEnum("Gold,Silver,Bronze"), "sql"=>"service_level", "default_value"=>"Bronze", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("cost_unit", array("label"=>"Cost unit", "description"=>"Cost unit to compute global cost for this contract", "allowed_values"=>new ValueSetEnum("Devices,Persons,Applications,Global"), "sql"=>"cost_unit", "default_value"=>"Global", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("cost_freq", array("label"=>"Cost frequency", "description"=>"Frequency of cost for this contract", "allowed_values"=>new ValueSetEnum("Monthly,Yearly,Once"), "sql"=>"cost_freq", "default_value"=>"Once", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("cost", array("label"=>"Cost", "description"=>"Cost of this contract", "allowed_values"=>null, "sql"=>"cost", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("label"=>"Currency", "description"=>"Currency of cost for this contract", "allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("label"=>"Date of move to production", "description"=>"Date when the contract is on production", "allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("end_prod", array("label"=>"Date of end of production", "description"=>"Date when the contract is stopped", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the contract", "allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production, Notice, Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the contract", "allowed_values"=>new ValueSetEnum("Hardware,Software,Support,Licence"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeInteger("version_number", array("label"=>"Version number", "description"=>"Revision number for this contract", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); + + + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("service_name"); + MetaModel::Init_AddFilterFromAttribute("provider_id"); + MetaModel::Init_AddFilterFromAttribute("customer_id"); + MetaModel::Init_AddFilterFromAttribute("team_id"); + MetaModel::Init_AddFilterFromAttribute("team_name"); + MetaModel::Init_AddFilterFromAttribute("service_level"); + MetaModel::Init_AddFilterFromAttribute("end_prod"); + MetaModel::Init_AddFilterFromAttribute("status"); + MetaModel::Init_AddFilterFromAttribute("version_number"); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("type"); + + + // Life cycle + MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created contract", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Negotiating", array("label"=>"Negotiating", "description"=>"The contract is being worked on", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Signed", array("label"=>"Signed", "description"=>"The contract has been signed", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The contract is effective in production", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Notice", array("label"=>"Notice", "description"=>"The contract is about to be terminated", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Finished", array("label"=>"Finished", "description"=>"The contract is terminated", "attribute_inherit"=>null, + "attribute_list"=>array())); + + MetaModel::Init_DefineStimulus("ev_freeze_version", new StimulusUserAction(array("label"=>"Freeze this version", "description"=>"This version of the contract is published"))); + MetaModel::Init_DefineStimulus("ev_sign", new StimulusUserAction(array("label"=>"Sign this contract", "description"=>"This contract is being signed"))); + MetaModel::Init_DefineStimulus("ev_begin", new StimulusUserAction(array("label"=>"Move to production", "description"=>"The contract becomes applicable in production"))); + MetaModel::Init_DefineStimulus("ev_notice", new StimulusUserAction(array("label"=>"Start notice period", "description"=>"The end date of the contract is approaching"))); + MetaModel::Init_DefineStimulus("ev_terminate", new StimulusUserAction(array("label"=>"Ends this contract", "description"=>"The contract is ending"))); + MetaModel::Init_DefineStimulus("ev_elapsed", new StimulusUserAction(array("label"=>"Times up [Do not click!]", "description"=>"The contract over"))); + + MetaModel::Init_DefineTransition("New", "ev_freeze_version", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Negotiating", "ev_freeze_version", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Negotiating", "ev_sign", array("target_state"=>"Signed", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Negotiating", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Signed", "ev_freeze_version", array("target_state"=>"Signed", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Signed", "ev_begin", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Signed", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Production", "ev_freeze_version", array("target_state"=>"Production", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Production", "ev_elapsed", array("target_state"=>"Notice", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Production", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Notice", "ev_elapsed", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Notice", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); + + + MetaModel::Init_SetZListItems('details', array('name', 'status', 'customer_id', 'service_name','provider_id','type','description','team_id','service_level','cost','currency','cost_unit','cost_freq','move2prod_date','end_prod', 'version_number')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'customer_id', 'provider_id','service_name','service_level','type')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status','service_name','provider_id','team_name','service_level','type')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'service_name','provider_id','team_name', 'service_level', 'customer_id')); // Criteria of the advanced search form + + } + + // State machine actions + public function IncrementVersion($sStimulusCode) + { + $this->Set('version_number', $this->Get('version_number') + 1); + return true; + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Contract +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInfraContract extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "InfraContractLinks", + "description" => "Infra covered by a contract", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "coverage", // ???? + "state_attcode" => "", + "reconc_keys" => array("infra_id","contract_id"), // ???? + "db_table" => "infra_contract_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "label"=>"Contract name", "description"=>"Contract id", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract name", "description"=>"Name of the contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("coverage", array("label"=>"coverage", "description"=>"coverage for the given infra", "allowed_values"=>null, "sql"=>"coverage", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("service_level", array("label"=>"service level", "description"=>"service level for the given infra", "allowed_values"=>null, "sql"=>"sla", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + + MetaModel::Init_AddFilterFromAttribute("infra_id"); + MetaModel::Init_AddFilterFromAttribute("contract_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('infra_id', 'contract_id', 'coverage','service_level')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('infra_id', 'infra_status','contract_id' , 'coverage','service_level')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'contract_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'contract_id')); // Criteria of the advanced search form + } + + +} +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any contact and a Contract +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkContactContract extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "ContactContractLink", + "description" => "Contact associated to a contract", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "role", // ???? + "state_attcode" => "", + "reconc_keys" => array("role"), // ???? + "db_table" => "contact_Contract", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"The contact linked to contract", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("label"=>"Contact E-mail", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "label"=>"Contract", "description"=>"Contract ID", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract name", "description"=>"Name of the contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this contact for this contract", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("contract_id"); + MetaModel::Init_AddFilterFromAttribute("contact_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('contract_id', 'contact_id', 'role')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('contract_id', 'contact_id', 'role')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('contract_id', 'contact_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('contract_id', 'contact_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('contract_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('contact_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('role', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } + +} +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Contract and a Document +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkDocumentContract extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "DocumentsContractLinks", + "description" => "A link between a document and another contract", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "link_type", + "state_attcode" => "", + "reconc_keys" => array("doc_name", "contract_name"), + "db_table" => "documents_contracts", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document Name", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "label"=>"Contract", "description"=>"Contract linked to this document", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"contract name", "description"=>"name of the linked contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link_type", "description"=>"Type of the link", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("doc_id"); + MetaModel::Init_AddFilterFromAttribute("doc_name"); + MetaModel::Init_AddFilterFromAttribute("contract_id"); + MetaModel::Init_AddFilterFromAttribute("contract_name"); + MetaModel::Init_AddFilterFromAttribute("link_type"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('doc_id', 'contract_name', 'link_type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('doc_id', 'contract_name', 'link_type')); // Attributes to be displayed for a list + } +} + + +?> diff --git a/business/business_itopbegins.class.inc.php b/business/business_itopbegins.class.inc.php new file mode 100644 index 0000000000..5ac001c57c --- /dev/null +++ b/business/business_itopbegins.class.inc.php @@ -0,0 +1,261 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + + +/////////////////////////////////////////////////////////////////////////////// +// Business implementation demo +/////////////////////////////////////////////////////////////////////////////// + + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbContact extends CMDBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "klassContact", + "description" => "klass contact description", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "att_contact_name", + "state_attcode" => "", + "reconc_keys" => array("att_contact_name"), + "db_table" => "contact", + "db_key_field" => "contactid", + "db_finalclass_field" => "actualclass", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("att_contact_name", array("label"=>"name of the contact", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeInteger("att_contact_availability", array("label"=>"degree of availability in percent", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"availability"))); + MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("att_contact_name"); + MetaModel::Init_AddFilterFromAttribute("att_contact_availability"); + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbPerson extends cmdbContact +{ + public static function Init() + { + $oValsDunsNumber = new ValueSetObjects("cmdbCompany: att_company_dunsnumber Begins with '$[duns_prm::]'", "att_company_dunsnumber", array("att_company_dunsnumber"=>true)); + + $aParams = array + ( + "category" => "blah", + "name" => "klassPerson", + "description" => "klass person description", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "att_contact_name", + "state_attcode" => "", + "reconc_keys" => array("att_contact_name"), + "db_table" => "person", + "db_key_field" => "personid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("att_person_email", array("label"=>"iMaile", "description"=>"imelle", "allowed_values"=>$oValsDunsNumber, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeString("att_person_name", array("label"=>"secName", "description"=>"secondary name", "allowed_values"=>new ValueSetEnum(array("nom1", "nom2", "nom10", "no", "noms", "")), "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("att_person_email"); + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbSubcontractor extends cmdbPerson +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "klassSubcontractor", + "description" => "klass subcontractor description", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "att_contact_name", + "state_attcode" => "", + "reconc_keys" => array("att_contact_name"), + "db_table" => "subcontractor", + "db_key_field" => "subcontractorid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("att_contractinfo", array("label"=>"contract info", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"contractinfo"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_provider", array("label"=>"ssii", "description"=>"blah", "allowed_values"=>null, "sql"=>"provider", "targetclass"=>"cmdbProvider", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_provider_ref", array("label"=>"ref", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_provider", "target_attcode"=>"att_provider_ref"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_tutor", array("label"=>"tutor", "description"=>"blah", "allowed_values"=>null, "sql"=>"tutor", "targetclass"=>"cmdbPerson", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_email", array("label"=>"tutor email", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_email"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_secondname", array("label"=>"2ndname (ext field)", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_name"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("extatt_subcontractor_tutor_secondname"); + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbCrowd extends cmdbObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "klassCrowd", + "description" => "klass crowd description", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "att_crowd_peoplecount", + "state_attcode" => "", + "reconc_keys" => array("att_crowd_peoplecount"), + "db_table" => "crowd", + "db_key_field" => "crowdid", + "db_finalclass_field" => "crowdclass", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeInteger("att_crowd_peoplecount", array("label"=>"people count", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"peoplecount"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("att_crowd_peoplecount"); + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbCompany extends cmdbCrowd +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "klassCompany", + "description" => "klass company description", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "att_company_dunsnumber", + "state_attcode" => "", + "reconc_keys" => array("att_company_dunsnumber"), + "db_table" => "company", + "db_key_field" => "companyid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("att_company_dunsnumber", array("label"=>"duns number", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"dunsnumber"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("att_company_dunsnumber"); + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbProvider extends cmdbCompany +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "klassProvider", + "description" => "klass provider description", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "att_provider_ref", + "state_attcode" => "", + "reconc_keys" => array("att_provider_ref"), + "db_table" => "provider", + "db_key_field" => "providerid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeInteger("att_provider_ref", array("label"=>"provider ref", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"providerref"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("att_provider_ref"); + } +} + + +?> diff --git a/business/business_test.class.inc.php b/business/business_test.class.inc.php new file mode 100644 index 0000000000..3a51b8f933 --- /dev/null +++ b/business/business_test.class.inc.php @@ -0,0 +1,366 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +/////////////////////////////////////////////////////////////////////////////// +// Business implementation demo +/////////////////////////////////////////////////////////////////////////////// + +MetaModel::RegisterRelation("Potes", array("description"=>"ceux dont l'email ressemble au mien", "verb_down"=>"est pote de", "verb_up"=>"est pote de")); + + +MetaModel::RegisterZList("list1", array("description"=>"une premiere list, just for fun", "type"=>"attributes")); +MetaModel::RegisterZList("list2", array("description"=>"la secunda e meliora", "type"=>"attributes")); +MetaModel::RegisterZList("list3", array("description"=>"la variante qui tue", "type"=>"filters")); + + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbObjectHomeMade extends cmdbObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "anyObject", + "description" => "std object", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(""), + "db_table" => "", + "db_key_field" => "", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + } + + public static function GetRelationQueries($sRelCode) + { + //trigger_error("GetRelationQueries: cmdbObjectHomeMade"); + switch ($sRelCode) + { + case "Potes": + $aRels = array("xxxx" => array("sQuery"=>"cmdbContact: pkey = 40", "bPropagate"=>true, "iDistance"=>3)); + return $aRels; + } + } +} + + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbContact extends cmdbObjectHomeMade +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Contact", + "description" => "Un object que l'on peut communiquer avec", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "etat", + "reconc_keys" => array("name"), + "db_table" => "contact", + "db_key_field" => "contactid", + "db_finalclass_field" => "actualclass", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("etat", array("label"=>"l'etat", "description"=>"les etats d'ame d'eric", "allowed_values"=>new ValueSetEnum('justborn, 15, 21'), "sql"=>"etat", "default_value"=>"justborn", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"nom", "description"=>"ze equipe", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"iMaile", "description"=>"imelle", "allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("owner", array("label"=>"owned by", "description"=>"organization owning the team", "allowed_values"=>null, "sql"=>"ownerorg", "targetclass"=>"cmdbOrga", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ownername", array("label"=>"owned by", "description"=>"name of organization owning the team", "allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_name_"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ownertnut", array("label"=>"owntnut", "description"=>"blah tnut blah", "allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_dunsnumber_"))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSet("myworkshops", array("label"=>"held workshops", "description"=>"blah tnut blah", "depends_on"=>array(), "linked_class"=>"cmdbLiens", "ext_key_to_me"=>"tocontact", "count_min"=>1, "count_max"=>10, "allowed_values"=>null))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("owner"); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("ownername"); + + MetaModel::Init_SetZListItems("list1", array("name", "email")); + MetaModel::Init_SetZListItems("list2", array()); + MetaModel::Init_SetZListItems("list3", array("ownername")); + + MetaModel::Init_DefineState("justborn", array("label"=>"just born", "description"=>"too young to die", "attribute_inherit"=>null, "attribute_list"=>array("owner"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("15", array("label"=>"student", "description"=>"stupid age", "attribute_inherit"=>"justborn", "attribute_list"=>array("owner"=>OPT_ATT_MUSTPROMPT, "email"=>OPT_ATT_MUSTPROMPT))); + MetaModel::Init_DefineState("21", array("label"=>"old", "description"=>"one foot in the grave", "attribute_inherit"=>"15", "attribute_list"=>array("email"=>OPT_ATT_READONLY|OPT_ATT_MUSTCHANGE))); + + MetaModel::Init_DefineStimulus("toschool", new StimulusUserAction(array("label"=>"go to school", "description"=>"start learning stupid things"))); + MetaModel::Init_DefineStimulus("raise", new StimulusUserAction(array("label"=>"grow!", "description"=>"eat tons of BigMACs"))); + + MetaModel::Init_DefineTransition("justborn", "toschool", array("target_state"=>"15", "actions"=>array('MyLifecycleHandler', 'MyLifecycleHandler2'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("15", "raise", array("target_state"=>"21", "actions"=>null, "user_restriction"=>null)); + } + + public static function GetRelationQueries($sRelCode) + { + //trigger_error("GetRelationQueries: cmdbContact"); + switch ($sRelCode) + { + case "Potes": + $aRels = array( + "zz1" => array("sQuery"=>"cmdbContact: name Begins with '\$[this.name::]' AND pkey != \$[this.pkey::]", "bPropagate"=>false, "iDistance"=>3), + "zz2" => array("sQuery"=>"cmdbContact: owner = \$[this.owner::] AND owner != 2", "bPropagate"=>false, "iDistance"=>3), + ); + return array_merge($aRels, parent::GetRelationQueries($sRelCode)); + } + } + + public function MyLifecycleHandler($sStimulusCode) + { + echo "

    youhou!

    "; + return true; + } + public function MyLifecycleHandler2($sStimulusCode) + { + echo "

    ... les papous...

    "; + return true; + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbTeam extends cmdbContact +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Equipado", + "description" => "Un regroupement de gens", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "email", + "state_attcode" => "", + "reconc_keys" => array("email"), + "db_table" => "team", + "db_key_field" => "teamid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_OverloadAttributeParams("email", array("label"=>"email2", "description"=>"emailleu22")); + MetaModel::Init_AddAttribute(new AttributeInteger("headcount", array("label"=>"nombre", "description"=>"combien ils sont", "allowed_values"=>null, "sql"=>"headcount", "default_value"=>654321, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("headcount"); + + MetaModel::Init_SetZListItems("noneditable", array("name")); + } + + public function ComputeValues() + { + //echo "Set(), function ComputeValues has been found for ".get_class($this)."
    \n"; + $this->Set("name", $this->Get("email")." and ".$this->Get("headcount")); + } + + public static function GetRelationQueries($sRelCode) + { + //trigger_error("GetRelationQueries: cmdbTeam"); + switch ($sRelCode) + { + case "Potes": + //$aRels = array("Relies on" => array("sQuery"=>"cmdbContact: name Begins with 'Louis'", "bPropagate"=>false, "iDistance"=>3)); + return array_merge(array(), parent::GetRelationQueries($sRelCode)); + } + } +} + + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbOrga extends cmdbObjectHomeMade +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Organization", + "description" => "Une entite qui possede des choses", + "key_type" => "", + "key_label" => "", + "name_attcode" => "_name_", + "state_attcode" => "", + "reconc_keys" => array("_name_"), + "db_table" => "organization", + "db_key_field" => "orgid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("_name_", array("label"=>"namo", "description"=>"official company name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("_status_", array("label"=>"step", "description"=>"step or status, etc.", "allowed_values"=>null, "sql"=>"status", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumber_", array("label"=>"duns debile number", "description"=>"une bonne idee a OVSD", "allowed_values"=>null, "sql"=>"dunsnumber", "default_value"=>99007, "is_null_allowed"=>false, "depends_on"=>array()))); +// not yet allowed MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumberBY2_", array("label"=>"dummy duns", "description"=>"deux fois plus debile", "allowed_values"=>null, "sql"=>"dunsnumber * 3.141592654"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("_name_"); + + MetaModel::Init_SetZListItems("list1", array("_status_")); + MetaModel::Init_SetZListItems("list2", array()); + MetaModel::Init_SetZListItems("list3", array("_name_")); + } + +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbLiens extends cmdbObjectHomeMade +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Liens_entre_contacts_et_workshop", + "description" => "Une entite qui lie des contacts et workshops", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "function", + "state_attcode" => "", + "reconc_keys" => array("function"), + "db_table" => "role_ws", + "db_key_field" => "linkid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("function", array("label"=>"fonction", "description"=>"la fonction...", "allowed_values"=>null, "sql"=>"function", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("a1", array("label"=>"a1", "description"=>"a1", "allowed_values"=>null, "sql"=>"a1", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("a2", array("label"=>"a1", "description"=>"a2", "allowed_values"=>null, "sql"=>"a2", "default_value"=>"XXXX", "is_null_allowed"=>true, "depends_on"=>array()))); + + // What makes it being a link... + MetaModel::Init_AddAttribute(new AttributeExternalKey("toworkshop", array("label"=>"participates in", "description"=>"workshop in wich the person is participating", "allowed_values"=>null, "sql"=>"ws_id", "targetclass"=>"cmdbWorkshop", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ws_info", array("label"=>"name", "description"=>"namedescription", "allowed_values"=>null, "extkey_attcode"=>"toworkshop", "target_attcode"=>"namitus"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("tocontact", array("label"=>"is held by", "description"=>"people involved in that mess", "allowed_values"=>null, "sql"=>"contactid", "targetclass"=>"cmdbContact", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_info", array("label"=>"name", "description"=>"namedescription", "allowed_values"=>null, "extkey_attcode"=>"tocontact", "target_attcode"=>"name"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("function"); + + MetaModel::Init_SetZListItems("list1", array("toworkshop", "contact_info")); + MetaModel::Init_SetZListItems("list2", array("function")); + MetaModel::Init_SetZListItems("list3", array("function")); + } + + public static function GetRelationQueries($sRelCode) + { + throw new CoreException("GetRelationQueries: cmdbLiens"); + return array("Relies on" => array("sQuery"=>"", "bPropagate"=>true, "iDistance"=>3)); + } +} + +/** + * blah blah + * + * @package iTopUnitTests + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class cmdbWorkshop extends cmdbObjectHomeMade +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Workshop", + "description" => "Une entite qui pond des theories insensees", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "namitus", + "state_attcode" => "", + "reconc_keys" => array("namitus"), + "db_table" => "workshop", + "db_key_field" => "ws_id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("namitus", array("label"=>"namo", "description"=>"nom imbitique pour pondeurs de debilites", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("namitus"); + + MetaModel::Init_SetZListItems("list1", array("namitus")); + MetaModel::Init_SetZListItems("list2", array()); + MetaModel::Init_SetZListItems("list3", array("namitus")); + } + + public static function GetRelationQueries($sRelCode) + { + throw new CoreException("GetRelationQueries: cmdbWorkshop"); + return array("Relies on" => array("sQuery"=>"", "bPropagate"=>true, "iDistance"=>3)); + } +} + + +?> diff --git a/business/data.samples.inc.php b/business/data.samples.inc.php new file mode 100644 index 0000000000..8f76a3bd17 --- /dev/null +++ b/business/data.samples.inc.php @@ -0,0 +1,4845 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +$aCompanies = array(); +$aCompanies[] = array('name' => "iTop", 'domain' => "itop.com", 'code' => "ITOP"); +$aCompanies[] = array('name' => "R-Skin Design", 'domain' => "rskindesign.com", 'code' => "RSKIN"); +$aCompanies[] = array('name' => "Pen Shop", 'domain' => "penshop.com", 'code' => "PENSHOP"); +$aCompanies[] = array('name' => "Joyant", 'domain' => "joyan.com", 'code' => "JOYAN"); +$aCompanies[] = array('name' => "Inhabitants", 'domain' => "inhabitants.org", 'code' => "INH"); +$aCompanies[] = array('name' => "Electric Poulp", 'domain' => "epoulp.com", 'code' => "EPOULP"); +$aCompanies[] = array('name' => "Well Steam", 'domain' => "wellsteam.com", 'code' => "WEELSTM"); +$aCompanies[] = array('name' => "Express Engine", 'domain' => "expressengine.com", 'code' => "XPRESS"); +$aCompanies[] = array('name' => "Electric Bytes", 'domain' => "ebytes.com", 'code' => "EBYTES"); +$aCompanies[] = array('name' => "Iceberg", 'domain' => "iceberg.com", 'code' => "ICEBERG"); +$aCompanies[] = array('name' => "VThird", 'domain' => "vthird.com", 'code' => "V3RD"); +$aCompanies[] = array('name' => "AAssurance", 'domain' => "aasurance.com", 'code' => "AASSUR"); +$aCompanies[] = array('name' => "Webgets", 'domain' => "webgets.com", 'code' => "WEBGETS"); +$aCompanies[] = array('name' => "Brianair", 'domain' => array("brianair.co.uk", "brianair.com", "britanair.com"), 'code' => "BRAIR"); +$aCompanies[] = array('name' => "NetCMDB", 'domain' => array("netcmdb.com", "netcmdb.net", "octopus.com"), 'code' => "NECMDB"); +$aCompanies[] = array('name' => "Zen Garden", 'domain' => "zengarden.net", 'code' => "ZEN"); +$aCompanies[] = array('name' => "T-Supply", 'domain' => "tsupply.de", 'code' => "TSUPPLY"); +$aCompanies[] = array('name' => "Berliner Druckmaschinen", 'domain' => "bdm.de", 'code' => "BDM"); +$aCompanies[] = array('name' => "ZKS", 'domain' => "zks.com", 'code' => "ZKS"); +$aCompanies[] = array('name' => "Air Amrique", 'domain' => "airamerique.fr", 'code' => "AIRAM"); +$aCompanies[] = array('name' => "Radio Mongo", 'domain' => "radio-mongo.fr", 'code' => "MONGO"); +$aCompanies[] = array('name' => "Radio Andorre", 'domain' => "radio.ad", 'code' => "RADAND"); +$aCompanies[] = array('name' => "Idahos Mutual Life Insurance", 'domain' => "idahosmutual.com", 'code' => "IMLI"); +$aCompanies[] = array('name' => "Amott Laboratories", 'domain' => "amott.com", 'code' => "AMOTT"); +$aCompanies[] = array('name' => "Hereford Financial Services Group", 'domain' => "hfsg.com", 'code' => "HFSG"); +$aCompanies[] = array('name' => "Techni Data Corp", 'domain' => array("technidata.com", "techniservices.com"), 'code' => "TECHNI"); +$aCompanies[] = array('name' => "Southwestern Mutual Life Insurance", 'domain' => array("swmutual.com", "swmutual-loans.com", "insurance.swmutual.com"), 'code' => "SWMUTUAL"); +$aCompanies[] = array('name' => "American Solar Power", 'domain' => "solarpower.com", 'code' => "ASP"); +$aCompanies[] = array('name' => "Altena", 'domain' => "altena.fr", 'code' => "ALTENA"); +$aCompanies[] = array('name' => "Groupe Commeco", 'domain' => "commeco.fr", 'code' => "COMMECO"); +$aCompanies[] = array('name' => "Virages", 'domain' => array("virages.fr", "virages.corp.net"), 'code' => "VIRAGES"); +$aCompanies[] = array('name' => "Ciments Lorrains", 'domain' => "cimlor.fr", 'code' => "CIMLOR"); +$aCompanies[] = array('name' => "Durand SA", 'domain' => "durand.fr", 'code' => "DURAND"); +$aCompanies[] = array('name' => "Transports Jeanton", 'domain' => "jeanton.fr", 'code' => "JEANTON"); +$aCompanies[] = array('name' => "Companie Gnrale du Bois", 'domain' => "cgb.fr", 'code' => "CGB"); +$aCompanies[] = array('name' => "Village Group", 'domain' => "village.com", 'code' => "VGP"); + +$aCompaniesCode = array(); +foreach($aCompanies as $aCompany) +{ + $aCompaniesCode[$aCompany['code']] = array('name' => $aCompany['name'], 'domain' => $aCompany['domain']); +} + +$aFirstNames = array(); +$aFirstNames[] = 'Enzo'; +$aFirstNames[] = 'Quentin'; +$aFirstNames[] = 'Mohamed'; +$aFirstNames[] = 'Lucas'; +$aFirstNames[] = 'Julien'; +$aFirstNames[] = 'Ethan'; +$aFirstNames[] = 'Nathan'; +$aFirstNames[] = 'Thomas'; +$aFirstNames[] = 'Nicolas'; +$aFirstNames[] = 'Alexandre'; +$aFirstNames[] = 'Theo'; +$aFirstNames[] = 'Yanis'; +$aFirstNames[] = 'Hugo'; +$aFirstNames[] = 'Rayan'; +$aFirstNames[] = 'Tom'; +$aFirstNames[] = 'Romain'; +$aFirstNames[] = 'Sacha'; +$aFirstNames[] = 'Xavier'; +$aFirstNames[] = 'Olivier'; +$aFirstNames[] = 'Antoine'; +$aFirstNames[] = 'Raphael'; +$aFirstNames[] = 'Louis'; +$aFirstNames[] = 'Clement'; +$aFirstNames[] = 'Maxime'; +$aFirstNames[] = 'Pierre'; +$aFirstNames[] = 'Jules'; +$aFirstNames[] = 'Mael'; +$aFirstNames[] = 'Axel'; +$aFirstNames[] = 'Guillaume'; +$aFirstNames[] = 'Benjamin'; +$aFirstNames[] = 'Gabriel'; +$aFirstNames[] = 'Maxence'; +$aFirstNames[] = 'Evan'; +$aFirstNames[] = 'Sebastien'; +$aFirstNames[] = 'Arthur'; +$aFirstNames[] = 'Noah'; +$aFirstNames[] = 'Kevin'; +$aFirstNames[] = 'Mathieu'; +$aFirstNames[] = 'Leo'; +$aFirstNames[] = 'Mathis'; +$aFirstNames[] = 'David'; +$aFirstNames[] = 'Florian'; +$aFirstNames[] = 'Paul'; +$aFirstNames[] = 'Tristan'; +$aFirstNames[] = 'Timeo'; +$aFirstNames[] = 'Simon'; +$aFirstNames[] = 'Matthieu'; +$aFirstNames[] = 'Cedric'; +$aFirstNames[] = 'Corentin'; +$aFirstNames[] = 'Vincent'; +$aFirstNames[] = 'Audrey'; +$aFirstNames[] = 'Aurelie'; +$aFirstNames[] = 'Manon'; +$aFirstNames[] = 'Jade'; +$aFirstNames[] = 'Lea'; +$aFirstNames[] = 'Chloe'; +$aFirstNames[] = 'Emma'; +$aFirstNames[] = 'Camille'; +$aFirstNames[] = 'Caroline'; +$aFirstNames[] = 'Marie'; +$aFirstNames[] = 'Sarah'; +$aFirstNames[] = 'Pauline'; +$aFirstNames[] = 'Lilou'; +$aFirstNames[] = 'Julie'; +$aFirstNames[] = 'Ines'; +$aFirstNames[] = 'Fanny'; +$aFirstNames[] = 'Melissa'; +$aFirstNames[] = 'Lola'; +$aFirstNames[] = 'Clara'; +$aFirstNames[] = 'Oceane'; +$aFirstNames[] = 'Sabrina'; +$aFirstNames[] = 'Mathilde'; +$aFirstNames[] = 'Laura'; +$aFirstNames[] = 'Ambre'; +$aFirstNames[] = 'Alexia'; +$aFirstNames[] = 'Elodie'; +$aFirstNames[] = 'Anais'; +$aFirstNames[] = 'Amandine'; +$aFirstNames[] = 'Sophie'; +$aFirstNames[] = 'Emilie'; +$aFirstNames[] = 'Celia'; +$aFirstNames[] = 'Malika'; +$aFirstNames[] = 'Margaux'; +$aFirstNames[] = 'Ursule'; +$aFirstNames[] = 'Louise'; +$aFirstNames[] = 'Ophelie'; +$aFirstNames[] = 'Charlotte'; +$aFirstNames[] = 'Gaetane'; +$aFirstNames[] = 'Abigail'; +$aFirstNames[] = 'Noemie'; +$aFirstNames[] = 'Coralie'; +$aFirstNames[] = 'Celine'; +$aFirstNames[] = 'Louna'; +$aFirstNames[] = 'Emeline'; +$aFirstNames[] = 'Lena'; +$aFirstNames[] = 'Marion'; +$aFirstNames[] = 'Solene'; +$aFirstNames[] = 'Alice'; +$aFirstNames[] = 'Lucie'; +$aFirstNames[] = 'Lisa'; +$aFirstNames[] = 'Jean'; +$aFirstNames[] = 'Philippe'; +$aFirstNames[] = 'Michel'; +$aFirstNames[] = 'Alain'; +$aFirstNames[] = 'Patrick'; +$aFirstNames[] = 'Pascal'; +$aFirstNames[] = 'Daniel'; +$aFirstNames[] = 'Christophe'; +$aFirstNames[] = 'Eric'; +$aFirstNames[] = 'Nicolas'; +$aFirstNames[] = 'Pierre'; +$aFirstNames[] = 'Christian'; +$aFirstNames[] = 'Laurent'; +$aFirstNames[] = 'Stephane'; +$aFirstNames[] = 'David'; +$aFirstNames[] = 'Olivier'; +$aFirstNames[] = 'Sebastien'; +$aFirstNames[] = 'Gerard'; +$aFirstNames[] = 'Frederic'; +$aFirstNames[] = 'Bernard'; +$aFirstNames[] = 'Julien'; +$aFirstNames[] = 'Dominique'; +$aFirstNames[] = 'Thierry'; +$aFirstNames[] = 'Claude'; +$aFirstNames[] = 'Francis'; +$aFirstNames[] = 'Alexandre'; +$aFirstNames[] = 'Denis'; +$aFirstNames[] = 'Francois'; +$aFirstNames[] = 'Didier'; +$aFirstNames[] = 'Vincent'; +$aFirstNames[] = 'Bruno'; +$aFirstNames[] = 'Thomas'; +$aFirstNames[] = 'Gilles'; +$aFirstNames[] = 'Jacques'; +$aFirstNames[] = 'Jerome'; +$aFirstNames[] = 'Herve'; +$aFirstNames[] = 'Marc'; +$aFirstNames[] = 'Fabrice'; +$aFirstNames[] = 'Patrice'; +$aFirstNames[] = 'Maxime'; +$aFirstNames[] = 'Anthony'; +$aFirstNames[] = 'Serge'; +$aFirstNames[] = 'Andre'; +$aFirstNames[] = 'Cedric'; +$aFirstNames[] = 'Romain'; +$aFirstNames[] = 'Guillaume'; +$aFirstNames[] = 'Arnaud'; +$aFirstNames[] = 'Franck'; +$aFirstNames[] = 'Kevin'; +$aFirstNames[] = 'Sylvain'; +$aFirstNames[] = 'Lea'; +$aFirstNames[] = 'Manon'; +$aFirstNames[] = 'Camille'; +$aFirstNames[] = 'Emma'; +$aFirstNames[] = 'Clara'; +$aFirstNames[] = 'Chloe'; +$aFirstNames[] = 'Oceane'; +$aFirstNames[] = 'Lisa'; +$aFirstNames[] = 'Laura'; +$aFirstNames[] = 'Lucie'; +$aFirstNames[] = 'Mathilde'; +$aFirstNames[] = 'Lola'; +$aFirstNames[] = 'Marie'; +$aFirstNames[] = 'Sarah'; +$aFirstNames[] = 'Celia'; +$aFirstNames[] = 'Eva'; +$aFirstNames[] = 'Julie'; +$aFirstNames[] = 'Justine'; +$aFirstNames[] = 'Emilie'; +$aFirstNames[] = 'Ines'; +$aFirstNames[] = 'Pauline'; +$aFirstNames[] = 'Anais'; +$aFirstNames[] = 'Maeva'; +$aFirstNames[] = 'Zoe'; +$aFirstNames[] = 'Juliette'; +$aFirstNames[] = 'Alice'; +$aFirstNames[] = 'Margaux'; +$aFirstNames[] = 'Louise'; +$aFirstNames[] = 'Morgane'; +$aFirstNames[] = 'Clemence'; +$aFirstNames[] = 'Lena'; +$aFirstNames[] = 'Jeanne'; +$aFirstNames[] = 'Melissa'; +$aFirstNames[] = 'Noemie'; +$aFirstNames[] = 'Charlotte'; +$aFirstNames[] = 'Elise'; +$aFirstNames[] = 'Elisa'; +$aFirstNames[] = 'Jade'; +$aFirstNames[] = 'Margot'; +$aFirstNames[] = 'Romane'; +$aFirstNames[] = 'Alexia'; +$aFirstNames[] = 'Carla'; +$aFirstNames[] = 'Alicia'; +$aFirstNames[] = 'Flavie'; +$aFirstNames[] = 'Marine'; +$aFirstNames[] = 'Lou'; +$aFirstNames[] = 'Marion'; +$aFirstNames[] = 'Claire'; +$aFirstNames[] = 'Solene'; +$aFirstNames[] = 'Amelie'; +$aFirstNames[] = 'Mary'; +$aFirstNames[] = 'Patricia'; +$aFirstNames[] = 'Linda'; +$aFirstNames[] = 'Barbara'; +$aFirstNames[] = 'Elizabeth'; +$aFirstNames[] = 'Jennifer'; +$aFirstNames[] = 'Maria'; +$aFirstNames[] = 'Susan'; +$aFirstNames[] = 'Margaret'; +$aFirstNames[] = 'Dorothy'; +$aFirstNames[] = 'Lisa'; +$aFirstNames[] = 'Nancy'; +$aFirstNames[] = 'Karen'; +$aFirstNames[] = 'Betty'; +$aFirstNames[] = 'Helen'; +$aFirstNames[] = 'Sandra'; +$aFirstNames[] = 'Donna'; +$aFirstNames[] = 'Carol'; +$aFirstNames[] = 'Ruth'; +$aFirstNames[] = 'Sharon'; +$aFirstNames[] = 'Michelle'; +$aFirstNames[] = 'Laura'; +$aFirstNames[] = 'Sarah'; +$aFirstNames[] = 'Kimberly'; +$aFirstNames[] = 'Deborah'; +$aFirstNames[] = 'Jessica'; +$aFirstNames[] = 'Shirley'; +$aFirstNames[] = 'Cynthia'; +$aFirstNames[] = 'Angela'; +$aFirstNames[] = 'Melissa'; +$aFirstNames[] = 'Brenda'; +$aFirstNames[] = 'Amy'; +$aFirstNames[] = 'Anna'; +$aFirstNames[] = 'Rebecca'; +$aFirstNames[] = 'Virginia'; +$aFirstNames[] = 'Kathleen'; +$aFirstNames[] = 'Pamela'; +$aFirstNames[] = 'Martha'; +$aFirstNames[] = 'Debra'; +$aFirstNames[] = 'Amanda'; +$aFirstNames[] = 'Stephanie'; +$aFirstNames[] = 'Carolyn'; +$aFirstNames[] = 'Christine'; +$aFirstNames[] = 'Marie'; +$aFirstNames[] = 'Janet'; +$aFirstNames[] = 'Catherine'; +$aFirstNames[] = 'Frances'; +$aFirstNames[] = 'Ann'; +$aFirstNames[] = 'Joyce'; +$aFirstNames[] = 'Diane'; +$aFirstNames[] = 'Alice'; +$aFirstNames[] = 'Julie'; +$aFirstNames[] = 'Heather'; +$aFirstNames[] = 'Teresa'; +$aFirstNames[] = 'Doris'; +$aFirstNames[] = 'Gloria'; +$aFirstNames[] = 'Evelyn'; +$aFirstNames[] = 'Jean'; +$aFirstNames[] = 'Cheryl'; +$aFirstNames[] = 'Mildred'; +$aFirstNames[] = 'Katherine'; +$aFirstNames[] = 'Joan'; +$aFirstNames[] = 'Ashley'; +$aFirstNames[] = 'Judith'; +$aFirstNames[] = 'Rose'; +$aFirstNames[] = 'Janice'; +$aFirstNames[] = 'Kelly'; +$aFirstNames[] = 'Nicole'; +$aFirstNames[] = 'Judy'; +$aFirstNames[] = 'Christina'; +$aFirstNames[] = 'Kathy'; +$aFirstNames[] = 'Theresa'; +$aFirstNames[] = 'Beverly'; +$aFirstNames[] = 'Denise'; +$aFirstNames[] = 'Tammy'; +$aFirstNames[] = 'Irene'; +$aFirstNames[] = 'Jane'; +$aFirstNames[] = 'Lori'; +$aFirstNames[] = 'Rachel'; +$aFirstNames[] = 'Marilyn'; +$aFirstNames[] = 'Andrea'; +$aFirstNames[] = 'Kathryn'; +$aFirstNames[] = 'Louise'; +$aFirstNames[] = 'Sara'; +$aFirstNames[] = 'Anne'; +$aFirstNames[] = 'Jacqueline'; +$aFirstNames[] = 'Wanda'; +$aFirstNames[] = 'Bonnie'; +$aFirstNames[] = 'Julia'; +$aFirstNames[] = 'Ruby'; +$aFirstNames[] = 'Lois'; +$aFirstNames[] = 'Tina'; +$aFirstNames[] = 'Phyllis'; +$aFirstNames[] = 'Norma'; +$aFirstNames[] = 'Paula'; +$aFirstNames[] = 'Diana'; +$aFirstNames[] = 'Annie'; +$aFirstNames[] = 'Lillian'; +$aFirstNames[] = 'Emily'; +$aFirstNames[] = 'Robin'; +$aFirstNames[] = 'Peggy'; +$aFirstNames[] = 'Crystal'; +$aFirstNames[] = 'Gladys'; +$aFirstNames[] = 'Rita'; +$aFirstNames[] = 'Dawn'; +$aFirstNames[] = 'Connie'; +$aFirstNames[] = 'Florence'; +$aFirstNames[] = 'Tracy'; +$aFirstNames[] = 'Edna'; +$aFirstNames[] = 'Tiffany'; +$aFirstNames[] = 'Carmen'; +$aFirstNames[] = 'Rosa'; +$aFirstNames[] = 'Cindy'; +$aFirstNames[] = 'Grace'; +$aFirstNames[] = 'Wendy'; +$aFirstNames[] = 'Victoria'; +$aFirstNames[] = 'Edith'; +$aFirstNames[] = 'Kim'; +$aFirstNames[] = 'Sherry'; +$aFirstNames[] = 'Sylvia'; +$aFirstNames[] = 'Josephine'; +$aFirstNames[] = 'Thelma'; +$aFirstNames[] = 'Shannon'; +$aFirstNames[] = 'Sheila'; +$aFirstNames[] = 'Ethel'; +$aFirstNames[] = 'Ellen'; +$aFirstNames[] = 'Elaine'; +$aFirstNames[] = 'Marjorie'; +$aFirstNames[] = 'Carrie'; +$aFirstNames[] = 'Charlotte'; +$aFirstNames[] = 'Monica'; +$aFirstNames[] = 'Esther'; +$aFirstNames[] = 'Pauline'; +$aFirstNames[] = 'Emma'; +$aFirstNames[] = 'Juanita'; +$aFirstNames[] = 'Anita'; +$aFirstNames[] = 'Rhonda'; +$aFirstNames[] = 'Hazel'; +$aFirstNames[] = 'Amber'; +$aFirstNames[] = 'Eva'; +$aFirstNames[] = 'Debbie'; +$aFirstNames[] = 'April'; +$aFirstNames[] = 'Leslie'; +$aFirstNames[] = 'Clara'; +$aFirstNames[] = 'Lucille'; +$aFirstNames[] = 'Jamie'; +$aFirstNames[] = 'Joanne'; +$aFirstNames[] = 'Eleanor'; +$aFirstNames[] = 'Valerie'; +$aFirstNames[] = 'Danielle'; +$aFirstNames[] = 'Megan'; +$aFirstNames[] = 'Alicia'; +$aFirstNames[] = 'Suzanne'; +$aFirstNames[] = 'Michele'; +$aFirstNames[] = 'Gail'; +$aFirstNames[] = 'Bertha'; +$aFirstNames[] = 'Darlene'; +$aFirstNames[] = 'Veronica'; +$aFirstNames[] = 'Jill'; +$aFirstNames[] = 'Erin'; +$aFirstNames[] = 'Geraldine'; +$aFirstNames[] = 'Lauren'; +$aFirstNames[] = 'Cathy'; +$aFirstNames[] = 'Joann'; +$aFirstNames[] = 'Lorraine'; +$aFirstNames[] = 'Lynn'; +$aFirstNames[] = 'Sally'; +$aFirstNames[] = 'Regina'; +$aFirstNames[] = 'Erica'; +$aFirstNames[] = 'Beatrice'; +$aFirstNames[] = 'Dolores'; +$aFirstNames[] = 'Bernice'; +$aFirstNames[] = 'Audrey'; +$aFirstNames[] = 'Yvonne'; +$aFirstNames[] = 'Annette'; +$aFirstNames[] = 'June'; +$aFirstNames[] = 'Samantha'; +$aFirstNames[] = 'Marion'; +$aFirstNames[] = 'Dana'; +$aFirstNames[] = 'Stacy'; +$aFirstNames[] = 'Ana'; +$aFirstNames[] = 'Renee'; +$aFirstNames[] = 'Ida'; +$aFirstNames[] = 'Vivian'; +$aFirstNames[] = 'Roberta'; +$aFirstNames[] = 'Holly'; +$aFirstNames[] = 'Brittany'; +$aFirstNames[] = 'Melanie'; +$aFirstNames[] = 'Loretta'; +$aFirstNames[] = 'Yolanda'; +$aFirstNames[] = 'Jeanette'; +$aFirstNames[] = 'Laurie'; +$aFirstNames[] = 'Katie'; +$aFirstNames[] = 'Kristen'; +$aFirstNames[] = 'Vanessa'; +$aFirstNames[] = 'Alma'; +$aFirstNames[] = 'Sue'; +$aFirstNames[] = 'Elsie'; +$aFirstNames[] = 'Beth'; +$aFirstNames[] = 'Jeanne'; +$aFirstNames[] = 'Vicki'; +$aFirstNames[] = 'Carla'; +$aFirstNames[] = 'Tara'; +$aFirstNames[] = 'Rosemary'; +$aFirstNames[] = 'Eileen'; +$aFirstNames[] = 'Terri'; +$aFirstNames[] = 'Gertrude'; +$aFirstNames[] = 'Lucy'; +$aFirstNames[] = 'Tonya'; +$aFirstNames[] = 'Ella'; +$aFirstNames[] = 'Stacey'; +$aFirstNames[] = 'Wilma'; +$aFirstNames[] = 'Gina'; +$aFirstNames[] = 'Kristin'; +$aFirstNames[] = 'Jessie'; +$aFirstNames[] = 'Natalie'; +$aFirstNames[] = 'Agnes'; +$aFirstNames[] = 'Vera'; +$aFirstNames[] = 'Willie'; +$aFirstNames[] = 'Charlene'; +$aFirstNames[] = 'Bessie'; +$aFirstNames[] = 'Delores'; +$aFirstNames[] = 'Melinda'; +$aFirstNames[] = 'Pearl'; +$aFirstNames[] = 'Arlene'; +$aFirstNames[] = 'Maureen'; +$aFirstNames[] = 'Colleen'; +$aFirstNames[] = 'Allison'; +$aFirstNames[] = 'Tamara'; +$aFirstNames[] = 'Joy'; +$aFirstNames[] = 'Georgia'; +$aFirstNames[] = 'Constance'; +$aFirstNames[] = 'Lillie'; +$aFirstNames[] = 'Claudia'; +$aFirstNames[] = 'Jackie'; +$aFirstNames[] = 'Marcia'; +$aFirstNames[] = 'Tanya'; +$aFirstNames[] = 'Nellie'; +$aFirstNames[] = 'Minnie'; +$aFirstNames[] = 'Marlene'; +$aFirstNames[] = 'Heidi'; +$aFirstNames[] = 'Glenda'; +$aFirstNames[] = 'Lydia'; +$aFirstNames[] = 'Viola'; +$aFirstNames[] = 'Courtney'; +$aFirstNames[] = 'Marian'; +$aFirstNames[] = 'Stella'; +$aFirstNames[] = 'Caroline'; +$aFirstNames[] = 'Dora'; +$aFirstNames[] = 'Jo'; +$aFirstNames[] = 'Vickie'; +$aFirstNames[] = 'Mattie'; +$aFirstNames[] = 'Terry'; +$aFirstNames[] = 'Maxine'; +$aFirstNames[] = 'Irma'; +$aFirstNames[] = 'Mabel'; +$aFirstNames[] = 'Marsha'; +$aFirstNames[] = 'Myrtle'; +$aFirstNames[] = 'Lena'; +$aFirstNames[] = 'Christy'; +$aFirstNames[] = 'Deanna'; +$aFirstNames[] = 'Patsy'; +$aFirstNames[] = 'Hilda'; +$aFirstNames[] = 'Gwendolyn'; +$aFirstNames[] = 'Jennie'; +$aFirstNames[] = 'Nora'; +$aFirstNames[] = 'Margie'; +$aFirstNames[] = 'Nina'; +$aFirstNames[] = 'Cassandra'; +$aFirstNames[] = 'Leah'; +$aFirstNames[] = 'Penny'; +$aFirstNames[] = 'Kay'; +$aFirstNames[] = 'Priscilla'; +$aFirstNames[] = 'Naomi'; +$aFirstNames[] = 'Carole'; +$aFirstNames[] = 'Brandy'; +$aFirstNames[] = 'Olga'; +$aFirstNames[] = 'Billie'; +$aFirstNames[] = 'Dianne'; +$aFirstNames[] = 'Tracey'; +$aFirstNames[] = 'Leona'; +$aFirstNames[] = 'Jenny'; +$aFirstNames[] = 'Felicia'; +$aFirstNames[] = 'Sonia'; +$aFirstNames[] = 'Miriam'; +$aFirstNames[] = 'Velma'; +$aFirstNames[] = 'Becky'; +$aFirstNames[] = 'Bobbie'; +$aFirstNames[] = 'Violet'; +$aFirstNames[] = 'Kristina'; +$aFirstNames[] = 'Toni'; +$aFirstNames[] = 'Misty'; +$aFirstNames[] = 'Mae'; +$aFirstNames[] = 'Shelly'; +$aFirstNames[] = 'Daisy'; +$aFirstNames[] = 'Ramona'; +$aFirstNames[] = 'Sherri'; +$aFirstNames[] = 'Erika'; +$aFirstNames[] = 'Katrina'; +$aFirstNames[] = 'Claire'; +$aFirstNames[] = 'Lindsey'; +$aFirstNames[] = 'Lindsay'; +$aFirstNames[] = 'Geneva'; +$aFirstNames[] = 'Guadalupe'; +$aFirstNames[] = 'Belinda'; +$aFirstNames[] = 'Margarita'; +$aFirstNames[] = 'Sheryl'; +$aFirstNames[] = 'Cora'; +$aFirstNames[] = 'Faye'; +$aFirstNames[] = 'Ada'; +$aFirstNames[] = 'Natasha'; +$aFirstNames[] = 'Sabrina'; +$aFirstNames[] = 'Isabel'; +$aFirstNames[] = 'Marguerite'; +$aFirstNames[] = 'Hattie'; +$aFirstNames[] = 'Harriet'; +$aFirstNames[] = 'Molly'; +$aFirstNames[] = 'Cecilia'; +$aFirstNames[] = 'Kristi'; +$aFirstNames[] = 'Brandi'; +$aFirstNames[] = 'Blanche'; +$aFirstNames[] = 'Sandy'; +$aFirstNames[] = 'Rosie'; +$aFirstNames[] = 'Joanna'; +$aFirstNames[] = 'Iris'; +$aFirstNames[] = 'Eunice'; +$aFirstNames[] = 'Angie'; +$aFirstNames[] = 'Inez'; +$aFirstNames[] = 'Lynda'; +$aFirstNames[] = 'Madeline'; +$aFirstNames[] = 'Amelia'; +$aFirstNames[] = 'Alberta'; +$aFirstNames[] = 'Genevieve'; +$aFirstNames[] = 'Monique'; +$aFirstNames[] = 'Jodi'; +$aFirstNames[] = 'Janie'; +$aFirstNames[] = 'Maggie'; +$aFirstNames[] = 'Kayla'; +$aFirstNames[] = 'Sonya'; +$aFirstNames[] = 'Jan'; +$aFirstNames[] = 'Lee'; +$aFirstNames[] = 'Kristine'; +$aFirstNames[] = 'Candace'; +$aFirstNames[] = 'Fannie'; +$aFirstNames[] = 'Maryann'; +$aFirstNames[] = 'Opal'; +$aFirstNames[] = 'Alison'; +$aFirstNames[] = 'Yvette'; +$aFirstNames[] = 'Melody'; +$aFirstNames[] = 'Luz'; +$aFirstNames[] = 'Susie'; +$aFirstNames[] = 'Olivia'; +$aFirstNames[] = 'Flora'; +$aFirstNames[] = 'Shelley'; +$aFirstNames[] = 'Kristy'; +$aFirstNames[] = 'Mamie'; +$aFirstNames[] = 'Lula'; +$aFirstNames[] = 'Lola'; +$aFirstNames[] = 'Verna'; +$aFirstNames[] = 'Beulah'; +$aFirstNames[] = 'Antoinette'; +$aFirstNames[] = 'Candice'; +$aFirstNames[] = 'Juana'; +$aFirstNames[] = 'Jeannette'; +$aFirstNames[] = 'Pam'; +$aFirstNames[] = 'Kelli'; +$aFirstNames[] = 'Hannah'; +$aFirstNames[] = 'Whitney'; +$aFirstNames[] = 'Bridget'; +$aFirstNames[] = 'Karla'; +$aFirstNames[] = 'Celia'; +$aFirstNames[] = 'Latoya'; +$aFirstNames[] = 'Patty'; +$aFirstNames[] = 'Shelia'; +$aFirstNames[] = 'Gayle'; +$aFirstNames[] = 'Della'; +$aFirstNames[] = 'Vicky'; +$aFirstNames[] = 'Lynne'; +$aFirstNames[] = 'Sheri'; +$aFirstNames[] = 'Marianne'; +$aFirstNames[] = 'Kara'; +$aFirstNames[] = 'Jacquelyn'; +$aFirstNames[] = 'Erma'; +$aFirstNames[] = 'Blanca'; +$aFirstNames[] = 'Myra'; +$aFirstNames[] = 'Leticia'; +$aFirstNames[] = 'Pat'; +$aFirstNames[] = 'Krista'; +$aFirstNames[] = 'Roxanne'; +$aFirstNames[] = 'Angelica'; +$aFirstNames[] = 'Johnnie'; +$aFirstNames[] = 'Robyn'; +$aFirstNames[] = 'Francis'; +$aFirstNames[] = 'Adrienne'; +$aFirstNames[] = 'Rosalie'; +$aFirstNames[] = 'Alexandra'; +$aFirstNames[] = 'Brooke'; +$aFirstNames[] = 'Bethany'; +$aFirstNames[] = 'Sadie'; +$aFirstNames[] = 'Bernadette'; +$aFirstNames[] = 'Traci'; +$aFirstNames[] = 'Jody'; +$aFirstNames[] = 'Kendra'; +$aFirstNames[] = 'Jasmine'; +$aFirstNames[] = 'Nichole'; +$aFirstNames[] = 'Rachael'; +$aFirstNames[] = 'Chelsea'; +$aFirstNames[] = 'Mable'; +$aFirstNames[] = 'Ernestine'; +$aFirstNames[] = 'Muriel'; +$aFirstNames[] = 'Marcella'; +$aFirstNames[] = 'Elena'; +$aFirstNames[] = 'Krystal'; +$aFirstNames[] = 'Angelina'; +$aFirstNames[] = 'Nadine'; +$aFirstNames[] = 'Kari'; +$aFirstNames[] = 'Estelle'; +$aFirstNames[] = 'Dianna'; +$aFirstNames[] = 'Paulette'; +$aFirstNames[] = 'Lora'; +$aFirstNames[] = 'Mona'; +$aFirstNames[] = 'Doreen'; +$aFirstNames[] = 'Rosemarie'; +$aFirstNames[] = 'Angel'; +$aFirstNames[] = 'Desiree'; +$aFirstNames[] = 'Antonia'; +$aFirstNames[] = 'Hope'; +$aFirstNames[] = 'Ginger'; +$aFirstNames[] = 'Janis'; +$aFirstNames[] = 'Betsy'; +$aFirstNames[] = 'Christie'; +$aFirstNames[] = 'Freda'; +$aFirstNames[] = 'Mercedes'; +$aFirstNames[] = 'Meredith'; +$aFirstNames[] = 'Lynette'; +$aFirstNames[] = 'Teri'; +$aFirstNames[] = 'Cristina'; +$aFirstNames[] = 'Eula'; +$aFirstNames[] = 'Leigh'; +$aFirstNames[] = 'Meghan'; +$aFirstNames[] = 'Sophia'; +$aFirstNames[] = 'Eloise'; +$aFirstNames[] = 'Rochelle'; +$aFirstNames[] = 'Gretchen'; +$aFirstNames[] = 'Cecelia'; +$aFirstNames[] = 'Raquel'; +$aFirstNames[] = 'Henrietta'; +$aFirstNames[] = 'Alyssa'; +$aFirstNames[] = 'Jana'; +$aFirstNames[] = 'Kelley'; +$aFirstNames[] = 'Gwen'; +$aFirstNames[] = 'Kerry'; +$aFirstNames[] = 'Jenna'; +$aFirstNames[] = 'Tricia'; +$aFirstNames[] = 'Laverne'; +$aFirstNames[] = 'Olive'; +$aFirstNames[] = 'Alexis'; +$aFirstNames[] = 'Tasha'; +$aFirstNames[] = 'Silvia'; +$aFirstNames[] = 'Elvira'; +$aFirstNames[] = 'Casey'; +$aFirstNames[] = 'Delia'; +$aFirstNames[] = 'Sophie'; +$aFirstNames[] = 'Kate'; +$aFirstNames[] = 'Patti'; +$aFirstNames[] = 'Lorena'; +$aFirstNames[] = 'Kellie'; +$aFirstNames[] = 'Sonja'; +$aFirstNames[] = 'Lila'; +$aFirstNames[] = 'Lana'; +$aFirstNames[] = 'Darla'; +$aFirstNames[] = 'May'; +$aFirstNames[] = 'Mindy'; +$aFirstNames[] = 'Essie'; +$aFirstNames[] = 'Mandy'; +$aFirstNames[] = 'Lorene'; +$aFirstNames[] = 'Elsa'; +$aFirstNames[] = 'Josefina'; +$aFirstNames[] = 'Jeannie'; +$aFirstNames[] = 'Miranda'; +$aFirstNames[] = 'Dixie'; +$aFirstNames[] = 'Lucia'; +$aFirstNames[] = 'Marta'; +$aFirstNames[] = 'Faith'; +$aFirstNames[] = 'Lela'; +$aFirstNames[] = 'Johanna'; +$aFirstNames[] = 'Shari'; +$aFirstNames[] = 'Camille'; +$aFirstNames[] = 'Tami'; +$aFirstNames[] = 'Shawna'; +$aFirstNames[] = 'Elisa'; +$aFirstNames[] = 'Ebony'; +$aFirstNames[] = 'Melba'; +$aFirstNames[] = 'Ora'; +$aFirstNames[] = 'Nettie'; +$aFirstNames[] = 'Tabitha'; +$aFirstNames[] = 'Ollie'; +$aFirstNames[] = 'Jaime'; +$aFirstNames[] = 'Winifred'; +$aFirstNames[] = 'Kristie'; +$aFirstNames[] = 'Marina'; +$aFirstNames[] = 'Alisha'; +$aFirstNames[] = 'Aimee'; +$aFirstNames[] = 'Rena'; +$aFirstNames[] = 'Myrna'; +$aFirstNames[] = 'Marla'; +$aFirstNames[] = 'Tammie'; +$aFirstNames[] = 'Latasha'; +$aFirstNames[] = 'Bonita'; +$aFirstNames[] = 'Patrice'; +$aFirstNames[] = 'Ronda'; +$aFirstNames[] = 'Sherrie'; +$aFirstNames[] = 'Addie'; +$aFirstNames[] = 'Francine'; +$aFirstNames[] = 'Deloris'; +$aFirstNames[] = 'Stacie'; +$aFirstNames[] = 'Adriana'; +$aFirstNames[] = 'Cheri'; +$aFirstNames[] = 'Shelby'; +$aFirstNames[] = 'Abigail'; +$aFirstNames[] = 'Celeste'; +$aFirstNames[] = 'Jewel'; +$aFirstNames[] = 'Cara'; +$aFirstNames[] = 'Adele'; +$aFirstNames[] = 'Rebekah'; +$aFirstNames[] = 'Lucinda'; +$aFirstNames[] = 'Dorthy'; +$aFirstNames[] = 'Chris'; +$aFirstNames[] = 'Effie'; +$aFirstNames[] = 'Trina'; +$aFirstNames[] = 'Reba'; +$aFirstNames[] = 'Shawn'; +$aFirstNames[] = 'Sallie'; +$aFirstNames[] = 'Aurora'; +$aFirstNames[] = 'Lenora'; +$aFirstNames[] = 'Etta'; +$aFirstNames[] = 'Lottie'; +$aFirstNames[] = 'Kerri'; +$aFirstNames[] = 'Trisha'; +$aFirstNames[] = 'Nikki'; +$aFirstNames[] = 'Estella'; +$aFirstNames[] = 'Francisca'; +$aFirstNames[] = 'Josie'; +$aFirstNames[] = 'Tracie'; +$aFirstNames[] = 'Marissa'; +$aFirstNames[] = 'Karin'; +$aFirstNames[] = 'Brittney'; +$aFirstNames[] = 'Janelle'; + +$aFirstNames[] = 'Lourdes'; +$aFirstNames[] = 'Laurel'; +$aFirstNames[] = 'Helene'; +$aFirstNames[] = 'Fern'; +$aFirstNames[] = 'Elva'; +$aFirstNames[] = 'Corinne'; +$aFirstNames[] = 'Kelsey'; +$aFirstNames[] = 'Ina'; +$aFirstNames[] = 'Bettie'; +$aFirstNames[] = 'Elisabeth'; +$aFirstNames[] = 'Aida'; +$aFirstNames[] = 'Caitlin'; +$aFirstNames[] = 'Ingrid'; +$aFirstNames[] = 'Iva'; +$aFirstNames[] = 'Eugenia'; +$aFirstNames[] = 'Christa'; +$aFirstNames[] = 'Goldie'; +$aFirstNames[] = 'Cassie'; +$aFirstNames[] = 'Maude'; +$aFirstNames[] = 'Jenifer'; +$aFirstNames[] = 'Therese'; +$aFirstNames[] = 'Frankie'; +$aFirstNames[] = 'Dena'; +$aFirstNames[] = 'Lorna'; +$aFirstNames[] = 'Janette'; +$aFirstNames[] = 'Latonya'; +$aFirstNames[] = 'Candy'; +$aFirstNames[] = 'Morgan'; +$aFirstNames[] = 'Consuelo'; +$aFirstNames[] = 'Tamika'; +$aFirstNames[] = 'Rosetta'; +$aFirstNames[] = 'Debora'; +$aFirstNames[] = 'Cherie'; +$aFirstNames[] = 'Polly'; +$aFirstNames[] = 'Dina'; +$aFirstNames[] = 'Jewell'; +$aFirstNames[] = 'Fay'; +$aFirstNames[] = 'Jillian'; +$aFirstNames[] = 'Dorothea'; +$aFirstNames[] = 'Nell'; +$aFirstNames[] = 'Trudy'; +$aFirstNames[] = 'Esperanza'; +$aFirstNames[] = 'Patrica'; +$aFirstNames[] = 'Kimberley'; +$aFirstNames[] = 'Shanna'; +$aFirstNames[] = 'Helena'; +$aFirstNames[] = 'Carolina'; +$aFirstNames[] = 'Cleo'; +$aFirstNames[] = 'Stefanie'; +$aFirstNames[] = 'Rosario'; +$aFirstNames[] = 'Ola'; +$aFirstNames[] = 'Janine'; +$aFirstNames[] = 'Mollie'; +$aFirstNames[] = 'Lupe'; +$aFirstNames[] = 'Alisa'; +$aFirstNames[] = 'Lou'; +$aFirstNames[] = 'Maribel'; +$aFirstNames[] = 'Susanne'; +$aFirstNames[] = 'Bette'; +$aFirstNames[] = 'Susana'; +$aFirstNames[] = 'Elise'; +$aFirstNames[] = 'Cecile'; +$aFirstNames[] = 'Isabelle'; +$aFirstNames[] = 'Lesley'; +$aFirstNames[] = 'Jocelyn'; +$aFirstNames[] = 'Paige'; +$aFirstNames[] = 'Joni'; +$aFirstNames[] = 'Rachelle'; +$aFirstNames[] = 'Leola'; +$aFirstNames[] = 'Daphne'; +$aFirstNames[] = 'Alta'; +$aFirstNames[] = 'Ester'; +$aFirstNames[] = 'Petra'; +$aFirstNames[] = 'Graciela'; +$aFirstNames[] = 'Imogene'; +$aFirstNames[] = 'Jolene'; +$aFirstNames[] = 'Keisha'; +$aFirstNames[] = 'Lacey'; +$aFirstNames[] = 'Glenna'; +$aFirstNames[] = 'Gabriela'; +$aFirstNames[] = 'Keri'; +$aFirstNames[] = 'Ursula'; +$aFirstNames[] = 'Lizzie'; +$aFirstNames[] = 'Kirsten'; +$aFirstNames[] = 'Shana'; +$aFirstNames[] = 'Adeline'; +$aFirstNames[] = 'Mayra'; +$aFirstNames[] = 'Jayne'; +$aFirstNames[] = 'Jaclyn'; +$aFirstNames[] = 'Gracie'; +$aFirstNames[] = 'Sondra'; +$aFirstNames[] = 'Carmela'; +$aFirstNames[] = 'Marisa'; +$aFirstNames[] = 'Rosalind'; +$aFirstNames[] = 'Charity'; +$aFirstNames[] = 'Tonia'; +$aFirstNames[] = 'Beatriz'; +$aFirstNames[] = 'Marisol'; +$aFirstNames[] = 'Clarice'; +$aFirstNames[] = 'Jeanine'; +$aFirstNames[] = 'Sheena'; +$aFirstNames[] = 'Angeline'; +$aFirstNames[] = 'Frieda'; +$aFirstNames[] = 'Lily'; +$aFirstNames[] = 'Robbie'; +$aFirstNames[] = 'Shauna'; +$aFirstNames[] = 'Millie'; +$aFirstNames[] = 'Claudette'; +$aFirstNames[] = 'Cathleen'; +$aFirstNames[] = 'Angelia'; +$aFirstNames[] = 'Gabrielle'; +$aFirstNames[] = 'Autumn'; +$aFirstNames[] = 'Katharine'; +$aFirstNames[] = 'Summer'; +$aFirstNames[] = 'Jodie'; +$aFirstNames[] = 'Staci'; +$aFirstNames[] = 'Lea'; +$aFirstNames[] = 'Christi'; +$aFirstNames[] = 'Jimmie'; +$aFirstNames[] = 'Justine'; +$aFirstNames[] = 'Elma'; +$aFirstNames[] = 'Luella'; +$aFirstNames[] = 'Margret'; +$aFirstNames[] = 'Dominique'; +$aFirstNames[] = 'Socorro'; +$aFirstNames[] = 'Rene'; +$aFirstNames[] = 'Martina'; +$aFirstNames[] = 'Margo'; +$aFirstNames[] = 'Mavis'; +$aFirstNames[] = 'Callie'; +$aFirstNames[] = 'Bobbi'; +$aFirstNames[] = 'Maritza'; +$aFirstNames[] = 'Lucile'; +$aFirstNames[] = 'Leanne'; +$aFirstNames[] = 'Jeannine'; +$aFirstNames[] = 'Deana'; +$aFirstNames[] = 'Aileen'; +$aFirstNames[] = 'Lorie'; +$aFirstNames[] = 'Ladonna'; +$aFirstNames[] = 'Willa'; +$aFirstNames[] = 'Manuela'; +$aFirstNames[] = 'Gale'; +$aFirstNames[] = 'Selma'; +$aFirstNames[] = 'Dolly'; +$aFirstNames[] = 'Sybil'; +$aFirstNames[] = 'Abby'; +$aFirstNames[] = 'Lara'; +$aFirstNames[] = 'Dale'; +$aFirstNames[] = 'Ivy'; +$aFirstNames[] = 'Dee'; +$aFirstNames[] = 'Winnie'; +$aFirstNames[] = 'Marcy'; +$aFirstNames[] = 'Luisa'; +$aFirstNames[] = 'Jeri'; +$aFirstNames[] = 'Magdalena'; +$aFirstNames[] = 'Ofelia'; +$aFirstNames[] = 'Meagan'; +$aFirstNames[] = 'Audra'; +$aFirstNames[] = 'Matilda'; +$aFirstNames[] = 'Leila'; +$aFirstNames[] = 'Cornelia'; +$aFirstNames[] = 'Bianca'; +$aFirstNames[] = 'Simone'; +$aFirstNames[] = 'Bettye'; +$aFirstNames[] = 'Randi'; +$aFirstNames[] = 'Virgie'; +$aFirstNames[] = 'Latisha'; +$aFirstNames[] = 'Barbra'; +$aFirstNames[] = 'Georgina'; +$aFirstNames[] = 'Eliza'; +$aFirstNames[] = 'Leann'; +$aFirstNames[] = 'Bridgette'; +$aFirstNames[] = 'Rhoda'; +$aFirstNames[] = 'Haley'; +$aFirstNames[] = 'Adela'; +$aFirstNames[] = 'Nola'; +$aFirstNames[] = 'Bernadine'; +$aFirstNames[] = 'Flossie'; +$aFirstNames[] = 'Ila'; +$aFirstNames[] = 'Greta'; +$aFirstNames[] = 'Ruthie'; +$aFirstNames[] = 'Nelda'; +$aFirstNames[] = 'Minerva'; +$aFirstNames[] = 'Lilly'; +$aFirstNames[] = 'Terrie'; +$aFirstNames[] = 'Letha'; +$aFirstNames[] = 'Hilary'; +$aFirstNames[] = 'Estela'; +$aFirstNames[] = 'Valarie'; +$aFirstNames[] = 'Brianna'; +$aFirstNames[] = 'Rosalyn'; +$aFirstNames[] = 'Earline'; +$aFirstNames[] = 'Catalina'; +$aFirstNames[] = 'Ava'; +$aFirstNames[] = 'Mia'; +$aFirstNames[] = 'Clarissa'; +$aFirstNames[] = 'Lidia'; +$aFirstNames[] = 'Corrine'; +$aFirstNames[] = 'Alexandria'; +$aFirstNames[] = 'Concepcion'; +$aFirstNames[] = 'Tia'; +$aFirstNames[] = 'Sharron'; +$aFirstNames[] = 'Rae'; +$aFirstNames[] = 'Dona'; +$aFirstNames[] = 'Ericka'; +$aFirstNames[] = 'Jami'; +$aFirstNames[] = 'Elnora'; +$aFirstNames[] = 'Chandra'; +$aFirstNames[] = 'Lenore'; +$aFirstNames[] = 'Neva'; +$aFirstNames[] = 'Marylou'; +$aFirstNames[] = 'Melisa'; +$aFirstNames[] = 'Tabatha'; +$aFirstNames[] = 'Serena'; +$aFirstNames[] = 'Avis'; +$aFirstNames[] = 'Allie'; +$aFirstNames[] = 'Sofia'; +$aFirstNames[] = 'Jeanie'; +$aFirstNames[] = 'Odessa'; +$aFirstNames[] = 'Nannie'; +$aFirstNames[] = 'Harriett'; +$aFirstNames[] = 'James'; +$aFirstNames[] = 'John'; +$aFirstNames[] = 'Robert'; +$aFirstNames[] = 'Michael'; +$aFirstNames[] = 'William'; +$aFirstNames[] = 'David'; +$aFirstNames[] = 'Richard'; +$aFirstNames[] = 'Charles'; +$aFirstNames[] = 'Joseph'; +$aFirstNames[] = 'Thomas'; +$aFirstNames[] = 'Christopher'; +$aFirstNames[] = 'Daniel'; +$aFirstNames[] = 'Paul'; +$aFirstNames[] = 'Mark'; +$aFirstNames[] = 'Donald'; +$aFirstNames[] = 'George'; +$aFirstNames[] = 'Kenneth'; +$aFirstNames[] = 'Steven'; +$aFirstNames[] = 'Edward'; +$aFirstNames[] = 'Brian'; +$aFirstNames[] = 'Ronald'; +$aFirstNames[] = 'Anthony'; +$aFirstNames[] = 'Kevin'; +$aFirstNames[] = 'Jason'; +$aFirstNames[] = 'Matthew'; +$aFirstNames[] = 'Gary'; +$aFirstNames[] = 'Timothy'; +$aFirstNames[] = 'Jose'; +$aFirstNames[] = 'Larry'; +$aFirstNames[] = 'Jeffrey'; +$aFirstNames[] = 'Frank'; +$aFirstNames[] = 'Scott'; +$aFirstNames[] = 'Eric'; +$aFirstNames[] = 'Stephen'; +$aFirstNames[] = 'Andrew'; +$aFirstNames[] = 'Raymond'; +$aFirstNames[] = 'Gregory'; +$aFirstNames[] = 'Joshua'; +$aFirstNames[] = 'Jerry'; +$aFirstNames[] = 'Dennis'; +$aFirstNames[] = 'Walter'; +$aFirstNames[] = 'Patrick'; +$aFirstNames[] = 'Peter'; +$aFirstNames[] = 'Harold'; +$aFirstNames[] = 'Douglas'; +$aFirstNames[] = 'Henry'; +$aFirstNames[] = 'Carl'; +$aFirstNames[] = 'Arthur'; +$aFirstNames[] = 'Ryan'; +$aFirstNames[] = 'Roger'; +$aFirstNames[] = 'Joe'; +$aFirstNames[] = 'Juan'; +$aFirstNames[] = 'Jack'; +$aFirstNames[] = 'Albert'; +$aFirstNames[] = 'Jonathan'; +$aFirstNames[] = 'Justin'; +$aFirstNames[] = 'Terry'; +$aFirstNames[] = 'Gerald'; +$aFirstNames[] = 'Keith'; +$aFirstNames[] = 'Samuel'; +$aFirstNames[] = 'Willie'; +$aFirstNames[] = 'Ralph'; +$aFirstNames[] = 'Lawrence'; +$aFirstNames[] = 'Nicholas'; +$aFirstNames[] = 'Roy'; +$aFirstNames[] = 'Benjamin'; +$aFirstNames[] = 'Bruce'; +$aFirstNames[] = 'Brandon'; +$aFirstNames[] = 'Adam'; +$aFirstNames[] = 'Harry'; +$aFirstNames[] = 'Fred'; +$aFirstNames[] = 'Wayne'; +$aFirstNames[] = 'Billy'; +$aFirstNames[] = 'Steve'; +$aFirstNames[] = 'Louis'; +$aFirstNames[] = 'Jeremy'; +$aFirstNames[] = 'Aaron'; +$aFirstNames[] = 'Randy'; +$aFirstNames[] = 'Howard'; +$aFirstNames[] = 'Eugene'; +$aFirstNames[] = 'Carlos'; +$aFirstNames[] = 'Russell'; +$aFirstNames[] = 'Bobby'; +$aFirstNames[] = 'Victor'; +$aFirstNames[] = 'Martin'; +$aFirstNames[] = 'Ernest'; +$aFirstNames[] = 'Phillip'; +$aFirstNames[] = 'Todd'; +$aFirstNames[] = 'Jesse'; +$aFirstNames[] = 'Craig'; +$aFirstNames[] = 'Alan'; +$aFirstNames[] = 'Shawn'; +$aFirstNames[] = 'Clarence'; +$aFirstNames[] = 'Sean'; +$aFirstNames[] = 'Philip'; +$aFirstNames[] = 'Chris'; +$aFirstNames[] = 'Johnny'; +$aFirstNames[] = 'Earl'; +$aFirstNames[] = 'Jimmy'; +$aFirstNames[] = 'Antonio'; +$aFirstNames[] = 'Danny'; +$aFirstNames[] = 'Bryan'; +$aFirstNames[] = 'Tony'; +$aFirstNames[] = 'Luis'; +$aFirstNames[] = 'Mike'; +$aFirstNames[] = 'Stanley'; +$aFirstNames[] = 'Leonard'; +$aFirstNames[] = 'Nathan'; +$aFirstNames[] = 'Dale'; +$aFirstNames[] = 'Manuel'; +$aFirstNames[] = 'Rodney'; +$aFirstNames[] = 'Curtis'; +$aFirstNames[] = 'Norman'; +$aFirstNames[] = 'Allen'; +$aFirstNames[] = 'Marvin'; +$aFirstNames[] = 'Vincent'; +$aFirstNames[] = 'Glenn'; +$aFirstNames[] = 'Jeffery'; +$aFirstNames[] = 'Travis'; +$aFirstNames[] = 'Jeff'; +$aFirstNames[] = 'Chad'; +$aFirstNames[] = 'Jacob'; +$aFirstNames[] = 'Lee'; +$aFirstNames[] = 'Melvin'; +$aFirstNames[] = 'Alfred'; +$aFirstNames[] = 'Kyle'; +$aFirstNames[] = 'Francis'; +$aFirstNames[] = 'Bradley'; +$aFirstNames[] = 'Jesus'; +$aFirstNames[] = 'Herbert'; +$aFirstNames[] = 'Frederick'; +$aFirstNames[] = 'Ray'; +$aFirstNames[] = 'Joel'; +$aFirstNames[] = 'Edwin'; +$aFirstNames[] = 'Don'; +$aFirstNames[] = 'Eddie'; +$aFirstNames[] = 'Ricky'; +$aFirstNames[] = 'Troy'; +$aFirstNames[] = 'Randall'; +$aFirstNames[] = 'Barry'; +$aFirstNames[] = 'Alexander'; +$aFirstNames[] = 'Bernard'; +$aFirstNames[] = 'Mario'; +$aFirstNames[] = 'Leroy'; +$aFirstNames[] = 'Francisco'; +$aFirstNames[] = 'Marcus'; +$aFirstNames[] = 'Micheal'; +$aFirstNames[] = 'Theodore'; +$aFirstNames[] = 'Clifford'; +$aFirstNames[] = 'Miguel'; +$aFirstNames[] = 'Oscar'; +$aFirstNames[] = 'Jay'; +$aFirstNames[] = 'Jim'; +$aFirstNames[] = 'Tom'; +$aFirstNames[] = 'Calvin'; +$aFirstNames[] = 'Alex'; +$aFirstNames[] = 'Jon'; +$aFirstNames[] = 'Ronnie'; +$aFirstNames[] = 'Bill'; +$aFirstNames[] = 'Lloyd'; +$aFirstNames[] = 'Tommy'; +$aFirstNames[] = 'Leon'; +$aFirstNames[] = 'Derek'; +$aFirstNames[] = 'Warren'; +$aFirstNames[] = 'Darrell'; +$aFirstNames[] = 'Jerome'; +$aFirstNames[] = 'Floyd'; +$aFirstNames[] = 'Leo'; +$aFirstNames[] = 'Alvin'; +$aFirstNames[] = 'Tim'; +$aFirstNames[] = 'Wesley'; +$aFirstNames[] = 'Gordon'; +$aFirstNames[] = 'Dean'; +$aFirstNames[] = 'Greg'; +$aFirstNames[] = 'Jorge'; +$aFirstNames[] = 'Dustin'; +$aFirstNames[] = 'Pedro'; +$aFirstNames[] = 'Derrick'; +$aFirstNames[] = 'Dan'; +$aFirstNames[] = 'Lewis'; +$aFirstNames[] = 'Zachary'; +$aFirstNames[] = 'Corey'; +$aFirstNames[] = 'Herman'; +$aFirstNames[] = 'Maurice'; +$aFirstNames[] = 'Vernon'; +$aFirstNames[] = 'Roberto'; +$aFirstNames[] = 'Clyde'; +$aFirstNames[] = 'Glen'; +$aFirstNames[] = 'Hector'; +$aFirstNames[] = 'Shane'; +$aFirstNames[] = 'Ricardo'; +$aFirstNames[] = 'Sam'; +$aFirstNames[] = 'Rick'; +$aFirstNames[] = 'Lester'; +$aFirstNames[] = 'Brent'; +$aFirstNames[] = 'Ramon'; +$aFirstNames[] = 'Charlie'; +$aFirstNames[] = 'Tyler'; +$aFirstNames[] = 'Gilbert'; +$aFirstNames[] = 'Gene'; +$aFirstNames[] = 'Marc'; +$aFirstNames[] = 'Reginald'; +$aFirstNames[] = 'Ruben'; +$aFirstNames[] = 'Brett'; +$aFirstNames[] = 'Angel'; +$aFirstNames[] = 'Nathaniel'; +$aFirstNames[] = 'Rafael'; +$aFirstNames[] = 'Leslie'; +$aFirstNames[] = 'Edgar'; +$aFirstNames[] = 'Milton'; +$aFirstNames[] = 'Raul'; +$aFirstNames[] = 'Ben'; +$aFirstNames[] = 'Chester'; +$aFirstNames[] = 'Cecil'; +$aFirstNames[] = 'Duane'; +$aFirstNames[] = 'Franklin'; +$aFirstNames[] = 'Andre'; +$aFirstNames[] = 'Elmer'; +$aFirstNames[] = 'Brad'; +$aFirstNames[] = 'Gabriel'; +$aFirstNames[] = 'Ron'; +$aFirstNames[] = 'Mitchell'; +$aFirstNames[] = 'Roland'; +$aFirstNames[] = 'Arnold'; +$aFirstNames[] = 'Harvey'; +$aFirstNames[] = 'Jared'; +$aFirstNames[] = 'Adrian'; +$aFirstNames[] = 'Karl'; +$aFirstNames[] = 'Cory'; +$aFirstNames[] = 'Claude'; +$aFirstNames[] = 'Erik'; +$aFirstNames[] = 'Darryl'; +$aFirstNames[] = 'Jamie'; +$aFirstNames[] = 'Neil'; +$aFirstNames[] = 'Jessie'; +$aFirstNames[] = 'Christian'; +$aFirstNames[] = 'Javier'; +$aFirstNames[] = 'Fernando'; +$aFirstNames[] = 'Clinton'; +$aFirstNames[] = 'Ted'; +$aFirstNames[] = 'Mathew'; +$aFirstNames[] = 'Tyrone'; +$aFirstNames[] = 'Darren'; +$aFirstNames[] = 'Lonnie'; +$aFirstNames[] = 'Lance'; +$aFirstNames[] = 'Cody'; +$aFirstNames[] = 'Julio'; +$aFirstNames[] = 'Kelly'; +$aFirstNames[] = 'Kurt'; +$aFirstNames[] = 'Allan'; +$aFirstNames[] = 'Nelson'; +$aFirstNames[] = 'Guy'; +$aFirstNames[] = 'Clayton'; +$aFirstNames[] = 'Hugh'; +$aFirstNames[] = 'Max'; +$aFirstNames[] = 'Dwayne'; +$aFirstNames[] = 'Dwight'; +$aFirstNames[] = 'Armando'; +$aFirstNames[] = 'Felix'; +$aFirstNames[] = 'Jimmie'; +$aFirstNames[] = 'Everett'; +$aFirstNames[] = 'Jordan'; +$aFirstNames[] = 'Ian'; +$aFirstNames[] = 'Wallace'; +$aFirstNames[] = 'Ken'; +$aFirstNames[] = 'Bob'; +$aFirstNames[] = 'Jaime'; +$aFirstNames[] = 'Casey'; +$aFirstNames[] = 'Alfredo'; +$aFirstNames[] = 'Alberto'; +$aFirstNames[] = 'Dave'; +$aFirstNames[] = 'Ivan'; +$aFirstNames[] = 'Johnnie'; +$aFirstNames[] = 'Sidney'; +$aFirstNames[] = 'Byron'; +$aFirstNames[] = 'Julian'; +$aFirstNames[] = 'Isaac'; +$aFirstNames[] = 'Morris'; +$aFirstNames[] = 'Clifton'; +$aFirstNames[] = 'Willard'; +$aFirstNames[] = 'Daryl'; +$aFirstNames[] = 'Ross'; +$aFirstNames[] = 'Virgil'; +$aFirstNames[] = 'Andy'; +$aFirstNames[] = 'Marshall'; +$aFirstNames[] = 'Salvador'; +$aFirstNames[] = 'Perry'; +$aFirstNames[] = 'Kirk'; +$aFirstNames[] = 'Sergio'; +$aFirstNames[] = 'Marion'; +$aFirstNames[] = 'Tracy'; +$aFirstNames[] = 'Seth'; +$aFirstNames[] = 'Kent'; +$aFirstNames[] = 'Terrance'; +$aFirstNames[] = 'Rene'; +$aFirstNames[] = 'Eduardo'; +$aFirstNames[] = 'Terrence'; +$aFirstNames[] = 'Enrique'; +$aFirstNames[] = 'Freddie'; +$aFirstNames[] = 'Wade'; +$aFirstNames[] = 'Austin'; +$aFirstNames[] = 'Stuart'; +$aFirstNames[] = 'Fredrick'; +$aFirstNames[] = 'Arturo'; +$aFirstNames[] = 'Alejandro'; +$aFirstNames[] = 'Jackie'; +$aFirstNames[] = 'Joey'; +$aFirstNames[] = 'Nick'; +$aFirstNames[] = 'Luther'; +$aFirstNames[] = 'Wendell'; +$aFirstNames[] = 'Jeremiah'; +$aFirstNames[] = 'Evan'; +$aFirstNames[] = 'Julius'; +$aFirstNames[] = 'Dana'; +$aFirstNames[] = 'Donnie'; +$aFirstNames[] = 'Otis'; +$aFirstNames[] = 'Shannon'; +$aFirstNames[] = 'Trevor'; +$aFirstNames[] = 'Oliver'; +$aFirstNames[] = 'Luke'; +$aFirstNames[] = 'Homer'; +$aFirstNames[] = 'Gerard'; +$aFirstNames[] = 'Doug'; +$aFirstNames[] = 'Kenny'; +$aFirstNames[] = 'Hubert'; +$aFirstNames[] = 'Angelo'; +$aFirstNames[] = 'Shaun'; +$aFirstNames[] = 'Lyle'; +$aFirstNames[] = 'Matt'; +$aFirstNames[] = 'Lynn'; +$aFirstNames[] = 'Alfonso'; +$aFirstNames[] = 'Orlando'; +$aFirstNames[] = 'Rex'; +$aFirstNames[] = 'Carlton'; +$aFirstNames[] = 'Ernesto'; +$aFirstNames[] = 'Cameron'; +$aFirstNames[] = 'Neal'; +$aFirstNames[] = 'Pablo'; +$aFirstNames[] = 'Lorenzo'; +$aFirstNames[] = 'Omar'; +$aFirstNames[] = 'Wilbur'; +$aFirstNames[] = 'Blake'; +$aFirstNames[] = 'Grant'; +$aFirstNames[] = 'Horace'; +$aFirstNames[] = 'Roderick'; +$aFirstNames[] = 'Kerry'; +$aFirstNames[] = 'Abraham'; +$aFirstNames[] = 'Willis'; +$aFirstNames[] = 'Rickey'; +$aFirstNames[] = 'Jean'; +$aFirstNames[] = 'Ira'; +$aFirstNames[] = 'Andres'; +$aFirstNames[] = 'Cesar'; +$aFirstNames[] = 'Johnathan'; +$aFirstNames[] = 'Malcolm'; +$aFirstNames[] = 'Rudolph'; +$aFirstNames[] = 'Damon'; +$aFirstNames[] = 'Kelvin'; +$aFirstNames[] = 'Rudy'; +$aFirstNames[] = 'Preston'; +$aFirstNames[] = 'Alton'; +$aFirstNames[] = 'Archie'; +$aFirstNames[] = 'Marco'; +$aFirstNames[] = 'Wm'; +$aFirstNames[] = 'Pete'; +$aFirstNames[] = 'Randolph'; +$aFirstNames[] = 'Garry'; +$aFirstNames[] = 'Geoffrey'; +$aFirstNames[] = 'Jonathon'; +$aFirstNames[] = 'Felipe'; +$aFirstNames[] = 'Bennie'; +$aFirstNames[] = 'Gerardo'; +$aFirstNames[] = 'Ed'; +$aFirstNames[] = 'Dominic'; +$aFirstNames[] = 'Robin'; +$aFirstNames[] = 'Loren'; +$aFirstNames[] = 'Delbert'; +$aFirstNames[] = 'Colin'; +$aFirstNames[] = 'Guillermo'; +$aFirstNames[] = 'Earnest'; +$aFirstNames[] = 'Lucas'; +$aFirstNames[] = 'Benny'; +$aFirstNames[] = 'Noel'; +$aFirstNames[] = 'Spencer'; +$aFirstNames[] = 'Rodolfo'; +$aFirstNames[] = 'Myron'; +$aFirstNames[] = 'Edmund'; +$aFirstNames[] = 'Garrett'; +$aFirstNames[] = 'Salvatore'; +$aFirstNames[] = 'Cedric'; +$aFirstNames[] = 'Lowell'; +$aFirstNames[] = 'Gregg'; +$aFirstNames[] = 'Sherman'; +$aFirstNames[] = 'Wilson'; +$aFirstNames[] = 'Devin'; +$aFirstNames[] = 'Sylvester'; +$aFirstNames[] = 'Kim'; +$aFirstNames[] = 'Roosevelt'; +$aFirstNames[] = 'Israel'; +$aFirstNames[] = 'Jermaine'; +$aFirstNames[] = 'Forrest'; +$aFirstNames[] = 'Wilbert'; +$aFirstNames[] = 'Leland'; +$aFirstNames[] = 'Simon'; +$aFirstNames[] = 'Guadalupe'; +$aFirstNames[] = 'Clark'; +$aFirstNames[] = 'Irving'; +$aFirstNames[] = 'Carroll'; +$aFirstNames[] = 'Bryant'; +$aFirstNames[] = 'Owen'; +$aFirstNames[] = 'Rufus'; +$aFirstNames[] = 'Woodrow'; +$aFirstNames[] = 'Sammy'; +$aFirstNames[] = 'Kristopher'; +$aFirstNames[] = 'Mack'; +$aFirstNames[] = 'Levi'; +$aFirstNames[] = 'Marcos'; +$aFirstNames[] = 'Gustavo'; +$aFirstNames[] = 'Jake'; +$aFirstNames[] = 'Lionel'; +$aFirstNames[] = 'Marty'; +$aFirstNames[] = 'Taylor'; +$aFirstNames[] = 'Ellis'; +$aFirstNames[] = 'Dallas'; +$aFirstNames[] = 'Gilberto'; +$aFirstNames[] = 'Clint'; +$aFirstNames[] = 'Nicolas'; +$aFirstNames[] = 'Laurence'; +$aFirstNames[] = 'Ismael'; +$aFirstNames[] = 'Orville'; +$aFirstNames[] = 'Drew'; +$aFirstNames[] = 'Jody'; +$aFirstNames[] = 'Ervin'; +$aFirstNames[] = 'Dewey'; +$aFirstNames[] = 'Al'; +$aFirstNames[] = 'Wilfred'; +$aFirstNames[] = 'Josh'; +$aFirstNames[] = 'Hugo'; +$aFirstNames[] = 'Ignacio'; +$aFirstNames[] = 'Caleb'; +$aFirstNames[] = 'Tomas'; +$aFirstNames[] = 'Sheldon'; +$aFirstNames[] = 'Erick'; +$aFirstNames[] = 'Frankie'; +$aFirstNames[] = 'Stewart'; +$aFirstNames[] = 'Doyle'; +$aFirstNames[] = 'Darrel'; +$aFirstNames[] = 'Rogelio'; +$aFirstNames[] = 'Terence'; +$aFirstNames[] = 'Santiago'; +$aFirstNames[] = 'Alonzo'; +$aFirstNames[] = 'Elias'; +$aFirstNames[] = 'Bert'; +$aFirstNames[] = 'Elbert'; +$aFirstNames[] = 'Ramiro'; +$aFirstNames[] = 'Conrad'; +$aFirstNames[] = 'Pat'; +$aFirstNames[] = 'Noah'; +$aFirstNames[] = 'Grady'; +$aFirstNames[] = 'Phil'; +$aFirstNames[] = 'Cornelius'; +$aFirstNames[] = 'Lamar'; +$aFirstNames[] = 'Rolando'; +$aFirstNames[] = 'Clay'; +$aFirstNames[] = 'Percy'; +$aFirstNames[] = 'Dexter'; +$aFirstNames[] = 'Bradford'; +$aFirstNames[] = 'Merle'; +$aFirstNames[] = 'Darin'; +$aFirstNames[] = 'Amos'; +$aFirstNames[] = 'Terrell'; +$aFirstNames[] = 'Moses'; +$aFirstNames[] = 'Irvin'; +$aFirstNames[] = 'Saul'; +$aFirstNames[] = 'Roman'; +$aFirstNames[] = 'Darnell'; +$aFirstNames[] = 'Randal'; +$aFirstNames[] = 'Tommie'; +$aFirstNames[] = 'Timmy'; +$aFirstNames[] = 'Darrin'; +$aFirstNames[] = 'Winston'; +$aFirstNames[] = 'Brendan'; +$aFirstNames[] = 'Toby'; +$aFirstNames[] = 'Van'; +$aFirstNames[] = 'Abel'; +$aFirstNames[] = 'Dominick'; +$aFirstNames[] = 'Boyd'; +$aFirstNames[] = 'Courtney'; +$aFirstNames[] = 'Jan'; +$aFirstNames[] = 'Emilio'; +$aFirstNames[] = 'Elijah'; +$aFirstNames[] = 'Cary'; +$aFirstNames[] = 'Domingo'; +$aFirstNames[] = 'Santos'; +$aFirstNames[] = 'Aubrey'; +$aFirstNames[] = 'Emmett'; +$aFirstNames[] = 'Marlon'; +$aFirstNames[] = 'Emanuel'; +$aFirstNames[] = 'Jerald'; +$aFirstNames[] = 'Edmond'; +$aFirstNames[] = 'Jan Jozef '; +$aFirstNames[] = 'Konrad'; +$aFirstNames[] = 'Lidia'; +$aFirstNames[] = 'Lorenz'; +$aFirstNames[] = 'Otto'; +$aFirstNames[] = 'Piotr'; +$aFirstNames[] = 'Wladyslaw'; + +$aNames = array(); +$aNames[] = 'Smith'; +$aNames[] = 'Johnson'; +$aNames[] = 'Williams'; +$aNames[] = 'Jones'; +$aNames[] = 'Brown'; +$aNames[] = 'Davis'; +$aNames[] = 'Miller'; +$aNames[] = 'Wilson'; +$aNames[] = 'Moore'; +$aNames[] = 'Taylor'; +$aNames[] = 'Anderson'; +$aNames[] = 'Thomas'; +$aNames[] = 'Jackson'; +$aNames[] = 'White'; +$aNames[] = 'Harris'; +$aNames[] = 'Martin'; +$aNames[] = 'Thompson'; +$aNames[] = 'Garcia'; +$aNames[] = 'Martinez'; +$aNames[] = 'Robinson'; +$aNames[] = 'Clark'; +$aNames[] = 'Rodriguez'; +$aNames[] = 'Lewis'; +$aNames[] = 'Lee'; +$aNames[] = 'Walker'; +$aNames[] = 'Hall'; +$aNames[] = 'Allen'; +$aNames[] = 'Young'; +$aNames[] = 'Hernandez'; +$aNames[] = 'King'; +$aNames[] = 'Wright'; +$aNames[] = 'Lopez'; +$aNames[] = 'Hill'; +$aNames[] = 'Scott'; +$aNames[] = 'Green'; +$aNames[] = 'Adams'; +$aNames[] = 'Baker'; +$aNames[] = 'Gonzalez'; +$aNames[] = 'Nelson'; +$aNames[] = 'Carter'; +$aNames[] = 'Mitchell'; +$aNames[] = 'Perez'; +$aNames[] = 'Roberts'; +$aNames[] = 'Turner'; +$aNames[] = 'Phillips'; +$aNames[] = 'Campbell'; +$aNames[] = 'Parker'; +$aNames[] = 'Evans'; +$aNames[] = 'Edwards'; +$aNames[] = 'Collins'; +$aNames[] = 'Stewart'; +$aNames[] = 'Sanchez'; +$aNames[] = 'Morris'; +$aNames[] = 'Rogers'; +$aNames[] = 'Reed'; +$aNames[] = 'Cook'; +$aNames[] = 'Morgan'; +$aNames[] = 'Bell'; +$aNames[] = 'Murphy'; +$aNames[] = 'Bailey'; +$aNames[] = 'Rivera'; +$aNames[] = 'Cooper'; +$aNames[] = 'Richardson'; +$aNames[] = 'Cox'; +$aNames[] = 'Howard'; +$aNames[] = 'Ward'; +$aNames[] = 'Torres'; +$aNames[] = 'Peterson'; +$aNames[] = 'Gray'; +$aNames[] = 'Ramirez'; +$aNames[] = 'James'; +$aNames[] = 'Watson'; +$aNames[] = 'Brooks'; +$aNames[] = 'Kelly'; +$aNames[] = 'Sanders'; +$aNames[] = 'Price'; +$aNames[] = 'Bennett'; +$aNames[] = 'Wood'; +$aNames[] = 'Barnes'; +$aNames[] = 'Ross'; +$aNames[] = 'Henderson'; +$aNames[] = 'Coleman'; +$aNames[] = 'Jenkins'; +$aNames[] = 'Perry'; +$aNames[] = 'Powell'; +$aNames[] = 'Long'; +$aNames[] = 'Patterson'; +$aNames[] = 'Hughes'; +$aNames[] = 'Flores'; +$aNames[] = 'Washington'; +$aNames[] = 'Butler'; +$aNames[] = 'Simmons'; +$aNames[] = 'Foster'; +$aNames[] = 'Gonzales'; +$aNames[] = 'Bryant'; +$aNames[] = 'Alexander'; +$aNames[] = 'Russell'; +$aNames[] = 'Griffin'; +$aNames[] = 'Diaz'; +$aNames[] = 'Hayes'; +$aNames[] = 'Myers'; +$aNames[] = 'Ford'; +$aNames[] = 'Hamilton'; +$aNames[] = 'Graham'; +$aNames[] = 'Sullivan'; +$aNames[] = 'Wallace'; +$aNames[] = 'Woods'; +$aNames[] = 'Cole'; +$aNames[] = 'West'; +$aNames[] = 'Jordan'; +$aNames[] = 'Owens'; +$aNames[] = 'Reynolds'; +$aNames[] = 'Fisher'; +$aNames[] = 'Ellis'; +$aNames[] = 'Harrison'; +$aNames[] = 'Gibson'; +$aNames[] = 'Mcdonald'; +$aNames[] = 'Cruz'; +$aNames[] = 'Marshall'; +$aNames[] = 'Ortiz'; +$aNames[] = 'Gomez'; +$aNames[] = 'Murray'; +$aNames[] = 'Freeman'; +$aNames[] = 'Wells'; +$aNames[] = 'Webb'; +$aNames[] = 'Simpson'; +$aNames[] = 'Stevens'; +$aNames[] = 'Tucker'; +$aNames[] = 'Porter'; +$aNames[] = 'Hunter'; +$aNames[] = 'Hicks'; +$aNames[] = 'Crawford'; +$aNames[] = 'Henry'; +$aNames[] = 'Boyd'; +$aNames[] = 'Mason'; +$aNames[] = 'Morales'; +$aNames[] = 'Kennedy'; +$aNames[] = 'Warren'; +$aNames[] = 'Dixon'; +$aNames[] = 'Ramos'; +$aNames[] = 'Reyes'; +$aNames[] = 'Burns'; +$aNames[] = 'Gordon'; +$aNames[] = 'Shaw'; +$aNames[] = 'Holmes'; +$aNames[] = 'Rice'; +$aNames[] = 'Robertson'; +$aNames[] = 'Hunt'; +$aNames[] = 'Black'; +$aNames[] = 'Daniels'; +$aNames[] = 'Palmer'; +$aNames[] = 'Mills'; +$aNames[] = 'Nichols'; +$aNames[] = 'Grant'; +$aNames[] = 'Knight'; +$aNames[] = 'Ferguson'; +$aNames[] = 'Rose'; +$aNames[] = 'Stone'; +$aNames[] = 'Hawkins'; +$aNames[] = 'Dunn'; +$aNames[] = 'Perkins'; +$aNames[] = 'Hudson'; +$aNames[] = 'Spencer'; +$aNames[] = 'Gardner'; +$aNames[] = 'Stephens'; +$aNames[] = 'Payne'; +$aNames[] = 'Pierce'; +$aNames[] = 'Berry'; +$aNames[] = 'Matthews'; +$aNames[] = 'Arnold'; +$aNames[] = 'Wagner'; +$aNames[] = 'Willis'; +$aNames[] = 'Ray'; +$aNames[] = 'Watkins'; +$aNames[] = 'Olson'; +$aNames[] = 'Carroll'; +$aNames[] = 'Duncan'; +$aNames[] = 'Snyder'; +$aNames[] = 'Hart'; +$aNames[] = 'Cunningham'; +$aNames[] = 'Bradley'; +$aNames[] = 'Lane'; +$aNames[] = 'Andrews'; +$aNames[] = 'Ruiz'; +$aNames[] = 'Harper'; +$aNames[] = 'Fox'; +$aNames[] = 'Riley'; +$aNames[] = 'Armstrong'; +$aNames[] = 'Carpenter'; +$aNames[] = 'Weaver'; +$aNames[] = 'Greene'; +$aNames[] = 'Lawrence'; +$aNames[] = 'Elliott'; +$aNames[] = 'Chavez'; +$aNames[] = 'Sims'; +$aNames[] = 'Austin'; +$aNames[] = 'Peters'; +$aNames[] = 'Kelley'; +$aNames[] = 'Franklin'; +$aNames[] = 'Lawson'; +$aNames[] = 'Fields'; +$aNames[] = 'Gutierrez'; +$aNames[] = 'Ryan'; +$aNames[] = 'Schmidt'; +$aNames[] = 'Carr'; +$aNames[] = 'Vasquez'; +$aNames[] = 'Castillo'; +$aNames[] = 'Wheeler'; +$aNames[] = 'Chapman'; +$aNames[] = 'Oliver'; +$aNames[] = 'Montgomery'; +$aNames[] = 'Richards'; +$aNames[] = 'Williamson'; +$aNames[] = 'Johnston'; +$aNames[] = 'Banks'; +$aNames[] = 'Meyer'; +$aNames[] = 'Bishop'; +$aNames[] = 'Mccoy'; +$aNames[] = 'Howell'; +$aNames[] = 'Alvarez'; +$aNames[] = 'Morrison'; +$aNames[] = 'Hansen'; +$aNames[] = 'Fernandez'; +$aNames[] = 'Garza'; +$aNames[] = 'Harvey'; +$aNames[] = 'Little'; +$aNames[] = 'Burton'; +$aNames[] = 'Stanley'; +$aNames[] = 'Nguyen'; +$aNames[] = 'George'; +$aNames[] = 'Jacobs'; +$aNames[] = 'Reid'; +$aNames[] = 'Kim'; +$aNames[] = 'Fuller'; +$aNames[] = 'Lynch'; +$aNames[] = 'Dean'; +$aNames[] = 'Gilbert'; +$aNames[] = 'Garrett'; +$aNames[] = 'Romero'; +$aNames[] = 'Welch'; +$aNames[] = 'Larson'; +$aNames[] = 'Frazier'; +$aNames[] = 'Burke'; +$aNames[] = 'Hanson'; +$aNames[] = 'Day'; +$aNames[] = 'Mendoza'; +$aNames[] = 'Moreno'; +$aNames[] = 'Bowman'; +$aNames[] = 'Medina'; +$aNames[] = 'Fowler'; +$aNames[] = 'Brewer'; +$aNames[] = 'Hoffman'; +$aNames[] = 'Carlson'; +$aNames[] = 'Silva'; +$aNames[] = 'Pearson'; +$aNames[] = 'Holland'; +$aNames[] = 'Douglas'; +$aNames[] = 'Fleming'; +$aNames[] = 'Jensen'; +$aNames[] = 'Vargas'; +$aNames[] = 'Byrd'; +$aNames[] = 'Davidson'; +$aNames[] = 'Hopkins'; +$aNames[] = 'May'; +$aNames[] = 'Terry'; +$aNames[] = 'Herrera'; +$aNames[] = 'Wade'; +$aNames[] = 'Soto'; +$aNames[] = 'Walters'; +$aNames[] = 'Curtis'; +$aNames[] = 'Neal'; +$aNames[] = 'Caldwell'; +$aNames[] = 'Lowe'; +$aNames[] = 'Jennings'; +$aNames[] = 'Barnett'; +$aNames[] = 'Graves'; +$aNames[] = 'Jimenez'; +$aNames[] = 'Horton'; +$aNames[] = 'Shelton'; +$aNames[] = 'Barrett'; +$aNames[] = 'Obrien'; +$aNames[] = 'Castro'; +$aNames[] = 'Sutton'; +$aNames[] = 'Gregory'; +$aNames[] = 'Mckinney'; +$aNames[] = 'Lucas'; +$aNames[] = 'Miles'; +$aNames[] = 'Craig'; +$aNames[] = 'Rodriquez'; +$aNames[] = 'Chambers'; +$aNames[] = 'Holt'; +$aNames[] = 'Lambert'; +$aNames[] = 'Fletcher'; +$aNames[] = 'Watts'; +$aNames[] = 'Bates'; +$aNames[] = 'Hale'; +$aNames[] = 'Rhodes'; +$aNames[] = 'Pena'; +$aNames[] = 'Beck'; +$aNames[] = 'Newman'; +$aNames[] = 'Haynes'; +$aNames[] = 'Mcdaniel'; +$aNames[] = 'Mendez'; +$aNames[] = 'Bush'; +$aNames[] = 'Vaughn'; +$aNames[] = 'Parks'; +$aNames[] = 'Dawson'; +$aNames[] = 'Santiago'; +$aNames[] = 'Norris'; +$aNames[] = 'Hardy'; +$aNames[] = 'Love'; +$aNames[] = 'Steele'; +$aNames[] = 'Curry'; +$aNames[] = 'Powers'; +$aNames[] = 'Schultz'; +$aNames[] = 'Barker'; +$aNames[] = 'Guzman'; +$aNames[] = 'Page'; +$aNames[] = 'Munoz'; +$aNames[] = 'Ball'; +$aNames[] = 'Keller'; +$aNames[] = 'Chandler'; +$aNames[] = 'Weber'; +$aNames[] = 'Leonard'; +$aNames[] = 'Walsh'; +$aNames[] = 'Lyons'; +$aNames[] = 'Ramsey'; +$aNames[] = 'Wolfe'; +$aNames[] = 'Schneider'; +$aNames[] = 'Mullins'; +$aNames[] = 'Benson'; +$aNames[] = 'Sharp'; +$aNames[] = 'Bowen'; +$aNames[] = 'Daniel'; +$aNames[] = 'Barber'; +$aNames[] = 'Cummings'; +$aNames[] = 'Hines'; +$aNames[] = 'Baldwin'; +$aNames[] = 'Griffith'; +$aNames[] = 'Valdez'; +$aNames[] = 'Hubbard'; +$aNames[] = 'Salazar'; +$aNames[] = 'Reeves'; +$aNames[] = 'Warner'; +$aNames[] = 'Stevenson'; +$aNames[] = 'Burgess'; +$aNames[] = 'Santos'; +$aNames[] = 'Tate'; +$aNames[] = 'Cross'; +$aNames[] = 'Garner'; +$aNames[] = 'Mann'; +$aNames[] = 'Mack'; +$aNames[] = 'Moss'; +$aNames[] = 'Thornton'; +$aNames[] = 'Dennis'; +$aNames[] = 'Mcgee'; +$aNames[] = 'Farmer'; +$aNames[] = 'Delgado'; +$aNames[] = 'Aguilar'; +$aNames[] = 'Vega'; +$aNames[] = 'Glover'; +$aNames[] = 'Manning'; +$aNames[] = 'Cohen'; +$aNames[] = 'Harmon'; +$aNames[] = 'Rodgers'; +$aNames[] = 'Robbins'; +$aNames[] = 'Newton'; +$aNames[] = 'Todd'; +$aNames[] = 'Blair'; +$aNames[] = 'Higgins'; +$aNames[] = 'Ingram'; +$aNames[] = 'Reese'; +$aNames[] = 'Cannon'; +$aNames[] = 'Strickland'; +$aNames[] = 'Townsend'; +$aNames[] = 'Potter'; +$aNames[] = 'Goodwin'; +$aNames[] = 'Walton'; +$aNames[] = 'Rowe'; +$aNames[] = 'Hampton'; +$aNames[] = 'Ortega'; +$aNames[] = 'Patton'; +$aNames[] = 'Swanson'; +$aNames[] = 'Joseph'; +$aNames[] = 'Francis'; +$aNames[] = 'Goodman'; +$aNames[] = 'Maldonado'; +$aNames[] = 'Yates'; +$aNames[] = 'Becker'; +$aNames[] = 'Erickson'; +$aNames[] = 'Hodges'; +$aNames[] = 'Rios'; +$aNames[] = 'Conner'; +$aNames[] = 'Adkins'; +$aNames[] = 'Webster'; +$aNames[] = 'Norman'; +$aNames[] = 'Malone'; +$aNames[] = 'Hammond'; +$aNames[] = 'Flowers'; +$aNames[] = 'Cobb'; +$aNames[] = 'Moody'; +$aNames[] = 'Quinn'; +$aNames[] = 'Blake'; +$aNames[] = 'Maxwell'; +$aNames[] = 'Pope'; +$aNames[] = 'Floyd'; +$aNames[] = 'Osborne'; +$aNames[] = 'Paul'; +$aNames[] = 'Mccarthy'; +$aNames[] = 'Guerrero'; +$aNames[] = 'Lindsey'; +$aNames[] = 'Estrada'; +$aNames[] = 'Sandoval'; +$aNames[] = 'Gibbs'; +$aNames[] = 'Tyler'; +$aNames[] = 'Gross'; +$aNames[] = 'Fitzgerald'; +$aNames[] = 'Stokes'; +$aNames[] = 'Doyle'; +$aNames[] = 'Sherman'; +$aNames[] = 'Saunders'; +$aNames[] = 'Wise'; +$aNames[] = 'Colon'; +$aNames[] = 'Gill'; +$aNames[] = 'Alvarado'; +$aNames[] = 'Greer'; +$aNames[] = 'Padilla'; +$aNames[] = 'Simon'; +$aNames[] = 'Waters'; +$aNames[] = 'Nunez'; +$aNames[] = 'Ballard'; +$aNames[] = 'Schwartz'; +$aNames[] = 'Mcbride'; +$aNames[] = 'Houston'; +$aNames[] = 'Christensen'; +$aNames[] = 'Klein'; +$aNames[] = 'Pratt'; +$aNames[] = 'Briggs'; +$aNames[] = 'Parsons'; +$aNames[] = 'Mclaughlin'; +$aNames[] = 'Zimmerman'; +$aNames[] = 'French'; +$aNames[] = 'Buchanan'; +$aNames[] = 'Moran'; +$aNames[] = 'Copeland'; +$aNames[] = 'Roy'; +$aNames[] = 'Pittman'; +$aNames[] = 'Brady'; +$aNames[] = 'Mccormick'; +$aNames[] = 'Holloway'; +$aNames[] = 'Brock'; +$aNames[] = 'Poole'; +$aNames[] = 'Frank'; +$aNames[] = 'Logan'; +$aNames[] = 'Owen'; +$aNames[] = 'Bass'; +$aNames[] = 'Marsh'; +$aNames[] = 'Drake'; +$aNames[] = 'Wong'; +$aNames[] = 'Jefferson'; +$aNames[] = 'Park'; +$aNames[] = 'Morton'; +$aNames[] = 'Abbott'; +$aNames[] = 'Sparks'; +$aNames[] = 'Patrick'; +$aNames[] = 'Norton'; +$aNames[] = 'Huff'; +$aNames[] = 'Clayton'; +$aNames[] = 'Massey'; +$aNames[] = 'Lloyd'; +$aNames[] = 'Figueroa'; +$aNames[] = 'Carson'; +$aNames[] = 'Bowers'; +$aNames[] = 'Roberson'; +$aNames[] = 'Barton'; +$aNames[] = 'Tran'; +$aNames[] = 'Lamb'; +$aNames[] = 'Harrington'; +$aNames[] = 'Casey'; +$aNames[] = 'Boone'; +$aNames[] = 'Cortez'; +$aNames[] = 'Clarke'; +$aNames[] = 'Mathis'; +$aNames[] = 'Singleton'; +$aNames[] = 'Wilkins'; +$aNames[] = 'Cain'; +$aNames[] = 'Bryan'; +$aNames[] = 'Underwood'; +$aNames[] = 'Hogan'; +$aNames[] = 'Mckenzie'; +$aNames[] = 'Collier'; +$aNames[] = 'Luna'; +$aNames[] = 'Phelps'; +$aNames[] = 'Mcguire'; +$aNames[] = 'Allison'; +$aNames[] = 'Bridges'; +$aNames[] = 'Wilkerson'; +$aNames[] = 'Nash'; +$aNames[] = 'Summers'; +$aNames[] = 'Atkins'; +$aNames[] = 'Wilcox'; +$aNames[] = 'Pitts'; +$aNames[] = 'Conley'; +$aNames[] = 'Marquez'; +$aNames[] = 'Burnett'; +$aNames[] = 'Richard'; +$aNames[] = 'Cochran'; +$aNames[] = 'Chase'; +$aNames[] = 'Davenport'; +$aNames[] = 'Hood'; +$aNames[] = 'Gates'; +$aNames[] = 'Clay'; +$aNames[] = 'Ayala'; +$aNames[] = 'Sawyer'; +$aNames[] = 'Roman'; +$aNames[] = 'Vazquez'; +$aNames[] = 'Dickerson'; +$aNames[] = 'Hodge'; +$aNames[] = 'Acosta'; +$aNames[] = 'Flynn'; +$aNames[] = 'Espinoza'; +$aNames[] = 'Nicholson'; +$aNames[] = 'Monroe'; +$aNames[] = 'Wolf'; +$aNames[] = 'Morrow'; +$aNames[] = 'Kirk'; +$aNames[] = 'Randall'; +$aNames[] = 'Anthony'; +$aNames[] = 'Whitaker'; +$aNames[] = 'Oconnor'; +$aNames[] = 'Skinner'; +$aNames[] = 'Ware'; +$aNames[] = 'Molina'; +$aNames[] = 'Kirby'; +$aNames[] = 'Huffman'; +$aNames[] = 'Bradford'; +$aNames[] = 'Charles'; +$aNames[] = 'Gilmore'; +$aNames[] = 'Dominguez'; +$aNames[] = 'Oneal'; +$aNames[] = 'Bruce'; +$aNames[] = 'Lang'; +$aNames[] = 'Combs'; +$aNames[] = 'Kramer'; +$aNames[] = 'Heath'; +$aNames[] = 'Hancock'; +$aNames[] = 'Gallagher'; +$aNames[] = 'Gaines'; +$aNames[] = 'Shaffer'; +$aNames[] = 'Short'; +$aNames[] = 'Wiggins'; +$aNames[] = 'Mathews'; +$aNames[] = 'Mcclain'; +$aNames[] = 'Fischer'; +$aNames[] = 'Wall'; +$aNames[] = 'Small'; +$aNames[] = 'Melton'; +$aNames[] = 'Hensley'; +$aNames[] = 'Bond'; +$aNames[] = 'Dyer'; +$aNames[] = 'Cameron'; +$aNames[] = 'Grimes'; +$aNames[] = 'Contreras'; +$aNames[] = 'Christian'; +$aNames[] = 'Wyatt'; +$aNames[] = 'Baxter'; +$aNames[] = 'Snow'; +$aNames[] = 'Mosley'; +$aNames[] = 'Shepherd'; +$aNames[] = 'Larsen'; +$aNames[] = 'Hoover'; +$aNames[] = 'Beasley'; +$aNames[] = 'Glenn'; +$aNames[] = 'Petersen'; +$aNames[] = 'Whitehead'; +$aNames[] = 'Meyers'; +$aNames[] = 'Keith'; +$aNames[] = 'Garrison'; +$aNames[] = 'Vincent'; +$aNames[] = 'Shields'; +$aNames[] = 'Horn'; +$aNames[] = 'Savage'; +$aNames[] = 'Olsen'; +$aNames[] = 'Schroeder'; +$aNames[] = 'Hartman'; +$aNames[] = 'Woodard'; +$aNames[] = 'Mueller'; +$aNames[] = 'Kemp'; +$aNames[] = 'Deleon'; +$aNames[] = 'Booth'; +$aNames[] = 'Patel'; +$aNames[] = 'Calhoun'; +$aNames[] = 'Wiley'; +$aNames[] = 'Eaton'; +$aNames[] = 'Cline'; +$aNames[] = 'Navarro'; +$aNames[] = 'Harrell'; +$aNames[] = 'Lester'; +$aNames[] = 'Humphrey'; +$aNames[] = 'Parrish'; +$aNames[] = 'Duran'; +$aNames[] = 'Hutchinson'; +$aNames[] = 'Hess'; +$aNames[] = 'Dorsey'; +$aNames[] = 'Bullock'; +$aNames[] = 'Robles'; +$aNames[] = 'Beard'; +$aNames[] = 'Dalton'; +$aNames[] = 'Avila'; +$aNames[] = 'Vance'; +$aNames[] = 'Rich'; +$aNames[] = 'Blackwell'; +$aNames[] = 'York'; +$aNames[] = 'Johns'; +$aNames[] = 'Blankenship'; +$aNames[] = 'Trevino'; +$aNames[] = 'Salinas'; +$aNames[] = 'Campos'; +$aNames[] = 'Pruitt'; +$aNames[] = 'Moses'; +$aNames[] = 'Callahan'; +$aNames[] = 'Golden'; +$aNames[] = 'Montoya'; +$aNames[] = 'Hardin'; +$aNames[] = 'Guerra'; +$aNames[] = 'Mcdowell'; +$aNames[] = 'Carey'; +$aNames[] = 'Stafford'; +$aNames[] = 'Gallegos'; +$aNames[] = 'Henson'; +$aNames[] = 'Wilkinson'; +$aNames[] = 'Booker'; +$aNames[] = 'Merritt'; +$aNames[] = 'Miranda'; +$aNames[] = 'Atkinson'; +$aNames[] = 'Orr'; +$aNames[] = 'Decker'; +$aNames[] = 'Hobbs'; +$aNames[] = 'Preston'; +$aNames[] = 'Tanner'; +$aNames[] = 'Knox'; +$aNames[] = 'Pacheco'; +$aNames[] = 'Stephenson'; +$aNames[] = 'Glass'; +$aNames[] = 'Rojas'; +$aNames[] = 'Serrano'; +$aNames[] = 'Marks'; +$aNames[] = 'Hickman'; +$aNames[] = 'English'; +$aNames[] = 'Sweeney'; +$aNames[] = 'Strong'; +$aNames[] = 'Prince'; +$aNames[] = 'Mcclure'; +$aNames[] = 'Conway'; +$aNames[] = 'Walter'; +$aNames[] = 'Roth'; +$aNames[] = 'Maynard'; +$aNames[] = 'Farrell'; +$aNames[] = 'Lowery'; +$aNames[] = 'Hurst'; +$aNames[] = 'Nixon'; +$aNames[] = 'Weiss'; +$aNames[] = 'Trujillo'; +$aNames[] = 'Ellison'; +$aNames[] = 'Sloan'; +$aNames[] = 'Juarez'; +$aNames[] = 'Winters'; +$aNames[] = 'Mclean'; +$aNames[] = 'Randolph'; +$aNames[] = 'Leon'; +$aNames[] = 'Boyer'; +$aNames[] = 'Villarreal'; +$aNames[] = 'Mccall'; +$aNames[] = 'Gentry'; +$aNames[] = 'Carrillo'; +$aNames[] = 'Kent'; +$aNames[] = 'Ayers'; +$aNames[] = 'Lara'; +$aNames[] = 'Shannon'; +$aNames[] = 'Sexton'; +$aNames[] = 'Pace'; +$aNames[] = 'Hull'; +$aNames[] = 'Leblanc'; +$aNames[] = 'Browning'; +$aNames[] = 'Velasquez'; +$aNames[] = 'Leach'; +$aNames[] = 'Chang'; +$aNames[] = 'House'; +$aNames[] = 'Sellers'; +$aNames[] = 'Herring'; +$aNames[] = 'Noble'; +$aNames[] = 'Foley'; +$aNames[] = 'Bartlett'; +$aNames[] = 'Mercado'; +$aNames[] = 'Landry'; +$aNames[] = 'Durham'; +$aNames[] = 'Walls'; +$aNames[] = 'Barr'; +$aNames[] = 'Mckee'; +$aNames[] = 'Bauer'; +$aNames[] = 'Rivers'; +$aNames[] = 'Everett'; +$aNames[] = 'Bradshaw'; +$aNames[] = 'Pugh'; +$aNames[] = 'Velez'; +$aNames[] = 'Rush'; +$aNames[] = 'Estes'; +$aNames[] = 'Dodson'; +$aNames[] = 'Morse'; +$aNames[] = 'Sheppard'; +$aNames[] = 'Weeks'; +$aNames[] = 'Camacho'; +$aNames[] = 'Bean'; +$aNames[] = 'Barron'; +$aNames[] = 'Livingston'; +$aNames[] = 'Middleton'; +$aNames[] = 'Spears'; +$aNames[] = 'Branch'; +$aNames[] = 'Blevins'; +$aNames[] = 'Chen'; +$aNames[] = 'Kerr'; +$aNames[] = 'Mcconnell'; +$aNames[] = 'Hatfield'; +$aNames[] = 'Harding'; +$aNames[] = 'Ashley'; +$aNames[] = 'Solis'; +$aNames[] = 'Herman'; +$aNames[] = 'Frost'; +$aNames[] = 'Giles'; +$aNames[] = 'Blackburn'; +$aNames[] = 'William'; +$aNames[] = 'Pennington'; +$aNames[] = 'Woodward'; +$aNames[] = 'Finley'; +$aNames[] = 'Mcintosh'; +$aNames[] = 'Koch'; +$aNames[] = 'Best'; +$aNames[] = 'Solomon'; +$aNames[] = 'Mccullough'; +$aNames[] = 'Dudley'; +$aNames[] = 'Nolan'; +$aNames[] = 'Blanchard'; +$aNames[] = 'Rivas'; +$aNames[] = 'Brennan'; +$aNames[] = 'Mejia'; +$aNames[] = 'Kane'; +$aNames[] = 'Benton'; +$aNames[] = 'Joyce'; +$aNames[] = 'Buckley'; +$aNames[] = 'Haley'; +$aNames[] = 'Valentine'; +$aNames[] = 'Maddox'; +$aNames[] = 'Russo'; +$aNames[] = 'Mcknight'; +$aNames[] = 'Buck'; +$aNames[] = 'Moon'; +$aNames[] = 'Mcmillan'; +$aNames[] = 'Crosby'; +$aNames[] = 'Berg'; +$aNames[] = 'Dotson'; +$aNames[] = 'Mays'; +$aNames[] = 'Roach'; +$aNames[] = 'Church'; +$aNames[] = 'Chan'; +$aNames[] = 'Richmond'; +$aNames[] = 'Meadows'; +$aNames[] = 'Faulkner'; +$aNames[] = 'Oneill'; +$aNames[] = 'Knapp'; +$aNames[] = 'Kline'; +$aNames[] = 'Barry'; +$aNames[] = 'Ochoa'; +$aNames[] = 'Jacobson'; +$aNames[] = 'Gay'; +$aNames[] = 'Avery'; +$aNames[] = 'Hendricks'; +$aNames[] = 'Horne'; +$aNames[] = 'Shepard'; +$aNames[] = 'Hebert'; +$aNames[] = 'Cherry'; +$aNames[] = 'Cardenas'; +$aNames[] = 'Mcintyre'; +$aNames[] = 'Whitney'; +$aNames[] = 'Waller'; +$aNames[] = 'Holman'; +$aNames[] = 'Donaldson'; +$aNames[] = 'Cantu'; +$aNames[] = 'Terrell'; +$aNames[] = 'Morin'; +$aNames[] = 'Gillespie'; +$aNames[] = 'Fuentes'; +$aNames[] = 'Tillman'; +$aNames[] = 'Sanford'; +$aNames[] = 'Bentley'; +$aNames[] = 'Peck'; +$aNames[] = 'Key'; +$aNames[] = 'Salas'; +$aNames[] = 'Rollins'; +$aNames[] = 'Gamble'; +$aNames[] = 'Dickson'; +$aNames[] = 'Battle'; +$aNames[] = 'Santana'; +$aNames[] = 'Cabrera'; +$aNames[] = 'Cervantes'; +$aNames[] = 'Howe'; +$aNames[] = 'Hinton'; +$aNames[] = 'Hurley'; +$aNames[] = 'Spence'; +$aNames[] = 'Zamora'; +$aNames[] = 'Yang'; +$aNames[] = 'Mcneil'; +$aNames[] = 'Suarez'; +$aNames[] = 'Case'; +$aNames[] = 'Petty'; +$aNames[] = 'Gould'; +$aNames[] = 'Mcfarland'; +$aNames[] = 'Sampson'; +$aNames[] = 'Carver'; +$aNames[] = 'Bray'; +$aNames[] = 'Rosario'; +$aNames[] = 'Macdonald'; +$aNames[] = 'Stout'; +$aNames[] = 'Hester'; +$aNames[] = 'Melendez'; +$aNames[] = 'Dillon'; +$aNames[] = 'Farley'; +$aNames[] = 'Hopper'; +$aNames[] = 'Galloway'; +$aNames[] = 'Potts'; +$aNames[] = 'Bernard'; +$aNames[] = 'Joyner'; +$aNames[] = 'Stein'; +$aNames[] = 'Aguirre'; +$aNames[] = 'Osborn'; +$aNames[] = 'Mercer'; +$aNames[] = 'Bender'; +$aNames[] = 'Franco'; +$aNames[] = 'Rowland'; +$aNames[] = 'Sykes'; +$aNames[] = 'Benjamin'; +$aNames[] = 'Travis'; +$aNames[] = 'Pickett'; +$aNames[] = 'Crane'; +$aNames[] = 'Sears'; +$aNames[] = 'Mayo'; +$aNames[] = 'Dunlap'; +$aNames[] = 'Hayden'; +$aNames[] = 'Wilder'; +$aNames[] = 'Mckay'; +$aNames[] = 'Coffey'; +$aNames[] = 'Mccarty'; +$aNames[] = 'Ewing'; +$aNames[] = 'Cooley'; +$aNames[] = 'Vaughan'; +$aNames[] = 'Bonner'; +$aNames[] = 'Cotton'; +$aNames[] = 'Holder'; +$aNames[] = 'Stark'; +$aNames[] = 'Ferrell'; +$aNames[] = 'Cantrell'; +$aNames[] = 'Fulton'; +$aNames[] = 'Lynn'; +$aNames[] = 'Lott'; +$aNames[] = 'Calderon'; +$aNames[] = 'Rosa'; +$aNames[] = 'Pollard'; +$aNames[] = 'Hooper'; +$aNames[] = 'Burch'; +$aNames[] = 'Mullen'; +$aNames[] = 'Fry'; +$aNames[] = 'Riddle'; +$aNames[] = 'Levy'; +$aNames[] = 'David'; +$aNames[] = 'Duke'; +$aNames[] = 'Odonnell'; +$aNames[] = 'Guy'; +$aNames[] = 'Michael'; +$aNames[] = 'Britt'; +$aNames[] = 'Frederick'; +$aNames[] = 'Daugherty'; +$aNames[] = 'Berger'; +$aNames[] = 'Dillard'; +$aNames[] = 'Alston'; +$aNames[] = 'Jarvis'; +$aNames[] = 'Frye'; +$aNames[] = 'Riggs'; +$aNames[] = 'Chaney'; +$aNames[] = 'Odom'; +$aNames[] = 'Duffy'; +$aNames[] = 'Fitzpatrick'; +$aNames[] = 'Valenzuela'; +$aNames[] = 'Merrill'; +$aNames[] = 'Mayer'; +$aNames[] = 'Alford'; +$aNames[] = 'Mcpherson'; +$aNames[] = 'Acevedo'; +$aNames[] = 'Donovan'; +$aNames[] = 'Barrera'; +$aNames[] = 'Albert'; +$aNames[] = 'Cote'; +$aNames[] = 'Reilly'; +$aNames[] = 'Compton'; +$aNames[] = 'Raymond'; +$aNames[] = 'Mooney'; +$aNames[] = 'Mcgowan'; +$aNames[] = 'Craft'; +$aNames[] = 'Cleveland'; +$aNames[] = 'Clemons'; +$aNames[] = 'Wynn'; +$aNames[] = 'Nielsen'; +$aNames[] = 'Baird'; +$aNames[] = 'Stanton'; +$aNames[] = 'Snider'; +$aNames[] = 'Rosales'; +$aNames[] = 'Bright'; +$aNames[] = 'Witt'; +$aNames[] = 'Stuart'; +$aNames[] = 'Hays'; +$aNames[] = 'Holden'; +$aNames[] = 'Rutledge'; +$aNames[] = 'Kinney'; +$aNames[] = 'Clements'; +$aNames[] = 'Castaneda'; +$aNames[] = 'Slater'; +$aNames[] = 'Hahn'; +$aNames[] = 'Emerson'; +$aNames[] = 'Conrad'; +$aNames[] = 'Burks'; +$aNames[] = 'Delaney'; +$aNames[] = 'Pate'; +$aNames[] = 'Lancaster'; +$aNames[] = 'Sweet'; +$aNames[] = 'Justice'; +$aNames[] = 'Tyson'; +$aNames[] = 'Sharpe'; +$aNames[] = 'Whitfield'; +$aNames[] = 'Talley'; +$aNames[] = 'Macias'; +$aNames[] = 'Irwin'; +$aNames[] = 'Burris'; +$aNames[] = 'Ratliff'; +$aNames[] = 'Mccray'; +$aNames[] = 'Madden'; +$aNames[] = 'Kaufman'; +$aNames[] = 'Beach'; +$aNames[] = 'Goff'; +$aNames[] = 'Cash'; +$aNames[] = 'Bolton'; +$aNames[] = 'Mcfadden'; +$aNames[] = 'Levine'; +$aNames[] = 'Good'; +$aNames[] = 'Byers'; +$aNames[] = 'Kirkland'; +$aNames[] = 'Kidd'; +$aNames[] = 'Workman'; +$aNames[] = 'Carney'; +$aNames[] = 'Dale'; +$aNames[] = 'Mcleod'; +$aNames[] = 'Holcomb'; +$aNames[] = 'England'; +$aNames[] = 'Finch'; +$aNames[] = 'Head'; +$aNames[] = 'Burt'; +$aNames[] = 'Hendrix'; +$aNames[] = 'Sosa'; +$aNames[] = 'Haney'; +$aNames[] = 'Franks'; +$aNames[] = 'Sargent'; +$aNames[] = 'Nieves'; +$aNames[] = 'Downs'; +$aNames[] = 'Rasmussen'; +$aNames[] = 'Bird'; +$aNames[] = 'Hewitt'; +$aNames[] = 'Lindsay'; +$aNames[] = 'Le'; +$aNames[] = 'Foreman'; +$aNames[] = 'Valencia'; +$aNames[] = 'Oneil'; +$aNames[] = 'Delacruz'; +$aNames[] = 'Vinson'; +$aNames[] = 'Dejesus'; +$aNames[] = 'Hyde'; +$aNames[] = 'Forbes'; +$aNames[] = 'Gilliam'; +$aNames[] = 'Guthrie'; +$aNames[] = 'Wooten'; +$aNames[] = 'Huber'; +$aNames[] = 'Barlow'; +$aNames[] = 'Boyle'; +$aNames[] = 'Mcmahon'; +$aNames[] = 'Buckner'; +$aNames[] = 'Rocha'; +$aNames[] = 'Puckett'; +$aNames[] = 'Langley'; +$aNames[] = 'Knowles'; +$aNames[] = 'Cooke'; +$aNames[] = 'Velazquez'; +$aNames[] = 'Whitley'; +$aNames[] = 'Noel'; +$aNames[] = 'Vang'; +$aNames[] = 'Shea'; +$aNames[] = 'Rouse'; +$aNames[] = 'Hartley'; +$aNames[] = 'Mayfield'; +$aNames[] = 'Elder'; +$aNames[] = 'Rankin'; +$aNames[] = 'Hanna'; +$aNames[] = 'Cowan'; +$aNames[] = 'Lucero'; +$aNames[] = 'Arroyo'; +$aNames[] = 'Slaughter'; +$aNames[] = 'Haas'; +$aNames[] = 'Oconnell'; +$aNames[] = 'Minor'; +$aNames[] = 'Kendrick'; +$aNames[] = 'Shirley'; +$aNames[] = 'Kendall'; +$aNames[] = 'Boucher'; +$aNames[] = 'Archer'; +$aNames[] = 'Boggs'; +$aNames[] = 'Odell'; +$aNames[] = 'Dougherty'; +$aNames[] = 'Andersen'; +$aNames[] = 'Newell'; +$aNames[] = 'Crowe'; +$aNames[] = 'Wang'; +$aNames[] = 'Friedman'; +$aNames[] = 'Bland'; +$aNames[] = 'Swain'; +$aNames[] = 'Holley'; +$aNames[] = 'Adam'; +$aNames[] = 'Adami'; +$aNames[] = 'Adriaen'; +$aNames[] = 'Adriaensen'; +$aNames[] = 'Adriaenssen'; +$aNames[] = 'Adriaenssens'; +$aNames[] = 'Adriencense'; +$aNames[] = 'Adriensence'; +$aNames[] = 'Adrienssens'; +$aNames[] = 'Aelter'; +$aNames[] = 'Aelterman'; +$aNames[] = 'Aelters'; +$aNames[] = 'Aerens'; +$aNames[] = 'Aerts'; +$aNames[] = 'Aertsens'; +$aNames[] = 'Albumazard'; +$aNames[] = 'Alloo'; +$aNames[] = 'Alsteen'; +$aNames[] = 'Andersson'; +$aNames[] = 'Andr'; +$aNames[] = 'Andries'; +$aNames[] = 'Andriessen'; +$aNames[] = 'Anthon'; +$aNames[] = 'Antoine'; +$aNames[] = 'Appelbaum'; +$aNames[] = 'Applaer'; +$aNames[] = 'Arimont'; +$aNames[] = 'Arquin'; +$aNames[] = 'Arteman'; +$aNames[] = 'Baert'; +$aNames[] = 'Bartholomeeus'; +$aNames[] = 'Bastien'; +$aNames[] = 'Bastin'; +$aNames[] = 'Baugnet'; +$aNames[] = 'Baugniet'; +$aNames[] = 'Baugniez'; +$aNames[] = 'Bauwens'; +$aNames[] = 'Beauve'; +$aNames[] = 'Beck'; +$aNames[] = 'Beckers'; +$aNames[] = 'Bernard'; +$aNames[] = 'Bertrand'; +$aNames[] = 'Bietm'; +$aNames[] = 'Blaas'; +$aNames[] = 'Blankaert'; +$aNames[] = 'Blanquaert'; +$aNames[] = 'Blondeel'; +$aNames[] = 'Blondeeuw'; +$aNames[] = 'Blondoo'; +$aNames[] = 'Bodart'; +$aNames[] = 'Bodson'; +$aNames[] = 'Boeck'; +$aNames[] = 'Boesmans'; +$aNames[] = 'Bogaert'; +$aNames[] = 'Bogaerts'; +$aNames[] = 'Bogemans'; +$aNames[] = 'Booghmans'; +$aNames[] = 'Borremans'; +$aNames[] = 'Borsu'; +$aNames[] = 'Borsus'; +$aNames[] = 'Borsut'; +$aNames[] = 'Bosmans'; +$aNames[] = 'Bouch'; +$aNames[] = 'Bouchhout'; +$aNames[] = 'Bouillre'; +$aNames[] = 'Bouillet'; +$aNames[] = 'Boulanger'; +$aNames[] = 'Bourton'; +$aNames[] = 'Bouxin'; +$aNames[] = 'Brasseur'; +$aNames[] = 'Brouck'; +$aNames[] = 'Broucke'; +$aNames[] = 'Broucq'; +$aNames[] = 'Broucque'; +$aNames[] = 'Brouhier'; +$aNames[] = 'Brug'; +$aNames[] = 'Bruggesman'; +$aNames[] = 'Bruynseel'; +$aNames[] = 'Bruynseels'; +$aNames[] = 'Burger'; +$aNames[] = 'Burghgraeve'; +$aNames[] = 'Burgmeester'; +$aNames[] = 'Burton'; +$aNames[] = 'Burtont'; +$aNames[] = 'Buyle'; +$aNames[] = 'Calbert'; +$aNames[] = 'Callebaut'; +$aNames[] = 'Callebert'; +$aNames[] = 'Callebout'; +$aNames[] = 'Camby'; +$aNames[] = 'Cappelaere'; +$aNames[] = 'Cappelaire'; +$aNames[] = 'Cappelier'; +$aNames[] = 'Cappeliez'; +$aNames[] = 'Cappellier'; +$aNames[] = 'Carbonez'; +$aNames[] = 'Carbonnez'; +$aNames[] = 'Carlier'; +$aNames[] = 'Casteau'; +$aNames[] = 'Castel'; +$aNames[] = 'Castiaux'; +$aNames[] = 'Cauderlier'; +$aNames[] = 'Caudron'; +$aNames[] = 'Cauvel'; +$aNames[] = 'Cauvet'; +$aNames[] = 'Cauvin'; +$aNames[] = 'Cavard'; +$aNames[] = 'Ceulemans'; +$aNames[] = 'Chantry'; +$aNames[] = 'Charlier'; +$aNames[] = 'Chneboit'; +$aNames[] = 'Chestay'; +$aNames[] = 'Chestia'; +$aNames[] = 'Chrispeels'; +$aNames[] = 'Christiaens'; +$aNames[] = 'Christoffel'; +$aNames[] = 'Claes'; +$aNames[] = 'Claessens'; +$aNames[] = 'Claeys'; +$aNames[] = 'Claus'; +$aNames[] = 'Clban'; +$aNames[] = 'Clbant'; +$aNames[] = 'Clerx'; +$aNames[] = 'Colinus'; +$aNames[] = 'Collard'; +$aNames[] = 'Colleye'; +$aNames[] = 'Collignon'; +$aNames[] = 'Collin'; +$aNames[] = 'Colson'; +$aNames[] = 'Cool'; +$aNames[] = 'Cools'; +$aNames[] = 'Coppens'; +$aNames[] = 'Corain'; +$aNames[] = 'Corijn'; +$aNames[] = 'Corin'; +$aNames[] = 'Cornelis'; +$aNames[] = 'Cornet'; +$aNames[] = 'Corrin'; +$aNames[] = 'Corring'; +$aNames[] = 'Corringer'; +$aNames[] = 'Coryn'; +$aNames[] = 'Coudyser'; +$aNames[] = 'Couhysder'; +$aNames[] = 'Coutijser'; +$aNames[] = 'Coutiser'; +$aNames[] = 'Crab'; +$aNames[] = 'Crabbe'; +$aNames[] = 'Crama'; +$aNames[] = 'Crpez'; +$aNames[] = 'Crespel'; +$aNames[] = 'Crevisse'; +$aNames[] = 'Crevits'; +$aNames[] = 'Crispeel'; +$aNames[] = 'Crispeels'; +$aNames[] = 'Crispel'; +$aNames[] = 'Crispiels'; +$aNames[] = 'Cuvelier'; +$aNames[] = 'Cuypers'; +$aNames[] = 'Daan'; +$aNames[] = 'Daels'; +$aNames[] = 'Daems'; +$aNames[] = 'Dalmans'; +$aNames[] = 'Damard'; +$aNames[] = 'Damart'; +$aNames[] = 'Danis'; +$aNames[] = 'Dany'; +$aNames[] = 'Danys'; +$aNames[] = 'Dapvril'; +$aNames[] = 'Daufresne'; +$aNames[] = 'Dawance'; +$aNames[] = 'Debacker'; +$aNames[] = 'Debaere'; +$aNames[] = 'Debakker'; +$aNames[] = 'Debaut'; +$aNames[] = 'Debecker'; +$aNames[] = 'Debekker'; +$aNames[] = 'Debled'; +$aNames[] = 'Deboschere'; +$aNames[] = 'Deboscker'; +$aNames[] = 'Deboskre'; +$aNames[] = 'Debosscher'; +$aNames[] = 'Debosschere'; +$aNames[] = 'Debusschere'; +$aNames[] = 'Debuyst'; +$aNames[] = 'Declerck'; +$aNames[] = 'Declercq'; +$aNames[] = 'Decock'; +$aNames[] = 'Decocq'; +$aNames[] = 'Decrucq'; +$aNames[] = 'Decruyenaere'; +$aNames[] = 'Defaux'; +$aNames[] = 'Defawe'; +$aNames[] = 'Degroote'; +$aNames[] = 'Dehoorne'; +$aNames[] = 'Dehorne'; +$aNames[] = 'Dehornes'; +$aNames[] = 'Deilgat'; +$aNames[] = 'Dejong'; +$aNames[] = 'Dejonghe'; +$aNames[] = 'Dekale'; +$aNames[] = 'Dekimpe'; +$aNames[] = 'Dekoch'; +$aNames[] = 'Dekuiper'; +$aNames[] = 'Dekyndt'; +$aNames[] = 'Delacuvellerie'; +$aNames[] = 'Delafosse'; +$aNames[] = 'Delahaye'; +$aNames[] = 'Delahayes'; +$aNames[] = 'Delbouille'; +$aNames[] = 'Delboulle'; +$aNames[] = 'Delcorps'; +$aNames[] = 'Delflache'; +$aNames[] = 'Delfosse'; +$aNames[] = 'Delgat'; +$aNames[] = 'Delhaye'; +$aNames[] = 'Delhoste'; +$aNames[] = 'Delhotte'; +$aNames[] = 'Delmare'; +$aNames[] = 'Delmer'; +$aNames[] = 'Delobbe'; +$aNames[] = 'Delobe'; +$aNames[] = 'Delobes'; +$aNames[] = 'Delplace'; +$aNames[] = 'Delvaux'; +$aNames[] = 'Demain'; +$aNames[] = 'Demeiere'; +$aNames[] = 'Demeyer'; +$aNames[] = 'Demoor'; +$aNames[] = 'Demoore'; +$aNames[] = 'Demunck'; +$aNames[] = 'Demuynck'; +$aNames[] = 'Den'; +$aNames[] = 'Denaeyer'; +$aNames[] = 'Denayer'; +$aNames[] = 'Deneyer'; +$aNames[] = 'Denis'; +$aNames[] = 'Denoor'; +$aNames[] = 'Depannemaecker'; +$aNames[] = 'Depelsemacker'; +$aNames[] = 'Depelsemaeker'; +$aNames[] = 'Depelsenaire'; +$aNames[] = 'Depelseneer'; +$aNames[] = 'Depercenaire'; +$aNames[] = 'Depester'; +$aNames[] = 'Depireux'; +$aNames[] = 'Depierreux'; +$aNames[] = 'Depireux'; +$aNames[] = 'Depoorter'; +$aNames[] = 'Depoortere'; +$aNames[] = 'Depooter'; +$aNames[] = 'Depootere'; +$aNames[] = 'Deporter'; +$aNames[] = 'Deportere'; +$aNames[] = 'Depoterre'; +$aNames[] = 'Deprez'; +$aNames[] = 'Deramaix'; +$aNames[] = 'Deroosse'; +$aNames[] = 'Desandrouins'; +$aNames[] = 'Descamps'; +$aNames[] = 'Deschepper'; +$aNames[] = 'Desmedt'; +$aNames[] = 'Desmet'; +$aNames[] = 'Desmets'; +$aNames[] = 'Desmeytere'; +$aNames[] = 'Desmidt'; +$aNames[] = 'Desmidts'; +$aNames[] = 'Desmit'; +$aNames[] = 'Desmyter'; +$aNames[] = 'Desmytter'; +$aNames[] = 'Desmyttere'; +$aNames[] = 'Desprs'; +$aNames[] = 'Despret'; +$aNames[] = 'Desprets'; +$aNames[] = 'Despretz'; +$aNames[] = 'Desprey'; +$aNames[] = 'Desprez'; +$aNames[] = 'Destoute'; +$aNames[] = 'Deswart'; +$aNames[] = 'Deswarte'; +$aNames[] = 'Dethier'; +$aNames[] = 'Deur'; +$aNames[] = 'Deurwaerder'; +$aNames[] = 'Devis'; +$aNames[] = 'Devloo'; +$aNames[] = 'Devos'; +$aNames[] = 'Devriend'; +$aNames[] = 'Dewever'; +$aNames[] = 'Dewit'; +$aNames[] = 'Dewitte'; +$aNames[] = 'Dewyse'; +$aNames[] = 'Dhaeyer'; +$aNames[] = "D'Haeyer"; +$aNames[] = 'Dhoeraen'; +$aNames[] = "D'Hoeraen"; +$aNames[] = "D'Hoolaege"; +$aNames[] = 'Dierckx'; +$aNames[] = 'Dierik'; +$aNames[] = 'Doeraene'; +$aNames[] = 'Dolhaeghe'; +$aNames[] = 'Domiens'; +$aNames[] = 'Dominicus'; +$aNames[] = 'Dondaine'; +$aNames[] = 'Dondeine'; +$aNames[] = 'Dondenne'; +$aNames[] = 'Dondeyne'; +$aNames[] = 'Doolaeghe'; +$aNames[] = 'Doolaegue'; +$aNames[] = 'Doolage'; +$aNames[] = 'Doorn'; +$aNames[] = 'Doorne'; +$aNames[] = 'Doorneman'; +$aNames[] = 'Draier'; +$aNames[] = 'Dresselaers'; +$aNames[] = 'Dubled'; +$aNames[] = 'Dubois'; +$aNames[] = 'Dumont'; +$aNames[] = 'Dupont'; +$aNames[] = 'Duquesnay'; +$aNames[] = 'Duquesne'; +$aNames[] = 'Duquesnoy'; +$aNames[] = 'Ebrard'; +$aNames[] = 'Eeckeman'; +$aNames[] = 'Eerkens'; +$aNames[] = 'Erckens'; +$aNames[] = 'Erk'; +$aNames[] = 'Erken'; +$aNames[] = 'Erkens'; +$aNames[] = 'Etienne'; +$aNames[] = 'Euvrard'; +$aNames[] = 'Evert'; +$aNames[] = 'Evrard'; +$aNames[] = 'Evras'; +$aNames[] = 'Evrat'; +$aNames[] = 'Eyck'; +$aNames[] = 'Eysermans'; +$aNames[] = 'Fawat'; +$aNames[] = 'Faweux'; +$aNames[] = 'Fee'; +$aNames[] = 'Felix'; +$aNames[] = 'Flamenck'; +$aNames[] = 'Floche'; +$aNames[] = 'Floquet'; +$aNames[] = 'Fontaine'; +$aNames[] = 'Fonteyne'; +$aNames[] = 'Fraigany'; +$aNames[] = 'Fraigneux'; +$aNames[] = 'Francoeur'; +$aNames[] = 'Franois'; +$aNames[] = 'Francon'; +$aNames[] = 'Frankel'; +$aNames[] = 'Franken'; +$aNames[] = 'Frankeur'; +$aNames[] = 'Frans'; +$aNames[] = 'Fransman'; +$aNames[] = 'Fransolet'; +$aNames[] = 'Franzman'; +$aNames[] = 'Frijer'; +$aNames[] = 'Gabriels'; +$aNames[] = 'Gadisseur'; +$aNames[] = 'Gadisseux'; +$aNames[] = 'Gasthuys'; +$aNames[] = 'Gaudisseu'; +$aNames[] = 'Geeregat'; +$aNames[] = 'Geerts'; +$aNames[] = 'Geerts'; +$aNames[] = 'Geets'; +$aNames[] = 'Gehucht'; +$aNames[] = 'Geiregat'; +$aNames[] = 'Gendebien'; +$aNames[] = 'Genot'; +$aNames[] = 'Georges'; +$aNames[] = 'Grard'; +$aNames[] = 'Gerlache'; +$aNames[] = 'Gerlaxhe'; +$aNames[] = 'Germay'; +$aNames[] = 'Germa'; +$aNames[] = 'Germeau'; +$aNames[] = 'Ghiste'; +$aNames[] = 'Gidts'; +$aNames[] = 'Giets'; +$aNames[] = 'Gilles'; +$aNames[] = 'Gillet'; +$aNames[] = 'Gilson'; +$aNames[] = 'Gits'; +$aNames[] = 'Glaze'; +$aNames[] = 'Glazeman'; +$aNames[] = 'Goethals'; +$aNames[] = 'Goffin'; +$aNames[] = 'Gomaert'; +$aNames[] = 'Gomardt'; +$aNames[] = 'Goor'; +$aNames[] = 'Goossens'; +$aNames[] = 'Goud'; +$aNames[] = 'Goudman'; +$aNames[] = 'Goudsmith'; +$aNames[] = 'Gourdet'; +$aNames[] = 'Gousson'; +$aNames[] = 'Graas'; +$aNames[] = 'Greggs'; +$aNames[] = 'Gregh'; +$aNames[] = 'Grgoire'; +$aNames[] = 'Gregoor'; +$aNames[] = 'Grewis'; +$aNames[] = 'Groot'; +$aNames[] = 'Groote'; +$aNames[] = 'Grotaers'; +$aNames[] = 'Guillaume'; +$aNames[] = 'Guyaux'; +$aNames[] = 'Haesen'; +$aNames[] = 'Haesevoets'; +$aNames[] = 'Halasi'; +$aNames[] = 'Halazy'; +$aNames[] = 'Hamers'; +$aNames[] = 'Hanssens'; +$aNames[] = 'Hardas'; +$aNames[] = 'Hardat'; +$aNames[] = 'Hardy'; +$aNames[] = 'Heerbrant'; +$aNames[] = 'Hendrick'; +$aNames[] = 'Hendrickx'; +$aNames[] = 'Hendriks'; +$aNames[] = 'Henry'; +$aNames[] = 'Herbrand'; +$aNames[] = 'Herbrandt'; +$aNames[] = 'Herbrant'; +$aNames[] = 'Herman'; +$aNames[] = 'Hermann'; +$aNames[] = 'Hermans'; +$aNames[] = 'Herten'; +$aNames[] = 'Hertogs'; +$aNames[] = 'Hertogue'; +$aNames[] = 'Heylen'; +$aNames[] = 'Heymans'; +$aNames[] = 'Heynemans'; +$aNames[] = 'Heyrman'; +$aNames[] = 'Hinck'; +$aNames[] = 'Hinckel'; +$aNames[] = 'Hincker'; +$aNames[] = 'Hinkel'; +$aNames[] = 'Hinkels'; +$aNames[] = 'Hinkens'; +$aNames[] = 'Hinker'; +$aNames[] = 'Hinkle'; +$aNames[] = 'Hoefnagel'; +$aNames[] = 'Hoefnagels'; +$aNames[] = 'Holemans'; +$aNames[] = 'Honnay'; +$aNames[] = 'Horlin'; +$aNames[] = 'Houvenaghel'; +$aNames[] = 'Hoyois'; +$aNames[] = 'Hubert'; +$aNames[] = 'Huig'; +$aNames[] = 'Ickx'; +$aNames[] = 'Istace'; +$aNames[] = 'Istasse'; +$aNames[] = 'Jaak'; +$aNames[] = 'Jaap'; +$aNames[] = 'Jacob'; +$aNames[] = 'Jacobs'; +$aNames[] = 'Jacques'; +$aNames[] = 'Jacquet'; +$aNames[] = 'Jan'; +$aNames[] = 'Janhes'; +$aNames[] = 'Jansen'; +$aNames[] = 'Janssen'; +$aNames[] = 'Janssens'; +$aNames[] = 'Jef'; +$aNames[] = 'Jenot'; +$aNames[] = 'Jeuniaux'; +$aNames[] = 'Joire'; +$aNames[] = 'Jone'; +$aNames[] = 'Joneau'; +$aNames[] = 'Jonet'; +$aNames[] = 'Jonet'; +$aNames[] = 'Jongers'; +$aNames[] = 'Jonn'; +$aNames[] = 'Jonnet'; +$aNames[] = 'Jordaens'; +$aNames[] = 'Jorez'; +$aNames[] = 'Joris'; +$aNames[] = 'Jorissen'; +$aNames[] = 'Jozef'; +$aNames[] = 'Julianus'; +$aNames[] = 'Julius'; +$aNames[] = 'Jurgen'; +$aNames[] = 'Kaalman'; +$aNames[] = 'Kaisin'; +$aNames[] = 'Keetels'; +$aNames[] = 'Kenens'; +$aNames[] = 'Kenes'; +$aNames[] = 'Kenis'; +$aNames[] = 'Kennens'; +$aNames[] = 'Kennes'; +$aNames[] = 'Kennis'; +$aNames[] = 'Kesteloot'; +$aNames[] = 'Ketel'; +$aNames[] = 'Ketelsmit'; +$aNames[] = 'Kiecken'; +$aNames[] = 'Kimpe'; +$aNames[] = 'Kinnen'; +$aNames[] = 'Klein'; +$aNames[] = 'Kleineman'; +$aNames[] = 'Kleiner'; +$aNames[] = 'Kleinerman'; +$aNames[] = 'Kleinman'; +$aNames[] = 'Klerk'; +$aNames[] = 'Kleynen'; +$aNames[] = 'Klingeleers'; +$aNames[] = 'Kobus'; +$aNames[] = 'Koeck'; +$aNames[] = 'Konninckx'; +$aNames[] = 'Koolman'; +$aNames[] = 'Korring'; +$aNames[] = 'Kramers'; +$aNames[] = 'Kreemers'; +$aNames[] = 'Kuipers'; +$aNames[] = 'Labbez'; +$aNames[] = 'Lacroix'; +$aNames[] = 'Laenen'; +$aNames[] = 'Laenens'; +$aNames[] = 'Lafontaine'; +$aNames[] = 'Lambert'; +$aNames[] = 'Lambrechts'; +$aNames[] = 'Lanen'; +$aNames[] = 'Lanens'; +$aNames[] = 'Langlez'; +$aNames[] = 'Lapayre'; +$aNames[] = 'Laseur'; +$aNames[] = 'Laseure'; +$aNames[] = 'Lauffer'; +$aNames[] = 'Laurent'; +$aNames[] = 'Lauwers'; +$aNames[] = 'Le Demunck'; +$aNames[] = 'Leboutte'; +$aNames[] = 'Lebrun'; +$aNames[] = 'Leclerc'; +$aNames[] = 'Leclercq'; +$aNames[] = 'Lecocq'; +$aNames[] = 'Lecomte'; +$aNames[] = 'Ledecq'; +$aNames[] = 'Leenhard'; +$aNames[] = 'Leenhart'; +$aNames[] = 'Lefebvre'; +$aNames[] = 'Lefvre'; +$aNames[] = 'Legrand'; +$aNames[] = 'Lejeune'; +$aNames[] = 'Lemaire'; +$aNames[] = 'Lemmens'; +$aNames[] = 'Lemonnier'; +$aNames[] = 'Lemounie'; +$aNames[] = 'Lenaerts'; +$aNames[] = 'Lnel'; +$aNames[] = 'Lnelle'; +$aNames[] = 'Lennel'; +$aNames[] = 'Lonard'; +$aNames[] = 'Lepoutre'; +$aNames[] = 'Leprette'; +$aNames[] = 'Lepropre'; +$aNames[] = 'Leroy'; +$aNames[] = 'Lescohy'; +$aNames[] = 'Lesoil'; +$aNames[] = 'Lesoile'; +$aNames[] = 'Lesoille'; +$aNames[] = 'Levecq'; +$aNames[] = 'Lewek'; +$aNames[] = 'Libert'; +$aNames[] = 'Liens'; +$aNames[] = 'Liephoudt'; +$aNames[] = 'Liepot'; +$aNames[] = 'Liepout'; +$aNames[] = 'Lieseborghs'; +$aNames[] = 'Liesenborghs'; +$aNames[] = 'Lietaer'; +$aNames[] = 'Lietaert'; +$aNames[] = 'Lietar'; +$aNames[] = 'Litar'; +$aNames[] = 'Litard'; +$aNames[] = 'Litart'; +$aNames[] = 'Lievens'; +$aNames[] = 'Lievesoons'; +$aNames[] = 'Lievevrouw'; +$aNames[] = 'Lievrouw'; +$aNames[] = 'Livrouw'; +$aNames[] = 'Lievrow'; +$aNames[] = 'Linglay'; +$aNames[] = 'Linglet'; +$aNames[] = 'Liphout'; +$aNames[] = 'Lisenborgh'; +$aNames[] = 'Lisenborgs'; +$aNames[] = 'Locreille'; +$aNames[] = 'Locrel'; +$aNames[] = 'Locrelle'; +$aNames[] = 'Lode'; +$aNames[] = 'Loo'; +$aNames[] = 'Lorfvre'; +$aNames[] = 'Lorphvre'; +$aNames[] = 'Losseau'; +$aNames[] = 'Losset'; +$aNames[] = 'Louis'; +$aNames[] = 'Louzeau'; +$aNames[] = 'Lowie'; +$aNames[] = 'Ludovicus'; +$aNames[] = 'Lugen'; +$aNames[] = 'Lugens'; +$aNames[] = 'Lust'; +$aNames[] = 'Lustig'; +$aNames[] = 'Luyer'; +$aNames[] = 'Luyrik'; +$aNames[] = 'Luyten'; +$aNames[] = 'Lyphoudt'; +$aNames[] = 'Lyphout'; +$aNames[] = 'Maca'; +$aNames[] = 'Maertens'; +$aNames[] = 'Maes'; +$aNames[] = 'Maessen'; +$aNames[] = 'Mahieu'; +$aNames[] = 'Maka'; +$aNames[] = 'Malchamp'; +$aNames[] = 'Malchamps'; +$aNames[] = 'Malmedier'; +$aNames[] = 'Malmedy'; +$aNames[] = 'Malmendier'; +$aNames[] = 'Mangon'; +$aNames[] = 'Maqua'; +$aNames[] = 'Marchal'; +$aNames[] = 'Marckx'; +$aNames[] = 'Marcus'; +$aNames[] = 'Mardaga'; +$aNames[] = 'Marchal'; +$aNames[] = 'Maria'; +$aNames[] = 'Mark'; +$aNames[] = 'Markgraff'; +$aNames[] = 'Martens'; +$aNames[] = 'Martin'; +$aNames[] = 'Martins'; +$aNames[] = 'Massart'; +$aNames[] = 'Masson'; +$aNames[] = 'Mathieu'; +$aNames[] = 'Mathissen'; +$aNames[] = 'Mathy'; +$aNames[] = 'Matthys'; +$aNames[] = 'Mauchamp'; +$aNames[] = 'Mauchamps'; +$aNames[] = 'Maurichon'; +$aNames[] = 'Maurissen'; +$aNames[] = 'Maurits'; +$aNames[] = 'Mayeur'; +$aNames[] = 'Mayeux'; +$aNames[] = 'Mechelaere'; +$aNames[] = 'Meert'; +$aNames[] = 'Meertens'; +$aNames[] = 'Meester'; +$aNames[] = 'Meeus'; +$aNames[] = 'Melaerts'; +$aNames[] = 'Mellaerts'; +$aNames[] = 'Merchi'; +$aNames[] = 'Merchier'; +$aNames[] = 'Mergeai'; +$aNames[] = 'Mergeay'; +$aNames[] = 'Merjai'; +$aNames[] = 'Merjay'; +$aNames[] = 'Mertens'; +$aNames[] = 'Mertes'; +$aNames[] = 'Merts'; +$aNames[] = 'Mertz'; +$aNames[] = 'Meulemans'; +$aNames[] = 'Meulemeesters'; +$aNames[] = 'Meunier'; +$aNames[] = 'Meurice'; +$aNames[] = 'Mewis'; +$aNames[] = 'Mewissen'; +$aNames[] = 'Michal'; +$aNames[] = 'Michaux'; +$aNames[] = 'Michel'; +$aNames[] = 'Michiels'; +$aNames[] = 'Mixhel'; +$aNames[] = 'Mochamps'; +$aNames[] = 'Moens'; +$aNames[] = 'Moeyaert'; +$aNames[] = 'Moiling'; +$aNames[] = 'Moinil'; +$aNames[] = 'Molemans'; +$aNames[] = 'Molenaers'; +$aNames[] = 'Monceau'; +$aNames[] = 'Moncia'; +$aNames[] = 'Monciaux'; +$aNames[] = 'Monsay'; +$aNames[] = 'Monteyne'; +$aNames[] = 'Moreau'; +$aNames[] = 'Mouyart'; +$aNames[] = 'Moyaert'; +$aNames[] = 'Mullenders'; +$aNames[] = 'Munck'; +$aNames[] = 'Muynck'; +$aNames[] = 'Nachtegael'; +$aNames[] = 'Nagelmaekers'; +$aNames[] = 'Nagels'; +$aNames[] = 'Natus'; +$aNames[] = 'Neel'; +$aNames[] = 'Neels'; +$aNames[] = 'Neuray'; +$aNames[] = 'Neureau'; +$aNames[] = 'Neuret'; +$aNames[] = 'Neurot'; +$aNames[] = 'Neuts'; +$aNames[] = 'Neuven'; +$aNames[] = 'Neven'; +$aNames[] = 'Nguyen'; +$aNames[] = 'Nicolas'; +$aNames[] = 'Nicolaus'; +$aNames[] = 'Nicolus'; +$aNames[] = 'Nijs'; +$aNames[] = 'Niklaas'; +$aNames[] = 'Nol'; +$aNames[] = 'Nuts'; +$aNames[] = 'Nuttin'; +$aNames[] = 'Ochin'; +$aNames[] = 'Olivier'; +$aNames[] = 'Olyff'; +$aNames[] = 'Paindavaine'; +$aNames[] = 'Pannaye'; +$aNames[] = 'Parmentier'; +$aNames[] = 'Pas'; +$aNames[] = 'Pauss'; +$aNames[] = 'Pauwels'; +$aNames[] = 'Peeters'; +$aNames[] = 'Pelser'; +$aNames[] = 'Pelsmaeker'; +$aNames[] = 'Peschon'; +$aNames[] = 'Peschoniez'; +$aNames[] = 'Pester'; +$aNames[] = 'Petersen'; +$aNames[] = 'Petit'; +$aNames[] = 'Pierre'; +$aNames[] = 'Piet'; +$aNames[] = 'Pieters'; +$aNames[] = 'Pietersen'; +$aNames[] = 'Piette'; +$aNames[] = 'Pirard'; +$aNames[] = 'Piron'; +$aNames[] = 'Pirotte'; +$aNames[] = 'Plaats'; +$aNames[] = 'Poels'; +$aNames[] = 'Poelsmans'; +$aNames[] = 'Poncelet'; +$aNames[] = 'Pools'; +$aNames[] = 'Posson'; +$aNames[] = 'Potstainer'; +$aNames[] = 'Potter'; +$aNames[] = 'Pottiaux'; +$aNames[] = 'Potti'; +$aNames[] = 'Potty'; +$aNames[] = 'Poyon'; +$aNames[] = 'Praat'; +$aNames[] = 'Premereur'; +$aNames[] = 'Premmereur'; +$aNames[] = 'Prevostel'; +$aNames[] = 'Priesse'; +$aNames[] = 'Prisse'; +$aNames[] = 'Proost'; +$aNames[] = 'Prost'; +$aNames[] = 'Proust'; +$aNames[] = 'Putman'; +$aNames[] = 'Putmans'; +$aNames[] = 'Putmans'; +$aNames[] = 'Puttemans'; +$aNames[] = 'Puttemans'; +$aNames[] = 'Quaisin'; +$aNames[] = 'Quesnay'; +$aNames[] = 'Quesne'; +$aNames[] = 'Quesneau'; +$aNames[] = 'Quesnel'; +$aNames[] = 'Quesney'; +$aNames[] = 'Quesnoy'; +$aNames[] = 'Queval'; +$aNames[] = 'Raes'; +$aNames[] = 'Ramael'; +$aNames[] = 'Raucent'; +$aNames[] = 'Rauscent'; +$aNames[] = 'Rausin'; +$aNames[] = 'Raussain'; +$aNames[] = 'Raussent'; +$aNames[] = 'Raussin'; +$aNames[] = 'Raveydts'; +$aNames[] = 'Ravignat'; +$aNames[] = 'Remy'; +$aNames[] = 'Renard'; +$aNames[] = 'Retelet'; +$aNames[] = 'Ricaart'; +$aNames[] = 'Ricaert'; +$aNames[] = 'Ricard'; +$aNames[] = 'Robaert'; +$aNames[] = 'Robbert'; +$aNames[] = 'Robert'; +$aNames[] = 'Roels'; +$aNames[] = 'Roland'; +$aNames[] = 'Rooseels'; +$aNames[] = 'Roosengardt'; +$aNames[] = 'Rosseel'; +$aNames[] = 'Rousseau'; +$aNames[] = 'Saintmaux'; +$aNames[] = 'Saint-Maux'; +$aNames[] = 'Sanctorum'; +$aNames[] = 'Santilman'; +$aNames[] = 'Schmitz'; +$aNames[] = 'Schnock'; +$aNames[] = 'Schoenmakers'; +$aNames[] = 'Schoenman'; +$aNames[] = 'Schoone'; +$aNames[] = 'Scorier'; +$aNames[] = 'Scuvie'; +$aNames[] = 'Scuvie'; +$aNames[] = 'Segers'; +$aNames[] = 'Seghers'; +$aNames[] = 'Seppen'; +$aNames[] = 'Servais'; +$aNames[] = 'Shoen'; +$aNames[] = 'Sijmen'; +$aNames[] = 'Simoens'; +$aNames[] = 'Simon'; +$aNames[] = 'Simons'; +$aNames[] = 'Sinnesal'; +$aNames[] = 'Sinnesal'; +$aNames[] = 'Slagmolder'; +$aNames[] = 'Slagmulder'; +$aNames[] = 'Slamulder'; +$aNames[] = 'Smal'; +$aNames[] = 'Smeets'; +$aNames[] = 'Smet'; +$aNames[] = 'Smets'; +$aNames[] = 'Smit'; +$aNames[] = 'Smolders'; +$aNames[] = 'Smulders'; +$aNames[] = 'Somers'; +$aNames[] = 'Sottiaux'; +$aNames[] = 'Spinette'; +$aNames[] = 'Sprecher'; +$aNames[] = 'Stas'; +$aNames[] = 'Stass'; +$aNames[] = 'Stassaert'; +$aNames[] = 'Stassar'; +$aNames[] = 'Stassard'; +$aNames[] = 'Stassart'; +$aNames[] = 'Stasse'; +$aNames[] = 'Stassiaux'; +$aNames[] = 'Stassin'; +$aNames[] = 'Stassinet'; +$aNames[] = 'Statius'; +$aNames[] = 'Steculorum'; +$aNames[] = 'Stefaans'; +$aNames[] = 'Stercken'; +$aNames[] = 'Sterckmans'; +$aNames[] = 'Sterckx'; +$aNames[] = 'Stevens'; +$aNames[] = 'Stier'; +$aNames[] = 'Stiers'; +$aNames[] = 'Stievens'; +$aNames[] = 'Stine'; +$aNames[] = 'Stoffel'; +$aNames[] = 'Stordair'; +$aNames[] = 'Stordeur'; +$aNames[] = 'Stoutmans'; +$aNames[] = 'Swart'; +$aNames[] = 'Swarte'; +$aNames[] = 'Tack'; +$aNames[] = 'Taverner'; +$aNames[] = 'Teissant'; +$aNames[] = 'Terreur'; +$aNames[] = 'Thijs'; +$aNames[] = 'Thiry'; +$aNames[] = 'Thissen'; +$aNames[] = 'Thomas'; +$aNames[] = 'Thonnisen'; +$aNames[] = 'Thuiliau'; +$aNames[] = 'Thuiliaux'; +$aNames[] = 'Thuiliet'; +$aNames[] = 'Thys'; +$aNames[] = 'Tibaut'; +$aNames[] = 'Timmerman'; +$aNames[] = 'Timmermans'; +$aNames[] = 'Tjampens'; +$aNames[] = "T'Jampens"; +$aNames[] = 'Toussaint'; +$aNames[] = 'Trausch'; +$aNames[] = 'Tuiliau'; +$aNames[] = 'Tuiliaux'; +$aNames[] = 'Tuilliet'; +$aNames[] = 'Tuin'; +$aNames[] = 'Tumson'; +$aNames[] = 'Tweelinckx'; +$aNames[] = 'Urbain'; +$aNames[] = 'Urting'; +$aNames[] = 'Vanbattel'; +$aNames[] = 'Vanbergh'; +$aNames[] = 'Vandamme'; +$aNames[] = 'Vandenberghe'; +$aNames[] = 'Vandenbossche'; +$aNames[] = 'Vandenbussche'; +$aNames[] = 'Vandendorpe'; +$aNames[] = 'Vandeputte'; +$aNames[] = 'Vanderhorst'; +$aNames[] = 'Vanderlinden'; +$aNames[] = 'Vanderplaetsen'; +$aNames[] = 'Vandevelde'; +$aNames[] = 'Vandoolaeghe'; +$aNames[] = 'Vandorpe'; +$aNames[] = 'Vanlierde'; +$aNames[] = 'Vanp'; +$aNames[] = 'Vanpede'; +$aNames[] = 'Vanpe'; +$aNames[] = 'Vansteertegem'; +$aNames[] = 'Vecq'; +$aNames[] = 'Veld'; +$aNames[] = 'Veldmann'; +$aNames[] = 'Vellemans'; +$aNames[] = 'Veraghe'; +$aNames[] = 'Veraghen'; +$aNames[] = 'Verbeeck'; +$aNames[] = 'Verbeke'; +$aNames[] = 'Verbruggen'; +$aNames[] = 'Vercammen'; +$aNames[] = 'Vercheval'; +$aNames[] = 'Verdoolaeg(H)E'; +$aNames[] = 'Verhaege'; +$aNames[] = 'Verhaegen'; +$aNames[] = 'Verhaeghe'; +$aNames[] = 'Verhaeghen'; +$aNames[] = 'Verhaegue'; +$aNames[] = 'Verhage'; +$aNames[] = 'Verhagen'; +$aNames[] = 'Verhaghe'; +$aNames[] = 'Verhelst'; +$aNames[] = 'Verheyen'; +$aNames[] = 'Verhoeven'; +$aNames[] = 'Verlinden'; +$aNames[] = 'Vermeer'; +$aNames[] = 'Vermeersch'; +$aNames[] = 'Vermeiren'; +$aNames[] = 'Vermeren'; +$aNames[] = 'Vermeulen'; +$aNames[] = 'Vermotte'; +$aNames[] = 'Verplaetse'; +$aNames[] = 'Verplancke'; +$aNames[] = 'Verplancken'; +$aNames[] = 'Verschueren'; +$aNames[] = 'Verslijke'; +$aNames[] = 'Verslycke'; +$aNames[] = 'Verstraete'; +$aNames[] = 'Verstraeten'; +$aNames[] = 'Vervoort'; +$aNames[] = 'Vet'; +$aNames[] = 'Vette'; +$aNames[] = 'Viatour'; +$aNames[] = 'Vieutems'; +$aNames[] = 'Vieuxtemps'; +$aNames[] = 'Vilain'; +$aNames[] = 'Vincent'; +$aNames[] = 'Vinchent'; +$aNames[] = 'Visje'; +$aNames[] = 'Vlaamsche'; +$aNames[] = 'Vlaeminck'; +$aNames[] = 'Vlaemynck'; +$aNames[] = 'Vlaminck'; +$aNames[] = 'Vlamynck'; +$aNames[] = 'Vlemincks'; +$aNames[] = 'Vleminckx'; +$aNames[] = 'Vleminx'; +$aNames[] = 'Vlemynckx'; +$aNames[] = 'Vogels'; +$aNames[] = 'Volckaert'; +$aNames[] = 'Volkaert'; +$aNames[] = 'Volkaerts'; +$aNames[] = 'Volkart'; +$aNames[] = 'Volkert'; +$aNames[] = 'Voller'; +$aNames[] = 'Vos'; +$aNames[] = 'Vossen'; +$aNames[] = 'Vrank'; +$aNames[] = 'Vrindt'; +$aNames[] = 'Vrolijt'; +$aNames[] = 'Vrolyck'; +$aNames[] = 'Vullers'; +$aNames[] = 'Wagemans'; +$aNames[] = 'Wagenmann'; +$aNames[] = 'Waghon'; +$aNames[] = 'Wagon'; +$aNames[] = 'Walle'; +$aNames[] = 'Wastiaux'; +$aNames[] = 'Watrigant'; +$aNames[] = 'Watriquant'; +$aNames[] = 'Watteau'; +$aNames[] = 'Watteau'; +$aNames[] = 'Watteaux'; +$aNames[] = 'Watteaux'; +$aNames[] = 'Wattecamp'; +$aNames[] = 'Wattecamps'; +$aNames[] = 'Wattecant'; +$aNames[] = 'Watteel'; +$aNames[] = 'Wattel'; +$aNames[] = 'Wattelle'; +$aNames[] = 'Wattiau'; +$aNames[] = 'Wattiaux'; +$aNames[] = 'Wattieaux'; +$aNames[] = 'Wauters'; +$aNames[] = 'Weers'; +$aNames[] = 'Weerts'; +$aNames[] = 'Wek'; +$aNames[] = 'Wevers'; +$aNames[] = 'Weynen'; +$aNames[] = 'Wilbaert'; +$aNames[] = 'Wilfart'; +$aNames[] = 'Willems'; +$aNames[] = 'Willock'; +$aNames[] = 'Willocq'; +$aNames[] = 'Wilock'; +$aNames[] = 'Wintgens'; +$aNames[] = 'Wouter'; +$aNames[] = 'Wouters'; +$aNames[] = 'Wuyts'; +$aNames[] = 'Wylock'; +$aNames[] = 'Wylocke'; +$aNames[] = 'Yildirim'; +$aNames[] = 'Yilmaz'; +$aNames[] = 'Zadelaar'; +$aNames[] = 'Zegers'; +$aNames[] = 'Zeggers'; +$aNames[] = 'Zgres'; +$aNames[] = 'Bernard'; +$aNames[] = 'Bertrand'; +$aNames[] = 'Blanc'; +$aNames[] = 'Bonnet'; +$aNames[] = 'David'; +$aNames[] = 'Dubois'; +$aNames[] = 'Durand'; +$aNames[] = 'Fournier'; +$aNames[] = 'Garcia'; +$aNames[] = 'Girard'; +$aNames[] = 'Lambert'; +$aNames[] = 'Laurent'; +$aNames[] = 'Lefebvre'; +$aNames[] = 'Leroy'; +$aNames[] = 'Martin'; +$aNames[] = 'Michel'; +$aNames[] = 'Moreau'; +$aNames[] = 'Morel'; +$aNames[] = 'Petit'; +$aNames[] = 'Rang'; +$aNames[] = 'Richard'; +$aNames[] = 'Robert'; +$aNames[] = 'Rousseau'; +$aNames[] = 'Roux'; +$aNames[] = 'Simon'; +$aNames[] = 'Thomas'; +$aNames[] = 'Andr'; +$aNames[] = 'Chevalier'; +$aNames[] = 'Clment'; +$aNames[] = 'Dupont'; +$aNames[] = 'Faure'; +$aNames[] = 'Franois'; +$aNames[] = 'Garnier'; +$aNames[] = 'Gauthier'; +$aNames[] = 'Gautier'; +$aNames[] = 'Guerin'; +$aNames[] = 'Henry'; +$aNames[] = 'Lefvre'; +$aNames[] = 'Legrand'; +$aNames[] = 'Lopez'; +$aNames[] = 'Martinez'; +$aNames[] = 'Masson'; +$aNames[] = 'Mathieu'; +$aNames[] = 'Mercier'; +$aNames[] = 'Morin'; +$aNames[] = 'Muller'; +$aNames[] = 'Nicolas'; +$aNames[] = 'Perrin'; +$aNames[] = 'Rang'; +$aNames[] = 'Robin'; +$aNames[] = 'Roussel'; +$aNames[] = 'Vincent'; +$aNames[] = 'Arnaud'; +$aNames[] = 'Blanchand'; +$aNames[] = 'Boyer'; +$aNames[] = 'Brun'; +$aNames[] = 'Brunet'; +$aNames[] = 'Denis'; +$aNames[] = 'Dufour'; +$aNames[] = 'Dumont'; +$aNames[] = 'Duval'; +$aNames[] = 'Fabre'; +$aNames[] = 'Fontaine'; +$aNames[] = 'Gaillard'; +$aNames[] = 'Giraud'; +$aNames[] = 'Joly'; +$aNames[] = 'Lemaire'; +$aNames[] = 'Lucas'; +$aNames[] = 'Marchand'; +$aNames[] = 'Meunier'; +$aNames[] = 'Meyer'; +$aNames[] = 'Nol'; +$aNames[] = 'Perez'; +$aNames[] = 'Rang'; +$aNames[] = 'Roche'; +$aNames[] = 'Roy'; +$aNames[] = 'Sanchez'; +$aNames[] = 'Vidal'; +$aNames[] = 'Adamcki'; +$aNames[] = 'Adamczak'; +$aNames[] = 'Adamczewski'; +$aNames[] = 'Adamczyk'; +$aNames[] = 'Adamiak'; +$aNames[] = 'Adamowicz'; +$aNames[] = 'Adamowitz'; +$aNames[] = 'Adamski'; +$aNames[] = 'Adamsky'; +$aNames[] = 'Adamus'; +$aNames[] = 'Ambroziak'; +$aNames[] = 'Ambroziewicz'; +$aNames[] = 'Ambrozy'; +$aNames[] = 'Andrzeg'; +$aNames[] = 'Andrzejak'; +$aNames[] = 'Antczak'; +$aNames[] = 'Antkowiak'; +$aNames[] = 'Augustyniak'; +$aNames[] = 'Balcerzak'; +$aNames[] = 'Balcrowiak'; +$aNames[] = 'Balczarek'; +$aNames[] = 'Banaszak'; +$aNames[] = 'Baranowski'; +$aNames[] = 'Bejm'; +$aNames[] = 'Biczysko'; +$aNames[] = 'Biernaciak'; +$aNames[] = 'Biernacki'; +$aNames[] = 'Biernaczyk'; +$aNames[] = 'Biernat'; +$aNames[] = 'Blaszak'; +$aNames[] = 'Blaszczak'; +$aNames[] = 'Blaszczyk'; +$aNames[] = 'Bloch'; +$aNames[] = 'Bloszak'; +$aNames[] = 'Boguslawiak'; +$aNames[] = 'Bor'; +$aNames[] = 'Brama'; +$aNames[] = 'Bressler'; +$aNames[] = 'Brezisky'; +$aNames[] = 'Brzezinski'; +$aNames[] = 'Buczak'; +$aNames[] = 'Buczek'; +$aNames[] = 'Buczko'; +$aNames[] = 'Buczkowski'; +$aNames[] = 'Buczynski'; +$aNames[] = 'Bugajny'; +$aNames[] = 'Bukowski'; +$aNames[] = 'Bulinski'; +$aNames[] = 'Bulinsky'; +$aNames[] = 'Cepak'; +$aNames[] = 'Cepek'; +$aNames[] = 'Ceremuga'; +$aNames[] = 'Crmuga'; +$aNames[] = 'Cernota'; +$aNames[] = 'Charbul'; +$aNames[] = 'Chlebowski'; +$aNames[] = 'Chmiel'; +$aNames[] = 'Chodkowski'; +$aNames[] = 'Ciemior'; +$aNames[] = 'Ciepluch'; +$aNames[] = 'Cieplucha'; +$aNames[] = 'Ciesielski'; +$aNames[] = 'Cieslak'; +$aNames[] = 'Cieslik'; +$aNames[] = 'Cyganek'; +$aNames[] = 'Cyganik'; +$aNames[] = 'Cygankiewicz'; +$aNames[] = 'Czajka'; +$aNames[] = 'Czajkowski'; +$aNames[] = 'Czapka'; +$aNames[] = 'Czapski'; +$aNames[] = 'Czeremcha'; +$aNames[] = 'Czeremuga'; +$aNames[] = 'Dabbor'; +$aNames[] = 'Dabik'; +$aNames[] = 'Dabrowski'; +$aNames[] = 'Dambek'; +$aNames[] = 'Dambik'; +$aNames[] = 'Damian'; +$aNames[] = 'Danielak'; +$aNames[] = 'Danielczak'; +$aNames[] = 'Danilevitch'; +$aNames[] = 'Danilewicz'; +$aNames[] = 'Dawidowicz'; +$aNames[] = 'Deka'; +$aNames[] = 'Dembski'; +$aNames[] = 'Dembsky'; +$aNames[] = 'Dobosz'; +$aNames[] = 'Dobosz'; +$aNames[] = 'Dobrowolski'; +$aNames[] = 'Domin'; +$aNames[] = 'Dominiak'; +$aNames[] = 'Dominiczak'; +$aNames[] = 'Dominik'; +$aNames[] = 'Dominikowski'; +$aNames[] = 'Doroszewski'; +$aNames[] = 'Dowbor'; +$aNames[] = 'Dudek'; +$aNames[] = 'Erazmus'; +$aNames[] = 'Feliks'; +$aNames[] = 'Fialek'; +$aNames[] = 'Fijal'; +$aNames[] = 'Fijalek'; +$aNames[] = 'Fijalkowski'; +$aNames[] = 'Filipczak'; +$aNames[] = 'Filipczuk'; +$aNames[] = 'Filipek'; +$aNames[] = 'Filipiak'; +$aNames[] = 'Filipiuk'; +$aNames[] = 'Filipkowski'; +$aNames[] = 'Filipowicz'; +$aNames[] = 'Filipowski'; +$aNames[] = 'Filipski'; +$aNames[] = 'Firlej'; +$aNames[] = 'Florack'; +$aNames[] = 'Florczak'; +$aNames[] = 'Fracczak'; +$aNames[] = 'Frackowiak'; +$aNames[] = 'Franciscek'; +$aNames[] = 'Fratczak'; +$aNames[] = 'Frydryszak'; +$aNames[] = 'Gabryelczyk'; +$aNames[] = 'Gabrysiak'; +$aNames[] = 'Gaik'; +$aNames[] = 'Galinski'; +$aNames[] = 'Galka'; +$aNames[] = 'Gasztowtt'; +$aNames[] = 'Glebocki'; +$aNames[] = 'Glinczanki'; +$aNames[] = 'Glowacki'; +$aNames[] = 'Gniewek'; +$aNames[] = 'Godes'; +$aNames[] = 'Gods'; +$aNames[] = 'Godesh'; +$aNames[] = 'Gomolka'; +$aNames[] = 'Gongal'; +$aNames[] = 'Goszczynski'; +$aNames[] = 'Grabara'; +$aNames[] = 'Grabarczyk'; +$aNames[] = 'Grabarz'; +$aNames[] = 'Grabowski'; +$aNames[] = 'Grajoszek'; +$aNames[] = 'Gregorz'; +$aNames[] = 'Gryczkowiak'; +$aNames[] = 'Grzegorczyk'; +$aNames[] = 'Grzeskowiak'; +$aNames[] = 'Grzybek'; +$aNames[] = 'Grzybowski'; +$aNames[] = 'Habiera'; +$aNames[] = 'Halborn'; +$aNames[] = 'Harbul'; +$aNames[] = 'Heilpern'; +$aNames[] = 'Heilporn'; +$aNames[] = 'Heleniak'; +$aNames[] = 'Heltowni'; +$aNames[] = 'Hercberg'; +$aNames[] = 'Ickowicz'; +$aNames[] = 'Idaszek'; +$aNames[] = 'Idkowiak'; +$aNames[] = 'Iglicki'; +$aNames[] = 'Ignasiak'; +$aNames[] = 'Ignatczak'; +$aNames[] = 'Iszezuk'; +$aNames[] = 'Izydorczyk'; +$aNames[] = 'Jablonowski'; +$aNames[] = 'Jachowicz'; +$aNames[] = 'Jackowiak'; +$aNames[] = 'Jackowska'; +$aNames[] = 'Jackowski'; +$aNames[] = 'Jan'; +$aNames[] = 'Janas'; +$aNames[] = 'Janiak'; +$aNames[] = 'Janicki'; +$aNames[] = 'Janik'; +$aNames[] = 'Jankowiak'; +$aNames[] = 'Jankowski'; +$aNames[] = 'Janow'; +$aNames[] = 'Janowczyk'; +$aNames[] = 'Janowiak'; +$aNames[] = 'Janowicz'; +$aNames[] = 'Janowiec'; +$aNames[] = 'Janowski'; +$aNames[] = 'Janusz'; +$aNames[] = 'Jarosz'; +$aNames[] = 'Jaroszek'; +$aNames[] = 'Jaroszewicz'; +$aNames[] = 'Jaroszewski'; +$aNames[] = 'Jaroszuk'; +$aNames[] = 'Jaroszynski'; +$aNames[] = 'Jasicki'; +$aNames[] = 'Jasinski'; +$aNames[] = 'Jaskowiak'; +$aNames[] = 'Jedlenska'; +$aNames[] = 'Jedruch'; +$aNames[] = 'Jedrych'; +$aNames[] = 'Jedrzej'; +$aNames[] = 'Jedrzejak'; +$aNames[] = 'Jedrzejczak'; +$aNames[] = 'Jedrzejczy'; +$aNames[] = 'Jedrzejczyk'; +$aNames[] = 'Jedrzejewski'; +$aNames[] = 'Jelenski'; +$aNames[] = 'Jerzmanowski'; +$aNames[] = 'Jezierski'; +$aNames[] = 'Jodiowski'; +$aNames[] = 'Joskowiak'; +$aNames[] = 'Jozefiak'; +$aNames[] = 'Jozwiak'; +$aNames[] = 'Julia'; +$aNames[] = 'Juliusz'; +$aNames[] = 'Jura'; +$aNames[] = 'Jurak'; +$aNames[] = 'Juras'; +$aNames[] = 'Jurasz'; +$aNames[] = 'Juraszek'; +$aNames[] = 'Jurczak'; +$aNames[] = 'Jurczuk'; +$aNames[] = 'Jurczyk'; +$aNames[] = 'Jurczynski'; +$aNames[] = 'Jurek'; +$aNames[] = 'Martin'; +$aNames[] = 'Durand'; +$aNames[] = 'Robert'; +$aNames[] = 'Dupont'; +$aNames[] = 'Bernard'; +$aNames[] = 'Dubois'; +$aNames[] = 'Petit'; +$aNames[] = 'Cohen'; +$aNames[] = 'Richard'; +$aNames[] = 'Moreau'; +$aNames[] = 'Thomas'; +$aNames[] = 'Nguyen'; +$aNames[] = 'Simon'; +$aNames[] = 'Laurent'; +$aNames[] = 'Michel'; +$aNames[] = 'Levy'; +$aNames[] = 'Vincent'; +$aNames[] = 'David'; +$aNames[] = 'Bertrand'; +$aNames[] = 'Rousseau'; +$aNames[] = 'Leroy'; +$aNames[] = 'Girard'; +$aNames[] = 'Fournier'; +$aNames[] = 'Roux'; +$aNames[] = 'Andre'; +$aNames[] = 'Garnier'; +$aNames[] = 'Mercier'; +$aNames[] = 'Morin'; +$aNames[] = 'Jean'; +$aNames[] = 'Denis'; +$aNames[] = 'Henry'; +$aNames[] = 'Duval'; +$aNames[] = 'Morel'; +$aNames[] = 'Nicolas'; +$aNames[] = 'Francois'; +$aNames[] = 'Blanc'; +$aNames[] = 'Faure'; +$aNames[] = 'Pierre'; +$aNames[] = 'Garcia'; +$aNames[] = 'Lambert'; +$aNames[] = 'Gautier'; +$aNames[] = 'Lefebvre'; +$aNames[] = 'Meunier'; +$aNames[] = 'Lefevre'; +$aNames[] = 'Perrin'; +$aNames[] = 'Legrand'; +$aNames[] = 'Clement'; +$aNames[] = 'Mathieu'; +$aNames[] = 'Bonnet'; +$aNames[] = 'Chevalier'; +$aNames[] = 'Muller'; +$aNames[] = 'Fontaine'; +$aNames[] = 'Robin'; +$aNames[] = 'Blanchard'; +$aNames[] = 'Perez'; +$aNames[] = 'Guerin'; +$aNames[] = 'Masson'; +$aNames[] = 'Roger'; +$aNames[] = 'Gauthier'; +$aNames[] = 'Dupuis'; +$aNames[] = 'Martinez'; +$aNames[] = 'Leroux'; +$aNames[] = 'Bourgeois'; +$aNames[] = 'Dumont'; +$aNames[] = 'Dupond'; +$aNames[] = 'Paul'; +$aNames[] = 'Louis'; +$aNames[] = 'Dufour'; +$aNames[] = 'Meyer'; +$aNames[] = 'Lacroix'; +$aNames[] = 'Noel'; +$aNames[] = 'Fabre'; +$aNames[] = 'Lopez'; +$aNames[] = 'Gerard'; +$aNames[] = 'Lemaire'; +$aNames[] = 'Roussel'; +$aNames[] = 'Berger'; +$aNames[] = 'Barbier'; +$aNames[] = 'Boyer'; +$aNames[] = 'Brun'; +$aNames[] = 'Vidal'; +$aNames[] = 'Giraud'; +$aNames[] = 'Fernandez'; +$aNames[] = 'Marie'; +$aNames[] = 'Roche'; +$aNames[] = 'Colin'; +$aNames[] = 'Tran'; +$aNames[] = 'Marchand'; +$aNames[] = 'Lemoine'; +$aNames[] = 'Charpentier'; + +$aCountries = array(); +$aCountries[] = 'France'; +$aCountries[] = 'United Kingdom'; +$aCountries[] = 'Germany'; +$aCountries[] = 'United States of America'; +$aCountries[] = 'Canada'; +$aCountries[] = 'Switzerland'; +$aCountries[] = 'Belgium'; +$aCountries[] = 'Poland'; +$aCountries[] = 'Slovakia'; +$aCountries[] = 'Italy'; +$aCountries[] = 'Spain'; +$aCountries[] = 'Bulgaria'; + +$aCities = array(); +$aCities[]="Orvault"; +$aCities[]="Oullins"; +$aCities[]="Oyonnax"; +$aCities[]="Ozoir-la-Ferrire"; +$aCities[]="Palaiseau"; +$aCities[]="Pantin"; +$aCities[]="Paris"; +$aCities[]="Pau"; +$aCities[]="Prigueux"; +$aCities[]="Perpignan"; +$aCities[]="Le Perreux-sur-Marne"; +$aCities[]="Pessac"; +$aCities[]="Le Petit-Quevilly"; +$aCities[]="Pierrefitte-sur-Seine"; +$aCities[]="Plaisir"; +$aCities[]="Le Plessis-Robinson"; +$aCities[]="Poissy"; +$aCities[]="Poitiers"; +$aCities[]="Pontault-Combault"; +$aCities[]="Pontoise"; +$aCities[]="Le Port"; +$aCities[]="La Possession"; +$aCities[]="Puteaux"; +$aCities[]="Le Puy-en-Velay"; +$aCities[]="Quimper"; +$aCities[]="Rambouillet"; +$aCities[]="Reims"; +$aCities[]="Rennes"; +$aCities[]="Rez"; +$aCities[]="Rillieux-la-Pape"; +$aCities[]="Ris-Orangis"; +$aCities[]="Roanne"; +$aCities[]="Rochefort"; +$aCities[]="La Rochelle"; +$aCities[]="La Roche-sur-Yon"; +$aCities[]="Rodez"; +$aCities[]="Romainville"; +$aCities[]="Romans-sur-Isre"; +$aCities[]="Rosny-sous-Bois"; +$aCities[]="Roubaix"; +$aCities[]="Rouen"; +$aCities[]="Rueil-Malmaison"; +$aCities[]="Saint-Andr"; +$aCities[]="Saint-Benot"; +$aCities[]="Saint-Brieuc"; +$aCities[]="Saint-Chamond"; +$aCities[]="Saint-Cloud"; +$aCities[]="Saint-Denis"; +$aCities[]="Saint-Denis"; +$aCities[]="Saint-Di-des-Vosges"; +$aCities[]="Saint-Dizier"; +$aCities[]="Sainte-Foy-ls-Lyon"; +$aCities[]="Sainte-Genevive-des-Bois"; +$aCities[]="Saintes"; +$aCities[]="Saint-tienne"; +$aCities[]="Saint-tienne-du-Rouvray"; +$aCities[]="Saint-Germain-en-Laye"; +$aCities[]="Saint-Herblain"; +$aCities[]="Saint-Joseph"; +$aCities[]="Saint-Laurent-du-Var"; +$aCities[]="Saint-Leu"; +$aCities[]="Saint-L"; +$aCities[]="Saint-Louis"; +$aCities[]="Saint-Louis"; +$aCities[]="Saint-Malo"; +$aCities[]="Saint-Martin-d'Hres"; +$aCities[]="Saint-Maur-des-Fosss"; +$aCities[]="Saint-Mdard-en-Jalles"; +$aCities[]="Saint-Michel-sur-Orge"; +$aCities[]="Saint-Nazaire"; +$aCities[]="Saint-Ouen"; +$aCities[]="Saint-Paul"; +$aCities[]="Saint-Pierre"; +$aCities[]="Saint-Pol-sur-Mer"; +$aCities[]="Saint-Priest"; +$aCities[]="Saint-Quentin"; +$aCities[]="Saint-Raphal"; +$aCities[]="Saint-Sbastien-sur-Loire"; +$aCities[]="Salon-de-Provence"; +$aCities[]="Sannois"; +$aCities[]="Sarcelles"; +$aCities[]="Sarreguemines"; +$aCities[]="Sartrouville"; +$aCities[]="Saumur"; +$aCities[]="Savigny-le-Temple"; +$aCities[]="Savigny-sur-Orge"; +$aCities[]="Schiltigheim"; +$aCities[]="Sedan"; +$aCities[]="Sens"; +$aCities[]="Ste"; +$aCities[]="Sevran"; +$aCities[]="Svres"; +$aCities[]="La Seyne-sur-Mer"; +$aCities[]="Six-Fours-les-Plages"; +$aCities[]="Soissons"; +$aCities[]="Sotteville-ls-Rouen"; +$aCities[]="Stains"; +$aCities[]="Strasbourg"; +$aCities[]="Sucy-en-Brie"; +$aCities[]="Suresnes"; +$aCities[]="Talence"; +$aCities[]="Le Tampon"; +$aCities[]="Tarbes"; +$aCities[]="Taverny"; +$aCities[]="La Teste-de-Buch"; +$aCities[]="Thiais"; +$aCities[]="Thionville"; +$aCities[]="Thonon-les-Bains"; +$aCities[]="Torcy"; +$aCities[]="Toulon"; +$aCities[]="Toulouse"; +$aCities[]="Tourcoing"; +$aCities[]="Tournefeuille"; +$aCities[]="Tours"; +$aCities[]="Trappes"; +$aCities[]="Tremblay-en-France"; +$aCities[]="Troyes"; +$aCities[]="Les Ulis"; +$aCities[]="Valence"; +$aCities[]="Valenciennes"; +$aCities[]="La Valette-du-Var"; +$aCities[]="Vallauris"; +$aCities[]="Vanduvre-ls-Nancy"; +$aCities[]="Vannes"; +$aCities[]="Vanves"; +$aCities[]="Vaulx-en-Velin"; +$aCities[]="Vlizy-Villacoublay"; +$aCities[]="Vnissieux"; +$aCities[]="Verdun"; +$aCities[]="Vernon"; +$aCities[]="Versailles"; +$aCities[]="Vertou"; +$aCities[]="Vichy"; +$aCities[]="Vienne"; +$aCities[]="Vierzon"; +$aCities[]="Vigneux-sur-Seine"; +$aCities[]="Villefranche-sur-Sane"; +$aCities[]="Villejuif"; +$aCities[]="Villemomble"; +$aCities[]="Villenave-d'Ornon"; +$aCities[]="Villeneuve-d'Ascq"; +$aCities[]="Villeneuve-la-Garenne"; +$aCities[]="Villeneuve-Saint-Georges"; +$aCities[]="Villeneuve-sur-Lot"; +$aCities[]="Villeparisis"; +$aCities[]="Villepinte"; +$aCities[]="Villeurbanne"; +$aCities[]="Villiers-le-Bel"; +$aCities[]="Villiers-sur-Marne"; +$aCities[]="Vincennes"; +$aCities[]="Viry-Chtillon"; +$aCities[]="Vitrolles"; +$aCities[]="Vitry-sur-Seine"; +$aCities[]="Voiron"; +$aCities[]="Wattrelos"; +$aCities[]="Yerres"; +$aCities[]="Madrid"; +$aCities[]="Matar"; +$aCities[]="Mlaga"; +$aCities[]="Manzanares"; +$aCities[]="Marbella"; +$aCities[]="Marines"; +$aCities[]="Melilla"; +$aCities[]="Mijas"; +$aCities[]="Mstoles"; +$aCities[]="Murcie"; +$aCities[]="Sabadell"; +$aCities[]="Sagonte"; +$aCities[]="Salamanque"; +$aCities[]="Saint-Sbastien"; +$aCities[]="San Antonio de Benagber"; +$aCities[]="Sant Boi de Llobregat"; +$aCities[]="Santa Cruz de Tenerife"; +$aCities[]="Santander"; +$aCities[]="Saragosse"; +$aCities[]="Sgovia"; +$aCities[]="Serra"; +$aCities[]="Sville"; +$aCities[]="Sitges"; +$aCities[]="Sller"; +$aCities[]="Soria"; +$aCities[]="Aberdeen"; +$aCities[]="Aberystwyth"; +$aCities[]="Ashford"; +$aCities[]="Armagh"; +$aCities[]="Amersham"; +$aCities[]="Andover"; +$aCities[]="Arundel"; +$aCities[]="Abingdon"; +$aCities[]="Aylesbury"; +$aCities[]="Ayr"; +$aCities[]="Barrow-in-Furness"; +$aCities[]="Bath"; +$aCities[]="Bangor"; +$aCities[]="Bangor"; +$aCities[]="Belfast"; +$aCities[]="Birmingham"; +$aCities[]="Bournemouth"; +$aCities[]="Bradford"; +$aCities[]="Brighton"; +$aCities[]="Bristol"; +$aCities[]="Barrow-in-Furness"; +$aCities[]="Bath"; +$aCities[]="Bangor"; +$aCities[]="Bangor"; +$aCities[]="Belfast"; +$aCities[]="Birmingham"; +$aCities[]="Bournemouth"; +$aCities[]="Bradford"; +$aCities[]="Brighton"; +$aCities[]="Bristol"; +$aCities[]="Eastbourne"; +$aCities[]="dimbourg"; +$aCities[]="Ely"; +$aCities[]="Epsom"; +$aCities[]="Eton"; +$aCities[]="Evanton"; +$aCities[]="Exeter"; +$aCities[]="Lancaster"; +$aCities[]="Larne"; +$aCities[]="Leeds"; +$aCities[]="Leicester"; +$aCities[]="Lichfield"; +$aCities[]="Lincoln"; +$aCities[]="Lisburn"; +$aCities[]="Liverpool"; +$aCities[]="Londres"; +$aCities[]="Londonderry"; +$aCities[]="Newcastle-upon-Tyne"; +$aCities[]="Newport"; +$aCities[]="Newry"; +$aCities[]="Norwich"; +$aCities[]="Nottingham"; +$aCities[]="Reading"; +$aCities[]="Ripon"; +$aCities[]="Richmond"; +$aCities[]="Ripley"; +$aCities[]="Rugby"; +$aCities[]="Wakefield"; +$aCities[]="Wells"; +$aCities[]="Westminster"; +$aCities[]="Weymouth"; +$aCities[]="Wick"; +$aCities[]="Wigan"; +$aCities[]="Winchester"; +$aCities[]="Windsor"; +$aCities[]="Wolverhampton"; +$aCities[]="Worcester"; +$aCities[]="Wye"; +$aCities[]="Abbeyville"; +$aCities[]="Abbeyville"; +$aCities[]="Aberdeen"; +$aCities[]="Abilene"; +$aCities[]="Abilene"; +$aCities[]="Acron"; +$aCities[]="Ada"; +$aCities[]="Adelanto"; +$aCities[]="Affton"; +$aCities[]="Agoura Hills"; +$aCities[]="Aitkin"; +$aCities[]="Akron"; +$aCities[]="Alabaster"; +$aCities[]="Alameda"; +$aCities[]="Alamogordo"; +$aCities[]="Albany"; +$aCities[]="Albany"; +$aCities[]="Albany"; +$aCities[]="Albuquerque"; +$aCities[]="Alexandria"; +$aCities[]="Alhambra"; +$aCities[]="Alhambra"; +$aCities[]="Aliso Viejo"; +$aCities[]="Allentown"; +$aCities[]="Alton"; +$aCities[]="Alturas"; +$aCities[]="Amador City"; +$aCities[]="Amarillo"; +$aCities[]="American Canyon"; +$aCities[]="Ames"; +$aCities[]="Amesbury"; +$aCities[]="Amherst"; +$aCities[]="Amherst"; +$aCities[]="Anaheim"; +$aCities[]="Anchorage"; +$aCities[]="Anderson"; +$aCities[]="Anderson"; +$aCities[]="Andover"; +$aCities[]="Angels Camp"; +$aCities[]="Ann Arbor"; +$aCities[]="Annapolis"; +$aCities[]="Anniston"; +$aCities[]="Antioch"; +$aCities[]="Apex"; +$aCities[]="Apple Valley"; +$aCities[]="Appleton"; +$aCities[]="Appleton"; +$aCities[]="Appleton"; +$aCities[]="Appomattox"; +$aCities[]="Arcadia"; +$aCities[]="Arcata"; +$aCities[]="Arlington"; +$aCities[]="Arlington"; +$aCities[]="Arlington"; +$aCities[]="Armonk"; +$aCities[]="Arnaudville"; +$aCities[]="Arnold"; +$aCities[]="Arroyo Grande"; +$aCities[]="Artesia"; +$aCities[]="Arvin"; +$aCities[]="Asbury Park"; +$aCities[]="Asheville"; +$aCities[]="Ashland"; +$aCities[]="Aspen"; +$aCities[]="Assonet"; +$aCities[]="Atascadero"; +$aCities[]="Athens"; +$aCities[]="Athens"; +$aCities[]="Atherton"; +$aCities[]="Atlanta"; +$aCities[]="Atlantic City"; +$aCities[]="Attica"; +$aCities[]="Atwater"; +$aCities[]="Auburn"; +$aCities[]="Auburn"; +$aCities[]="Augusta"; +$aCities[]="Augusta"; +$aCities[]="Aurora"; +$aCities[]="Austin"; +$aCities[]="Autaugaville"; +$aCities[]="Ava"; +$aCities[]="Avalon"; +$aCities[]="Ave Maria"; +$aCities[]="Avenal"; +$aCities[]="Avon"; +$aCities[]="Avondale"; +$aCities[]="Azusa"; +$aCities[]="Bakersfield"; +$aCities[]="Baltimore"; +$aCities[]="Baton Rouge"; +$aCities[]="Bay Minette"; +$aCities[]="Berkeley"; +$aCities[]="Bessemer"; +$aCities[]="Billings"; +$aCities[]="Billingsley"; +$aCities[]="Biloxi"; +$aCities[]="Birmingham"; +$aCities[]="Bismarck"; +$aCities[]="Boise"; +$aCities[]="Boston"; +$aCities[]="Branson"; +$aCities[]="Brownsville"; +$aCities[]="Buffalo"; +$aCities[]="Burlington"; +$aCities[]="Cambridge"; +$aCities[]="Canton"; +$aCities[]="Carmel"; +$aCities[]="Carson City"; +$aCities[]="Casper"; +$aCities[]="Cedar Rapids"; +$aCities[]="Champaign"; +$aCities[]="Charleston"; +$aCities[]="Charleston"; +$aCities[]="Charlotte"; +$aCities[]="Charlottesville"; +$aCities[]="Chattanooga"; +$aCities[]="Cheyenne"; +$aCities[]="Chicago"; +$aCities[]="Cicero"; +$aCities[]="Cincinnati"; +$aCities[]="Cleveland"; +$aCities[]="Colby"; +$aCities[]="Colorado Springs"; +$aCities[]="Columbia"; +$aCities[]="Columbus"; +$aCities[]="Concord"; +$aCities[]="Corpus Christi"; +$aCities[]="Cullman"; +$aCities[]="Dallas"; +$aCities[]="Daphne"; +$aCities[]="Davenport"; +$aCities[]="Dayton"; +$aCities[]="Daytona Beach"; +$aCities[]="Denver"; +$aCities[]="Des Moines"; +$aCities[]="Detroit"; +$aCities[]="Dodge City"; +$aCities[]="Dothan"; +$aCities[]="Dover"; +$aCities[]="Duluth"; +$aCities[]="El Paso"; +$aCities[]="Enterprise"; +$aCities[]="ri"; +$aCities[]="Eugene"; +$aCities[]="Evansville"; +$aCities[]="Fairfield"; +$aCities[]="Fairbanks"; +$aCities[]="Fargo"; +$aCities[]="Fayetteville"; +$aCities[]="Fayetteville"; +$aCities[]="Flagstaff"; +$aCities[]="Flint"; +$aCities[]="Fort Collins"; +$aCities[]="Fort Lauderdale"; +$aCities[]="Fort Worth"; +$aCities[]="Frankfort"; +$aCities[]="Fresno"; +$aCities[]="Grand Rapids"; +$aCities[]="Great Falls"; +$aCities[]="Green Bay"; +$aCities[]="Greensboro"; +$aCities[]="Greenville"; +$aCities[]="Harrisburg"; +$aCities[]="Hartford"; +$aCities[]="Hays"; +$aCities[]="Helena"; +$aCities[]="Honolulu"; +$aCities[]="Hoover"; +$aCities[]="Hot Springs"; +$aCities[]="Houston"; +$aCities[]="Huntington"; +$aCities[]="Huntsville"; +$aCities[]="Jackson"; +$aCities[]="Jacksonville"; +$aCities[]="Jefferson City"; +$aCities[]="Joplin"; +$aCities[]="Juneau"; +$aCities[]="Idaho Falls"; +$aCities[]="Indianapolis"; +$aCities[]="Lafayette"; +$aCities[]="Lancaster"; +$aCities[]="Lansing"; +$aCities[]="Laredo"; +$aCities[]="Las Cruces"; +$aCities[]="Las Vegas"; +$aCities[]="Leavenworth"; +$aCities[]="Lexington"; +$aCities[]="Lincoln"; +$aCities[]="Little Rock"; +$aCities[]="Littleton"; +$aCities[]="Los Alamos"; +$aCities[]="Los Angeles"; +$aCities[]="Louisville"; +$aCities[]="Lubbock"; +$aCities[]="Madison"; +$aCities[]="Manchester"; +$aCities[]="McAllen"; +$aCities[]="Melbourne"; +$aCities[]="Memphis"; +$aCities[]="Mesa"; +$aCities[]="Miami"; +$aCities[]="Milbrook"; +$aCities[]="Milwaukee"; +$aCities[]="Minneapolis"; +$aCities[]="Missoula"; +$aCities[]="Mobile"; +$aCities[]="Modesto"; +$aCities[]="Monterey"; +$aCities[]="Montgomery"; +$aCities[]="Montpelier"; +$aCities[]="Mountain View"; +$aCities[]="Muncie"; +$aCities[]="Nashville"; +$aCities[]="New Bedford"; +$aCities[]="New Haven"; +$aCities[]="Newport"; +$aCities[]="New York"; +$aCities[]="Niagara Falls"; +$aCities[]="Norfolk"; +$aCities[]="La Nouvelle-Orlans"; +$aCities[]="Newark"; +$aCities[]="Oakland"; +$aCities[]="Oklahoma City"; +$aCities[]="Olympia"; +$aCities[]="Omaha"; +$aCities[]="Orem"; +$aCities[]="Orlando"; +$aCities[]="Palm Springs"; +$aCities[]="Palo Alto"; +$aCities[]="Pearl Harbor"; +$aCities[]="Pensacola"; +$aCities[]="Peoria"; +$aCities[]="Philadelphie"; +$aCities[]="Phoenix"; +$aCities[]="Pierre"; +$aCities[]="Pittsburgh"; +$aCities[]="Point du lac"; +$aCities[]="Portland"; +$aCities[]="Portland"; +$aCities[]="Prattville"; +$aCities[]="Providence"; +$aCities[]="Racine"; +$aCities[]="Raleigh"; +$aCities[]="Rapid City"; +$aCities[]="Redding"; +$aCities[]="Redmond"; +$aCities[]="Redwood City"; +$aCities[]="Reno"; +$aCities[]="Richmond"; +$aCities[]="Roanoke"; +$aCities[]="Rochester"; +$aCities[]="Rockford"; +$aCities[]="Sacramento"; +$aCities[]="Saint Louis"; +$aCities[]="Saint Paul"; +$aCities[]="Salem"; +$aCities[]="Salina"; +$aCities[]="Salt Lake City"; +$aCities[]="San Antonio"; +$aCities[]="San Bernardino"; +$aCities[]="San Diego"; +$aCities[]="San Francisco"; +$aCities[]="San Jos"; +$aCities[]="San Luis Obispo"; +$aCities[]="San Mateo"; +$aCities[]="Santa Barbara"; +$aCities[]="Santa Cruz"; +$aCities[]="Santa Fe"; +$aCities[]="Sarasota"; +$aCities[]="Saratoga"; +$aCities[]="Savannah"; +$aCities[]="Scranton"; +$aCities[]="Seattle"; +$aCities[]="Shreveport"; +$aCities[]="Sioux Falls"; +$aCities[]="South Bend"; +$aCities[]="Spokane"; +$aCities[]="Springfield"; +$aCities[]="Springfield"; +$aCities[]="Springfield"; +$aCities[]="Stamford"; +$aCities[]="Stockton"; +$aCities[]="Syracuse"; +$aCities[]="Tacoma"; +$aCities[]="Tallahassee"; +$aCities[]="Tampa"; +$aCities[]="Taunton"; +$aCities[]="Toledo"; +$aCities[]="Topeka"; +$aCities[]="Trenton"; +$aCities[]="Tucson"; +$aCities[]="Tulsa"; +$aCities[]="Tuscaloosa"; +$aCities[]="Tuskegee"; +$aCities[]="Waco"; +$aCities[]="Washington, DC"; +$aCities[]="Waterbury"; +$aCities[]="Wichita"; +$aCities[]="Williamsburg"; +$aCities[]="Wilmington"; +$aCities[]="Wilmington"; +$aCities[]="Worcester"; +$aCities[]="Babenhausen"; +$aCities[]="Bacharach"; +$aCities[]="Backnang"; +$aCities[]="Bad Aibling"; +$aCities[]="Bad Arolsen"; +$aCities[]="Bad Bentheim"; +$aCities[]="Bad Bergzabern"; +$aCities[]="Bad Berka"; +$aCities[]="Bad Berleburg"; +$aCities[]="Bad Berneck im Fichtelgebirge"; +$aCities[]="Bad Bevensen"; +$aCities[]="Bad Bibra"; +$aCities[]="Bad Blankenburg"; +$aCities[]="Bad Bramstedt"; +$aCities[]="Bad Breisig"; +$aCities[]="Bad Brckenau"; +$aCities[]="Bad Buchau"; +$aCities[]="Bad Camberg"; +$aCities[]="Bad Colberg-Heldburg"; +$aCities[]="Bad Doberan"; +$aCities[]="Bad Driburg"; +$aCities[]="Bad Dben"; +$aCities[]="Bad Drkheim"; +$aCities[]="Bad Drrenberg"; +$aCities[]="Bad Drrheim"; +$aCities[]="Bad Elster"; +$aCities[]="Bad Ems"; +$aCities[]="Baden-Baden"; +$aCities[]="Bad Fallingbostel"; +$aCities[]="Bad Frankenhausen"; +$aCities[]="Bad Freienwalde"; +$aCities[]="Bad Friedrichshall"; +$aCities[]="Bad Gandersheim"; +$aCities[]="Bad Gottleuba-Berggiehbel"; +$aCities[]="Bad Griesbach im Rottal"; +$aCities[]="Bad Grund"; +$aCities[]="Bad Harzburg"; +$aCities[]="Bad Herrenalb"; +$aCities[]="Bad Hersfeld"; +$aCities[]="Bad Homburg vor der Hhe"; +$aCities[]="Bad Honnef"; +$aCities[]="Bad Hnningen"; +$aCities[]="Calau"; +$aCities[]="Calbe"; +$aCities[]="Calw"; +$aCities[]="Camburg"; +$aCities[]="Castrop-Rauxel"; +$aCities[]="Celle"; +$aCities[]="Cham"; +$aCities[]="Chemnitz"; +$aCities[]="Clausthal-Zellerfeld"; +$aCities[]="Cloppenburg"; +$aCities[]="Coburg"; +$aCities[]="Cochem"; +$aCities[]="Coesfeld"; +$aCities[]="Colditz"; +$aCities[]="Coswig"; +$aCities[]="Coswig"; +$aCities[]="Crailsheim"; +$aCities[]="Creglingen"; +$aCities[]="Creussen"; +$aCities[]="Creuzburg"; +$aCities[]="Crimmitschau"; +$aCities[]="Crivitz"; +$aCities[]="Cuxhaven"; +$aCities[]="Bad Iburg"; +$aCities[]="Bad Karlshafen"; +$aCities[]="Bad Kissingen"; +$aCities[]="Bad Knig"; +$aCities[]="Bad Knigshofen im Grabfeld"; +$aCities[]="Bad Ksen"; +$aCities[]="Bad Kstritz"; +$aCities[]="Bad Ktzting"; +$aCities[]="Bad Kreuznach"; +$aCities[]="Bad Krozingen"; +$aCities[]="Bad Laasphe"; +$aCities[]="Bad Langensalza"; +$aCities[]="Bad Lauchstdt"; +$aCities[]="Bad Lausick"; +$aCities[]="Bad Lauterberg im Harz"; +$aCities[]="Bad Liebenstein"; +$aCities[]="Bad Liebenwerda"; +$aCities[]="Bad Liebenzell"; +$aCities[]="Bad Lippspringe"; +$aCities[]="Bad Lobenstein"; +$aCities[]="Bad Marienberg"; +$aCities[]="Bad Mergentheim"; +$aCities[]="Bad Mnder am Deister"; +$aCities[]="Bad Mnster am Stein-Ebernburg"; +$aCities[]="Bad Mnstereifel"; +$aCities[]="Bad Muskau"; +$aCities[]="Bad Nauheim"; +$aCities[]="Bad Nenndorf"; +$aCities[]="Bad Neuenahr-Ahrweiler"; +$aCities[]="Bad Neustadt an der Saale"; +$aCities[]="Bad Oeynhausen"; +$aCities[]="Bad Oldesloe"; +$aCities[]="Bad Orb"; +$aCities[]="Bad Pyrmont"; +$aCities[]="Bad Rappenau"; +$aCities[]="Bad Reichenhall"; +$aCities[]="Bad Rodach"; +$aCities[]="Bad Sachsa"; +$aCities[]="Bad Sckingen"; +$aCities[]="Bad Salzdetfurth"; +$aCities[]="Bad Salzuflen"; +$aCities[]="Bad Salzungen"; +$aCities[]="Bad Saulgau"; +$aCities[]="Bad Schandau"; +$aCities[]="Bad Schmiedeberg"; +$aCities[]="Bad Schussenried"; +$aCities[]="Bad Schwalbach"; +$aCities[]="Bad Schwartau"; +$aCities[]="Bad Segeberg"; +$aCities[]="Bad Soden am Taunus"; +$aCities[]="Bad Soden-Salmnster"; +$aCities[]="Bad Sooden-Allendorf"; +$aCities[]="Bad Staffelstein"; +$aCities[]="Bad Sulza"; +$aCities[]="Bad Slze"; +$aCities[]="Bad Teinach-Zavelstein"; +$aCities[]="Bad Tennstedt"; +$aCities[]="Bad Tlz"; +$aCities[]="Bad Urach"; +$aCities[]="Bad Vilbel"; +$aCities[]="Bad Waldsee"; +$aCities[]="Bad Wildbad"; +$aCities[]="Bad Wildungen"; +$aCities[]="Bad Wilsnack"; +$aCities[]="Bad Wimpfen"; +$aCities[]="Bad Windsheim"; +$aCities[]="Bad Wrishofen"; +$aCities[]="Bad Wnnenberg"; +$aCities[]="Bad Wurzach"; +$aCities[]="Baesweiler"; +$aCities[]="Baiersdorf"; +$aCities[]="Balingen"; +$aCities[]="Ballenstedt"; +$aCities[]="Balve"; +$aCities[]="Bamberg"; +$aCities[]="Barby"; +$aCities[]="Bargteheide"; +$aCities[]="Barmstedt"; +$aCities[]="Brnau"; +$aCities[]="Barntrup"; +$aCities[]="Barsinghausen"; +$aCities[]="Barth"; +$aCities[]="Baruth/Mark"; +$aCities[]="Bassum"; +$aCities[]="Battenberg"; +$aCities[]="Baumholder"; +$aCities[]="Baunach"; +$aCities[]="Baunatal"; +$aCities[]="Bautzen"; +$aCities[]="Bayreuth"; +$aCities[]="Bebra"; +$aCities[]="Beckum"; +$aCities[]="Bedburg"; +$aCities[]="Beelitz"; +$aCities[]="Beerfelden"; +$aCities[]="Beeskow"; +$aCities[]="Beilngries"; +$aCities[]="Beilstein"; +$aCities[]="Belgern"; +$aCities[]="Belzig"; +$aCities[]="Bendorf"; +$aCities[]="Benneckenstein"; +$aCities[]="Bensheim"; +$aCities[]="Berching"; +$aCities[]="Berga/Elster"; +$aCities[]="Bergen"; +$aCities[]="Bergen auf Rgen"; +$aCities[]="Bergheim"; +$aCities[]="Bergisch Gladbach"; +$aCities[]="Bergkamen"; +$aCities[]="Bergneustadt"; +$aCities[]="Berka/Werra"; +$aCities[]="Berlin"; +$aCities[]="Bernau bei Berlin"; +$aCities[]="Bernburg"; +$aCities[]="Bernkastel-Kues"; +$aCities[]="Bernsdorf"; +$aCities[]="Bernstadt a. d. Eigen"; +$aCities[]="Bersenbrck"; +$aCities[]="Besigheim"; +$aCities[]="Betzdorf"; +$aCities[]="Betzenstein"; +$aCities[]="Beverungen"; +$aCities[]="Bexbach"; +$aCities[]="Biberach an der Ri"; +$aCities[]="Biedenkopf"; +$aCities[]="Bielefeld"; +$aCities[]="Biesenthal"; +$aCities[]="Bietigheim-Bissingen"; +$aCities[]="Billerbeck"; +$aCities[]="Bingen am Rhein"; +$aCities[]="Birkenfeld"; +$aCities[]="Bischofsheim an der Rhn"; +$aCities[]="Bischofswerda"; +$aCities[]="Bismark"; +$aCities[]="Bitburg"; +$aCities[]="Bitterfeld"; +$aCities[]="Blankenburg"; +$aCities[]="Blankenhain"; +$aCities[]="Bleckede"; +$aCities[]="Bleicherode"; +$aCities[]="Blieskastel"; +$aCities[]="Blomberg"; +$aCities[]="Blumberg"; +$aCities[]="Bobingen"; +$aCities[]="Bblingen"; +$aCities[]="Bocholt"; +$aCities[]="Bochum"; +$aCities[]="Bockenem"; +$aCities[]="Bodenwerder"; +$aCities[]="Bogen"; +$aCities[]="Bhlen"; +$aCities[]="Boizenburg/Elbe"; +$aCities[]="Bonn"; +$aCities[]="Bonndorf im Schwarzwald"; +$aCities[]="Bnnigheim"; +$aCities[]="Bopfingen"; +$aCities[]="Boppard"; +$aCities[]="Borgentreich"; +$aCities[]="Borgholzhausen"; +$aCities[]="Borken"; +$aCities[]="Borken"; +$aCities[]="Borkum"; +$aCities[]="Borna"; +$aCities[]="Bornheim"; +$aCities[]="Bottrop"; +$aCities[]="Boxberg"; +$aCities[]="Brackenheim"; +$aCities[]="Brake"; +$aCities[]="Brakel"; +$aCities[]="Bramsche"; +$aCities[]="Brandenburg an der Havel"; +$aCities[]="Brand-Erbisdorf"; +$aCities[]="Brandis"; +$aCities[]="Braubach"; +$aCities[]="Braunfels"; +$aCities[]="Braunlage"; +$aCities[]="Brunlingen"; +$aCities[]="Braunsbedra"; +$aCities[]="Braunschweig"; +$aCities[]="Breckerfeld"; +$aCities[]="Bredstedt"; +$aCities[]="Brehna"; +$aCities[]="Breisach am Rhein"; +$aCities[]="Bremen"; +$aCities[]="Bremerhaven"; +$aCities[]="Bremervrde"; +$aCities[]="Bretten"; +$aCities[]="Breuberg"; +$aCities[]="Brilon"; +$aCities[]="Brotterode"; +$aCities[]="Bruchkbel"; +$aCities[]="Bruchsal"; +$aCities[]="Brck"; +$aCities[]="Brel"; +$aCities[]="Brhl"; +$aCities[]="Brunsbttel"; +$aCities[]="Brssow"; +$aCities[]="Buchen"; +$aCities[]="Buchholz in der Nordheide"; +$aCities[]="Buchloe"; +$aCities[]="Bckeburg"; +$aCities[]="Buckow"; +$aCities[]="Bdelsdorf"; +$aCities[]="Bdingen"; +$aCities[]="Bhl"; +$aCities[]="Bnde"; +$aCities[]="Bren"; +$aCities[]="Burg"; +$aCities[]="Burgau"; +$aCities[]="Burgbernheim"; +$aCities[]="Burgdorf"; +$aCities[]="Brgel"; +$aCities[]="Burghausen"; +$aCities[]="Burgkunstadt"; +$aCities[]="Burglengenfeld"; +$aCities[]="Burgstdt"; +$aCities[]="Burg Stargard"; +$aCities[]="Burgwedel"; +$aCities[]="Burladingen"; +$aCities[]="Burscheid"; +$aCities[]="Brstadt"; +$aCities[]="Buttelstedt"; +$aCities[]="Buttstdt"; +$aCities[]="Butzbach"; +$aCities[]="Btzow"; +$aCities[]="Buxtehude"; + +?> diff --git a/business/incident.business.php b/business/incident.business.php new file mode 100644 index 0000000000..c41d7dd6bf --- /dev/null +++ b/business/incident.business.php @@ -0,0 +1,367 @@ + + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +//////////////////////////////////////////////////////////////////////////////////// +/** +* An Incident Ticket +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizIncidentTicket extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Incident", + "description" => "Incident ticket", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "ticket_status", + "reconc_keys" => array("title"), + "db_table" => "incident", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/ticket.html", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"TicketID", "description"=>"Refence number ofr this incident", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Incident", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'customer_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + // SetPossibleValues("status",array("Open","Monitored","Closed")); + MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("label"=>"Initial Situation", "description"=>"Initial situation of the Incident", "allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("label"=>"Current Situation", "description"=>"Current situation of the Incident", "allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + // dfinir une date de dfaut maintenant, alias creation ou modification du ticket + MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closed Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>null, "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); + + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Managed by Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "depends_on"=>array("workgroup_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Managed by Agent", "description"=>"mail of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); + // Comment afficher le first + last name de l'agent ? Est-ce utile d'ajouter ce champ? + MetaModel::Init_AddAttribute(new AttributeText("action_log", array("label"=>"Action Logs", "description"=>"List all action performed during the incident", "allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Severity", "description"=>"Field defining the criticity if the incident", "allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"criticity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("label"=>"Assignment Count", "description"=>"Number of times this ticket was assigned or reassigned", "allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("resolution", array("label"=>"Resolution", "description"=>"Description of the resolution", "allowed_values"=>null, "sql"=>"resolution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are not meeting the SLA", "linked_class"=>"lnkInfraTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("label"=>"Related Tickets", "description"=>"Other incident tickets related to this one", "linked_class"=>"lnkRelatedTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"rel_ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual')))); + + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("title"); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("customer_id"); + MetaModel::Init_AddFilterFromAttribute("caller_id"); + MetaModel::Init_AddFilterFromAttribute("ticket_status"); + MetaModel::Init_AddFilterFromAttribute("start_date"); + MetaModel::Init_AddFilterFromAttribute("last_update"); + MetaModel::Init_AddFilterFromAttribute("end_date"); + MetaModel::Init_AddFilterFromAttribute("workgroup_id"); + MetaModel::Init_AddFilterFromAttribute("agent_id"); + MetaModel::Init_AddFilterFromAttribute("severity"); + MetaModel::Init_AddFilterFromAttribute("assignment_count"); + + // doit-on aussi ajouter un filtre sur les extfields li une extkey ? ici le name de l'agent? + + // Display lists + MetaModel::Init_SetZListItems('details', array('name','title', 'customer_id', 'type','ticket_status', 'severity','start_date', 'initial_situation', 'current_situation','caller_id', 'impact', 'last_update', 'next_update','end_date', 'assignment_count', 'workgroup_id','agent_id','action_log','resolution')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('name', 'title', 'customer_id', 'type','ticket_status','severity','start_date', 'initial_situation')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'title', 'customer_id', 'caller_id','type', 'ticket_status', 'severity','start_date', 'last_update','end_date','agent_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'customer_id','caller_id','type','ticket_status', 'severity','start_date', 'last_update', 'end_date','agent_id')); // Criteria of the advanced search form + + // State machine + MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'next_update' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_HIDDEN, + "title"=>OPT_ATT_MANDATORY, "customer_id"=>OPT_ATT_MANDATORY, "caller_id"=>OPT_ATT_MANDATORY, "initial_situation"=>OPT_ATT_MANDATORY, "start_date"=>OPT_ATT_MANDATORY, "workgroup_id"=>OPT_ATT_MANDATORY, + "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT))); + MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Ticket is assigned to somebody", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "customer_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_MUSTCHANGE, "agent_id"=>OPT_ATT_MUSTCHANGE))); + MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "customer_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array("workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY, "resolution"=>OPT_ATT_MANDATORY, "end_date"=>OPT_ATT_MANDATORY))); + + MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this ticket", "description"=>"Assign this ticket to a group and an agent"))); + MetaModel::Init_DefineStimulus("ev_reassign", new StimulusUserAction(array("label"=>"Reassign this ticket", "description"=>"Reassign this ticket to a different group and agent"))); + MetaModel::Init_DefineStimulus("ev_start_working", new StimulusUserAction(array("label"=>"Work on this ticket", "description"=>"Start working on this ticket"))); + MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this ticket", "description"=>"Close/resolve this ticket"))); + + MetaModel::Init_DefineTransition("New", "ev_assign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Assigned", "ev_start_working", array("target_state"=>"WorkInProgress", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("WorkInProgress", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("WorkInProgress", "ev_close", array("target_state"=>"Closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); + + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('customer_id', $oGenerator->GetOrganizationId()); + $this->Set('title', $oGenerator->GenerateString("enum(Site,Server,Line)| |enum(is down,is flip-flopping,is not responding)")); + $this->Set('agent_id', $oGenerator->GenerateKey("bizPerson", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('ticket_status', $oGenerator->GenerateString("enum(Open,Closed,Closed,Monitored)")); + $this->Set('start_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); + $this->Set('last_update', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); + $this->Set('end_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); + } + + public static function GetUIPage() + { + return './UI.php'; + } + + // State machine actions + public function IncrementAssignmentCount($sStimulusCode) + { + $this->Set('assignment_count', $this->Get('assignment_count') + 1); + return true; + } + + public function SetClosureDate($sStimulusCode) + { + $this->Set('end_date', time()); + return true; + } + + public function ComputeFields() + { + if ($this->GetKey() > 0) + { + $sName = sprintf('I-%06d', $this->GetKey()); + } + else + { + $sName = "Id not set"; + } + $this->Set('name', $sName); + } +} + + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Incident +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkRelatedTicket extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Related Ticket", + "description" => "Ticket related to a ticket", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "impact", // ???? + "state_attcode" => "", + "reconc_keys" => array("impact"), // ???? + "db_table" => "related_ticket", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("rel_ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Related Ticket id", "description"=>"The related ticket", "allowed_values"=>null, "sql"=>"rel_ticket_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("rel_ticket_name", array("label"=>"Related ticket", "description"=>"Name of the related ticket", "allowed_values"=>null, "extkey_attcode"=> 'rel_ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact on the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("rel_ticket_id"); + MetaModel::Init_AddFilterFromAttribute("ticket_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('rel_ticket_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('rel_ticket_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('rel_ticket_id', 'ticket_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('rel_ticket_id', 'ticket_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Incident +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInfraTicket extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Infra Ticket", + "description" => "Infra impacted by a ticket", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "impact", // ???? + "state_attcode" => "", + "reconc_keys" => array("impact"), // ???? + "db_table" => "infra_ticket", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("infra_id"); + MetaModel::Init_AddFilterFromAttribute("ticket_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'ticket_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'ticket_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Contqct and a Incident +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkContactTicket extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Contact Ticket", + "description" => "Contacts to be notify for a ticket", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "role", // ???? + "state_attcode" => "", + "reconc_keys" => array("role"), // ???? + "db_table" => "contact_ticket", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"Contact to Notify", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"Contact email", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("contact_id"); + MetaModel::Init_AddFilterFromAttribute("ticket_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('contact_id', 'ticket_id', 'role')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('contact_id', 'ticket_id', 'role')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('contact_id', 'ticket_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('contact_id', 'ticket_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } + +} +//////////////////////////////////////////////////////////////////////////////////// +//** +//* A workgroup is a queue in a given call tracking system +//* It belongs to a team and a given organization +//////////////////////////////////////////////////////////////////////////////////// +class bizWorkgroup extends logRealObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Workgroup", + "description" => "Call tracking workgroup", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "workgroups", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team owning the workgroup", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team name", "description"=>"name of the team", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this work group", "allowed_values"=>new ValueSetEnum("1st level support,2nd level support,3rd level support"), "sql"=>"role", "default_value"=>"1st level support", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("org_name"); + MetaModel::Init_AddFilterFromAttribute("team_id"); + MetaModel::Init_AddFilterFromAttribute("role"); + + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'team_id', 'role')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'team_id','role')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'team_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'team_id','role')); // Criteria of the advanced search form + + } +} + + + +?> diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php new file mode 100644 index 0000000000..31e561e923 --- /dev/null +++ b/business/itop.business.class.inc.php @@ -0,0 +1,1629 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +/** + * Possible values for the statuses of objects + */ +$oAllowedStatuses = new ValueSetEnum('production,implementation,obsolete'); + +/** + * Relation graphs + */ +MetaModel::RegisterRelation("impacts", array("description"=>"objects being functionaly impacted", "verb_down"=>"impacts", "verb_up"=>"is impacted by")); + +//////////////////////////////////////////////////////////////////////////////////// +/** +* An organization that owns some objects +* +* An organization "owns" some persons (its employees) but also some other objects +* (its assets) like buildings, computers, furniture... +* the services that they provides, the contracts/OLA they have signed as customer +* +* Organization ownership might be used to manage the R/W access to the object +*/ +//////////////////////////////////////////////////////////////////////////////////// +/** + * itop.business.class.inc.php + * User defined objects, implements the business need + * + * @package iTopBizModelSamples + * @author Erwan Taloc + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class bizOrganization extends cmdbAbstractObject +{ + public static function Init() + { + global $oAllowedStatuses; + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Organization", + "description" => "Organizational structure: can be Company and/or Department", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("name"), + "db_table" => "organizations", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array() ))); + MetaModel::Init_AddAttribute(new AttributeString("code", array("label"=>"Code", "description"=>"Organization code (Siret, DUNS,...)", "allowed_values"=>null, "sql"=>"code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array() ))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"bizOrganization", "label"=>"Parent Id", "description"=>"Parent organization", "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Name", "description"=>"Name of the parent organization", "allowed_values"=>null, "extkey_attcode"=> 'parent_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("code"); + MetaModel::Init_AddFilterFromAttribute("status"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'code', 'status', 'parent_id')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'parent_id')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'code', 'status')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'code', 'status')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + //$this->SetKey($oGenerator->GetOrganizationCode()); + $this->Set('name', $oGenerator->GetOrganizationName()); + $this->Set('code', $oGenerator->GetOrganizationCode()); + $this->Set('status', 'implementation'); + $this->Set('parent_id', 1); + + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Class of objects owned by some organization +* +* This is the root class of all the objects that can be "owned" by an organization +* +* A Real Object +* can be supported by Contacts, having a specific role (same contact with multiple roles?) +* can be documented by Documents +*/ +//////////////////////////////////////////////////////////////////////////////////// +/** + * itop.business.class.inc.php + * User defined objects, implements the business need + * + * @package iTopBizModelSamples + * @author Erwan Taloc + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class logRealObject extends cmdbAbstractObject +{ + public static function Init() + { + global $oAllowedStatuses; + + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Object", + "description" => "Any CMDB object", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("name"), + "db_table" => "objects", + "db_key_field" => "id", + "db_finalclass_field" => "obj_class", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization Id", "description"=>"ID of the object owner organization", "allowed_values"=>new ValueSetObjects("bizOrganization: status Contains 'implementation'", 'name', array('name'=>true)), "sql"=>"org_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("status"); + MetaModel::Init_AddFilterFromAttribute("org_id"); + MetaModel::Init_AddFilterFromAttribute("org_name"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'status', 'org_id')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('org_id', $oGenerator->GetOrganizationId()); + $this->Set('name', ""); + $this->Set('status', $oGenerator->GenerateString("enum(implementation,production)")); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Any kind of thing that can be contacted (person, team, hotline...) +* A contact can: +* be linked to any Real Object with a role +* be part of a GroupContact +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizContact extends logRealObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Contact", + "description" => "Contact", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "contacts", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department of the contact", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("phone", array("label"=>"Phone", "description"=>"Telephone", "allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location Id", "description"=>"Id of the location where the contact is located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"Name of the location where the contact is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("org_name"); + MetaModel::Init_AddFilterFromAttribute("email"); + MetaModel::Init_AddFilterFromAttribute("phone"); + MetaModel::Init_AddFilterFromAttribute("location_id"); + MetaModel::Init_AddFilterFromAttribute("location_name"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'email', 'location_id', 'phone')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('org_id', $oGenerator->GetOrganizationId()); + $this->Set('name', ""); + $this->Set('email', ""); + $this->Set('phone', $oGenerator->GenerateString("enum(+1,+33,+44,+49,+421)| |number(100-999)| |number(000-999)")); + $this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() ))); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Physical person only +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizPerson extends bizContact +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Person", + "description" => "Person", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "first_name", "name"), // comment en dfinir plusieurs + // "reconc_keys" => array("org_name", "employe_number"), + "db_table" => "persons", // Can it use the same physical DB table as any contact ? + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/person.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"first Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("employe_number", array("label"=>"Employe Number", "description"=>"employe number", "allowed_values"=>null, "sql"=>"employe_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("first_name"); + MetaModel::Init_AddFilterFromAttribute("employe_number"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone', 'employe_number')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employe_number')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employe_number')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + parent::Generate($oGenerator); + $this->Set('name', $oGenerator->GenerateLastName()); + $this->Set('first_name', $oGenerator->GenerateFirstName()); + $this->Set('email', $oGenerator->GenerateEmail($this->Get('first_name'), $this->Get('name'))); + $this->Set('phone', $oGenerator->GenerateString("enum(+1,+33,+44,+49,+421)| |number(100-999)| |number(000-999)")); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* A team is basically a contact which is also a group of contacts +* (and thus a team can contain other teams) +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizTeam extends bizContact +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Team", + "description" => "A group of contacts", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "teams", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/team.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'email', 'location_id', 'phone')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id')); // Criteria of the advanced search form + } +} + + +//////////////////////////////////////////////////////////////////////////////////// +/** +* An electronic document, with version tracking +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizDocument extends logRealObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Document", + "description" => "Document", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "documents", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/document.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning the document", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("scope", array("label"=>"scope", "description"=>"Scope of this document", "allowed_values"=>new ValueSetEnum("organization,hardware support"), "sql"=>"scope", "default_value"=>"organization", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("scope"); + MetaModel::Init_AddFilterFromAttribute("description"); + + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope','description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'scope')); // Criteria of the advanced search form + + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* A version of an electronic document +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizDocVersion extends cmdbAbstractObject +{ + public static function Init() + { + global $oAllowedStatuses; + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "DocumentVersion", + "description" => "A version of a document", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "version_number", + "state_attcode" => "", + "reconc_keys" => array("docname", "version_number"), + "db_table" => "document_versions", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/document.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("document", array("targetclass"=>"bizDocument", "label"=>"document", "description"=>"The main document", "allowed_values"=>null, "sql"=>"document_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("docname", array("label"=>"document name", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'document', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("version_number", array("label"=>"version number", "description"=>"Service name", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"status", "description"=>"Status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"type", "description"=>"Type", "allowed_values"=>new ValueSetEnum("local,draft"), "sql"=>"type", "default_value"=>"local", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeURL("url", array("label"=>"URL", "description"=>"Hyperlink to the version", "allowed_values"=>null, "target"=>"_blank", "sql"=>"url", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("document"); + MetaModel::Init_AddFilterFromAttribute("docname"); + MetaModel::Init_AddFilterFromAttribute("version_number"); + MetaModel::Init_AddFilterFromAttribute("status"); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("description"); + + MetaModel::Init_SetZListItems('details', array('docname', 'status', 'version_number', 'type','url','description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('version_number', 'status', 'type', 'url')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('docname', 'type')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('docname', 'type')); // Criteria o + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Object and a Document +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkDocumentRealObject extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "DocumentsLinks", + "description" => "A link between a document and another object", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "link_type", + "state_attcode" => "", + "reconc_keys" => array("doc_name", "object_name"), + "db_table" => "documents_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document Name", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "label"=>"object", "description"=>"Object linked", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("label"=>"object name", "description"=>"name of the linked object", "allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link_type", "description"=>"Type of the link", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("doc_id"); + MetaModel::Init_AddFilterFromAttribute("doc_name"); + MetaModel::Init_AddFilterFromAttribute("object_id"); + MetaModel::Init_AddFilterFromAttribute("object_name"); + MetaModel::Init_AddFilterFromAttribute("link_type"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('doc_id', 'object_name', 'link_type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('doc_id', 'object_name', 'link_type')); // Attributes to be displayed for a list + } +} + + + + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Object and a contact +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkContactRealObject extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "ContactsLinks", + "description" => "A link between a contact and another object", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "role", + "state_attcode" => "", + "reconc_keys" => array("contact_name", "object_name"), + "db_table" => "contacts_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "label"=>"Contact", "description"=>"The contact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("label"=>"Contact name", "description"=>"name of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("label"=>"Phone", "description"=>"Phone number of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"eMail", "description"=>"eMail address of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "label"=>"object", "description"=>"Object linked", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("label"=>"Object name", "description"=>"name of the linked object", "allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("contact_id"); + MetaModel::Init_AddFilterFromAttribute("contact_name"); + MetaModel::Init_AddFilterFromAttribute("object_id"); + MetaModel::Init_AddFilterFromAttribute("object_name"); + MetaModel::Init_AddFilterFromAttribute("role"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('contact_id', 'contact_phone', 'contact_email', 'object_id', 'role')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('contact_id', 'contact_phone', 'contact_email', 'object_id', 'role')); // Attributes to be displayed for a list + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Any Infrastructure object (bizLocation, bizDevice, bizApplication, bizCircuit, bizInterface) +* An infrastructure object: +* can be covered by an OLA +* can support the delivery of a Service +* can be part of an GroupInfra +*/ +//////////////////////////////////////////////////////////////////////////////////// +class logInfra extends logRealObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Infra", + "description" => "Infrastructure real object", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "infra", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Severity", "description"=>"Severity for this infrastructure", "allowed_values"=>new ValueSetEnum("high,medium,low"), "sql"=>"severity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("severity"); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* bizLocation (Region, Country, City, Site, Building, Floor, Room, Rack,...) +* pourrait tre mis en plusieurs sous objects, puisqu'une adresse sur region n'a pas trop de sens +* +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizLocation extends logInfra +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Location", + "description" => "Any type of location: Region, Country, City, Site, Building, Floor, Room, Rack,...", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "location", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/location.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeText("address", array("label"=>"Address", "description"=>"The postal address of the location", "allowed_values"=>null, "sql"=>"address", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("country", array("label"=>"Country", "description"=>"Country of the location", "allowed_values"=>null, "sql"=>"country", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_location_id", array("targetclass"=>"bizLocation", "label"=>"Parent Location", "description"=>"where is the real object physically located", "allowed_values"=>null, "sql"=>"parent_location_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_location_name", array("label"=>"Parent location (Name)", "description"=>"name of the parent location", "allowed_values"=>null, "extkey_attcode"=> 'parent_location_id', "target_attcode"=>"name"))); + + // on veut pouvoir rechercher une location qui soit un descendant (pas obligatoirement direct) d'une Location, on fait comment ? + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("country"); + MetaModel::Init_AddFilterFromAttribute("address"); + MetaModel::Init_AddFilterFromAttribute("parent_location_id"); + MetaModel::Init_AddFilterFromAttribute("parent_location_name"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'address', 'country', 'parent_location_id')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'country')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'country', 'parent_location_name')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'address', 'country', 'parent_location_id', 'org_id')); // Criteria of the advanced search form + } + + public function ComputeValues() + { + /* + $this->Set("location_id", $this->GetKey()); + // Houston, I've got an issue, as this field is calculated, I should reload the object... ? + $this->Set("location_name", "abc (to be finalized)"); + */ + } + + function DisplayDetails(web_page $oPage) + { + parent::DisplayDetails($oPage); +/* + parent::DisplayDetails($oPage); + + + + $oSearchFilter = new CMDBSearchFilter('bizServer'); + $oSearchFilter->AddCondition('location_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Servers"); + $oPage->p("$count server(s) at this location:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('bizNetworkDevice'); + $oSearchFilter->AddCondition('location_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Network Devices"); + $oPage->p("$count Network Device(s) at this location:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('bizPC'); + $oSearchFilter->AddCondition('location_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("PCs"); + $oPage->p("$count PC(s) at this location:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('bizPerson'); + $oSearchFilter->AddCondition('location_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Contacts"); + $oPage->p("$count person(s) located to this location:"); + $this->DisplaySet($oPage, $oSet); + } + + $oSearchFilter = new CMDBSearchFilter('lnkDocumentRealObject'); + $oSearchFilter->AddCondition('object_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Details"); + $oPage->p("$count Document(s) linked to this location:"); + $this->DisplaySet($oPage, $oSet); + } +*/ + + } + + + public function Generate(cmdbDataGenerator $oGenerator) + { + parent::Generate($oGenerator); + $sLastName = $oGenerator->GenerateLastName(); + $sCityName = $oGenerator->GenerateCityName(); + $this->Set('name', $sCityName); + $this->Set('country', $oGenerator->GenerateCountryName()); + $this->Set('address', $oGenerator->GenerateString("number(1-999)| |enum(rue,rue,rue,place,avenue,av.,route de)| |$sLastName| |number(0000-9999)|0 |$sCityName")); + $this->Set('parent_location_id', 1); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Circuit (one end only) +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizCircuit extends logInfra +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Circuit", + "description" => "Any type of circuit", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "carrier_name", "carrier_ref", "name"), // inherited attributes + "db_table" => "circuits", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/Circuits.html", + ); + + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"speed", "description"=>"speed of the circuit", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location ID", "description"=>"Id of the location", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface_id", array("targetclass"=>"bizInterface", "label"=>"Interface Id", "description"=>"id of the interface", "allowed_values"=>null, "sql"=>"interface_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface_name", array("label"=>"Interface", "description"=>"Name of the interface", "allowed_values"=>null, "extkey_attcode"=> 'interface_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_id", array("targetclass"=>"bizOrganization", "label"=>"Carrier ID", "description"=>"Organization ID of the provider of the Circuit", "allowed_values"=>null, "sql"=>"provider_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("carrier_name", array("label"=>"Carrier", "description"=>"Name of the carrier", "allowed_values"=>null, "extkey_attcode"=> 'provider_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("carrier_ref", array("label"=>"Carrier reference", "description"=>"reference of the circuit used by the carrier", "allowed_values"=>null, "sql"=>"carrier_ref", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("speed"); + MetaModel::Init_AddFilterFromAttribute("location_id"); + MetaModel::Init_AddFilterFromAttribute("location_name"); + MetaModel::Init_AddFilterFromAttribute("interface_id"); + MetaModel::Init_AddFilterFromAttribute("provider_id"); + MetaModel::Init_AddFilterFromAttribute("carrier_ref"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'speed', 'provider_id', 'carrier_ref', 'location_id')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'provider_id', 'carrier_ref', 'speed')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'carrier_ref', 'speed', 'provider_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'carrier_ref', 'speed', 'provider_id')); // Criteria of the advanced search form + } + + public function ComputeValues() + { +/* + $oLocatedObject = MetaModel::GetObject("Located Object", $this->Get("located_object_id")); + + $this->Set("location_id", $oLocatedObject->Get("location_id")); + // Houston, I've got an issue, as this field is calculated, I should reload the object... + $this->Set("location_name", "abc (to be finalized)"); + + $this->Set("device_id", $oLocatedObject->Get("device_id")); + // Houston, I've got an issue, as this field is calculated, I should reload the object... + $this->Set("device_name", "abc (to be finalized)"); + + $this->Set("interface_id", $oLocatedObject->Get("interface_id")); + // Houston, I've got an issue, as this field is calculated, I should reload the object... + $this->Set("interface_name", "abc (to be finalized)"); +*/ + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Any Device Network Interface +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizInterface extends logInfra +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Interface", + "description" => "Interface", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "device_name", "name"), + "db_table" => "interfaces", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/interface.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "label"=>"Device", "description"=>"Device on which the interface is physically located", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device", "description"=>"name of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_id", array("label"=>"Device location", "description"=>"location of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_id"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_name", array("label"=>"Device location", "description"=>"name of the location of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_name"))); + + MetaModel::Init_AddAttribute(new AttributeEnum("logical_type", array("label"=>"Logical type", "description"=>"Logical type of interface", "allowed_values"=>new ValueSetEnum("primary,secondary,backup,port,logical"), "sql"=>"logical_type", "default_value"=>"port", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("physical_type", array("label"=>"Physical type", "description"=>"Physical type of interface", "allowed_values"=>new ValueSetEnum("ethernet,framerelay,atm,vlan"), "sql"=>"physical_type", "default_value"=>"ethernet", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("ip_address", array("label"=>"IP address", "description"=>"address IP for this interface", "allowed_values"=>null, "sql"=>"ip_address", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mask", array("label"=>"Subnet Mask", "description"=>"Subnet mask for this interface", "allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mac", array("label"=>"MAC address", "description"=>"MAC address for this interface", "allowed_values"=>null, "sql"=>"mac", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"Speed (Kb/s)", "description"=>"speed of this interface", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("label"=>"Duplex", "description"=>"Duplex configured for this interface", "allowed_values"=>new ValueSetEnum("half,full,unknown"), "sql"=>"duplex", "default_value"=>"unknown", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("device_id"); + MetaModel::Init_AddFilterFromAttribute("device_name"); + MetaModel::Init_AddFilterFromAttribute("device_location_id"); + MetaModel::Init_AddFilterFromAttribute("logical_type"); + MetaModel::Init_AddFilterFromAttribute("physical_type"); + MetaModel::Init_AddFilterFromAttribute("ip_address"); + MetaModel::Init_AddFilterFromAttribute("mac"); + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'device_id', 'device_location_id','severity','logical_type','physical_type','ip_address','mask','mac','speed','duplex')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'device_id','severity')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'ip_address','mac','device_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'device_id', 'org_id')); // Criteria of the advanced search form + } + + function DisplayDetails(web_page $oPage) + { + parent::DisplayDetails($oPage); + /* + $oSearchFilter = new CMDBSearchFilter('lnkInterfaces'); + $oSearchFilter->AddCondition('interface1_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Connected interfaces"); + $oPage->p("$count interface(s) connected to this device:"); + $this->DisplaySet($oPage, $oSet); + } + */ + } + + public function ComputeValues() + { + /* + // my location is the location of my device + $oDevice = MetaModel::GetObject("bizDevice", $this->Get("device_id")); + $this->Set("location_id", $oDevice->Get("location_id")); + // Houston, I've got an issue, as this field is calculated, I should reload the object... + $this->Set("location_name", "abc (to be finalized)"); + + // my device is given by my Creator + + // my interface is myself + $this->Set("interface_id", $this->GetKey()); + // Houston, I've got an issue, as this field is calculated, I should reload the object... + $this->Set("interface_name", "abc (to be finalized)"); + */ + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Interfaces +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInterfaces extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "InterfacesLinks", + "description" => "A link between two interfaces", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "link_type", + "state_attcode" => "", + "reconc_keys" => array("interface1_id", "interface2_id"), + "db_table" => "interfaces_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface1", "description"=>"The interface1", "sql"=>"interface1_id", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("label"=>"Interface1 name", "description"=>"name of the interface1", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface2", "description"=>"The interface2", "sql"=>"interface2_id", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("label"=>"Interface2 name", "description"=>"name of the interface2", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_device_id", array("label"=>"Device1", "description"=>"device", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"device_id"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_device_name", array("label"=>"Device name", "description"=>"name of the device", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"device_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_device_id", array("label"=>"Device2", "description"=>"device", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"device_id"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_device_name", array("label"=>"Device name", "description"=>"name of the device", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"device_name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link type", "description"=>" Definition of the link", "sql"=>"link_type", "default_value"=>"", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("interface1_id"); + MetaModel::Init_AddFilterFromAttribute("interface1_name"); + MetaModel::Init_AddFilterFromAttribute("interface2_id"); + MetaModel::Init_AddFilterFromAttribute("interface2_name"); + MetaModel::Init_AddFilterFromAttribute("interface2_device_name"); + MetaModel::Init_AddFilterFromAttribute("link_type"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('interface1_id', 'interface2_id', 'link_type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('interface1_id', 'interface1_device_id', 'interface2_id', 'interface2_device_id', 'link_type')); // Attributes to be displayed for the complete details + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* Any electronic device +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizDevice extends logInfra +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Device", + "description" => "Electronic devices", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "devices", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"where is the located object physically located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location name", "description"=>"name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("country", array("label"=>"Country", "description"=>"country where the device is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"country"))); + MetaModel::Init_AddAttribute(new AttributeString("brand", array("label"=>"Brand", "description"=>"The manufacturer of the device", "allowed_values"=>null, "sql"=>"brand", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"The model number of the device", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("serial_number", array("label"=>"Serial Number", "description"=>"The serial number of the device", "allowed_values"=>null, "sql"=>"serial_number", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("location_id"); + MetaModel::Init_AddFilterFromAttribute("country"); + MetaModel::Init_AddFilterFromAttribute("brand"); + MetaModel::Init_AddFilterFromAttribute("model"); + MetaModel::Init_AddFilterFromAttribute("serial_number"); + } + + public static function GetRelationQueries($sRelCode) + { + switch ($sRelCode) + { + case "impacts": + $aRels = array( + "connected device" => array("sQuery"=>"bizDevice: PKEY IS device_id IN (bizInterface: PKEY IS interface2_id IN (lnkInterfaces: interface1_id IN (bizInterface: device_id = \$[this.pkey::])))", "bPropagate"=>true, "iDistance"=>3), + "hosted app" => array("sQuery"=>"bizApplication: infra_id = \$[this.pkey::]", "bPropagate"=>true, "iDistance"=>3), + ); + return array_merge($aRels, parent::GetRelationQueries($sRelCode)); + } + } + + public function ComputeValues() + { + /* + // my location is the location of my device (external field) + $this->Set("location_id", $this->Get("device_location_id")); + // Houston, I've got an issue, as this field is calculated, I should reload the object... + $this->Set("location_name", "abc (to be finalized)"); + + // my device is myself + $this->Set("device_id", $this->GetKey()); + + // my interface is "nothing" + $this->Set("interface_id", null); + */ + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* A personal computer +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizPC extends bizDevice +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "PC", + "description" => "Personal Computers", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "pcs", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/pc.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of computer", "allowed_values"=>new ValueSetEnum("desktop PC,laptop"), "sql"=>"type", "default_value"=>"desktop PC", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"CPU", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("label"=>"HDD Size", "description"=>"Size of the hard drive", "allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_family", array("label"=>"OS Family", "description"=>"Type of operating system", "allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_version", array("label"=>"OS Version", "description"=>"Detailed version number of the operating system", "allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("label"=>"Shipment number", "description"=>"Number for tracking shipment", "allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("memory_size"); + MetaModel::Init_AddFilterFromAttribute("cpu"); + MetaModel::Init_AddFilterFromAttribute("hdd_size"); + MetaModel::Init_AddFilterFromAttribute("os_family"); + MetaModel::Init_AddFilterFromAttribute("os_version"); + MetaModel::Init_AddFilterFromAttribute("mgmt_ip"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status','severity', 'org_id', 'location_id', 'brand', 'model','os_family','os_version','mgmt_ip','default_gateway','shipment_number','serial_number', 'type', 'cpu', 'memory_size', 'hdd_size')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'severity', 'org_id', 'location_id', 'brand', 'model', 'type')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'severity','type', 'brand', 'model','os_family','mgmt_ip')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type', 'brand', 'model', 'cpu', 'memory_size', 'hdd_size')); // Criteria of the advanced search form + } + + function DisplayDetails(web_page $oPage) + { + parent::DisplayDetails($oPage); + /* + parent::DisplayDetails($oPage); + $oSearchFilter = new CMDBSearchFilter('lnkContactRealObject'); + $oSearchFilter->AddCondition('object_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Contacts"); + $oPage->p("$count contact(s) linked to this PC:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('bizInterface'); + $oSearchFilter->AddCondition('device_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Interfaces"); + $oPage->p("$count interface(s) for this device:"); + $this->DisplaySet($oPage, $oSet); + } + */ + } + + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('org_id', $oGenerator->GetOrganizationId()); + $this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('name', $oGenerator->GenerateString("enum(pc,pc,pc,pc,pc,win,redhat,linux,srv,workstation)|number(000-999)|.|domain()")); + $this->Set('brand', $oGenerator->GenerateString("enum(Hewlett-Packard,Dell,Compaq,Siemens,Packard Bell,IBM,Gateway,Medion,Sony)")); + $this->Set('model', $oGenerator->GenerateString("enum(Vectra,Deskpro,Dimension,Optiplex,Latitude,Precision,Vaio)")); + $this->Set('serial_number', $oGenerator->GenerateString("enum(FR,US,TW,CH)|number(000000-999999)")); + $this->Set('memory_size', $oGenerator->GenerateString("enum(128,256,384,512,768,1024,1536,2048)")); + $this->Set('cpu', $oGenerator->GenerateString("enum(Pentium III,Pentium 4, Pentium M,Core Duo,Core 2 Duo,Celeron,Opteron,Thurion,Athlon)")); + $this->Set('hdd_size', $oGenerator->GenerateString("enum(40,60,80,120,200,300)")); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* A server +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizServer extends bizDevice +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Server", + "description" => "Computer Servers", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "status", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "servers", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/server.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("In Store,Shipped,Plugged,Production Candidate,In Production,In Change,Being Deconfigured,Obsolete"), "sql"=>"status", "default_value"=>"In Store", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"Model", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("number_of_cpus", array("label"=>"Number of CPUs", "description"=>"Number of CPUs", "allowed_values"=>null, "sql"=>"number_of_cpus", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("label"=>"HDD Size", "description"=>"Size of the hard drive", "allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hdd_free_size", array("label"=>"Free HDD Size", "description"=>"Size of the free space on the hard drive(s)", "allowed_values"=>null, "sql"=>"hdd_free_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_family", array("label"=>"OS Family", "description"=>"Type of operating system", "allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_version", array("label"=>"OS Version", "description"=>"Detailed version number of the operating system", "allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("label"=>"Shipment number", "description"=>"Number for tracking shipment", "allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("memory_size"); + MetaModel::Init_AddFilterFromAttribute("cpu"); + MetaModel::Init_AddFilterFromAttribute("number_of_cpus"); + MetaModel::Init_AddFilterFromAttribute("hdd_size"); + MetaModel::Init_AddFilterFromAttribute("hdd_free_size"); + MetaModel::Init_AddFilterFromAttribute("os_family"); + MetaModel::Init_AddFilterFromAttribute("os_version"); + + + // Life cycle + MetaModel::Init_DefineState("In Store", array("label"=>"InStore", "description"=>"Device in store", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Shipped", array("label"=>"Shipped", "description"=>"The device had been shipped to future location", "attribute_inherit"=>null, + "attribute_list"=>array("location_id"=>OPT_ATT_MANDATORY,"serial_number"=>OPT_ATT_MANDATORY,"shipment_number"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Plugged", array("label"=>"Plugged", "description"=>"The device is connected to the network", "attribute_inherit"=>null, + "attribute_list"=>array("location_id"=>OPT_ATT_MANDATORY,"mgmt_ip"=>OPT_ATT_MANDATORY,"name"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Production Candidate", array("label"=>"Pre-Production", "description"=>"The device is ready to be move to production", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("In Production", array("label"=>"Production", "description"=>"The device is on production", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("In Change", array("label"=>"InChange", "description"=>"A change is being performed on the device", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Being Deconfigured", array("label"=>"BeingDeconfigured", "description"=>"The device is about to be removed from is current location", "attribute_inherit"=>null, + "attribute_list"=>array())); + MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The device is no more used", "attribute_inherit"=>null, + "attribute_list"=>array())); + + + MetaModel::Init_DefineStimulus("ev_store", new StimulusUserAction(array("label"=>"Store this server", "description"=>"This server is move to storage"))); + MetaModel::Init_DefineStimulus("ev_ship", new StimulusUserAction(array("label"=>"Ship this server", "description"=>"This server is shipped to futur location"))); + MetaModel::Init_DefineStimulus("ev_plug", new StimulusUserAction(array("label"=>"Plug this server", "description"=>"The server is pluuged on the network"))); + MetaModel::Init_DefineStimulus("ev_configuration_finished", new StimulusUserAction(array("label"=>"Configuration finished", "description"=>"The device is ready to move to production evaluation"))); + MetaModel::Init_DefineStimulus("ev_val_failed", new StimulusUserAction(array("label"=>"Review configuration", "description"=>"The configuration for this server is not completed"))); + MetaModel::Init_DefineStimulus("ev_mtp", new StimulusUserAction(array("label"=>"Move to Production", "description"=>"The server is moved to production"))); + MetaModel::Init_DefineStimulus("ev_start_change", new StimulusUserAction(array("label"=>"Change Start [No Click]", "description"=>"A change starts for this server"))); + MetaModel::Init_DefineStimulus("ev_end_change", new StimulusUserAction(array("label"=>"End Change [No Click]", "description"=>"No more change running for this server"))); + MetaModel::Init_DefineStimulus("ev_decommission", new StimulusUserAction(array("label"=>"Decommission", "description"=>"The server is being decommissioned"))); + MetaModel::Init_DefineStimulus("ev_obsolete", new StimulusUserAction(array("label"=>"Obsolete", "description"=>"The server is no more used"))); + MetaModel::Init_DefineStimulus("ev_recycle", new StimulusUserAction(array("label"=>"Recycle this server", "description"=>"The server is move back to deconfiguration"))); + + MetaModel::Init_DefineTransition("In Store", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("In Store", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Shipped", "ev_store", array("target_state"=>"In Store", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Shipped", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Plugged", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Plugged", "ev_store", array("target_state"=>"In Store", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Plugged", "ev_configuration_finished", array("target_state"=>"Production Candidate", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Production Candidate", "ev_val_failed", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Production Candidate", "ev_mtp", array("target_state"=>"In Production", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("In Production", "ev_start_change", array("target_state"=>"In Change", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("In Production", "ev_obsolete", array("target_state"=>"Obsolete", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("In Production", "ev_decommission", array("target_state"=>"Being Deconfigured", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("In Change", "ev_end_change", array("target_state"=>"In Production", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Being Deconfigured", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Being Deconfigured", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Being Deconfigured", "ev_store", array("target_state"=>"In Store", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Being Deconfigured", "ev_obsolete", array("target_state"=>"Obsolete", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Obsolete", "ev_recycle", array("target_state"=>"Being Deconfigured", "actions"=>array(), "user_restriction"=>null)); + + + + + + // Display lists + + MetaModel::Init_SetZListItems('details', array('name', 'mgmt_ip','default_gateway','status', 'severity','org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version','serial_number','shipment_number', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status','severity', 'org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status','severity', 'brand', 'model', 'os_family', 'location_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status','brand', 'model', 'os_family', 'os_version', 'location_id', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Criteria of the advanced search form + } + + function DisplayDetails(web_page $oPage) + { + parent::DisplayDetails($oPage); + /* + parent::DisplayDetails($oPage); + $oSearchFilter = new CMDBSearchFilter('lnkContactRealObject'); + $oSearchFilter->AddCondition('object_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Contacts"); + $oPage->p("$count contact(s) for this server:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('bizInterface'); + $oSearchFilter->AddCondition('device_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Interfaces"); + $oPage->p("$count interface(s) for this server:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('Application'); + $oSearchFilter->AddCondition('infra_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Installed applications"); + $oPage->p("$count application(s) installed on this server:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('bizPatch'); + $oSearchFilter->AddCondition('infra_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Installed patches"); + $oPage->p("$count patch(s) installed on this server:"); + $this->DisplaySet($oPage, $oSet); + } + */ + + + } + + + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('org_id', $oGenerator->GetOrganizationId()); + $this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('name', $oGenerator->GenerateString("enum(pc,pc,pc,pc,pc,win,redhat,linux,srv,workstation)|number(000-999)|.|domain()")); + $this->Set('brand', $oGenerator->GenerateString("enum(Hewlett-Packard,Dell,Compaq,Siemens,Packard Bell,IBM,Gateway,Medion,Sony)")); + $this->Set('model', $oGenerator->GenerateString("enum(Vectra,Deskpro,Dimension,Optiplex,Latitude,Precision,Vaio)")); + $this->Set('serial_number', $oGenerator->GenerateString("enum(FR,US,TW,CH)|number(000000-999999)")); + $this->Set('memory_size', $oGenerator->GenerateString("enum(512,1024,2048,4096,2048,4096,8192,8192,8192,16384,32768)")); + $this->Set('cpu', $oGenerator->GenerateString("enum(Pentium III,Pentium 4,Pentium M,Core Duo,Core 2 Duo,Celeron,Opteron,Thurion,Athlon)")); + $this->Set('number_of_cpu', $oGenerator->GenerateString("enum(1,1,2,2,2,2,2,4,4,8)")); + $this->Set('hdd_size', $oGenerator->GenerateString("enum(500,1024,500,1024,500,1024,2048)")); + $this->Set('hdd_free_size', $this->Get('hdd_size')*$oGenerator->GenerateString("number(20-80)")); + $this->Set('os_family', $oGenerator->GenerateString("enum(Windows,Windows,Windows,Linux,Windows,Linux,Windows,Linux,Linux,HP-UX,Solaris,AIX)")); + $this->Set('os_version', $oGenerator->GenerateString("enum(XP,XP,XP,RH EL 4,RH EL 5,SuSE 10.3,SuSE 10.4,11.11,11.11i)")); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* A network device +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizNetworkDevice extends bizDevice +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Network Device", + "description" => "A network device", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "network_devices", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/network.device.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of device", "allowed_values"=>new ValueSetEnum("switch,router,firewall,load balancer,hub,WAN accelerator"), "sql"=>"type", "default_value"=>"switch", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("ip_address", array("label"=>"Mgmt IP", "description"=>"Management IP address", "allowed_values"=>null, "sql"=>"ip_address", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("ios_version", array("label"=>"IOS version", "description"=>"IOS (software) version", "allowed_values"=>null, "sql"=>"ios_version", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("memory", array("label"=>"Memory", "description"=>"Memory description", "allowed_values"=>null, "sql"=>"memory", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("snmp_read", array("label"=>"SNMP Community (Read)", "description"=>"SNMP Read Community String", "allowed_values"=>null, "sql"=>"snmp_read", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("snmp_write", array("label"=>"SNMP Community (Write)", "description"=>"SNMP Write Community String", "allowed_values"=>null, "sql"=>"snmp_write", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("ip_address"); + MetaModel::Init_AddFilterFromAttribute("ios_version"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status','severity','org_id', 'location_id', 'brand','model','type','ip_address','default_gateway','serial_number','ios_version','memory','snmp_read','snmp_write')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status','brand','model','type','ip_address')); // Attributes to be displayed for a list + + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'location_id', 'brand','model','type','ip_address')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'location_id', 'brand','model','type','ip_address','serial_number','ios_version','snmp_read','snmp_write')); // Criteria of the advanced search form + + + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('org_id', $oGenerator->GetOrganizationId()); + $this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('name', $oGenerator->GenerateString("enum(sw,swi,switch,rout,rtr,gw)|number(000-999)|.|domain()")); + $this->Set('brand', $oGenerator->GenerateString("enum(Hewlett-Packard,Cisco,3Com,Avaya,Alcatel,Cabletron,Extrem Networks,Juniper,Netgear,Synopitcs,Xylan)")); + $this->Set('model', $oGenerator->GenerateString("enum(Procurve ,Catalyst ,Multiswitch ,C)|enum(25,26,36,40,65)|enum(00,09,10,50)")); + $this->Set('serial_number', $oGenerator->GenerateString("enum(FAA,AGA,PAD,COB,DFE)|number(0000-9999)|enum(M,X,L)")); + $this->Set('ip_address', $oGenerator->GenerateString("number(10-248)|.|number(1-254)|.|number(1-254)|.|number(1-254)")); + $this->Set('ios_version', $oGenerator->GenerateString("enum(9,10,12)|.|enum(0,1,2)|enum(,,,,XP,.5.1)")); + $this->Set('snmp_read', $oGenerator->GenerateString("enum(Ew,+0,**,Ps)|number(00-99)|enum(+,=,],;, )|enum(Aze,Vbn,Bbn,+9+,-9-,#)")); + $this->Set('snmp_write', $oGenerator->GenerateString("enum(M3,l3,$,*,Zz,Ks,jh)|number(00-99)|enum(A*e,V%n,Bbn,+,-,#)|number(0-9)")); + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* A "Solution" +*/ +//////////////////////////////////////////////////////////////////////////////////// +class bizInfraGroup extends logInfra +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Infra Group", + "description" => "A group of infrastructure elements", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_name", "name"), // inherited attributes + "db_table" => "infra_group", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/group.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of groupe", "allowed_values"=>new ValueSetEnum("Monitoring,Reporting,list"), "sql"=>"type", "default_value"=>"list", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"usage of the Group", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_group_id", array("targetclass"=>"bizInfraGroup", "label"=>"Parent Group", "description"=>"including group", "allowed_values"=>null, "sql"=>"parent_group_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_group_name", array("label"=>"Parent Group (Name)", "description"=>"name of the parent group", "allowed_values"=>null, "extkey_attcode"=> 'parent_group_id', "target_attcode"=>"name"))); + + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("type"); + MetaModel::Init_AddFilterFromAttribute("parent_group_id"); + MetaModel::Init_AddFilterFromAttribute("parent_group_name"); + + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'type', 'description','parent_group_id')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'type', 'description')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'type')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type', 'description', 'org_id')); // Criteria of the advanced search form + } + + function DisplayDetails(web_page $oPage) + { + parent::DisplayDetails($oPage); + /* + $oSearchFilter = new CMDBSearchFilter('lnkInfraGrouping'); + $oSearchFilter->AddCondition('infra_group_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("RelatedInfrastructure"); + $oPage->p("Infrastructure Link to this group:"); + $this->DisplaySet($oPage, $oSet); + } + $oSearchFilter = new CMDBSearchFilter('lnkContactRealObject'); + $oSearchFilter->AddCondition('object_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("TeamLinks"); + $oPage->p("People concerned by this group:"); + $this->DisplaySet($oPage, $oSet); + } +*/ + } + + + + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('org_id', $oGenerator->GetOrganizationId()); + $this->Set('name', $oGenerator->GenerateString("enum(ov_nnm_,ovpi_,vitalnet_,datacenter_,web_farm_)|number(000-999)")); + $this->Set('type', $oGenerator->GenerateString("enum(Application,Infrastructure)")); + } +} +//////////////////////////////////////////////////////////////////////////////////// +//** +//* An application is an instance of a software install on a PC or Server +//* +//////////////////////////////////////////////////////////////////////////////////// +class bizApplication extends logInfra +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Application", + "description" => "General application", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("device_name", "name"), // inherited attributes + "db_table" => "applications", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/application.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Hosting device", "description"=>"The device where application is installed", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Hosting device", "description"=>"Name of the device where application is installed", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installed date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("version", array("label"=>"Version", "description"=>"Application version", "allowed_values"=>null, "sql"=>"version", "default_value"=>"undefined", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("function", array("label"=>"Function", "description"=>"Function provided by this application", "allowed_values"=>null, "sql"=>"function", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("function"); + MetaModel::Init_AddFilterFromAttribute("version"); + MetaModel::Init_AddFilterFromAttribute("device_id"); + + + + + // Display lists + MetaModel::Init_SetZListItems('details', array('name','device_id','org_id','status','install_date', 'version','function')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name','device_id', 'version', 'function')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'device_id','version','function')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'device_id','version','function')); // Criteria of the advanced search form + + } + + public static function GetRelationQueries($sRelCode) + { + switch ($sRelCode) + { + case "impacts": + $aRels = array( + "client app" => array("sQuery"=>"bizApplication: PKEY IS client_id IN (lnkClientServer: server_id = \$[this.pkey::])", "bPropagate"=>true, "iDistance"=>3), + ); + return array_merge($aRels, parent::GetRelationQueries($sRelCode)); + } + } + + function DisplayDetails(web_page $oPage) + { + parent::DisplayDetails($oPage); + /* + $oSearchFilter = new CMDBSearchFilter('lnkClientServer'); + $oSearchFilter->AddCondition('server_id', $this->GetKey(), '='); + $oSet = new CMDBObjectSet($oSearchFilter); + $count = $oSet->Count(); + if ($count > 0) + { + $oPage->SetCurrentTab("Connected clients"); + $oPage->p("Client applications impacted when down:"); + $this->DisplaySet($oPage, $oSet); + } +*/ + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Group +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInfraGrouping extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Infra Grouping", + "description" => "Infra part of an Infra Group", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "impact", + "state_attcode" => "", + "reconc_keys" => array(""), + "db_table" => "infra_grouping", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"Infrastructure part of the group", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_group_id", array("targetclass"=>"bizInfraGroup", "jointype"=> '', "label"=>"Group Name", "description"=>"Name of the group", "allowed_values"=>null, "sql"=>"infra_group_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("group_name", array("label"=>"Group name", "description"=>"Name of the group containing infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_group_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Relation", "description"=>"Relation between this group and infra", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"none", "is_null_allowed"=>true, "depends_on"=>array()))); + // impact should modelized: enum (eg: if the group si dead when infra is dead) + + MetaModel::Init_AddFilterFromAttribute("infra_id"); + MetaModel::Init_AddFilterFromAttribute("infra_group_id"); + MetaModel::Init_AddFilterFromAttribute("impact"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('infra_id','infra_status', 'impact', 'infra_group_id')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('infra_id','infra_status', 'impact', 'infra_group_id')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'infra_group_id', 'impact')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'infra_group_id', 'impact')); // Criteria of the advanced search form + } + + public function Generate(cmdbDataGenerator $oGenerator) + { + $this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('infra_group_id', $oGenerator->GenerateKey("bizInfraGroup", array('org_id' =>$oGenerator->GetOrganizationId() ))); + $this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)")); + } + +} + + + + + + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between two applications, one is the server side and the scond one the client*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkClientServer extends logRealObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "ClientServerLinks", + "description" => "Link between client server application", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "relation", // ???? + "state_attcode" => "", + "reconc_keys" => array("relation"), // ???? + "db_table" => "clientserver_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("client_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Client", "description"=>"The client part of the link", "allowed_values"=>null, "sql"=>"client_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("client_name", array("label"=>"Client", "description"=>"Name of the client", "allowed_values"=>null, "extkey_attcode"=> 'client_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("server_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Server", "description"=>"the server part of the link", "allowed_values"=>null, "sql"=>"server_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("server_name", array("label"=>"Server", "description"=>"Name of the server", "allowed_values"=>null, "extkey_attcode"=> 'server_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("relation", array("label"=>"Relation", "description"=>"Type of relation between both application", "allowed_values"=>null, "sql"=>"relation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("client_id"); + MetaModel::Init_AddFilterFromAttribute("server_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('client_id', 'server_id', 'relation')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('client_id', 'server_id', 'relation')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('client_id', 'server_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('client_id', 'server_id')); // Criteria of the advanced search form + } + + +} + +//////////////////////////////////////////////////////////////////////////////////// +//** +//* A patch is an application or OS fixe for an infrastructure +//* +//////////////////////////////////////////////////////////////////////////////////// +class bizPatch extends logRealObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Patch", + "description" => "Patch installed on infrastucture", + "key_type" => "", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("device_name", "name"), // inherited attributes + "db_table" => "patches", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Device", "description"=>"The Device where patch is installed", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device name", "description"=>"Name of the impacted device", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installed date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"description du patch", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("patch_type", array("label"=>"Type", "description"=>"type de patch", "allowed_values"=>new ValueSetEnum("OS,Application"), "sql"=>"patch_type", "default_value"=>"OS", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + + MetaModel::Init_AddFilterFromAttribute("patch_type"); + MetaModel::Init_AddFilterFromAttribute("device_id"); + + + + + // Display lists + MetaModel::Init_SetZListItems('details', array('name','device_id', 'install_date', 'patch_type','description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name','device_id', 'patch_type','install_date')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'device_id','patch_type')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'device_id','patch_type')); // Criteria of the advanced search form + + } +} + +/*** Insert here all modules requires for ITOP application ***/ + +require_once('incident.business.php'); +require_once('ServiceMgmt.business.php'); +require_once('ChangeMgmt.php'); +require_once('KEDB.php') +?> diff --git a/business/templates/Circuits.html b/business/templates/Circuits.html new file mode 100644 index 0000000000..c3de83c890 --- /dev/null +++ b/business/templates/Circuits.html @@ -0,0 +1,12 @@ + + +$class$: pkey = $pkey$ + + + bizInterface: PKEY IS interface_id IN (bizCircuit: pkey = $pkey$) + + diff --git a/business/templates/application.html b/business/templates/application.html new file mode 100644 index 0000000000..4e3c7575e7 --- /dev/null +++ b/business/templates/application.html @@ -0,0 +1,19 @@ + + +bizApplication: pkey = $pkey$ + + + lnkClientServer: server_id = $pkey$ + + + lnkClientServer: client_id = $pkey$ + + + lnkInfraContract: infra_id = $pkey$ + + + diff --git a/business/templates/change.html b/business/templates/change.html new file mode 100644 index 0000000000..694b26fd63 --- /dev/null +++ b/business/templates/change.html @@ -0,0 +1,17 @@ + + +$class$: pkey = $pkey$ + + + lnkInfraChangeTicket: ticket_id = $pkey$ + + + lnkContactChange: change_id = $pkey$ + + + +
    diff --git a/business/templates/contract.html b/business/templates/contract.html new file mode 100644 index 0000000000..574c6e37e8 --- /dev/null +++ b/business/templates/contract.html @@ -0,0 +1,18 @@ + + +bizContract: pkey = $pkey$ + + + lnkInfraContract: contract_id = $pkey$ + + + lnkContactContract: contract_id = $pkey$ + + + lnkDocumentContract: contract_id = $pkey$ + + diff --git a/business/templates/default.html b/business/templates/default.html new file mode 100644 index 0000000000..238a714d85 --- /dev/null +++ b/business/templates/default.html @@ -0,0 +1,8 @@ + + +$class$: pkey = $pkey$ + diff --git a/business/templates/document.html b/business/templates/document.html new file mode 100644 index 0000000000..470559e8fa --- /dev/null +++ b/business/templates/document.html @@ -0,0 +1,13 @@ + + +$class$: pkey = $pkey$ + + + bizDocVersion: document = $pkey$ + + + diff --git a/business/templates/group.html b/business/templates/group.html new file mode 100644 index 0000000000..11bc838458 --- /dev/null +++ b/business/templates/group.html @@ -0,0 +1,16 @@ + + +bizInfraGroup: pkey = $pkey$ + + + lnkInfraGrouping: infra_group_id = $pkey$ + + + lnkContactRealObject: object_id = $pkey$ + + + diff --git a/business/templates/interface.html b/business/templates/interface.html new file mode 100644 index 0000000000..3f1fe7f95a --- /dev/null +++ b/business/templates/interface.html @@ -0,0 +1,14 @@ + + +bizInterface: pkey = $pkey$ + + + lnkInterfaces: interface1_id = $pkey$ + lnkInterfaces: interface2_id = $pkey$ + + + diff --git a/business/templates/knownError.html b/business/templates/knownError.html new file mode 100644 index 0000000000..3c321384da --- /dev/null +++ b/business/templates/knownError.html @@ -0,0 +1,11 @@ + +bizKnownError: pkey = $pkey$ + + + lnkInfraError: error_id = $pkey$ + + diff --git a/business/templates/location.html b/business/templates/location.html new file mode 100644 index 0000000000..af2df49e01 --- /dev/null +++ b/business/templates/location.html @@ -0,0 +1,25 @@ + + +bizLocation: pkey = $pkey$ + + + bizContact: location_id = $pkey$ + + + bizServer: location_id = $pkey$ + + + bizPC: location_id = $pkey$ + + + bizNetworkDevice: location_id = $pkey$ + + + + lnkDocumentRealObject: object_id = $pkey$ + + diff --git a/business/templates/network.device.html b/business/templates/network.device.html new file mode 100644 index 0000000000..cb3a323851 --- /dev/null +++ b/business/templates/network.device.html @@ -0,0 +1,25 @@ + + +bizNetworkDevice: pkey = $pkey$ + + + bizInterface: device_id = $pkey$ + + + lnkContactRealObject: object_id = $pkey$ + + + bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + + + bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) + + + lnkDocumentRealObject: object_id = $pkey$ + + + diff --git a/business/templates/pc.html b/business/templates/pc.html new file mode 100644 index 0000000000..55597a4acb --- /dev/null +++ b/business/templates/pc.html @@ -0,0 +1,27 @@ + + +bizPC: pkey = $pkey$ + + + bizApplication: device_id = $pkey$ + + + bizPatch: device_id = $pkey$ + + + lnkContactRealObject: object_id = $pkey$ + + + bizInterface: device_id = $pkey$ + + + bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + + + bizDocument: PKEY IS doc_id IN (lnkDocumentRealObject: object_id = $pkey$) + + diff --git a/business/templates/person.html b/business/templates/person.html new file mode 100644 index 0000000000..9bc09ad331 --- /dev/null +++ b/business/templates/person.html @@ -0,0 +1,18 @@ + + +bizPerson: pkey = $pkey$ + + + lnkContactRealObject: object_id = $pkey$ + + + lnkContactRealObject: contact_id = $pkey$ + + + lnkDocumentRealObject: object_id = $pkey$ + + diff --git a/business/templates/server.html b/business/templates/server.html new file mode 100644 index 0000000000..f6f5793ed6 --- /dev/null +++ b/business/templates/server.html @@ -0,0 +1,30 @@ + + +bizServer: pkey = $pkey$ + + + bizApplication: device_id = $pkey$ + + + bizPatch: device_id = $pkey$ + + + bizInterface: device_id = $pkey$ + + + lnkContactRealObject: object_id = $pkey$ + + + bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + + + bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) + + + lnkDocumentRealObject: object_id = $pkey$ + + diff --git a/business/templates/service.html b/business/templates/service.html new file mode 100644 index 0000000000..747ab187ab --- /dev/null +++ b/business/templates/service.html @@ -0,0 +1,13 @@ + + +$class$: pkey = $pkey$ + + + bizContract: service_id = $pkey$ + + + diff --git a/business/templates/software.html b/business/templates/software.html new file mode 100644 index 0000000000..ee51b9651b --- /dev/null +++ b/business/templates/software.html @@ -0,0 +1,13 @@ + + +bizSoftware: pkey = $pkey$ + + + bizApplication: soft_id = $pkey$ + + + diff --git a/business/templates/team.html b/business/templates/team.html new file mode 100644 index 0000000000..05e0ec2d2b --- /dev/null +++ b/business/templates/team.html @@ -0,0 +1,21 @@ + + +$class$: pkey = $pkey$ + + + bizContact: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$) + + + bizTeam: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$) + + + logInfra: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$) + + + bizDocument: PKEY IS doc_id IN (lnkDocumentRealObject: object_id = $pkey$) + + diff --git a/business/templates/ticket.html b/business/templates/ticket.html new file mode 100644 index 0000000000..1c034cc7b2 --- /dev/null +++ b/business/templates/ticket.html @@ -0,0 +1,21 @@ + + +$class$: pkey = $pkey$ + + + lnkInfraTicket: ticket_id = $pkey$ + + + lnkRelatedTicket: ticket_id = $pkey$ + + + lnkContactTicket: ticket_id = $pkey$ + + + + +
    diff --git a/business/test_farm.class.inc.php b/business/test_farm.class.inc.php new file mode 100644 index 0000000000..5f7b4ecc36 --- /dev/null +++ b/business/test_farm.class.inc.php @@ -0,0 +1,272 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +/////////////////////////////////////////////////////////////////////////////// +// Business implementation demo +/////////////////////////////////////////////////////////////////////////////// + +//todo MetaModel::RegisterRelation("Potes", array("description"=>"ceux dont l'email ressemble au mien", "verb_down"=>"est pote de", "verb_up"=>"est pote de")); + + +//todo MetaModel::RegisterZList("list1", array("description"=>"une premiere list, just for fun", "type"=>"attributes")); +//todo MetaModel::RegisterZList("list2", array("description"=>"la secunda e meliora", "type"=>"attributes")); +//todo MetaModel::RegisterZList("list3", array("description"=>"la variante qui tue", "type"=>"filters")); + + +class Animal extends cmdbObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Animal", + "description" => "An animal", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(""), + "db_table" => "animals", + "db_key_field" => "animalid", + "db_finalclass_field" => "actualclass", + ); + MetaModel::Init_Params($aParams); + + MetaModel::Init_AddAttribute(new AttributeEnum("sex", array("label"=>"sex", "description"=>"sex", "allowed_values"=>new ValueSetEnum('male, female'), "sql"=>"sex", "default_value"=>"male", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("species", array("label"=>"species", "description"=>"species", "allowed_values"=>null, "sql"=>"species", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("speed", array("label"=>"walk speed", "description"=>"maximum possible speed m.s-1", "allowed_values"=>null, "sql"=>"speed", "default_value"=>4, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("mother", array("label"=>"mother", "description"=>"mother", "allowed_values"=>null, "sql"=>"mother", "targetclass"=>"Animal", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("father", array("label"=>"father", "description"=>"father", "allowed_values"=>null, "sql"=>"father", "targetclass"=>"Animal", "is_null_allowed"=>true, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("sex"); + MetaModel::Init_AddFilterFromAttribute("species"); + MetaModel::Init_AddFilterFromAttribute("speed"); + MetaModel::Init_AddFilterFromAttribute("mother"); + MetaModel::Init_AddFilterFromAttribute("father"); + } +} + + +class Mammal extends Animal +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Mammal", + "description" => "An animal with some characteristics", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "mammals", + "db_key_field" => "mammalid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("height", array("label"=>"height", "description"=>"size in centimeters", "allowed_values"=>null, "sql"=>"height", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("birth", array("label"=>"birth date", "description"=>"birth date", "allowed_values"=>null, "sql"=>"birth", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("member", array("label"=>"member", "description"=>"leader", "allowed_values"=>null, "sql"=>"member", "targetclass"=>"Group", "is_null_allowed"=>true, "depends_on"=>array()))); + +// ? MetaModel::Init_AddAttribute(new AttributeLinkedSet("a2a", array("label"=>"animal to animal", "description"=>"interanimal relations", "depends_on"=>array(), "linked_class"=>"Animal2animal", "ext_key_to_me"=>"animal1", "count_min"=>0, "count_max"=>10, "allowed_values"=>null))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("height"); + MetaModel::Init_AddFilterFromAttribute("birth"); + MetaModel::Init_AddFilterFromAttribute("member"); + } +} + +class Bird extends Animal +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Bird", + "description" => "Un regroupement de gens", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "birds", + "db_key_field" => "birdid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('geese', 'rooster', 'chicken', 'turckey', 'pie', 'corbeau'))); + + MetaModel::Init_InheritFilters(); + } +} + +class WalkingBird extends Bird +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "WalkingBird", + "description" => "A bird which nevers flies", + "key_type" => "", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "walkingbirds", + "db_key_field" => "walkingbirdid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('geese', 'rooster', 'chicken', 'turckey'))); + MetaModel::Init_InheritFilters(); + } +} + +class FlyingBird extends Bird +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "FlyingBird", + "description" => "A bird which nevers flies", + "key_type" => "", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "flyingbirds", + "db_key_field" => "flyingbirdid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('pie', 'corbeau'))); + MetaModel::Init_AddAttribute(new AttributeInteger("flyingspeed", array("label"=>"flying speed", "description"=>"flying at ms.s-1", "allowed_values"=>null, "sql"=>"headcount", "default_value"=>10, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("flyingspeed"); + } +} + +class AnimalRelation extends cmdbObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "AnimalRelation", + "description" => "Link between two animals", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "a2a", + "db_key_field" => "linkid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // What makes it being a link... + MetaModel::Init_AddAttribute(new AttributeExternalKey("animal1", array("label"=>"source", "description"=>"the animal which does ...", "allowed_values"=>null, "sql"=>"a1", "targetclass"=>"Animal", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("animal2", array("label"=>"target", "description"=>"the animal to which something is done...", "allowed_values"=>null, "sql"=>"a2", "targetclass"=>"Animal", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("animal1"); + MetaModel::Init_AddFilterFromAttribute("animal2"); + } +} + + +class EaterToEaten extends AnimalRelation +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "EaterToEaten", + "description" => "Animal 1 eats animal 2", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "eatertoeaten", + "db_key_field" => "eatertoeatonid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("DeadOrAlive", array("label"=>"DeadOrAlive", "description"=>"State in which it is ok for the eater to proceed", "allowed_values"=>new ValueSetEnum('dead, fresh, cooked'), "sql"=>"deadoralive", "default_value"=>"fresh", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("DeadOrAlive"); + } +} + +class Group extends cmdbObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "blah", + "name" => "Group", + "description" => "Group of animals", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "groups", + "db_key_field" => "groupid", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("leader", array("label"=>"leader", "description"=>"leader", "allowed_values"=>null, "sql"=>"leader", "targetclass"=>"Mammal", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("leader_name", array("label"=>"Leader Name", "description"=>"Name of the leader (defined on Mammal)", "allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("leader_speed", array("label"=>"Leader Name", "description"=>"Speed of the leader (defined on Animal)", "allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"speed"))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("leader"); + MetaModel::Init_AddFilterFromAttribute("leader_name"); + MetaModel::Init_AddFilterFromAttribute("leader_speed"); + } +} + + +?> diff --git a/config-dist.php b/config-dist.php new file mode 100644 index 0000000000..db5babff2c --- /dev/null +++ b/config-dist.php @@ -0,0 +1,38 @@ + 'localhost', + 'db_user' => 'itop', + 'db_pwd' => '1T0p', + 'db_name' => 'itopv06', + 'db_subname' => '', // use it to differentiate two applications instances running on the same DB +); + +// Modules: file names should be specified as a absolute paths + +$MyModules = array( + 'application' => array ( + '../application/menunode.class.inc.php', + '../application/audit.rule.class.inc.php', + // to be continued... + ), + 'business' => array ( + '../business/itop.business.class.inc.php' + // to be continued... + ), + 'addons' => array ( + 'user rights' => '../addons/userrights/userrightsmatrix.class.inc.php', + // other modules to come later + ) +); + + +?> diff --git a/config-test-farm.php b/config-test-farm.php new file mode 100644 index 0000000000..3d1ab9381a --- /dev/null +++ b/config-test-farm.php @@ -0,0 +1,35 @@ + 'localhost', + 'db_user' => 'RomainDBLogin', + 'db_pwd' => '', + 'db_name' => 'TestFarm', + 'db_subname' => '', // use it to differentiate two applications instances running on the same DB +); + +// Modules: file names should be specified as a absolute paths + +$MyModules = array( + 'application' => array ( + // to be continued... + ), + 'business' => array ( + '../business/test_farm.class.inc.php', + // to be continued... + ), + 'addons' => array ( + //'user rights' => '/addons/userrights/userrightsnull.class.inc.php', // or userrightsmatrix.class.inc.php + // other modules to come later + ) +); + +?> diff --git a/config-test-mymodel.php b/config-test-mymodel.php new file mode 100644 index 0000000000..e3176169d9 --- /dev/null +++ b/config-test-mymodel.php @@ -0,0 +1,36 @@ + 'localhost', + 'db_user' => 'itop', + 'db_pwd' => '1T0p', + 'db_name' => 'TestBizModelGenericItop', + 'db_subname' => 'tribute2itop', // use it to differentiate two applications instances running on the same DB +); + +// Modules: file names should be specified as a absolute paths + +$MyModules = array( + 'application' => array ( + // to be continued... + ), + 'business' => array ( + '../business/business_test.class.inc.php' + // to be continued... + ), + 'addons' => array ( + // other modules to come later + ) +); + + +?> diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php new file mode 100644 index 0000000000..8ee89a5ff3 --- /dev/null +++ b/core/MyHelpers.class.inc.php @@ -0,0 +1,491 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class MyHelpers +{ + public static function CheckValueInArray($sDescription, $value, $aData) + { + if (!in_array($value, $aData)) + { + self::HandleWrongValue($sDescription, $value, $aData); + } + } + + public static function CheckKeyInArray($sDescription, $key, $aData) + { + if (!array_key_exists($key, $aData)) + { + self::HandleWrongValue($sDescription, $key, array_keys($aData)); + } + } + + public static function HandleWrongValue($sDescription, $value, $aData) + { + if (count($aData) == 0) + { + $sArrayDesc = "{}"; + } + else + { + $sArrayDesc = "{".implode(", ", $aData)."}"; + } + // exit! + throw new CoreException("Wrong value for $sDescription, found '$value' while expecting a value in $sArrayDesc"); + } + + // getmicrotime() + // format sss.mmmuuupppnnn + public static function getmicrotime() + { + list($usec, $sec) = explode(" ",microtime()); + return ((float)$usec + (float)$sec); + } + + /* + * MakeSQLComment + * converts hash into text comment which we can use in a (mySQL) query + */ + public static function MakeSQLComment ($aHash) + { + if (empty($aHash)) return ""; + $sComment = ""; + { + foreach($aHash as $sKey=>$sValue) + { + $sComment .= "\n-- ". $sKey ."=>" . $sValue; + } + } + return $sComment; + } + + public static function var_dump_html($aWords, $bFullDisplay = false) + { + echo "
    \n";
    +		if ($bFullDisplay)
    +		{
    +			print_r($aWords); // full dump!
    +		}
    +		else
    +		{
    +			var_dump($aWords); // truncate things when they are too big
    +		}
    +		echo "\n
    \n"; + } + + public static function arg_dump_html() + { + echo "
    \n";
    +		echo "GET:\n";
    +		var_dump($_GET);
    +		echo "POST:\n";
    +		var_dump($_POST);
    +		echo "\n
    \n"; + } + + public static function var_dump_string($var) + { + ob_start(); + print_r($var); + $sRet = ob_get_clean(); + return $sRet; + } + + protected static function first_diff_line($s1, $s2) + { + $aLines1 = explode("\n", $s1); + $aLines2 = explode("\n", $s2); + for ($i = 0 ; $i < min(count($aLines1), count($aLines2)) ; $i++) + { + if ($aLines1[$i] != $aLines2[$i]) return $i; + } + return false; + } + + protected static function highlight_line($sMultiline, $iLine, $sHighlightStart = '', $sHightlightEnd = '') + { + $aLines = explode("\n", $sMultiline); + $aLines[$iLine] = $sHighlightStart.$aLines[$iLine].$sHightlightEnd; + return implode("\n", $aLines); + } + + protected static function first_diff($s1, $s2) + { + // do not work fine with multiline strings + $iLen1 = strlen($s1); + $iLen2 = strlen($s2); + for ($i = 0 ; $i < min($iLen1, $iLen2) ; $i++) + { + if ($s1[$i] !== $s2[$i]) return $i; + } + return false; + } + + protected static function last_diff($s1, $s2) + { + // do not work fine with multiline strings + $iLen1 = strlen($s1); + $iLen2 = strlen($s2); + for ($i = 0 ; $i < min(strlen($s1), strlen($s2)) ; $i++) + { + if ($s1[$iLen1 - $i - 1] !== $s2[$iLen2 - $i - 1]) return array($iLen1 - $i, $iLen2 - $i); + } + return false; + } + + protected static function text_cmp_html($sText1, $sText2, $sHighlight) + { + $iDiffPos = self::first_diff_line($sText1, $sText2); + $sDisp1 = self::highlight_line($sText1, $iDiffPos, '
    ', '
    '); + $sDisp2 = self::highlight_line($sText2, $iDiffPos, '
    ', '
    '); + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "\n"; + echo "
    $sDisp1
    $sDisp2
    \n"; + } + + protected static function string_cmp_html($s1, $s2, $sHighlight) + { + $iDiffPos = self::first_diff($s1, $s2); + if ($iDiffPos === false) + { + echo "strings are identical"; + return; + } + $sStart = substr($s1, 0, $iDiffPos); + + $aLastDiff = self::last_diff($s1, $s2); + $sEnd = substr($s1, $aLastDiff[0]); + + $sMiddle1 = substr($s1, $iDiffPos, $aLastDiff[0] - $iDiffPos); + $sMiddle2 = substr($s2, $iDiffPos, $aLastDiff[1] - $iDiffPos); + + echo "

    $sStart$sMiddle1$sEnd

    \n"; + echo "

    $sStart$sMiddle2$sEnd

    \n"; + } + + protected static function object_cmp_html($oObj1, $oObj2, $sHighlight) + { + $sObj1 = self::var_dump_string($oObj1); + $sObj2 = self::var_dump_string($oObj2); + return self::text_cmp_html($sObj1, $sObj2, $sHighlight); + } + + public static function var_cmp_html($var1, $var2, $sHighlight = 'color:red; font-weight:bold;') + { + if (is_object($var1)) + { + return self::object_cmp_html($var1, $var2, $sHighlight); + } + else if (count(explode("\n", $var1)) > 1) + { + // multiline string + return self::text_cmp_html($var1, $var2, $sHighlight); + } + else + { + return self::string_cmp_html($var1, $var2, $sHighlight); + } + } + + public static function get_callstack_html($iLevelsToIgnore = 0, $aCallStack = null) + { + if ($aCallStack == null) $aCallStack = debug_backtrace(); + + $aCallStack = array_slice($aCallStack, $iLevelsToIgnore); + + $aDigestCallStack = array(); + $bFirstLine = true; + foreach ($aCallStack as $aCallInfo) + { + $sLine = empty($aCallInfo['line']) ? "" : $aCallInfo['line']; + $sFile = empty($aCallInfo['file']) ? "" : $aCallInfo['file']; + $sClass = empty($aCallInfo['class']) ? "" : $aCallInfo['class']; + $sType = empty($aCallInfo['type']) ? "" : $aCallInfo['type']; + $sFunction = empty($aCallInfo['function']) ? "" : $aCallInfo['function']; + + if ($bFirstLine) + { + $bFirstLine = false; + // For this line do not display the "function name" because + // that will be the name of our error handler for sure ! + $sFunctionInfo = "N/A"; + } + else + { + $args = ''; + if (empty($aCallInfo['args'])) $aCallInfo['args'] = array(); + foreach ($aCallInfo['args'] as $a) + { + if (!empty($args)) + { + $args .= ', '; + } + switch (gettype($a)) + { + case 'integer': + case 'double': + $args .= $a; + break; + case 'string': + $a = Str::pure2html(self::beautifulstr($a, 1024, true, true)); + $args .= "\"$a\""; + break; + case 'array': + $args .= 'Array('.count($a).')'; + break; + case 'object': + $args .= 'Object('.get_class($a).')'; + break; + case 'resource': + $args .= 'Resource('.strstr($a, '#').')'; + break; + case 'boolean': + $args .= $a ? 'True' : 'False'; + break; + case 'NULL': + $args .= 'Null'; + break; + default: + $args .= 'Unknown'; + } + } + $sFunctionInfo = "$sClass $sType $sFunction($args)"; + } + $aDigestCallStack[] = array('File'=>$sFile, 'Line'=>$sLine, 'Function'=>$sFunctionInfo); + } + return self::make_table_from_assoc_array($aDigestCallStack); + } + + public static function dump_callstack($iLevelsToIgnore = 0, $aCallStack = null) + { + return self::get_callstack_html($iLevelsToIgnore, $aCallStack); + } + + /////////////////////////////////////////////////////////////////////////////// + // Source: New + // Last modif: 2004/12/20 RQU + /////////////////////////////////////////////////////////////////////////////// + public static function make_table_from_assoc_array(&$aData) + { + if (!is_array($aData)) throw new CoreException("make_table_from_assoc_array: Error - the passed argument is not an array"); + $aFirstRow = reset($aData); + if (count($aData) == 0) return ''; + if (!is_array($aFirstRow)) throw new CoreException("make_table_from_assoc_array: Error - the passed argument is not a bi-dimensional array"); + $sOutput = ""; + $sOutput .= "\n"; + + // Table header + // + $sOutput .= " \n"; + foreach ($aFirstRow as $fieldname=>$trash) { + $sOutput .= " \n"; + } + $sOutput .= " \n"; + + // Table contents + // + $iCount = 0; + foreach ($aData as $aRow) { + $sStyle = ($iCount++ % 2 ? "STYLE=\"background-color : #eeeeee\"" : ""); + $sOutput .= " \n"; + foreach ($aRow as $data) { + if (strlen($data) == 0) { + $data = " "; + } + $sOutput .= " \n"; + } + $sOutput .= " \n"; + } + + $sOutput .= "
    ".$fieldname."
    ".$data."
    \n"; + return $sOutput; + } + + public static function debug_breakpoint($arg) + { + echo "

    Debug breakpoint

    \n"; + MyHelpers::var_dump_html($arg); + MyHelpers::dump_callstack(); + exit; + } + public static function debug_breakpoint_notempty($arg) + { + if (empty($arg)) return; + echo "

    Debug breakpoint (triggered on non-empty value)

    \n"; + MyHelpers::var_dump_html($arg); + MyHelpers::dump_callstack(); + exit; + } + + /** + * utf8... converts non ASCII chars into '?' + * Decided after some complex investigations, to have the tools work fine (Oracle+Perl vs mySQL+PHP...) + */ + public static function utf8($strText) + { + return iconv("WINDOWS-1252", "ASCII//TRANSLIT", $strText); + } + + /** + * xmlentities() + * ... same as htmlentities, but designed for xml ! + */ + public static function xmlentities($string) + { + return str_replace( array( '&', '"', "'", '<', '>' ), array ( '&' , '"', ''' , '<' , '>' ), $string ); + } + + /** + * xmlencode() + * Encodes a string so that for sure it can be output as an xml data string + */ + public static function xmlencode($string) + { + return xmlentities(iconv("UTF-8", "UTF-8//IGNORE",$string)); + } + + /////////////////////////////////////////////////////////////////////////////// + // Source: New - format strings for output + // Last modif: 2005/01/18 RQU + /////////////////////////////////////////////////////////////////////////////// + public static function beautifulstr($sLongString, $iMaxLen, $bShowLen=false, $bShowTooltip=true) + { + if (!is_string($sLongString)) throw new CoreException("beautifulstr: expect a string as 1st argument"); + + // Nothing to do if the string is short + if (strlen($sLongString) <= $iMaxLen) return $sLongString; + + // Truncate the string + $sSuffix = "..."; + if ($bShowLen) { + $sSuffix .= "(".strlen($sLongString)." chars)..."; + } + $sOutput = substr($sLongString, 0, $iMaxLen - strlen($sSuffix)).$sSuffix; + $sOutput = htmlspecialchars($sOutput); + + // Add tooltip if required + //if ($bShowTooltip) { + // $oTooltip = new gui_tooltip($sLongString); + // $sOutput = "get_mouseOver_code().">".$sOutput.""; + //} + return $sOutput; + } +} + +/** +Utility class: static methods for cleaning & escaping untrusted (i.e. +user-supplied) strings. +Any string can (usually) be thought of as being in one of these 'modes': +pure = what the user actually typed / what you want to see on the page / + what is actually stored in the DB +gpc = incoming GET, POST or COOKIE data +sql = escaped for passing safely to RDBMS via SQL (also, data from DB + queries and file reads if you have magic_quotes_runtime on--which + is rare) +html = safe for html display (htmlentities applied) +Always knowing what mode your string is in--using these methods to +convert between modes--will prevent SQL injection and cross-site scripting. +This class refers to its own namespace (so it can work in PHP 4--there is no +self keyword until PHP 5). Do not change the name of the class w/o changing +all the internal references. +Example usage: a POST value that you want to query with: +$username = Str::gpc2sql($_POST['username']); +*/ +//This sets SQL escaping to use slashes; for Sybase(/MSSQL)-style escaping +// ( ' --> '' ), set to true. +define('STR_SYBASE', false); +class Str +{ + public static function gpc2sql($gpc, $maxLength = false) + { + return self::pure2sql(self::gpc2pure($gpc), $maxLength); + } + public static function gpc2html($gpc, $maxLength = false) + { + return self::pure2html(self::gpc2pure($gpc), $maxLength); + } + public static function gpc2pure($gpc) + { + if (ini_get('magic_quotes_sybase')) $pure = str_replace("''", "'", $gpc); + else $pure = get_magic_quotes_gpc() ? stripslashes($gpc) : $gpc; + return $pure; + } + public static function html2pure($html) + { + return html_entity_decode($html); + } + public static function html2sql($html, $maxLength = false) + { + return self::pure2sql(self::html2pure($html), $maxLength); + } + public static function pure2html($pure, $maxLength = false) + { + return $maxLength + ? htmlentities(substr($pure, 0, $maxLength)) + : htmlentities($pure); + } + public static function pure2sql($pure, $maxLength = false) + { + if ($maxLength) $pure = substr($pure, 0, $maxLength); + return (STR_SYBASE) + ? str_replace("'", "''", $pure) + : addslashes($pure); + } + public static function sql2html($sql, $maxLength = false) + { + $pure = self::sql2pure($sql); + if ($maxLength) $pure = substr($pure, 0, $maxLength); + return self::pure2html($pure); + } + public static function sql2pure($sql) + { + return (STR_SYBASE) + ? str_replace("''", "'", $sql) + : stripslashes($sql); + } + + public static function xml2pure($xml) + { + // #@# - not implemented + return $xml; + } + public static function pure2xml($pure) + { + return self::xmlencode($pure); + } + + protected static function xmlentities($string) + { + return str_replace( array( '&', '"', "'", '<', '>' ), array ( '&' , '"', ''' , '<' , '>' ), $string ); + } + + /** + * xmlencode() + * Encodes a string so that for sure it can be output as an xml data string + */ + protected static function xmlencode($string) + { + return self::xmlentities(iconv("ISO-8859-1", "UTF-8//IGNORE",$string)); + } + + public static function islowcase($sString) + { + return (strtolower($sString) == $sString); + } +} + +?> diff --git a/core/archive.class.inc.php b/core/archive.class.inc.php new file mode 100644 index 0000000000..6403fffca9 --- /dev/null +++ b/core/archive.class.inc.php @@ -0,0 +1,323 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +/** + * iTopArchive a class to manipulate (read/write) iTop archives with their catalog + * Each iTop archive is a zip file that contains (at the root of the archive) + * a file called catalog.xml holding the description of the archive + */ +class iTopArchive +{ + const read = 0; + const create = ZipArchive::CREATE; + + protected $m_sZipPath; + protected $m_oZip; + protected $m_sVersion; + protected $m_sTitle; + protected $m_sDescription; + protected $m_aPackages; + protected $m_aErrorMessages; + + /** + * Construct an iTopArchive object + * @param $sArchivePath string The full path the archive file + * @param $iMode integrer Either iTopArchive::read for reading an existing archive or iTopArchive::create for creating a new one. Updating is not supported (yet) + */ + public function __construct($sArchivePath, $iMode = iTopArchive::read) + { + $this->m_sZipPath = $sArchivePath; + $this->m_oZip = new ZipArchive(); + $this->m_oZip->open($this->m_sZipPath, $iMode); + $this->m_aErrorMessages = array(); + $this->m_sVersion = '1.0'; + $this->m_sTitle = ''; + $this->m_sDescription = ''; + $this->m_aPackages = array(); + } + + public function SetTitle($sTitle) + { + $this->m_sTitle = $sTitle; + } + + public function SetDescription($sDescription) + { + $this->m_sDescription = $sDescription; + } + + public function GetTitle() + { + return $this->m_sTitle; + } + + public function GetDescription() + { + return $this->m_sDescription; + } + + public function GetPackages() + { + return $this->m_aPackages; + } + + public function __destruct() + { + $this->m_oZip->close(); + } + + /** + * Get the error message explaining the latest error encountered + * @return array All the error messages encountered during the validation + */ + public function GetErrors() + { + return $this->m_aErrorMessages; + } + + /** + * Read the catalog from the archive (zip) file + * @param sPath string Path the the zip file + * @return boolean True in case of success, false otherwise + */ + public function ReadCatalog() + { + if ($this->IsValid()) + { + $sXmlCatalog = $this->m_oZip->getFromName('catalog.xml'); + $oParser = xml_parser_create(); + xml_parse_into_struct($oParser, $sXmlCatalog, $aValues, $aIndexes); + xml_parser_free($oParser); + + $iIndex = $aIndexes['ARCHIVE'][0]; + $this->m_sVersion = $aValues[$iIndex]['attributes']['VERSION']; + $iIndex = $aIndexes['TITLE'][0]; + $this->m_sTitle = $aValues[$iIndex]['value']; + $iIndex = $aIndexes['DESCRIPTION'][0]; + if (array_key_exists('value', $aValues[$iIndex])) + { + // #@# implement a get_array_value(array, key, default) ? + $this->m_sDescription = $aValues[$iIndex]['value']; + } + + foreach($aIndexes['PACKAGE'] as $iIndex) + { + $this->m_aPackages[$aValues[$iIndex]['attributes']['HREF']] = array( 'type' => $aValues[$iIndex]['attributes']['TYPE'], 'title'=> $aValues[$iIndex]['attributes']['TITLE'], 'description' => $aValues[$iIndex]['value']); + } + + //echo "Archive path: {$this->m_sZipPath}
    \n"; + //echo "Archive format version: {$this->m_sVersion}
    \n"; + //echo "Title: {$this->m_sTitle}
    \n"; + //echo "Description: {$this->m_sDescription}
    \n"; + //foreach($this->m_aPackages as $aFile) + //{ + // echo "{$aFile['title']} ({$aFile['type']}): {$aFile['description']}
    \n"; + //} + } + return true; + } + + public function WriteCatalog() + { + $sXml = "\n"; // split the XML closing tag that disturbs PSPad's syntax coloring + $sXml .= "\n"; + $sXml .= "{$this->m_sTitle}\n"; + $sXml .= "{$this->m_sDescription}\n"; + foreach($this->m_aPackages as $sFileName => $aFile) + { + $sXml .= "{$aFile['description']}\n"; + } + $sXml .= ""; + $this->m_oZip->addFromString('catalog.xml', $sXml); + } + + /** + * Add a package to the archive + * @param string $sExternalFilePath The path to the file to be added to the archive as a package (directories are not yet implemented) + * @param string $sFilePath The name of the file inside the archive + * @param string $sTitle A short title for this package + * @param string $sType Type of the package. SQL scripts must be of type 'text/sql' + * @param string $sDescription A longer description of the purpose of this package + * @return none + */ + public function AddPackage($sExternalFilePath, $sFilePath, $sTitle, $sType, $sDescription) + { + $this->m_aPackages[$sFilePath] = array('title' => $sTitle, 'type' => $sType, 'description' => $sDescription); + $this->m_oZip->addFile($sExternalFilePath, $sFilePath); + } + + /** + * Reads the contents of the given file from the archive + * @param string $sFileName The path to the file inside the archive + * @return string The content of the file read from the archive + */ + public function GetFileContents($sFileName) + { + return $this->m_oZip->getFromName($sFileName); + } + + /** + * Extracts the contents of the given file from the archive + * @param string $sFileName The path to the file inside the archive + * @param string $sDestinationFileName The path of the file to write + * @return none + */ + public function ExtractToFile($sFileName, $sDestinationFileName) + { + $iBufferSize = 64 * 1024; // Read 64K at a time + $oZipStream = $this->m_oZip->getStream($sFileName); + $oDestinationStream = fopen($sDestinationFileName, 'wb'); + while (!feof($oZipStream)) { + $sContents = fread($oZipStream, $iBufferSize); + fwrite($oDestinationStream, $sContents); + } + fclose($oZipStream); + fclose($oDestinationStream); + } + + /** + * Apply a SQL script taken from the archive. The package must be listed in the catalog and of type text/sql + * @param string $sFileName The path to the SQL package inside the archive + * @return boolean false in case of error, true otherwise + */ + public function ImportSql($sFileName, $sDatabase = 'itop') + { + if ( ($this->m_oZip->locateName($sFileName) == false) || (!isset($this->m_aPackages[$sFileName])) || ($this->m_aPackages[$sFileName]['type'] != 'text/sql')) + { + // invalid type or not listed in the catalog + return false; + } + $sTempName = tempnam("../tmp/", "sql"); + //echo "Extracting to: '$sTempName'
    \n"; + $this->ExtractToFile($sFileName, $sTempName); + // Note: the command line below works on Windows with the right path to mysql !!! + $sCommandLine = 'type "'.$sTempName.'" | "/iTop/MySQL Server 5.0/bin/mysql.exe" -u root '.$sDatabase; + //echo "Executing: '$sCommandLine'
    \n"; + exec($sCommandLine, $aOutput, $iRet); + //echo "Return code: $iRet
    \n"; + //echo "Output:
    \n";
    +		//print_r($aOutput);
    +		//echo "

    \n"; + unlink($sTempName); + return ($iRet == 0); + } + + /** + * Dumps some part of the specified MySQL database into the archive as a text/sql package + * @param $sTitle string A short title for this SQL script + * @param $sDescription string A longer description of the purpose of this SQL script + * @param $sFileName string The name of the package inside the archive + * @param $sDatabase string name of the database + * @param $aTables array array or table names. If empty, all tables are dumped + * @param $bStructureOnly boolean Whether or not to dump the data or just the schema + * @return boolean False in case of error, true otherwise + */ + public function AddDatabaseDump($sTitle, $sDescription, $sFileName, $sDatabase = 'itop', $aTables = array(), $bStructureOnly = true) + { + $sTempName = tempnam("../tmp/", "sql"); + $sNoData = $bStructureOnly ? "--no-data" : ""; + $sCommandLine = "\"/iTop/MySQL Server 5.0/bin/mysqldump.exe\" --user=root --opt $sNoData --result-file=$sTempName $sDatabase ".implode(" ", $aTables); + //echo "Executing command: '$sCommandLine'
    \n"; + exec($sCommandLine, $aOutput, $iRet); + //echo "Return code: $iRet
    \n"; + //echo "Output:
    \n";
    +		//print_r($aOutput);
    +		//echo "

    \n"; + if ($iRet == 0) + { + $this->AddPackage($sTempName, $sFileName, $sTitle, 'text/sql', $sDescription); + } + //unlink($sTempName); + return ($iRet == 0); + } + + /** + * Check the consistency of the archive + * @return boolean True if the archive file is consistent + */ + public function IsValid() + { + // TO DO: use a DTD to validate the XML instead of this hand-made validation + $bResult = true; + $aMandatoryTags = array('ARCHIVE' => array('VERSION'), + 'TITLE' => array(), + 'DESCRIPTION' => array(), + 'PACKAGE' => array('TYPE', 'HREF', 'TITLE')); + + $sXmlCatalog = $this->m_oZip->getFromName('catalog.xml'); + $oParser = xml_parser_create(); + xml_parse_into_struct($oParser, $sXmlCatalog, $aValues, $aIndexes); + xml_parser_free($oParser); + + foreach($aMandatoryTags as $sTag => $aAttributes) + { + // Check that all the required tags are present + if (!isset($aIndexes[$sTag])) + { + $this->m_aErrorMessages[] = "The XML catalog does not contain the mandatory tag $sTag."; + $bResult = false; + } + else + { + foreach($aIndexes[$sTag] as $iIndex) + { + switch($aValues[$iIndex]['type']) + { + case 'complete': + case 'open': + // Check that all the required attributes are present + foreach($aAttributes as $sAttribute) + { + if (!isset($aValues[$iIndex]['attributes'][$sAttribute])) + { + $this->m_aErrorMessages[] = "The tag $sTag ($iIndex) does not contain the required attribute $sAttribute."; + } + } + break; + + default: + // ignore other type of tags: close or cdata + } + } + } + } + return $bResult; + } +} +/* +// Unit test - reading an archive +$sArchivePath = '../tmp/archive.zip'; +$oArchive = new iTopArchive($sArchivePath, iTopArchive::read); +$oArchive->ReadCatalog(); +$oArchive->ImportSql('full_backup.sql'); + +// Writing an archive -- + +$sArchivePath = '../tmp/archive2.zip'; +$oArchive = new iTopArchive($sArchivePath, iTopArchive::create); +$oArchive->SetTitle('First Archive !'); +$oArchive->SetDescription('This is just a test. Does not contain a lot of useful data.'); +$oArchive->AddPackage('../tmp/schema.sql', 'test.sql', 'this is just a test', 'text/sql', 'My first attempt at creating an archive from PHP...'); +$oArchive->WriteCatalog(); + + +$sArchivePath = '../tmp/archive2.zip'; +$oArchive = new iTopArchive($sArchivePath, iTopArchive::create); +$oArchive->SetTitle('First Archive !'); +$oArchive->SetDescription('This is just a test. Does not contain a lot of useful data.'); +$oArchive->AddDatabaseDump('Test', 'This is my first automatic dump', 'schema.sql', 'itop', array('objects')); +$oArchive->WriteCatalog(); +*/ +?> diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php new file mode 100644 index 0000000000..d1af378a4a --- /dev/null +++ b/core/attributedef.class.inc.php @@ -0,0 +1,989 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class AttributeDefinition +{ + abstract public function GetType(); + abstract public function GetTypeDesc(); + abstract public function GetEditClass(); + abstract public function GetDBFieldType(); + + protected $m_sCode; + private $m_aParams = array(); + private $m_sHostClass = array(); + protected function Get($sParamName) {return $this->m_aParams[$sParamName];} + + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $this->m_aParams = $aParams; + $this->ConsistencyCheck(); + } + public function OverloadParams($aParams) + { + foreach ($aParams as $sParam => $value) + { + if (!array_key_exists($sParam, $this->m_aParams)) + { + throw new CoreException("Unknown attribute definition parameter '$sParam', please select a value in {".implode(", ", $this->m_aParams)."}"); + } + else + { + $this->m_aParams[$sParam] = $value; + } + } + } + public function SetHostClass($sHostClass) + { + $this->m_sHostClass = $sHostClass; + } + public function GetHostClass() + { + return $this->m_sHostClass; + } + + // Note: I could factorize this code with the parameter management made for the AttributeDef class + // to be overloaded + static protected function ListExpectedParams() + { + return array("label", "description", "allowed_values"); + } + + private function ConsistencyCheck() + { + + // Check that any mandatory param has been specified + // + $aExpectedParams = $this->ListExpectedParams(); + foreach($aExpectedParams as $sParamName) + { + if (!array_key_exists($sParamName, $this->m_aParams)) + { + $aBacktrace = debug_backtrace(); + $sTargetClass = $aBacktrace[2]["class"]; + $sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"]; + throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)"); + } + } + } + + // table, key field, name field + public function ListDBJoins() + { + return ""; + // e.g: return array("Site", "infrid", "name"); + } + public function IsDirectField() {return false;} + public function IsScalar() {return false;} + public function IsLinkSet() {return false;} + public function IsExternalKey($iType = EXTKEY_RELATIVE) {return false;} + public function IsExternalField() {return false;} + public function IsWritable() {return false;} + public function GetCode() {return $this->m_sCode;} + public function GetLabel() {return $this->Get("label");} + public function GetDescription() {return $this->Get("description");} + public function GetValuesDef() {return $this->Get("allowed_values");} + public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} + //public function IsSearchableStd() {return $this->Get("search_std");} + //public function IsSearchableGlobal() {return $this->Get("search_global");} + //public function IsMandatory() {return $this->Get("is_mandatory");} + //public function GetMinVal() {return $this->Get("min");} + //public function GetMaxVal() {return $this->Get("max");} + //public function GetSize() {return $this->Get("size");} + //public function GetCheckRegExp() {return $this->Get("regexp");} + //public function GetCheckFunc() {return $this->Get("checkfunc");} + + // Definition: real value is what will be stored in memory and maintained by MetaModel + // DBObject::Set() relies on MakeRealValue() + // MetaModel::MakeQuery() relies on RealValueToSQLValue() + // DBObject::FromRow() relies on SQLToRealValue() + public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!) + public function RealValueToSQLValue($value) {return $value;} // format value as a valuable SQL literal (quoted outside) + public function SQLValueToRealValue($value) {return $value;} // take the result of a fetch... and make it a PHP variable + + public function GetJSCheckFunc() + { + $sRegExp = $this->Get("regexp"); + if (empty($sRegExp)) return 'return true;'; + + return "return regexp('$sRegExp', myvalue);"; + } + public function CheckValue($value) + { + $sRegExp = $this->Get("regexp"); + if (empty($sRegExp)) return true; + + return preg_match(preg_escape($this->Get("regexp")), $value); + } + + public function MakeValue() + { + $sComputeFunc = $this->Get("compute_func"); + if (empty($sComputeFunc)) return null; + + return call_user_func($sComputeFunc); + } + + abstract public function DBGetUsedFields(); + abstract public function GetDefaultValue(); + + // + // To be overloaded in subclasses + // + + abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description" + abstract public function GetBasicFilterLooseOperator(); // returns an "opCode" + //abstract protected GetBasicFilterHTMLInput(); + abstract public function GetBasicFilterSQLExpr($sOpCode, $value); + + public function GetAsHTML($sValue) + { + return Str::pure2html($sValue); + } + + public function GetAsXML($sValue) + { + return Str::pure2xml($sValue); + } + + public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') + { + return str_replace($sSeparator, $sSepEscape, $sValue); + } +} + +/** + * Set of objects directly linked to an object, and being part of its definition + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeLinkedSet extends AttributeDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); + } + + public function GetType() {return "Array of objects";} + public function GetTypeDesc() {return "Any kind of objects [subclass] of the same class";} + public function GetEditClass() {return "List";} + public function GetDBFieldType() {return "N/A";} // should be moved out of the AttributeDef root class + + public function IsWritable() {return true;} + public function IsLinkSet() {return true;} + + public function GetDefaultValue() {return DBObjectSet::FromScratch($this->Get('linked_class'));} + + public function GetLinkedClass() {return $this->Get('linked_class');} + public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} + + public function DBGetUsedFields() {return array();} + public function GetBasicFilterOperators() {return array();} + public function GetBasicFilterLooseOperator() {return '';} + public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} + + public function GetAsHTML($sValue) + { + return "ERROR: LIST OF OBJECTS"; + } + + public function GetAsXML($sValue) + { + return "ERROR: LIST OF OBJECTS"; + } + + public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') + { + return "ERROR: LIST OF OBJECTS"; + } +} + +/** + * Set of objects linked to an object (n-n), and being part of its definition + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeLinkedSetIndirect extends AttributeLinkedSet +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote")); + } + public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); } +} + +/** + * Abstract class implementing default filters for a DB column + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeDBFieldVoid extends AttributeDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("depends_on", "sql")); + } + + public function GetType() {return "Void";} + public function GetTypeDesc() {return "Any kind of value, from the DB";} + public function GetEditClass() {return "String";} + public function GetDBFieldType() {return "VARCHAR(255)";} + + public function IsDirectField() {return true;} + public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetSQLExpr() {return $this->Get("sql");} + public function GetDefaultValue() {return "";} + public function IsNullAllowed() {return false;} + public function DBGetUsedFields() + { + // #@# bugge a mort... a suivre... + return array($this->Get("sql")); + } + + public function GetBasicFilterOperators() + { + return array("="=>"equals", "!="=>"differs from"); + } + public function GetBasicFilterLooseOperator() + { + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '=': + default: + return $this->GetSQLExpr()." = $sQValue"; + } + } +} + +/** + * Base class for all kind of DB attributes, with the exception of external keys + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeDBField extends AttributeDBFieldVoid +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); + } + public function GetDefaultValue() {return $this->Get("default_value");} + public function IsNullAllowed() {return strtolower($this->Get("is_null_allowed"));} +} + +/** + * Map an integer column to an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeInteger extends AttributeDBField +{ + static protected function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetType() {return "Integer";} + public function GetTypeDesc() {return "Numeric value (could be negative)";} + public function GetEditClass() {return "String";} + public function GetDBFieldType() {return "INT";} + + public function GetBasicFilterOperators() + { + return array( + "!="=>"differs from", + "="=>"equals", + ">"=>"greater (strict) than", + ">="=>"greater than", + "<"=>"less (strict) than", + "<="=>"less than", + "in"=>"in" + ); + } + public function GetBasicFilterLooseOperator() + { + // Unless we implement an "equals approximately..." or "same order of magnitude" + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '>': + return $this->GetSQLExpr()." > $sQValue"; + break; + case '>=': + return $this->GetSQLExpr()." >= $sQValue"; + break; + case '<': + return $this->GetSQLExpr()." < $sQValue"; + break; + case '<=': + return $this->GetSQLExpr()." <= $sQValue"; + break; + case 'in': + if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; + break; + + case '=': + default: + return $this->GetSQLExpr()." = \"$value\""; + } + } + + public function MakeRealValue($proposedValue) + { + //return intval($proposedValue); could work as well + return (int)$proposedValue; + } + public function RealValueToSQLValue($value) + { + assert(is_numeric($value)); + return $value; // supposed to be an int + } + public function SQLValueToRealValue($value) + { + // Use cast (int) or intval() ? + return (int)$value; + + } +} + +/** + * Map a varchar column (size < ?) to an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeString extends AttributeDBField +{ + static protected function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetType() {return "String";} + public function GetTypeDesc() {return "Alphanumeric string";} + public function GetEditClass() {return "String";} + public function GetDBFieldType() {return "VARCHAR(255)";} + + public function GetBasicFilterOperators() + { + return array( + "="=>"equals", + "!="=>"differs from", + "Like"=>"equals (no case)", + "NotLike"=>"differs from (no case)", + "Contains"=>"contains", + "Begins with"=>"begins with", + "Finishes with"=>"finishes with" + ); + } + public function GetBasicFilterLooseOperator() + { + return "Contains"; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '=': + case '!=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'Begins with': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%"); + case 'Finishes with': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value"); + case 'Contains': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); + case 'NotLike': + return $this->GetSQLExpr()." NOT LIKE $sQValue"; + case 'Like': + default: + return $this->GetSQLExpr()." LIKE $sQValue"; + } + } + + public function MakeRealValue($proposedValue) + { + return (string)$proposedValue; + // if (!settype($proposedValue, "string")) + // { + // throw new CoreException("Failed to change the type of '$proposedValue' to a string"); + // } + } + public function RealValueToSQLValue($value) + { + assert(is_string($value)); + return $value; + } + public function SQLValueToRealValue($value) + { + return $value; + } +} + +/** + * Map a text column (size > ?) to an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeText extends AttributeString +{ + public function GetType() {return "Text";} + public function GetTypeDesc() {return "Multiline character string";} + public function GetEditClass() {return "Text";} + public function GetDBFieldType() {return "TEXT";} + + public function GetAsHTML($sValue) + { + return str_replace("\n", "
    \n", parent::GetAsHTML($sValue)); + } + + public function GetAsXML($value) + { + return Str::pure2xml($value); + } + + public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',') + { + return str_replace("\n", "[newline]", parent::GetAsCSV($sValue, $sSeparator, $sSepEscape)); + } +} + +/** + * Map a enum column to an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeEnum extends AttributeString +{ + static protected function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetType() {return "Enum";} + public function GetTypeDesc() {return "List of predefined alphanumeric strings";} + public function GetEditClass() {return "String";} + public function GetDBFieldType() + { + $oValDef = $this->GetValuesDef(); + if ($oValDef) + { + $aValues = CMDBSource::Quote($oValDef->GetValues(array(), ""), true); + } + else + { + $aValues = array(); + } + if (count($aValues) > 0) + { + return "ENUM(".implode(", ", $aValues).")"; + } + else + { + return "VARCHAR(255)"; // ENUM() is not an allowed syntax! + } + } + + public function GetBasicFilterOperators() + { + return parent::GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + return parent::GetBasicFilterLooseOperator(); + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return parent::GetBasicFilterSQLExpr($sOpCode, $value); + } +} + +/** + * Map a date+time column to an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeDate extends AttributeDBField +{ + const MYDATEFORMAT = "Y-m-d H:i:s"; + //const MYDATETIMEZONE = "UTC"; + const MYDATETIMEZONE = "Europe/Paris"; + static protected $const_TIMEZONE = null; // set once for all upon object construct + + static public function InitStatics() + { + // Init static constant once for all (remove when PHP allows real static const) + self::$const_TIMEZONE = new DateTimeZone(self::MYDATETIMEZONE); + + // #@# Init default timezone -> do not get a notice... to be improved !!! + date_default_timezone_set(self::MYDATETIMEZONE); + } + + static protected function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetType() {return "Date";} + public function GetTypeDesc() {return "Date and time";} + public function GetEditClass() {return "Date";} + public function GetDBFieldType() {return "TIMESTAMP";} + + public function GetBasicFilterOperators() + { + return array( + "="=>"equals", + "!="=>"differs from", + "<"=>"before", + "<="=>"before", + ">"=>"after (strictly)", + ">="=>"after", + "SameDay"=>"same day (strip time)", + "SameMonth"=>"same year/month", + "SameYear"=>"same year", + "Today"=>"today", + ">|"=>"after today + N days", + "<|"=>"before today + N days", + "=|"=>"equals today + N days", + ); + } + public function GetBasicFilterLooseOperator() + { + // Unless we implement a "same xxx, depending on given precision" ! + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + + switch ($sOpCode) + { + case '=': + case '!=': + case '<': + case '<=': + case '>': + case '>=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'SameDay': + return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)"; + case 'SameMonth': + return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')"; + case 'SameYear': + return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)"; + case 'Today': + return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()"; + case '>|': + return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '<|': + return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '=|': + return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + default: + return $this->GetSQLExpr()." = $sQValue"; + } + } + + public function MakeRealValue($proposedValue) + { + if (!is_numeric($proposedValue)) + { + return $proposedValue; + } + else + { + return date("Y-m-d H:i:s", $proposedValue); + } + throw new CoreException("Invalid type for a date (found ".gettype($proposedValue)." and accepting string/int/DateTime)"); + return null; + } + public function RealValueToSQLValue($value) + { + if (empty($value)) + { + // Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null. + return '0000-00-00 00:00:00'; + } + return $value; + } + public function SQLValueToRealValue($value) + { + return $value; + } + + public function GetAsHTML($value) + { + return Str::pure2html($value); + } + + public function GetAsXML($value) + { + return Str::pure2xml($value); + } + + public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',') + { + return str_replace($sSeparator, $sSepEscape, $value); + } +} + +// Init static constant once for all (remove when PHP allows real static const) +AttributeDate::InitStatics(); + + +/** + * Map a foreign key to an attribute + * AttributeExternalKey and AttributeExternalField may be an external key + * the difference is that AttributeExternalKey corresponds to a column into the defined table + * where an AttributeExternalField corresponds to a column into another table (class) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeExternalKey extends AttributeDBFieldVoid +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed")); + } + + public function GetType() {return "Extkey";} + public function GetTypeDesc() {return "Link to another object";} + public function GetEditClass() {return "ExtKey";} + public function GetDBFieldType() {return "INT";} + + public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;} + public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");} + public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} + public function GetKeyAttCode() {return $this->GetCode();} + + + public function GetDefaultValue() {return 0;} + public function IsNullAllowed() {return $this->Get("is_null_allowed");} + + public function GetBasicFilterOperators() + { + return parent::GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + return parent::GetBasicFilterLooseOperator(); + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return parent::GetBasicFilterSQLExpr($sOpCode, $value); + } + + // overloaded here so that an ext key always have the answer to + // "what are you possible values?" + public function GetValuesDef() + { + $oValSetDef = $this->Get("allowed_values"); + if (!$oValSetDef) + { + // Let's propose every existing value + $oValSetDef = new ValueSetObjects($this->GetTargetClass()); + } + return $oValSetDef; + } +} + +/** + * An attribute which corresponds to an external key (direct or indirect) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeExternalField extends AttributeDefinition +{ + + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode")); + } + + public function GetType() {return "ExtkeyField";} + public function GetTypeDesc() {return "Field of an object pointed to by the current object";} + public function GetEditClass() {return "ExtField";} + public function GetDBFieldType() + { + // throw new CoreException("external attribute: does it make any sense to request its type ?"); + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetDBFieldType(); + } + + public function IsExternalKey($iType = EXTKEY_RELATIVE) + { + switch($iType) + { + case EXTKEY_ABSOLUTE: + // see further + $oRemoteAtt = $this->GetExtAttDef(); + return $oRemoteAtt->IsExternalKey($iType); + + case EXTKEY_RELATIVE: + return false; + + default: + throw new CoreException("Unexpected value for argument iType: '$iType'"); + } + } + + public function GetTargetClass($iType = EXTKEY_RELATIVE) + { + return $this->GetKeyAttDef($iType)->GetTargetClass(); + } + + public function IsExternalField() {return true;} + public function GetKeyAttCode() {return $this->Get("extkey_attcode");} + public function GetExtAttCode() {return $this->Get("target_attcode");} + + public function GetKeyAttDef($iType = EXTKEY_RELATIVE) + { + switch($iType) + { + case EXTKEY_ABSOLUTE: + // see further + $oRemoteAtt = $this->GetExtAttDef(); + if ($oRemoteAtt->IsExternalField()) + { + return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE); + } + else if ($oRemoteAtt->IsExternalKey()) + { + return $oRemoteAtt; + } + return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter ! + + case EXTKEY_RELATIVE: + return MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); + + default: + throw new CoreException("Unexpected value for argument iType: '$iType'"); + } + } + + public function GetExtAttDef() + { + $oKeyAttDef = $this->GetKeyAttDef(); + $oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->Get("targetclass"), $this->Get("target_attcode")); + return $oExtAttDef; + } + + public function GetSQLExpr() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetSQLExpr(); + } + public function DBGetUsedFields() + { + // No field is used but the one defined in the field of the external class + // #@# so what ? + return array(); + } + + public function GetDefaultValue() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetDefaultValue(); + } + public function IsNullAllowed() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->IsNullAllowed(); + } + + public function GetBasicFilterOperators() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterLooseOperator(); + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); + } + + public function MakeRealValue($proposedValue) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->MakeRealValue($proposedValue); + } + public function RealValueToSQLValue($value) + { + // This one could be used in case of filtering only + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->RealValueToSQLValue($value); + } + public function SQLValueToRealValue($value) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->SQLValueToRealValue($value); + } + public function GetAsHTML($value) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsHTML($value); + } + public function GetAsXML($value) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsXML($value); + } + public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',') + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsCSV($value); + } +} + +/** + * Map a varchar column to an URL (formats the ouput in HMTL) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeURL extends AttributeString +{ + static protected function ListExpectedParams() + { + //return parent::ListExpectedParams(); + return array_merge(parent::ListExpectedParams(), array("target", "label")); + } + + public function GetType() {return "Url";} + public function GetTypeDesc() {return "Absolute or relative URL as a text string";} + public function GetEditClass() {return "String";} + + public function GetAsHTML($sValue) + { + $sTarget = $this->Get("target"); + if (empty($sTarget)) $sTarget = "_blank"; + $sLabel = Str::pure2html($sValue); + if (strlen($sLabel) > 40) + { + // Truncate the length to about 40 characters, by removing the middle + $sLabel = substr($sLabel, 0, 25).'...'.substr($sLabel, -15); + } + return "
    $sLabel"; + } +} + +?> diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php new file mode 100644 index 0000000000..491852a22a --- /dev/null +++ b/core/bulkchange.class.inc.php @@ -0,0 +1,439 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class BulkChangeException extends CoreException +{ +} + +/** + * CellChangeSpec + * A series of classes, keeping the information about a given cell: could it be changed or not (and why)? + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class CellChangeSpec +{ + protected $m_proposedValue; + + public function __construct($proposedValue) + { + $this->m_proposedValue = $proposedValue; + } + + public function GetValue() + { + return $this->m_proposedValue; + } + + abstract public function GetDescription(); +} + + +class CellChangeSpec_Void extends CellChangeSpec +{ + public function GetDescription() + { + return $this->GetValue(); + } +} + +class CellChangeSpec_Unchanged extends CellChangeSpec +{ + public function GetDescription() + { + return $this->GetValue()." (unchanged)"; + } +} + +class CellChangeSpec_Init extends CellChangeSpec +{ + public function GetDescription() + { + return $this->GetValue(); + } +} + +class CellChangeSpec_Modify extends CellChangeSpec +{ + protected $m_previousValue; + + public function __construct($proposedValue, $previousValue) + { + $this->m_previousValue = $previousValue; + parent::__construct($proposedValue); + } + + public function GetDescription() + { + return $this->GetValue()." (previous: ".$this->m_previousValue.")"; + } +} + +class CellChangeSpec_Issue extends CellChangeSpec_Modify +{ + protected $m_sReason; + + public function __construct($proposedValue, $previousValue, $sReason) + { + $this->m_sReason = $sReason; + parent::__construct($proposedValue, $previousValue); + } + + public function GetDescription() + { + if (is_null($this->m_proposedValue)) + { + return 'Could not be changed - reason: '.$this->m_sReason; + } + return 'Could not be changed to "'.$this->GetValue().'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')'; + } +} + + +/** + * RowStatus + * A series of classes, keeping the information about a given row: could it be changed or not (and why)? + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class RowStatus +{ + public function __construct() + { + } + + abstract public function GetDescription(); +} + +class RowStatus_NoChange extends RowStatus +{ + public function GetDescription() + { + return "unchanged"; + } +} + +class RowStatus_NewObj extends RowStatus +{ + protected $m_iObjKey; + + public function __construct($iObjKey = null) + { + $this->m_iObjKey = $iObjKey; + } + + public function GetDescription() + { + if (is_null($this->m_iObjKey)) + { + return "Create"; + } + else + { + return 'Created ('.$this->m_iObjKey.')'; + } + } +} + +class RowStatus_Modify extends RowStatus +{ + protected $m_iChanged; + + public function __construct($iChanged) + { + $this->m_iChanged = $iChanged; + } + + public function GetDescription() + { + return "update ".$this->m_iChanged." cols"; + } +} + +class RowStatus_Issue extends RowStatus +{ + protected $m_sReason; + + public function __construct($sReason) + { + $this->m_sReason = $sReason; + } + + public function GetDescription() + { + return 'Skipped - reason:'.$this->m_sReason; + } +} + + +/** + * BulkChange + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class BulkChange +{ + protected $m_sClass; + protected $m_aData; + // Note: hereafter, iCol maybe actually be any acceptable key (string) + // #@# todo: rename the variables to sColIndex + protected $m_aAttList; // attcode => iCol + protected $m_aReconcilKeys;// iCol => attcode + protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol; + + public function __construct($sClass, $aData, $aAttList, $aReconcilKeys, $aExtKeys) + { + $this->m_sClass = $sClass; + $this->m_aData = $aData; + $this->m_aAttList = $aAttList; + $this->m_aReconcilKeys = $aReconcilKeys; + $this->m_aExtKeys = $aExtKeys; + } + + protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors) + { + $aResults = array(); + $aErrors = array(); + + // External keys reconciliation + // + foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) + { + $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); + $oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass()); + foreach ($aKeyConfig as $sForeignAttCode => $iCol) + { + // The foreign attribute is one of our reconciliation key + $sFieldId = MakeExtFieldSelectValue($sAttCode, $sForeignAttCode); + $oReconFilter->AddCondition($sForeignAttCode, $aRowData[$iCol], '='); + $aResults["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + } + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch($oExtObjects->Count()) + { + case 0: + $aErrors[$sAttCode] = "Object not found"; + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found'); + break; + case 1: + // Do change the external key attribute + $oForeignObj = $oExtObjects->Fetch(); + $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); + + // Report it + if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) + { + if ($oTargetObj->IsNew()) + { + $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + } + else + { + $aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + } + } + else + { + $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oTargetObj->Get($sAttCode)); + } + break; + default: + $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), "Found ".$oExtObjects->Count()." matches"); + } + } + + // Set the object attributes + // + foreach ($this->m_aAttList as $sAttCode => $iCol) + { + if (!$oTargetObj->CheckValue($sAttCode, $aRowData[$iCol])) + { + $aErrors[$sAttCode] = "Unexpected value"; + } + else + { + $oTargetObj->Set($sAttCode, $aRowData[$iCol]); + } + } + + // Reporting on fields + // + $aChangedFields = $oTargetObj->ListChanges(); + foreach ($this->m_aAttList as $sAttCode => $iCol) + { + if (isset($aErrors[$sAttCode])) + { + $aResults["col$iCol"]= new CellChangeSpec_Issue($aRowData[$iCol], $oTargetObj->Get($sAttCode), $aErrors[$sAttCode]); + } + elseif (array_key_exists($sAttCode, $aChangedFields)) + { + $originalValue = $oTargetObj->GetOriginal($sAttCode); + if ($oTargetObj->IsNew()) + { + $aResults["col$iCol"]= new CellChangeSpec_Init($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue); + } + else + { + $aResults["col$iCol"]= new CellChangeSpec_Modify($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue); + } + } + else + { + // By default... nothing happens + $aResults["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]); + } + } + + // Checks + // + if (!$oTargetObj->CheckConsistency()) + { + $aErrors["GLOBAL"] = "Attributes not consistent with each others"; + } + return $aResults; + } + + + protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null) + { + $oTargetObj = MetaModel::NewObject($this->m_sClass); + $aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors); + + if (count($aErrors) > 0) + { + $sErrors = implode(', ', $aErrors); + $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)"); + return; + } + + // Check that any external key will have a value proposed + // Could be said once for all rows !!! + foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAtt) + { + if (!$oAtt->IsExternalKey()) continue; + } + + // Optionaly record the results + // + if ($oChange) + { + $newID = $oTargetObj->DBInsertTrackedNoReload($oChange); + $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($newID); + } + else + { + $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj(); + } + + } + + protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null) + { + $aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors); + + // Reporting + // + if (count($aErrors) > 0) + { + $sErrors = implode(', ', $aErrors); + $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)"); + return; + } + + $aChangedFields = $oTargetObj->ListChanges(); + if (count($aChangedFields) > 0) + { + $aResult[$iRow]["__STATUS__"] = new RowStatus_Modify(count($aChangedFields)); + + // Optionaly record the results + // + if ($oChange) + { + $oTargetObj->DBUpdateTracked($oChange); + } + } + else + { + $aResult[$iRow]["__STATUS__"] = new RowStatus_NoChange(); + } + } + + public function Process(CMDBChange $oChange = null) + { + // Note: $oChange can be null, in which case the aim is to check what would be done + + // Compute the results + // + $aResult = array(); + foreach($this->m_aData as $iRow => $aRowData) + { + $oReconciliationFilter = new CMDBSearchFilter($this->m_sClass); + foreach($this->m_aReconcilKeys as $sAttCode) + { + $iCol = $this->m_aAttList[$sAttCode]; + $oReconciliationFilter->AddCondition($sAttCode, $aRowData[$iCol], '='); + } + $oReconciliationSet = new CMDBObjectSet($oReconciliationFilter); + switch($oReconciliationSet->Count()) + { + case 0: + $this->CreateObject($aResult, $iRow, $aRowData, $oChange); + // $aResult[$iRow]["__STATUS__"]=> set in CreateObject + $aResult[$iRow]["__RECONCILIATION__"] = "Object not found"; + break; + case 1: + $oTargetObj = $oReconciliationSet->Fetch(); + $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); + $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetKey(); + // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject + break; + default: + foreach ($this->m_aAttList as $sAttCode => $iCol) + { + $aResult[$iRow]["col$iCol"]= $aRowData[$iCol]; + } + $aResult[$iRow]["__RECONCILIATION__"] = "Found ".$oReconciliationSet->Count()." matches"; + $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("ambiguous reconciliation"); + } + + // Whatever happened, do report the reconciliation values + foreach($this->m_aReconcilKeys as $sAttCode) + { + $iCol = $this->m_aAttList[$sAttCode]; + $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + } + } + return $aResult; + } +} + + +?> diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php new file mode 100644 index 0000000000..02d011a040 --- /dev/null +++ b/core/cmdbchange.class.inc.php @@ -0,0 +1,44 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class CMDBChange extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "change", + "description" => "Changes tracking", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "date", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_change", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"misc. info", "description"=>"caller's defined information", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("date"); + MetaModel::Init_AddFilterFromAttribute("userinfo"); + } + +} + +?> diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php new file mode 100644 index 0000000000..0ed8776ded --- /dev/null +++ b/core/cmdbchangeop.class.inc.php @@ -0,0 +1,167 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class CMDBChangeOp extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "change operation", + "description" => "Change operations tracking", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop", + "db_key_field" => "id", + "db_finalclass_field" => "optype", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("label"=>"change", "description"=>"change", "allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("label"=>"date", "description"=>"date and time of the change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("label"=>"user", "description"=>"who made this change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo"))); + MetaModel::Init_AddAttribute(new AttributeString("objclass", array("label"=>"object class", "description"=>"object class", "allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("objkey", array("label"=>"object id", "description"=>"object id", "allowed_values"=>null, "sql"=>"objkey", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("objclass"); + MetaModel::Init_AddFilterFromAttribute("objkey"); + MetaModel::Init_AddFilterFromAttribute("date"); + MetaModel::Init_AddFilterFromAttribute("userinfo"); + } +} + + + +/** + * Record the creation of an object + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CMDBChangeOpCreate extends CMDBChangeOp +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "object creation", + "description" => "Object creation tracking", + "key_type" => "", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_create", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_InheritFilters(); + } +} + + +/** + * Record the deletion of an object + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CMDBChangeOpDelete extends CMDBChangeOp +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "object deletion", + "description" => "Object deletion tracking", + "key_type" => "", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_delete", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_InheritFilters(); + } +} + + +/** + * Record the modification of an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CMDBChangeOpSetAttribute extends CMDBChangeOp +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "object change", + "description" => "Object properties change tracking", + "key_type" => "", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"Attribute", "description"=>"code of the modified property", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("label"=>"Previous value", "description"=>"previous value of the attribute", "allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("label"=>"New value", "description"=>"new value of the attribute", "allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("attcode"); + MetaModel::Init_AddFilterFromAttribute("oldvalue"); + MetaModel::Init_AddFilterFromAttribute("newvalue"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list + } +} + +?> diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php new file mode 100644 index 0000000000..d49f7280b5 --- /dev/null +++ b/core/cmdbobject.class.inc.php @@ -0,0 +1,432 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +require_once('coreexception.class.inc.php'); + +require_once('config.class.inc.php'); + +require_once('attributedef.class.inc.php'); +require_once('filterdef.class.inc.php'); +require_once('stimulus.class.inc.php'); +require_once('valuesetdef.class.inc.php'); +require_once('MyHelpers.class.inc.php'); + +require_once('expression.class.inc.php'); + +require_once('cmdbsource.class.inc.php'); +require_once('sqlquery.class.inc.php'); +require_once('oql/oqlquery.class.inc.php'); +require_once('oql/oqlexception.class.inc.php'); +require_once('oql/oql-parser.php'); +require_once('oql/oql-lexer.php'); +require_once('oql/oqlinterpreter.class.inc.php'); + +require_once('dbobject.class.php'); +require_once('dbobjectsearch.class.php'); +require_once('dbobjectset.class.php'); + +require_once('cmdbchange.class.inc.php'); +require_once('cmdbchangeop.class.inc.php'); + +require_once('csvparser.class.inc.php'); +require_once('bulkchange.class.inc.php'); + +require_once('userrights.class.inc.php'); + +// +// Error handling +// To be finalized... or removed ? +// +function cmdbErrorHandler($errno, $errstr, $errfile, $errline) +{ +// font-family: Courier-New, Courier, Arial, Helevtica; + $sErrorStyle = " + background-color: #ffaaaa; + color: #000000; + border: 1px dashed #000000; + padding: 0.25em; + margin-top: 1em; + "; + $sCallStackStyle = " + font-size: smaller; + background-color: #ffcccc; + color: #000000; + border: 1px dashed #000000; + padding: 0.25em; + margin-top: 1em; + "; + + switch ($errno) + { + case E_USER_ERROR: + case E_ERROR: + echo "
    \n"; + echo "Error [$errno] $errstr
    \n"; + echo "
    \n"; + MyHelpers::dump_callstack(1); + echo "
    \n"; + echo "Hereafter the biz model internals:
    \n"; + echo "
    \n";
    +		MetaModel::static_var_dump();
    +		echo "
    \n"; + echo "Aborting...
    \n"; + echo "
    \n"; + exit(1); + break; + case E_USER_WARNING: + case E_WARNING: + echo "
    \n"; + echo "Warning [$errno] $errstr
    \n"; + echo "
    \n"; + MyHelpers::dump_callstack(1); + echo "
    \n"; + echo "
    \n"; + break; + case E_USER_NOTICE: + case E_NOTICE: + echo "
    \n"; + echo "Notice [$errno] $errstr
    \n"; + echo "
    \n"; + MyHelpers::dump_callstack(1); + echo "
    \n"; + echo "
    \n"; + break; + default: + echo "Unknown error type: [$errno] $errstr
    \n"; + MyHelpers::dump_callstack(1); + break; + } +} + +error_reporting(E_ALL | E_STRICT); +//set_error_handler("cmdbErrorHandler"); + + + +// +// +// + + +/** + * A persistent object, which changes are accurately recorded + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class CMDBObject extends DBObject +{ + protected $m_datCreated; + protected $m_datUpdated; + protected static $m_oCurrChange = null; + + + private function RecordObjCreation(CMDBChange $oChange) + { + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + private function RecordObjDeletion(CMDBChange $oChange, $objkey) + { + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpDelete"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $objkey); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + private function RecordAttChanges(CMDBChange $oChange, array $aValues) + { + // $aValues is an array of $sAttCode => $value + // + foreach ($aValues as $sAttCode=> $value) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + if ($oAttDef->IsLinkSet()) continue; // #@# temporary + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttribute"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + $oMyChangeOp->Set("oldvalue", $this->GetOriginal($sAttCode)); + $oMyChangeOp->Set("newvalue", $value); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + } + + public function DBInsert() + { + if(!is_object(self::$m_oCurrChange)) + { + throw new CoreException("DBInsert() could not be used here, please use DBInsertTracked() instead"); + } + return $this->DBInsertTracked_Internal(); + } + + public function DBInsertTracked(CMDBChange $oChange) + { + self::$m_oCurrChange = $oChange; + $ret = $this->DBInsertTracked_Internal(); + self::$m_oCurrChange = null; + return $ret; + } + + public function DBInsertTrackedNoReload(CMDBChange $oChange) + { + self::$m_oCurrChange = $oChange; + $ret = $this->DBInsertTracked_Internal(true); + self::$m_oCurrChange = null; + return $ret; + } + + protected function DBInsertTracked_Internal($bDoNotReload = false) + { + if ($bDoNotReload) + { + $ret = parent::DBInsertNoReload(); + } + else + { + $ret = parent::DBInsert(); + } + $this->RecordObjCreation(self::$m_oCurrChange); + return $ret; + } + + public function DBClone($newKey = null) + { + if(!self::$m_oCurrChange) + { + throw new CoreException("DBClone() could not be used here, please use DBCloneTracked() instead"); + } + return $this->DBCloneTracked_Internal(); + } + + public function DBCloneTracked(CMDBChange $oChange, $newKey = null) + { + self::$m_oCurrChange = $oChange; + $this->DBCloneTracked_Internal($newKey); + self::$m_oCurrChange = null; + } + + protected function DBCloneTracked_Internal($newKey = null) + { + $newKey = parent::DBClone($newKey); + $oClone = MetaModel::GetObject(get_class($this), $newKey); + + $oClone->RecordObjCreation(self::$m_oCurrChange); + return $newKey; + } + + public function DBUpdate() + { + if(!self::$m_oCurrChange) + { + throw new CoreException("DBUpdate() could not be used here, please use DBUpdateTracked() instead"); + } + return $this->DBUpdateTracked_internal(); + } + + public function DBUpdateTracked(CMDBChange $oChange) + { + self::$m_oCurrChange = $oChange; + $this->DBUpdateTracked_Internal(); + self::$m_oCurrChange = null; + } + + protected function DBUpdateTracked_Internal() + { + // Copy the changes list before the update (the list should be reset afterwards) + $aChanges = $this->ListChanges(); + if (count($aChanges) == 0) + { + throw new CoreWarning("Attempting to update an unchanged object"); + return; + } + + $ret = parent::DBUpdate(); + $this->RecordAttChanges(self::$m_oCurrChange, $aChanges); + return $ret; + } + + public function DBDelete() + { + if(!self::$m_oCurrChange) + { + throw new CoreException("DBDelete() could not be used here, please use DBDeleteTracked() instead"); + } + return $this->DBDeleteTracked_Internal(); + } + + public function DBDeleteTracked(CMDBChange $oChange) + { + self::$m_oCurrChange = $oChange; + $this->DBDeleteTracked_Internal(); + self::$m_oCurrChange = null; + } + + protected function DBDeleteTracked_Internal() + { + $prevkey = $this->GetKey(); + $ret = parent::DBDelete(); + $this->RecordObjDeletion(self::$m_oCurrChange, $prevkey); + return $ret; + } + + public static function BulkDelete(DBObjectSearch $oFilter) + { + if(!self::$m_oCurrChange) + { + throw new CoreException("BulkDelete() could not be used here, please use BulkDeleteTracked() instead"); + } + return $this->BulkDeleteTracked_Internal($oFilter); + } + + public static function BulkDeleteTracked(CMDBChange $oChange, DBObjectSearch $oFilter) + { + self::$m_oCurrChange = $oChange; + $this->BulkDeleteTracked_Internal($oFilter); + self::$m_oCurrChange = null; + } + + protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter) + { + throw new CoreWarning("Change tracking not tested for bulk operations"); + + // Get the list of objects to delete (and record data before deleting the DB records) + $oObjSet = new CMDBObjectSet($oFilter); + $aObjAndKeys = array(); // array of pkey=>object + while ($oItem = $oObjSet->Fetch()) + { + $aObjAndKeys[$oItem->GetKey()] = $oItem; + } + $oObjSet->FreeResult(); + + // Delete in one single efficient query + $ret = parent::BulkDelete($oFilter); + // Record... in many queries !!! + foreach($aObjAndKeys as $prevkey=>$oItem) + { + $oItem->RecordObjDeletion(self::$m_oCurrChange, $prevkey); + } + return $ret; + } + + public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) + { + if(!self::$m_oCurrChange) + { + throw new CoreException("BulkUpdate() could not be used here, please use BulkUpdateTracked() instead"); + } + return $this->BulkUpdateTracked_Internal($oFilter, $aValues); + } + + public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues) + { + self::$m_oCurrChange = $oChange; + $this->BulkUpdateTracked_Internal($oFilter, $aValues); + self::$m_oCurrChange = null; + } + + protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues) + { + // $aValues is an array of $sAttCode => $value + + // Get the list of objects to update (and load it before doing the change) + $oObjSet = new CMDBObjectSet($oFilter); + $oObjSet->Load(); + + // Update in one single efficient query + $ret = parent::BulkUpdate($oFilter, $aValues); + + // Record... in many queries !!! + while ($oItem = $oObjSet->Fetch()) + { + $aChangedValues = $oItem->ListChangedValues($aValues); + $oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues); + } + return $ret; + } +} + + + +/** + * TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design! + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class CMDBObjectSet extends DBObjectSet +{ + // this is the public interface (?) + + // I have to define those constructors here... :-( + // just to get the right object class in return. + // I have to think again to those things: maybe it will work fine if a have a constructor define here (?) + + static public function FromScratch($sClass) + { + $oFilter = new CMDBSearchFilter($sClass); + $oRetSet = new CMDBObjectSet($oFilter); // THE ONLY DIFF IS HERE + // NOTE: THIS DOES NOT WORK IF m_bLoaded is private... + // BUT IT THAT CASE YOU DO NOT GET ANY ERROR !!!!! + $oRetSet->m_bLoaded = true; // no DB load + return $oRetSet; + } + + static public function FromArray($sClass, $aObjects) + { + $oFilter = new CMDBSearchFilter($sClass); + $oRetSet = new CMDBObjectSet($oFilter); // THE ONLY DIFF IS HERE + // NOTE: THIS DOES NOT WORK IF m_bLoaded is private... + // BUT IT THAT CASE YOU DO NOT GET ANY ERROR !!!!! + $oRetSet->m_bLoaded = true; // no DB load + $oRetSet->AddObjectArray($aObjects); + return $oRetSet; + } + +} + +/** + * TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design! + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class CMDBSearchFilter extends DBObjectSearch +{ + // this is the public interface (?) +} + + +?> diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php new file mode 100644 index 0000000000..2f76df24f4 --- /dev/null +++ b/core/cmdbsource.class.inc.php @@ -0,0 +1,420 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +require_once('MyHelpers.class.inc.php'); + +class MySQLException extends CoreException +{ + public function __construct($sIssue, $aContext) + { + $aContext['mysql_error'] = mysql_error(); + parent::__construct($sIssue, $aContext); + } +} + + +class CMDBSource +{ + protected static $m_sDBHost; + protected static $m_sDBUser; + protected static $m_sDBPwd; + protected static $m_sDBName; + protected static $m_resDBLink; + + public static function Init($sServer, $sUser, $sPwd, $sSource = '') + { + self::$m_sDBHost = $sServer; + self::$m_sDBUser = $sUser; + self::$m_sDBPwd = $sPwd; + self::$m_sDBName = $sSource; + if (!self::$m_resDBLink = @mysql_pconnect($sServer, $sUser, $sPwd)) + { + throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer, 'user'=>$sUser)); + } + if (!empty($sSource)) + { + if (!mysql_select_db($sSource, self::$m_resDBLink)) + { + throw new MySQLException('Could not select DB', array('db_name'=>$sSource)); + } + } + } + + public static function ListDB() + { + $aDBs = self::QueryToCol('SHOW DATABASES', 'Database'); + // Show Database does return the DB names in lower case + return $aDBs; + } + + public static function IsDB($sSource) + { + try + { + $aDBs = self::ListDB(); + foreach($aDBs as $sDBName) + { + // perform a case insensitive test because on Windows the table names become lowercase :-( + if (strtolower($sDBName) == strtolower($sSource)) return true; + } + return false; + } + catch(Exception $e) + { + // In case we don't have rights to enumerate the databases + // Let's try to connect directly + return @mysql_select_db($sSource, self::$m_resDBLink); + } + + } + + public static function GetDBVersion() + { + $aVersions = self::QueryToCol('SELECT Version() as version', 'version'); + return $aVersions[0]; + } + + public static function SelectDB($sSource) + { + if (!mysql_select_db($sSource, self::$m_resDBLink)) + { + throw new MySQLException('Could not select DB', array('db_name'=>$sSource)); + } + self::$m_sDBName = $sSource; + } + + public static function CreateDB($sSource) + { + self::Query("CREATE DATABASE `$sSource`"); + self::SelectDB($sSource); + } + + public static function DropDB($sDBToDrop = '') + { + if (empty($sDBToDrop)) + { + $sDBToDrop = self::$m_sDBName; + } + self::Query("DROP DATABASE `$sDBToDrop`"); + if ($sDBToDrop == self::$m_sDBName) + { + self::$m_sDBName = ''; + } + } + + public static function CreateTable($sQuery) + { + $res = self::Query($sQuery); + self::_TablesInfoCacheReset(); // reset the table info cache! + return $res; + } + + public static function DropTable($sTable) + { + $res = self::Query("DROP TABLE `$sTable`"); + self::_TablesInfoCacheReset(true); // reset the table info cache! + return $res; + } + + public static function DBHost() {return self::$m_sDBHost;} + public static function DBUser() {return self::$m_sDBUser;} + public static function DBPwd() {return self::$m_sDBPwd;} + public static function DBName() {return self::$m_sDBName;} + + public static function Quote($value, $bAlways = false, $cQuoteStyle = "'") + { + // Quote variable and protect against SQL injection attacks + // Code found in the PHP documentation: quote_smart($value) + + // bAlways should be set to true when the purpose is to create a IN clause, + // otherwise and if there is a mix of strings and numbers, the clause + // would always be false + + if (is_array($value)) + { + $aRes = array(); + foreach ($value as $key => $itemvalue) + { + $aRes[$key] = self::Quote($itemvalue, $bAlways, $cQuoteStyle); + } + return $aRes; + } + + // Stripslashes + if (get_magic_quotes_gpc()) + { + $value = stripslashes($value); + } + // Quote if not a number or a numeric string + if ($bAlways || !is_numeric($value)) + { + $value = $cQuoteStyle . mysql_real_escape_string($value, self::$m_resDBLink) . $cQuoteStyle; + } + return $value; + } + + public static function Query($sSQLQuery) + { + // Add info into the query as a comment, for easier error tracking + // + //if ($user_contact) $aTraceInf['userID'] = $user_contact->get_key(); + //$aTraceInf['file'] = __FILE__; + if ($_SERVER['REQUEST_URI']) $aTraceInf['requestURI'] = $_SERVER['REQUEST_URI']; + $i = 0; + foreach(debug_backtrace() as $aCallData) + { + $sClass = key_exists("class", $aCallData) ? $aCallData["class"]."::" : ""; + //if ($aCallData['function'] !== 'mysql_simple_query' AND $sClass !== 'r2_set::') + //{ + if ($i == 3) break; + $aTraceInf['function'.$i] = $sClass.$aCallData["function"]." on line ".$aCallData['line']; + $i++; + //} + } + // disabled until we need it really! + // $sSQLQuery = $sSQLQuery.MyHelpers::MakeSQLComment($aTraceInf); + + $mu_t1 = MyHelpers::getmicrotime(); + $result = mysql_query($sSQLQuery, self::$m_resDBLink); + if (!$result) + { + throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery)); + } + $mu_t2 = MyHelpers::getmicrotime(); + // #@# todo - query_trace($sSQLQuery, $mu_t2 - $mu_t1); + + return $result; + } + + public static function GetInsertId() + { + return mysql_insert_id(self::$m_resDBLink); + } + public static function InsertInto($sSQLQuery) + { + if (self::Query($sSQLQuery)) + { + return self::GetInsertId(); + } + return false; + } + + public static function QueryToArray($sSql) + { + $aData = array(); + $result = mysql_query($sSql, self::$m_resDBLink); + if (!$result) + { + throw new MySQLException('Failed to issue SQL query', array('query' => $sSql)); + } + while ($aRow = mysql_fetch_array($result, MYSQL_BOTH)) + { + $aData[] = $aRow; + } + mysql_free_result($result); + return $aData; + } + + public static function QueryToCol($sSql, $col) + { + $aColumn = array(); + $aData = self::QueryToArray($sSql); + foreach($aData as $aRow) + { + @$aColumn[] = $aRow[$col]; + } + return $aColumn; + } + + public static function ExplainQuery($sSql) + { + $aData = array(); + $result = mysql_query("EXPLAIN $sSql", self::$m_resDBLink); + if (!$result) + { + throw new MySQLException('Failed to issue SQL query', array('query' => $sSql)); + } + + $aNames = array(); + for ($i = 0; $i < mysql_num_fields($result) ; $i++) + { + $meta = mysql_fetch_field($result, $i); + if (!$meta) + { + throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i)); + } + else + { + $aNames[] = $meta->name; + } + } + + $aData[] = $aNames; + while ($aRow = mysql_fetch_array($result, MYSQL_ASSOC)) + { + $aData[] = $aRow; + } + mysql_free_result($result); + return $aData; + } + + public static function TestQuery($sSql) + { + $result = mysql_query("EXPLAIN $sSql", self::$m_resDBLink); + if (!$result) + { + return mysql_error(); + } + + mysql_free_result($result); + return ''; + } + + public static function NbRows($result) + { + return mysql_num_rows($result); + } + + public static function FetchArray($result) + { + return mysql_fetch_array($result, MYSQL_ASSOC); + } + + public static function Seek($result, $iRow) + { + return mysql_data_seek($result, $iRow); + } + + public static function FreeResult($result) + { + return mysql_free_result($result); + } + + public static function IsTable($sTable) + { + $aTableInfo = self::GetTableInfo($sTable); + return (!empty($aTableInfo)); + } + + public static function IsKey($sTable, $iKey) + { + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return false; + if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false; + $aFieldData = $aTableInfo["Fields"][$iKey]; + if (!array_key_exists("Key", $aFieldData)) return false; + return ($aFieldData["Key"] == "PRI"); + } + + public static function IsAutoIncrement($sTable, $sField) + { + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return false; + if (!array_key_exists($sField, $aTableInfo["Fields"])) return false; + $aFieldData = $aTableInfo["Fields"][$sField]; + if (!array_key_exists("Extra", $aFieldData)) return false; + //MyHelpers::debug_breakpoint($aFieldData); + return (strstr($aFieldData["Extra"], "auto_increment")); + } + + public static function IsField($sTable, $sField) + { + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return false; + if (!array_key_exists($sField, $aTableInfo["Fields"])) return false; + return true; + } + + public static function IsNullAllowed($sTable, $sField) + { + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return false; + if (!array_key_exists($sField, $aTableInfo["Fields"])) return false; + $aFieldData = $aTableInfo["Fields"][$sField]; + return (strtolower($aFieldData["Null"]) == "yes"); + } + + // Returns an array of (fieldname => array of field info) + public static function GetTableFieldsList($sTable) + { + assert(!empty($sTable)); + + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return array(); // #@# or an error ? + + return array_keys($aTableInfo["Fields"]); + } + + // Cache the information about existing tables, and their fields + private static $m_aTablesInfo = array(); + private static function _TablesInfoCacheReset() + { + self::$m_aTablesInfo = array(); + } + private static function _TableInfoCacheInit($sTableName) + { + if (isset(self::$m_aTablesInfo[strtolower($sTableName)]) + && (self::$m_aTablesInfo[strtolower($sTableName)] != null)) return; + + try + { + // Check if the table exists + $aFields = self::QueryToArray("SHOW COLUMNS FROM `$sTableName`"); + // Note: without backticks, you get an error with some table names (e.g. "group") + foreach ($aFields as $aFieldData) + { + $sFieldName = $aFieldData["Field"]; + self::$m_aTablesInfo[strtolower($sTableName)]["Fields"][$sFieldName] = + array + ( + "Name"=>$aFieldData["Field"], + "Type"=>$aFieldData["Type"], + "Null"=>$aFieldData["Null"], + "Key"=>$aFieldData["Key"], + "Default"=>$aFieldData["Default"], + "Extra"=>$aFieldData["Extra"] + ); + } + } + catch(MySQLException $e) + { + // Table does not exist + self::$m_aTablesInfo[strtolower($sTableName)] = null; + } + } + //public static function EnumTables() + //{ + // self::_TablesInfoCacheInit(); + // return array_keys(self::$m_aTablesInfo); + //} + public static function GetTableInfo($sTable) + { + self::_TableInfoCacheInit($sTable); + + // perform a case insensitive match because on Windows the table names become lowercase :-( + //foreach(self::$m_aTablesInfo as $sTableName => $aInfo) + //{ + // if (strtolower($sTableName) == strtolower($sTable)) + // { + // return $aInfo; + // } + //} + return self::$m_aTablesInfo[strtolower($sTable)]; + //return null; + } +} + + +?> diff --git a/core/config.class.inc.php b/core/config.class.inc.php new file mode 100644 index 0000000000..94bfe87175 --- /dev/null +++ b/core/config.class.inc.php @@ -0,0 +1,272 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class ConfigException extends CoreException +{ +} + +class Config +{ + //protected $m_bIsLoaded = false; + protected $m_sFile = ''; + + protected $m_aAppModules; + protected $m_aDataModels; + protected $m_aAddons; + + protected $m_sDBHost; + protected $m_sDBUser; + protected $m_sDBPwd; + protected $m_sDBName; + protected $m_sDBSubname; + + public function __construct($sConfigFile, $bLoadConfig = true) + { + $this->m_sFile = $sConfigFile; + $this->m_aAppModules = array(); + $this->m_aDataModels = array(); + $this->m_aAddons = array(); + + $this->m_sDBHost = ''; + $this->m_sDBUser = ''; + $this->m_sDBPwd = ''; + $this->m_sDBName = ''; + $this->m_sDBSubname = ''; + if ($bLoadConfig) + { + $this->Load($sConfigFile); + $this->Verify(); + } + } + + protected function CheckFile($sPurpose, $sFileName) + { + if (!file_exists($sFileName)) + { + throw new ConfigException("Could not find $sPurpose file", array('file' => $sFileName)); + } + } + + protected function Load($sConfigFile) + { + $this->CheckFile('configuration', $sConfigFile); + + $sConfigCode = trim(file_get_contents($sConfigFile)); + + // This does not work on several lines + // preg_match('/^<\\?php(.*)\\?'.'>$/', $sConfigCode, $aMatches)... + // So, I've implemented a solution suggested in the PHP doc (search for phpWrapper) + try + { + ob_start(); + $sCode = str_replace('<'.'?php','<'.'?', $sConfigCode); + eval('?'.'>'.trim($sCode).'<'.'?'); + $sNoise = trim(ob_get_contents()); + ob_end_clean(); + } + catch (Exception $e) + { + // well, never reach in case of parsing error :-( + // will be improved in PHP 6 ? + throw new ConfigException('Error in configuration file', array('file' => $sConfigFile, 'error' => $e->getMessage())); + } + if (strlen($sNoise) > 0) + { + // Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack) + throw new ConfigException('Syntax error in configuration file', array('file' => $sConfigFile, 'error' => $sNoise)); + } + + if (!isset($MySettings) || !is_array($MySettings)) + { + throw new ConfigException('Missing array in configuration file', array('file' => $sConfigFile, 'expected' => '$MySettings')); + } + if (!isset($MyModules) || !is_array($MyModules)) + { + throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules')); + } + if (!array_key_exists('application', $MyModules)) + { + throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'application\']')); + } + if (!array_key_exists('business', $MyModules)) + { + throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'business\']')); + } + if (!array_key_exists('addons', $MyModules)) + { + throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']')); + } + if (!array_key_exists('user rights', $MyModules['addons'])) + { + $MyModules['addons']['user rights'] = '../addons/userrights/userrightsnull.class.inc.php'; + } + $this->m_aAppModules = $MyModules['application']; + $this->m_aDataModels = $MyModules['business']; + $this->m_aAddons = $MyModules['addons']; + + $this->m_sDBHost = trim($MySettings['db_host']); + $this->m_sDBUser = trim($MySettings['db_user']); + $this->m_sDBPwd = trim($MySettings['db_pwd']); + $this->m_sDBName = trim($MySettings['db_name']); + $this->m_sDBSubname = trim($MySettings['db_subname']); + } + + protected function Verify() + { + foreach ($this->m_aAppModules as $sModule => $sToInclude) + { + $this->CheckFile('application module', $sToInclude); + } + foreach ($this->m_aDataModels as $sModule => $sToInclude) + { + $this->CheckFile('business model', $sToInclude); + } + foreach ($this->m_aAddons as $sModule => $sToInclude) + { + $this->CheckFile('addon module', $sToInclude); + } + } + + public function GetAppModules() + { + return $this->m_aAppModules; + } + + public function GetDataModels() + { + return $this->m_aDataModels; + } + + public function GetAddons() + { + return $this->m_aAddons; + } + + public function GetDBHost() + { + return $this->m_sDBHost; + } + + public function GetDBName() + { + return $this->m_sDBName; + } + + public function GetDBSubname() + { + return $this->m_sDBSubname; + } + + public function GetDBUser() + { + return $this->m_sDBUser; + } + + public function GetDBPwd() + { + return $this->m_sDBPwd; + } + + public function SetDBHost($sDBHost) + { + $this->m_sDBHost = $sDBHost; + } + + public function SetDBName($sDBName) + { + $this->m_sDBName = $sDBName; + } + + public function SetDBSubname($sDBSubName) + { + $this->m_sDBSubname = $sDBSubName; + } + + public function SetDBUser($sUser) + { + $this->m_sDBUser = $sUser; + } + + public function SetDBPwd($sPwd) + { + $this->m_sDBPwd = $sPwd; + } + public function FileIsWritable() + { + return is_writable($this->m_sFile); + } + + /** + * Write the configuration to a file (php format) that can be reloaded later + * By default write to the same file that was specified when constructing the object + * @param $sFileName string Name of the file to write to (emtpy to write to the same file) + * @return boolean True otherwise throws an Exception + */ + public function WriteToFile($sFileName = '') + { + if (empty($sFileName)) + { + $sFileName = $this->m_sFile; + } + $hFile = @fopen($sFileName, 'w'); + if ($hFile !== false) + { + fwrite($hFile, " '{$this->m_sDBHost}',\n"); + fwrite($hFile, "\t'db_user' => '{$this->m_sDBUser}',\n"); + fwrite($hFile, "\t'db_pwd' => '{$this->m_sDBPwd}',\n"); + fwrite($hFile, "\t'db_name' => '{$this->m_sDBName}',\n"); + fwrite($hFile, "\t'db_subname' => '{$this->m_sDBSubname}',\n"); + fwrite($hFile, ");\n"); + + fwrite($hFile, "\n/**\n"); + fwrite($hFile, " *\n"); + fwrite($hFile, " * Data model modules to be loaded. Names should be specified as absolute paths\n"); + fwrite($hFile, " *\n"); + fwrite($hFile, " */\n"); + fwrite($hFile, "\$MyModules = array(\n"); + fwrite($hFile, "\t'application' => array (\n"); + fwrite($hFile, "\t\t'../application/menunode.class.inc.php',\n"); + fwrite($hFile, "\t\t'../application/audit.rule.class.inc.php',\n"); + fwrite($hFile, "\t\t// to be continued...\n"); + fwrite($hFile, "\t),\n"); + fwrite($hFile, "\t'business' => array (\n"); + fwrite($hFile, "\t\t'../business/itop.business.class.inc.php'\n"); + fwrite($hFile, "\t\t// to be continued...\n"); + fwrite($hFile, "\t),\n"); + fwrite($hFile, "\t'addons' => array (\n"); + fwrite($hFile, "\t\t'user rights' => '../addons/userrights/userrightsmatrix.class.inc.php',\n"); + fwrite($hFile, "\t\t// other modules to come later\n"); + fwrite($hFile, "\t)\n"); + fwrite($hFile, ");\n"); + fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting ! + return fclose($hFile); + } + else + { + throw new ConfigException("Could not write to configuration file", array('file' => $sFileName)); + } + } +} +?> diff --git a/core/coreexception.class.inc.php b/core/coreexception.class.inc.php new file mode 100644 index 0000000000..eb5ae8b9da --- /dev/null +++ b/core/coreexception.class.inc.php @@ -0,0 +1,63 @@ +m_sIssue = $sIssue; + $this->m_sImpact = $sImpact; + $this->m_aContextData = $aContextData ? $aContextData : array(); + + $sMessage = $sIssue; + if (!empty($sImpact)) $sMessage .= "($sImpact)"; + if (count($this->m_aContextData) > 0) + { + $sMessage .= ": "; + $aContextItems = array(); + foreach($this->m_aContextData as $sKey => $value) + { + if (is_array($value)) + { + $aPairs = array(); + foreach($value as $key => $val) + { + if (is_array($val)) + { + $aPairs[$key] = '('.implode(', ', $val).')'; + } + else + { + $aPairs[$key] = $val; + } + } + $sValue = '{'.implode('; ', $aPairs).'}'; + } + else + { + $sValue = $value; + } + $aContextItems[] = "$sKey = $sValue"; + } + $sMessage .= implode(', ', $aContextItems); + } + parent::__construct($sMessage, 0); + } + + public function getHtmlDesc($sHighlightHtmlBegin = '', $sHighlightHtmlEnd = '') + { + return $this->getMessage(); + } + + public function getTraceAsHtml() + { + $aBackTrace = $this->getTrace(); + return MyHelpers::get_callstack_html(0, $this->getTrace()); + // return "
    \n".$this->getTraceAsString()."
    \n"; + } +} + +class CoreWarning extends CoreException +{ +} + +?> diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php new file mode 100644 index 0000000000..c1e49f10ff --- /dev/null +++ b/core/csvparser.class.inc.php @@ -0,0 +1,192 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class CSVParserException extends CoreException +{ +} + + + + +/** + * CSVParser + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CSVParser +{ + private $m_sCSVData; + private $m_sSep; + private $m_iSkip; + + public function __construct($sTxt) + { + $this->m_sCSVData = $sTxt; + } + + public function SetSeparator($sSep) + { + $this->m_sSep = $sSep; + } + public function GetSeparator() + { + return $this->m_sSep; + } + + public function SetSkipLines($iSkip) + { + $this->m_iSkip = $iSkip; + } + public function GetSkipLines() + { + return $this->m_iSkip; + } + + public function GuessSeparator() + { + // Note: skip the first line anyway + + $aKnownSeps = array(';', ',', "\t"); // Use double quote for special chars!!! + $aStatsBySeparator = array(); + foreach ($aKnownSeps as $sSep) + { + $aStatsBySeparator[$sSep] = array(); + } + + foreach(split("\n", $this->m_sCSVData) as $sLine) + { + $sLine = trim($sLine); + if (substr($sLine, 0, 1) == '#') continue; + if (empty($sLine)) continue; + + $aLineCharsCount = count_chars($sLine, 0); + foreach ($aKnownSeps as $sSep) + { + $aStatsBySeparator[$sSep][] = $aLineCharsCount[ord($sSep)]; + } + } + + // Default to ',' + $this->SetSeparator(","); + + foreach ($aKnownSeps as $sSep) + { + // Note: this function is NOT available :-( + // stats_variance($aStatsBySeparator[$sSep]); + $iMin = min($aStatsBySeparator[$sSep]); + $iMax = max($aStatsBySeparator[$sSep]); + if (($iMin == $iMax) && ($iMax > 0)) + { + $this->SetSeparator($sSep); + break; + } + } + return $this->GetSeparator(); + } + + public function GuessSkipLines() + { + // Take the FIRST -valuable- LINE ONLY + // If there is a number, then for sure this is not a header line + // Otherwise, we may consider that there is one line to skip + foreach(split("\n", $this->m_sCSVData) as $sLine) + { + $sLine = trim($sLine); + if (substr($sLine, 0, 1) == '#') continue; + if (empty($sLine)) continue; + + foreach (split($this->m_sSep, $sLine) as $value) + { + if (is_numeric($value)) + { + $this->SetSkipLines(0); + return 0; + } + } + $this->SetSkipLines(1); + return 1; + } + } + + function ToArray($aFieldMap = null, $iMax = 0) + { + // $aFieldMap is an array of col_index=>col_name + // $iMax is to limit the count of rows computed + $aRes = array(); + + $iCount = 0; + $iSkipped = 0; + foreach(split("\n", $this->m_sCSVData) as $sLine) + { + $sLine = trim($sLine); + if (substr($sLine, 0, 1) == '#') continue; + if (empty($sLine)) continue; + + if ($iSkipped < $this->m_iSkip) + { + $iSkipped++; + continue; + } + + foreach (split($this->m_sSep, $sLine) as $iCol=>$sValue) + { + if (is_array($aFieldMap)) $sColRef = $aFieldMap[$iCol]; + else $sColRef = $iCol; + $aRes[$iCount][$sColRef] = $sValue; + } + + $iCount++; + if (($iMax > 0) && ($iCount >= $iMax)) break; + } + return $aRes; + } + + public function ListFields() + { + // Take the first valuable line + foreach(explode("\n", $this->m_sCSVData) as $sLine) + { + $sLine = trim($sLine); + if (substr($sLine, 0, 1) == '#') continue; + if (empty($sLine)) continue; + // We've got the first valuable line, that's it! + break; + } + + $aRet = array(); + foreach (explode($this->m_sSep, $sLine) as $iCol=>$value) + { + if ($this->m_iSkip == 0) + { + // No header to help us + $sLabel = "field $iCol"; + } + else + { + $sLabel = "$value"; + } + $aRet[] = $sLabel; + } + return $aRet; + } +} + + +?> diff --git a/core/data.generator.class.inc.php b/core/data.generator.class.inc.php new file mode 100644 index 0000000000..f9d284e3e2 --- /dev/null +++ b/core/data.generator.class.inc.php @@ -0,0 +1,362 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +/** + * Data Generator helper class + * + * This class is useful to generate a lot of sample data that look consistent + * for a given organization in order to simulate a real CMDB + */ +class cmdbDataGenerator +{ + protected $m_sOrganizationKey; + protected $m_sOrganizationCode; + protected $m_sOrganizationName; + protected $m_OrganizationDomains; + + /** + * Constructor + */ + public function __construct($sOrganizationId = "") + { + global $aCompanies, $aCompaniesCode; + if ($sOrganizationId == '') + { + // No organization provided, pick a random and unused one from our predefined list + $retries = 5*count($aCompanies); + while ( ($retries > 0) && !isset($this->m_sOrganizationCode)) // Stupid algorithm, but I'm too lazy to do something bulletproof tonight + { + $index = rand(0, count($aCompanies) - 1); + if (!$this->OrganizationExists($aCompanies[$index]['code'])) + { + $this->m_sOrganizationCode = $aCompanies[$index]['code']; + $this->m_sOrganizationName = $aCompanies[$index]['name']; + $this->m_OrganizationDomains = $aCompanies[$index]['domain']; + } + $retries--; + } + } + else + { + // A code has been provided, let's take the information we need from the organization itself + $this->m_sOrganizationId = $sOrganizationId; + $oOrg = $this->GetOrganization($sOrganizationId); + if ($oOrg == null) + { + echo "Unable to find the organization '$sOrganisationCode' in the database... can not add objects into this organization.
    \n"; + exit(); + } + $this->m_sOrganizationCode = $oOrg->Get('code'); + $this->m_sOrganizationName = $oOrg->Get('name'); + if (!isset($aCompaniesCode[$this->m_sOrganizationCode]['domain'])) + { + // Generate some probable domain names for this organization + $this->m_OrganizationDomains = array(strtolower($this->m_sOrganizationCode).".com", strtolower($this->m_sOrganizationCode).".org", strtolower($this->m_sOrganizationCode)."corp.net",); + } + else + { + // Pick the domain names for this organization from the predefined list + $this->m_OrganizationDomains = $aCompaniesCode[$this->m_sOrganizationCode]['domain']; + } + } + + if (!isset($this->m_sOrganizationCode)) + { + echo "Unable to find an organization code which is not already used... can not create a new organization. Enhance the list of fake organizations (\$aCompanies in data_sample.inc.php).
    \n"; + exit(); + } + } + + /** + * Get the current organization id used by the generator + * + * @return string The organization id + */ + public function GetOrganizationId() + { + return $this->m_sOrganizationId; + } + + /** + * Get the current organization id used by the generator + * + * @param string The organization id + * @return none + */ + public function SetOrganizationId($sId) + { + $this->m_sOrganizationId = $sId; + } + + /** + * Get the current organization code used by the generator + * + * @return string The organization code + */ + public function GetOrganizationCode() + { + return $this->m_sOrganizationCode; + } + + /** + * Get the current organization name used by the generator + * + * @return string The organization name + */ + function GetOrganizationName() + { + return $this->m_sOrganizationName; + } + + /** + * Get a pseudo random first name taken from a (big) prefedined list + * + * @return string A random first name + */ + function GenerateFirstName() + { + global $aFirstNames; + return $aFirstNames[rand(0, count($aFirstNames) - 1)]; + } + + /** + * Get a pseudo random last name taken from a (big) prefedined list + * + * @return string A random last name + */ + function GenerateLastName() + { + global $aNames; + return $aNames[rand(0, count($aNames) - 1)]; + } + + /** + * Get a pseudo random country name taken from a prefedined list + * + * @return string A random city name + */ + function GenerateCountryName() + { + global $aCountries; + return $aCountries[rand(0, count($aCountries) - 1)]; + } + + /** + * Get a pseudo random city name taken from a (big) prefedined list + * + * @return string A random city name + */ + function GenerateCityName() + { + global $aCities; + return $aCities[rand(0, count($aCities) - 1)]; + } + + /** + * Get a pseudo random email address made of the first name, last name and organization's domain + * + * @return string A random email address + */ + function GenerateEmail($sFirstName, $sLastName) + { + if (rand(1, 20) > 18) + { + // some people (let's say 5~10%) have an irregular email address + $sEmail = strtolower($this->CleanForEmail($sLastName))."@".strtolower($this->GenerateDomain()); + } + else + { + $sEmail = strtolower($this->CleanForEmail($sFirstName)).".".strtolower($this->CleanForEmail($sLastName))."@".strtolower($this->GenerateDomain()); + } + return $sEmail; + } + + /** + * Generate (pseudo) random strings that follow a given pattern + * + * The template is made of any number of 'parts' separated by pipes '|' + * Each part is either: + * - domain() => returns a domain name for the current organization + * - enum(aaa,bb,c,dddd) => returns randomly one of aaa,bb,c or dddd with the same + * probability of occurence. If you want to change the probability you can repeat some values + * i.e enum(most probable,most probable,most probable,most probable,most probable,rare) + * - number(xxx-yyy) => a random number between xxx and yyy (bounds included) + * note that if the first number (xxx) begins with a zero, then the result will zero padded + * to the same number of digits as xxx. + * All other 'part' that does not follow one of the above mentioned pattern is returned as is + * + * Example: GenerateString("enum(sw,rtr,gw)|number(00-99)|.|domain()") + * will produce strings like "sw01.netcmdb.com" or "rtr45.itop.org" + * + * @param string $sTemplate The template used for generating the string + * @return string The generated pseudo random the string + */ + function GenerateString($sTemplate) + { + $sResult = ""; + $aParts = split("\|", $sTemplate); + foreach($aParts as $sPart) + { + if (preg_match("/domain\(\)/", $sPart, $aMatches)) + { + $sResult .= strtolower($this->GenerateDomain()); + } + elseif (preg_match("/enum\((.+)\)/", $sPart, $aMatches)) + { + $sEnumValues = $aMatches[1]; + $aEnumValues = split(",", $sEnumValues); + $sResult .= $aEnumValues[rand(0, count($aEnumValues) - 1)]; + } + elseif (preg_match("/number\((\d+)-(\d+)\)/", $sPart, $aMatches)) + { + $sStartNumber = $aMatches[1]; + if ($sStartNumber[0] == '0') + { + // number must be zero padded + $sFormat = "%0".strlen($sStartNumber)."d"; + } + else + { + $sFormat = "%d"; + } + $sEndNumber = $aMatches[2]; + $sResult .= sprintf($sFormat, rand($sStartNumber, $sEndNumber)); + } + else + { + $sResult .= $sPart; + } + } + return $sResult; + } + + /** + * Generate a foreign key by picking a random element of the given class in a set limited by the given search criteria + * + * Example: GenerateKey("bizLocation", array('org_id', $oGenerator->GetOrganizationId()); + * will produce the foreign key of a Location object picked at random in the same organization + * + * @param string $sClass The name of the class to search for + * @param string $aFilterCriteria A hash array of filterCOde => FilterValue (the strict operator '=' is used ) + * @return mixed The key to an object of the given class, or null if none are found + */ + function GenerateKey($sClass, $aFilterCriteria) + { + $retKey = null; + $oFilter = new CMDBSearchFilter($sClass); + foreach($aFilterCriteria as $sFilterCode => $filterValue) + { + $oFilter->AddCondition($sFilterCode, $filterValue, '='); + } + $oSet = new CMDBObjectSet($oFilter); + if ($oSet->Count() > 0) + { + $max_count = $index = rand(1, $oSet->Count()); + do + { + $oObj = $oSet->Fetch(); + $index--; + } + while($index > 0); + + if (!is_object($oObj)) + { + echo "
    ";
    +				echo "ERROR: non empty set, but invalid object picked! class='$sClass'\n";
    +				echo "Index chosen: $max_count\n";
    +				echo "The set is supposed to contain ".$oSet->Count()." object(s)\n";
    +				echo "Filter criteria:\n";
    +				print_r($aFilterCriteria);
    +				echo "
    "; + } + else + { + $retKey = $oObj->GetKey(); + } + } + return $retKey; + } + /////////////////////////////////////////////////////////////////////////////// + // + // Protected methods + // + /////////////////////////////////////////////////////////////////////////////// + + /** + * Generate a (random) domain name consistent with the organization name & code + * + * The values are pulled from a (limited) predefined list. Note that a given + * organization may have several domain names, so the result may be random + * + * @return string A domain name (like netcnmdb.com) + */ + protected function GenerateDomain() + { + if (is_array($this->m_OrganizationDomains)) + { + $sDomain = $this->m_OrganizationDomains[rand(0, count($this->m_OrganizationDomains)-1)]; + } + else + { + $sDomain = $this->m_OrganizationDomains; + } + return $sDomain; + } + + /** + * Strips accented characters from a string in order to produce a suitable email address + * + * @param string The text string to clean + * @return string The cleanified text string + */ + protected function CleanForEmail($sText) + { + return str_replace(array("'", "", "", "", "", "", "", "", "", ""), array("", "e", "e", "e", "c", "a", "a", "n", "oe", "ae"), $sText); + } + + /** + * Check if an organization with the given code already exists in the database + * + * @param string $sCode The code to look for + * @return boolean true if the given organization exists, false otherwise + */ + protected function OrganizationExists($sCode) + { + $oFilter = new CMDBSearchFilter('bizOrganization'); + $oFilter->AddCondition('code', $sCode, '='); + $oSet = new CMDBObjectSet($oFilter); + return ($oSet->Count() > 0); + } + + /** + * Search for an organization with the given code in the database + * + * @param string $Id The organization Id to look for + * @return cmdbOrganization the organization if it exists, null otherwise + */ + protected function GetOrganization($sId) + { + $oOrg = null; + $oFilter = new CMDBSearchFilter('bizOrganization'); + $oFilter->AddCondition('pkey', $sId, '='); + $oSet = new CMDBObjectSet($oFilter); + if ($oSet->Count() > 0) + { + $oOrg = $oSet->Fetch(); // Let's take the first one found + } + return $oOrg; + } +} +?> diff --git a/core/dbobject.class.php b/core/dbobject.class.php new file mode 100644 index 0000000000..c9d69a0b90 --- /dev/null +++ b/core/dbobject.class.php @@ -0,0 +1,831 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +require_once('metamodel.class.php'); + + +/** + * A persistent object, as defined by the metamodel + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class DBObject +{ + private static $m_aMemoryObjectsByClass = array(); + + private $m_bIsInDB = false; // true IIF the object is mapped to a DB record + private $m_iKey = null; + private $m_aCurrValues = array(); + private $m_aOrigValues = array(); + + private $m_bFullyLoaded = false; // Compound objects can be partially loaded + private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode + + // Use the MetaModel::NewObject to build an object (do we have to force it?) + public function __construct($aRow = null) + { + if (!empty($aRow)) + { + $this->FromRow($aRow); + $this->m_bFullyLoaded = $this->IsFullyLoaded(); + return; + } + // Creation of brand new object + // + + $this->m_iKey = self::GetNextTempId(get_class($this)); + + // set default values + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue(); + $this->m_aOrigValues[$sAttCode] = null; + if ($oAttDef->IsExternalField()) + { + // This field has to be read from the DB + $this->m_aLoadedAtt[$sAttCode] = false; + } + else + { + // No need to trigger a reload for that attribute + // Let's consider it as being already fully loaded + $this->m_aLoadedAtt[$sAttCode] = true; + } + } + } + + public function IsNew() + { + return (!$this->m_bIsInDB); + } + + // Returns an Id for memory objects + static protected function GetNextTempId($sClass) + { + if (!array_key_exists($sClass, self::$m_aMemoryObjectsByClass)) + { + self::$m_aMemoryObjectsByClass[$sClass] = 0; + } + self::$m_aMemoryObjectsByClass[$sClass]++; + return (- self::$m_aMemoryObjectsByClass[$sClass]); + } + + public function __toString() + { + $sRet = ''; + $sClass = get_class($this); + $sRootClass = MetaModel::GetRootClass($sClass); + $iPKey = $this->GetKey(); + $sRet .= "$sClass::$iPKey
    \n"; + $sRet .= "
      \n"; + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + $sRet .= "
    • ".$oAttDef->GetLabel()." = ".$this->GetAsHtml($sAttCode)."
    • \n"; + } + $sRet .= "
    "; + return $sRet; + } + + // Restore initial values... mmmm, to be discussed + public function DBRevert() + { + $this->Reload(); + } + + protected function IsFullyLoaded() + { + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + @$bIsLoaded = $this->m_aLoadedAtt[$sAttCode]; + if ($bIsLoaded !== true) + { + return false; + } + } + return true; + } + + protected function Reload() + { + assert($this->m_bIsInDB); + $aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey); + if (empty($aRow)) + { + throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey); + } + $this->FromRow($aRow); + + // Process linked set attributes + // + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + if (!$oAttDef->IsLinkSet()) continue; + + // Load the link information + $sLinkClass = $oAttDef->GetLinkedClass(); + $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); + + // The class to target is not the current class, because if this is a derived class, + // it may differ from the target class, then things start to become confusing + $oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe); + $sMyClass = $oRemoteExtKeyAtt->GetTargetClass(); + + $oMyselfSearch = new DBObjectSearch($sMyClass); + $oMyselfSearch->AddCondition('id', $this->m_iKey, '='); + + $oLinkSearch = new DBObjectSearch($sLinkClass); + $oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe); + $oLinks = new DBObjectSet($oLinkSearch); + + $this->m_aCurrValues[$sAttCode] = $oLinks; + $this->m_aOrigValues[$sAttCode] = clone $this->m_aCurrValues[$sAttCode]; + $this->m_aLoadedAtt[$sAttCode] = true; + } + + $this->m_bFullyLoaded = true; + } + + protected function FromRow($aRow) + { + $this->m_iKey = null; + $this->m_bIsInDB = true; + $this->m_aCurrValues = array(); + $this->m_aOrigValues = array(); + $this->m_aLoadedAtt = array(); + + // Get the key + // + $sKeyField = "id"; + if (!array_key_exists($sKeyField, $aRow)) + { + // #@# Bug ? + throw new CoreException("Missing key for class '".get_class($this)."'"); + } + else + { + $iPKey = $aRow[$sKeyField]; + if (!self::IsValidPKey($iPKey)) + { + throw new CoreWarning("An object id must be an integer value ($iPKey)"); + } + $this->m_iKey = $iPKey; + } + + // Build the object from an array of "attCode"=>"value") + // + $bFullyLoaded = true; // ... set to false if any attribute is not found + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + // Say something, whatever the type of attribute + $this->m_aLoadedAtt[$sAttCode] = false; + + // Skip links (could not be loaded by the mean of this query) + if ($oAttDef->IsLinkSet()) continue; + + if (array_key_exists($sAttCode, $aRow)) + { + $sValue = $oAttDef->SQLValueToRealValue($aRow[$sAttCode]); + $this->m_aCurrValues[$sAttCode] = $sValue; + $this->m_aOrigValues[$sAttCode] = $sValue; + $this->m_aLoadedAtt[$sAttCode] = true; + } + else + { + // This attribute was expected and not found in the query columns + $bFullyLoaded = false; + } + } + return $bFullyLoaded; + } + + public function Set($sAttCode, $value) + { + if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this)))) + { + throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this)); + } + $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + if ($this->m_bIsInDB && !$this->m_bFullyLoaded) + { + // First time Set is called... ensure that the object gets fully loaded + // Otherwise we would lose the values on a further Reload + // + consistency does not make sense ! + $this->Reload(); + } + if($oAttDef->IsScalar() && !$oAttDef->IsNullAllowed() && is_null($value)) + { + throw new CoreWarning("null not allowed for attribute '$sAttCode', setting default value"); + $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue(); + return; + } + if ($oAttDef->IsExternalKey() && is_object($value)) + { + // Setting an external key with a whole object (instead of just an ID) + // let's initialize also the external fields that depend on it + // (useful when building objects in memory and not from a query) + if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass()))) + { + throw new CoreWarning("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored"); + $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue(); + } + else + { + $this->m_aCurrValues[$sAttCode] = $value->GetKey(); + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef) + { + if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode)) + { + $this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode()); + } + } + } + return; + } + if(!$oAttDef->IsScalar() && !is_object($value)) + { + throw new CoreWarning("scalar not allowed for attribute '$sAttCode', setting default value (empty list)"); + $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue(); + return; + } + if($oAttDef->IsLinkSet()) + { + if((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet')) + { + throw new CoreWarning("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)"); + $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue(); + return; + } + + $oObjectSet = $value; + $sSetClass = $oObjectSet->GetClass(); + $sLinkClass = $oAttDef->GetLinkedClass(); + // not working fine :-( if (!is_subclass_of($sSetClass, $sLinkClass)) + if ($sSetClass != $sLinkClass) + { + throw new CoreWarning("expecting a set of '$sLinkClass' objects (found a set of '$sSetClass'), setting default value (empty list)"); + $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue(); + return; + } + } + $this->m_aCurrValues[$sAttCode] = $oAttDef->MakeRealValue($value); + } + + public function Get($sAttCode) + { + if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this)))) + { + throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this)); + } + if ($this->m_bIsInDB && !$this->m_aLoadedAtt[$sAttCode]) + { + // #@# non-scalar attributes.... handle that differentely + $this->Reload(); + } + $this->ComputeFields(); + return $this->m_aCurrValues[$sAttCode]; + } + + public function GetOriginal($sAttCode) + { + if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this)))) + { + throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this)); + } + return $this->m_aOrigValues[$sAttCode]; + } + + public function ComputeFields() + { + if (is_callable(array($this, 'ComputeValues'))) + { + // First check that we are not currently computing the fields + // (yes, we need to do some things like Set/Get to compute the fields which will in turn trigger the update...) + foreach (debug_backtrace() as $aCallInfo) + { + if (!array_key_exists("class", $aCallInfo)) continue; + if ($aCallInfo["class"] != get_class($this)) continue; + if ($aCallInfo["function"] != "ComputeValues") continue; + return; //skip! + } + + $this->ComputeValues(); + } + } + + public function GetAsHTML($sAttCode) + { + $sClass = get_class($this); + $oAtt = MetaModel::GetAttributeDef($sClass, $sAttCode); + + $aExtKeyFriends = MetaModel::GetExtKeyFriends($sClass, $sAttCode); + if (count($aExtKeyFriends) > 0) + { + // This attribute is an ext key (in this class or in another class) + // The corresponding value is an id of the remote object + // Let's try to use the corresponding external fields for a sexy display + + $aAvailableFields = array(); + foreach ($aExtKeyFriends as $sDispAttCode => $oExtField) + { + $aAvailableFields[$oExtField->GetExtAttCode()] = $oExtField->GetAsHTML($this->Get($oExtField->GetCode())); + } + + $sTargetClass = $oAtt->GetTargetClass(EXTKEY_ABSOLUTE); + $aMakeHLink = array(get_class($this), 'MakeHyperLink'); + if (is_callable($aMakeHLink)) + { + return call_user_func($aMakeHLink, $sTargetClass, $this->Get($sAttCode), $aAvailableFields); + } + else + { + return $this->Get($sAttCode); + } + } + + // That's a standard attribute (might be an ext field or a direct field, etc.) + return $oAtt->GetAsHTML($this->Get($sAttCode)); + } + + public function GetAsXML($sAttCode) + { + $oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + return $oAtt->GetAsXML($this->Get($sAttCode)); + } + + public function GetAsCSV($sAttCode, $sSeparator = ';', $sSepEscape = ',') + { + $oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sSepEscape); + } + + // could be in the metamodel ? + public static function IsValidPKey($value) + { + return ((string)$value === (string)(int)$value); + } + + public function GetKey() + { + return $this->m_iKey; + } + public function SetKey($iNewKey) + { + if (!self::IsValidPKey($iNewKey)) + { + throw new CoreException("An object id must be an integer value ($iNewKey)"); + } + + if ($this->m_bIsInDB && !empty($this->m_iKey) && ($this->m_iKey != $iNewKey)) + { + throw new CoreException("Changing the key ({$this->m_iKey} to $iNewKey) on an object (class {".get_class($this).") wich already exists in the Database"); + } + $this->m_iKey = $iNewKey; + } + + public function GetName() + { + $sNameAttCode = MetaModel::GetNameAttributeCode(get_class($this)); + if (empty($sNameAttCode)) + { + return $this->m_iKey; + } + else + { + return $this->Get($sNameAttCode); + } + } + + public function GetState() + { + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (empty($sStateAttCode)) + { + return ''; + } + else + { + $aStates = MetaModel::EnumStates(get_class($this)); + return $aStates[$this->Get($sStateAttCode)]['label']; + } + } + + /** + * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) + * for the given attribute in the current state of the object + * @param string $sAttCode The code of the attribute + * @return integer Flags: the binary combination of the flags applicable to this attribute + */ + public function GetAttributeFlags($sAttCode) + { + $iFlags = 0; // By default (if no life cycle) no flag at all + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (!empty($sStateAttCode)) + { + $iFlags = MetaModel::GetAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode); + } + return $iFlags; + } + + // check if the given (or current) value is suitable for the attribute + public function CheckValue($sAttCode, $value = null) + { + if (!is_null($value)) + { + $toCheck = $value; + } + else + { + $toCheck = $this->Get($sAttCode); + } + + $oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + if ($oAtt->IsExternalKey()) + { + if (!$oAtt->IsNullAllowed() || ($toCheck != 0) ) + { + try + { + $oTargetObj = MetaModel::GetObject($oAtt->GetTargetClass(), $toCheck); + return true; + } + catch (CoreException $e) + { + return false; + } + } + } + return true; + } + + // check attributes together + public function CheckConsistency() + { + return true; + } + + // check if it is allowed to record the new object into the database + // a displayable error is returned + // Note: checks the values and consistency + public function CheckToInsert() + { + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + if (!$this->CheckValue($sAttCode)) return false; + } + if (!$this->CheckConsistency()) return false; + return true; + } + + // check if it is allowed to update the existing object into the database + // a displayable error is returned + // Note: checks the values and consistency + public function CheckToUpdate() + { + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + if (!$this->CheckValue($sAttCode)) return false; + } + if (!$this->CheckConsistency()) return false; + return true; + } + + // check if it is allowed to delete the existing object from the database + // a displayable error is returned + public function CheckToDelete() + { + return true; + } + + protected function ListChangedValues(array $aProposal) + { + $aDelta = array(); + foreach ($aProposal as $sAtt => $proposedValue) + { + if (!array_key_exists($sAtt, $this->m_aOrigValues) || ($this->m_aOrigValues[$sAtt] != $proposedValue)) + { + $aDelta[$sAtt] = $proposedValue; + } + } + return $aDelta; + } + + // List the attributes that have been changed + // Returns an array of attname => currentvalue + public function ListChanges() + { + return $this->ListChangedValues($this->m_aCurrValues); + } + + // used both by insert/update + private function DBWriteLinks() + { + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + if (!$oAttDef->IsLinkSet()) continue; + + $oLinks = $this->Get($sAttCode); + $oLinks->Rewind(); + while ($oLinkedObject = $oLinks->Fetch()) + { + $oLinkedObject->Set($oAttDef->GetExtKeyToMe(), $this->m_iKey); + $oLinkedObject->DBWrite(); + } + + // Delete the objects that were initialy present and disappeared from the list + // (if any) + $oOriginalSet = $this->m_aOrigValues[$sAttCode]; + if ($oOriginalSet != null) + { + $aOriginalList = $oOriginalSet->ToArray(); + $aNewSet = $oLinks->ToArray(); + $aToDelete = array_diff($aOriginalList, $aNewSet); + foreach ($aToDelete as $iKey => $oObject) + { + $oObject->DBDelete(); + } + } + } + } + + private function DBInsertSingleTable($sTableClass) + { + $sClass = get_class($this); + + // fields in first array, values in the second + $aFieldsToWrite = array(); + $aValuesToWrite = array(); + + if (!empty($this->m_iKey) && ($this->m_iKey >= 0)) + { + // Add it to the list of fields to write + $aFieldsToWrite[] = MetaModel::DBGetKey($sTableClass); + $aValuesToWrite[] = CMDBSource::Quote($this->m_iKey); + } + + foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode)) continue; + if ($oAttDef->IsDirectField()) + { + $aFieldsToWrite[] = $oAttDef->GetSQLExpr(); + $aValuesToWrite[] = CMDBSource::Quote($oAttDef->RealValueToSQLValue($this->m_aCurrValues[$sAttCode])); + } + } + + if (count($aValuesToWrite) == 0) return false; + + $sTable = MetaModel::DBGetTable($sTableClass); + $sInsertSQL = "INSERT INTO $sTable (".join(",", $aFieldsToWrite).") VALUES (".join(", ", $aValuesToWrite).")"; + + $iNewKey = CMDBSource::InsertInto($sInsertSQL); + // Note that it is possible to have a key defined here, and the autoincrement expected, this is acceptable in a non root class + if (empty($this->m_iKey)) + { + // Take the autonumber + $this->m_iKey = $iNewKey; + } + return $this->m_iKey; + } + + // Insert of record for the new object into the database + // Returns the key of the newly created object + public function DBInsertNoReload() + { + if ($this->m_bIsInDB) + { + throw new CoreException("The object already exists into the Database, you may want to use the clone function"); + } + + $sClass = get_class($this); + $sRootClass = MetaModel::GetRootClass($sClass); + + // Ensure the update of the values (we are accessing the data directly) + $this->ComputeFields(); + + if ($this->m_iKey < 0) + { + // This was a temporary "memory" key: discard it so that DBInsertSingleTable will not try to use it! + $this->m_iKey = null; + } + + // If not automatically computed, then check that the key is given by the caller + if (!MetaModel::IsAutoIncrementKey($sRootClass)) + { + if (empty($this->m_iKey)) + { + throw new CoreWarning("Missing key for the object to write - This class is supposed to have a user defined key, not an autonumber"); + } + } + + // First query built upon on the root class, because the ID must be created first + $this->m_iKey = $this->DBInsertSingleTable($sRootClass); + + // Then do the leaf class, if different from the root class + if ($sClass != $sRootClass) + { + $this->DBInsertSingleTable($sClass); + } + + // Then do the other classes + foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass) + { + if ($sParentClass == $sRootClass) continue; + if (MetaModel::DBGetTable($sParentClass) == "") continue; + $this->DBInsertSingleTable($sParentClass); + } + + $this->DBWriteLinks(); + + // Reload to update the external attributes + $this->m_bIsInDB = true; + return $this->m_iKey; + } + + public function DBInsert() + { + $this->DBInsertNoReload(); + $this->Reload(); + return $this->m_iKey; + } + + // Creates a copy of the current object into the database + // Returns the id of the newly created object + public function DBClone($iNewKey = null) + { + $this->m_bIsInDB = false; + $this->m_iKey = $iNewKey; + return $this->DBInsert(); + } + + // Update a record + public function DBUpdate() + { + if (!$this->m_bIsInDB) + { + throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead"); + } + $aChanges = $this->ListChanges(); + if (count($aChanges) == 0) + { + throw new CoreWarning("Attempting to update an unchanged object"); + return; + } + $bHasANewExternalKeyValue = false; + foreach($aChanges as $sAttCode => $valuecurr) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true; + if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]); + } + + // Update scalar attributes + if (count($aChanges) != 0) + { + $oFilter = new DBObjectSearch(get_class($this)); + $oFilter->AddCondition('id', $this->m_iKey, '='); + + $sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges); + CMDBSource::Query($sSQL); + } + + $this->DBWriteLinks(); + + // Reload to get the external attributes + if ($bHasANewExternalKeyValue) $this->Reload(); + + return $this->m_iKey; + } + + // Make the current changes persistent - clever wrapper for Insert or Update + public function DBWrite() + { + if ($this->m_bIsInDB) + { + return $this->DBUpdate(); + } + else + { + return $this->DBInsert(); + } + } + + // Delete a record + public function DBDelete() + { + $oFilter = new DBObjectSearch(get_class($this)); + $oFilter->AddCondition('id', $this->m_iKey, '='); + + $sSQL = MetaModel::MakeDeleteQuery($oFilter); + CMDBSource::Query($sSQL); + + $this->m_bIsInDB = false; + $this->m_iKey = null; + } + + public function EnumTransitions() + { + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (empty($sStateAttCode)) return array(); + + $sState = $this->Get(MetaModel::GetStateAttributeCode(get_class($this))); + return MetaModel::EnumTransitions(get_class($this), $sState); + } + + public function ApplyStimulus($sStimulusCode) + { + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (empty($sStateAttCode)) return false; + + MyHelpers::CheckKeyInArray('object lifecycle stimulus', $sStimulusCode, MetaModel::EnumStimuli(get_class($this))); + + $aStateTransitions = $this->EnumTransitions(); + $aTransitionDef = $aStateTransitions[$sStimulusCode]; + + // Change the state before proceeding to the actions, this is necessary because an action might + // trigger another stimuli (alternative: push the stimuli into a queue) + $this->Set($sStateAttCode, $aTransitionDef['target_state']); + + // $aTransitionDef is an + // array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD + + $bSuccess = true; + foreach ($aTransitionDef['actions'] as $sActionHandler) + { + // std PHP spec + $aActionCallSpec = array($this, $sActionHandler); + + if (!is_callable($aActionCallSpec)) + { + throw new CoreException("Unable to call action: ".get_class($this)."::$sActionHandler"); + return; + } + $bRet = call_user_func($aActionCallSpec, $sStimulusCode); + // if one call fails, the whole is considered as failed + if (!$bRet) $bSuccess = false; + } + + return $bSuccess; + } + + // Return an empty set for the parent of all + public static function GetRelationQueries($sRelCode) + { + return array(); + } + + public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array()) + { + foreach (MetaModel::EnumRelationQueries(get_class($this), $sRelCode) as $sDummy => $aQueryInfo) + { + MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo["sQuery"]); + $sQuery = $aQueryInfo["sQuery"]; + $bPropagate = $aQueryInfo["bPropagate"]; + $iDistance = $aQueryInfo["iDistance"]; + + $iDepth = $bPropagate ? $iMaxDepth - 1 : 0; + + $oFlt = DBObjectSearch::FromSibusQL($sQuery, array(), $this); + $oObjSet = new DBObjectSet($oFlt); + while ($oObj = $oObjSet->Fetch()) + { + $sRootClass = MetaModel::GetRootClass(get_class($oObj)); + $sObjKey = $oObj->GetKey(); + if (array_key_exists($sRootClass, $aResults)) + { + if (array_key_exists($sObjKey, $aResults[$sRootClass])) + { + continue; // already visited, skip + } + } + + $aResults[$sRootClass][$sObjKey] = $oObj; + if ($iDepth > 0) + { + $oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults); + } + } + } + return $aResults; + } +} + + +?> diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php new file mode 100644 index 0000000000..86d6e057ae --- /dev/null +++ b/core/dbobjectsearch.class.php @@ -0,0 +1,973 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + + +/** + * Sibusql - value set start + * @package iTopORM + * @info zis is private + */ +define('VS_START', '{'); +/** + * Sibusql - value set end + * @package iTopORM + */ +define('VS_END', '}'); + + +define('SIBUSQLPARAMREGEXP', "/\\$\\[(.*)\\:(.*)\\:(.*)\\]/U"); +define('SIBUSQLTHISREGEXP', "/this\\.(.*)/U"); + + +/** + * Define filters for a given class of objects (formerly named "filter") + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @mytagrom youpi + * @since 1.0 + * @version 1.1.1.1 $ + */ +class DBObjectSearch +{ + private $m_sClass; + private $m_sClassAlias; + private $m_aClasses; // queried classes (alias => class name) + private $m_oSearchCondition; + private $m_aFullText; + private $m_aPointingTo; + private $m_aReferencedBy; + private $m_aRelatedTo; + + public function __construct($sClass, $sClassAlias = '') + { + if (empty($sClassAlias)) $sClassAlias = $sClass; + assert('is_string($sClass)'); + assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference + // => idee d'un assert avec call stack (autre utilisation = echec sur query SQL) + if (empty($sClassAlias)) $sClassAlias = $sClass; + $this->m_sClass = $sClass; + $this->m_sClassAlias = $sClassAlias; + $this->m_aClasses = array($sClassAlias => $sClass); + $this->m_oSearchCondition = new TrueExpression; + $this->m_aFullText = array(); + $this->m_aPointingTo = array(); + $this->m_aReferencedBy = array(); + $this->m_aRelatedTo = array(); + } + + public function IsAny() + { + // #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false; + if (count($this->m_aFullText) > 0) return false; + if (count($this->m_aPointingTo) > 0) return false; + if (count($this->m_aReferencedBy) > 0) return false; + if (count($this->m_aRelatedTo) > 0) return false; + return true; + } + + public function Describe() + { + // To replace __Describe + } + + public function DescribeConditionPointTo($sExtKeyAttCode) + { + if (!isset($this->m_aPointingTo[$sExtKeyAttCode])) return ""; + $oFilter = $this->m_aPointingTo[$sExtKeyAttCode]; + if ($oFilter->IsAny()) return ""; + $oAtt = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode); + return $oAtt->GetLabel()." having ({$oFilter->DescribeConditions()})"; + } + + public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode) + { + if (!isset($this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode])) return ""; + $oFilter = $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]; + if ($oFilter->IsAny()) return ""; + $oAtt = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode); + return "being ".$oAtt->GetLabel()." for ".$sForeignClass."s in ({$oFilter->DescribeConditions()})"; + } + + public function DescribeConditionRelTo($aRelInfo) + { + $oFilter = $aRelInfo['flt']; + $sRelCode = $aRelInfo['relcode']; + $iMaxDepth = $aRelInfo['maxdepth']; + return "related ($sRelCode... peut mieux faire !, $iMaxDepth dig depth) to a {$oFilter->GetClass()} ({$oFilter->DescribeConditions()})"; + } + + public function DescribeConditions() + { + $aConditions = array(); + + $aCondFT = array(); + foreach($this->m_aFullText as $sFullText) + { + $aCondFT[] = " contain word(s) '$sFullText'"; + } + if (count($aCondFT) > 0) + { + $aConditions[] = "which ".implode(" and ", $aCondFT); + } + + // #@# todo - review textual description of the JOIN and search condition (is that still feasible?) + $aConditions[] = $this->RenderCondition(); + + $aCondPoint = array(); + foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter) + { + if ($oFilter->IsAny()) continue; + $aCondPoint[] = $this->DescribeConditionPointTo($sExtKeyAttCode); + } + if (count($aCondPoint) > 0) + { + $aConditions[] = implode(" and ", $aCondPoint); + } + + $aCondReferred= array(); + foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) + { + foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter) + { + if ($oForeignFilter->IsAny()) continue; + $aCondReferred[] = $this->DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode); + } + } + foreach ($this->m_aRelatedTo as $aRelInfo) + { + $aCondReferred[] = $this->DescribeConditionRelTo($aRelInfo); + } + if (count($aCondReferred) > 0) + { + $aConditions[] = implode(" and ", $aCondReferred); + } + + return implode(" and ", $aConditions); + } + + public function __DescribeHTML() + { + $sConditionDesc = $this->DescribeConditions(); + if (!empty($sConditionDesc)) + { + return "Objects of class '$this->m_sClass', $sConditionDesc"; + } + return "Any object of class '$this->m_sClass'"; + } + + protected function TransferConditionExpression($oFilter, $aTranslation) + { + $oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false); + $this->AddConditionExpression($oTranslated); + } + + public function ResetCondition() + { + $this->m_oSearchCondition = new TrueExpression(); + // ? is that usefull/enough, do I need to rebuild the list after the subqueries ? + // $this->m_aClasses = array($this->m_sClassAlias => $this->m_sClass); + } + + public function AddConditionExpression($oExpression) + { + $this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression); + } + + public function AddCondition($sFilterCode, $value, $sOpCode = null) + { + // #@# backward compatibility for pkey/id + if (strtolower(trim($sFilterCode)) == 'pkey') $sFilterCode = 'id'; +// #@# todo - obsolete smoothly, first send exceptions +// throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL())); + + MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->m_sClass)); + $oFilterDef = MetaModel::GetClassFilterDef($this->m_sClass, $sFilterCode); + + if (empty($sOpCode)) + { + $sOpCode = $oFilterDef->GetLooseOperator(); + } + MyHelpers::CheckKeyInArray('operator', $sOpCode, $oFilterDef->GetOperators()); + + // Preserve backward compatibility - quick n'dirty way to change that API semantic + // + $oField = new FieldExpression($sFilterCode, $this->m_sClassAlias); + switch($sOpCode) + { + case 'SameDay': + case 'SameMonth': + case 'SameYear': + case 'Today': + case '>|': + case '<|': + case '=|': + throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode)); + break; + + case "IN": + if (!is_array($value)) $value = array($value); + $sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')'; + $sOQLCondition = $oField->Render()." IN $sListExpr"; + break; + + case "NOTIN": + if (!is_array($value)) $value = array($value); + $sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')'; + $sOQLCondition = $oField->Render()." NOT IN $sListExpr"; + break; + + case 'Contains': + $oRightExpr = new ScalarExpression("%$value%"); + $sOperator = 'LIKE'; + break; + + case 'Begins with': + $oRightExpr = new ScalarExpression("$value%"); + $sOperator = 'LIKE'; + break; + + case 'Finishes with': + $oRightExpr = new ScalarExpression("%$value"); + $sOperator = 'LIKE'; + break; + + default: + $oRightExpr = new ScalarExpression($value); + $sOperator = $sOpCode; + } + + switch($sOpCode) + { + case "IN": + case "NOTIN": + $oNewCondition = Expression::FromOQL($sOQLCondition); + break; + + case 'Contains': + case 'Begins with': + case 'Finishes with': + default: + $oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr); + } + + $this->AddConditionExpression($oNewCondition); + } + + public function AddCondition_FullText($sFullText) + { + $this->m_aFullText[] = $sFullText; + } + + protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation) + { + $sOrigAlias = $this->m_sClassAlias; + if (array_key_exists($sOrigAlias, $aClassAliases)) + { + $this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->m_sClass); + // Translate the condition expression with the new alias + $aAliasTranslation[$sOrigAlias]['*'] = $this->m_sClassAlias; + } + + foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter) + { + $oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation); + } + + foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) + { + foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter) + { + $oForeignFilter->AddToNameSpace($aClassAliases, $aAliasTranslation); + } + } + } + + public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode) + { + $aAliasTranslation = array(); + $res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation); + $this->TransferConditionExpression($oFilter, $aAliasTranslation); + return $res; + } + + protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation) + { + if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode)) + { + throw new CoreWarning("The attribute code '$sExtKeyAttCode' is not an external key of the class '{$this->GetClass()}' - the condition will be ignored"); + } + $oAttExtKey = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode); + if(!MetaModel::IsSameFamilyBranch($oFilter->GetClass(), $oAttExtKey->GetTargetClass())) + { + throw new CoreException("The specified filter (pointing to {$oFilter->GetClass()}) is not compatible with the key '{$this->GetClass()}::$sExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}"); + } + + if (array_key_exists($sExtKeyAttCode, $this->m_aPointingTo)) + { + $this->m_aPointingTo[$sExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation); + } + else + { + $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation); + + // #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!! + // $oNewFilter = clone $oFilter; + // $oNewFilter->ResetCondition(); + + $this->m_aPointingTo[$sExtKeyAttCode] = $oFilter; + } + } + + public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode) + { + $aAliasTranslation = array(); + $res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation); + $this->TransferConditionExpression($oFilter, $aAliasTranslation); + return $res; + } + + protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation) + { + $sForeignClass = $oFilter->GetClass(); + $sForeignClassAlias = $oFilter->GetClassAlias(); + if (!MetaModel::IsValidKeyAttCode($sForeignClass, $sForeignExtKeyAttCode)) + { + throw new CoreException("The attribute code '$sForeignExtKeyAttCode' is not an external key of the class '{$sForeignClass}' - the condition will be ignored"); + } + $oAttExtKey = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode); + if(!MetaModel::IsSameFamilyBranch($this->GetClass(), $oAttExtKey->GetTargetClass())) + { + throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}"); + } + if (array_key_exists($sForeignClass, $this->m_aReferencedBy) && array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sForeignClass])) + { + $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation); + } + else + { + $oFilter->AddToNamespace($aClassAliases, $aAliasTranslation); + + // #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!! + //$oNewFilter = clone $oFilter; + //$oNewFilter->ResetCondition(); + + $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]= $oFilter; + } + } + + public function AddCondition_LinkedTo(DBObjectSearch $oLinkFilter, $sExtKeyAttCodeToMe, $sExtKeyAttCodeTarget, DBObjectSearch $oFilterTarget) + { + $oLinkFilterFinal = clone $oLinkFilter; + $oLinkFilterFinal->AddCondition_PointingTo($sExtKeyAttCodeToMe); + + $this->AddCondition_ReferencedBy($oLinkFilterFinal, $sExtKeyAttCodeToMe); + } + + public function AddCondition_RelatedTo(DBObjectSearch $oFilter, $sRelCode, $iMaxDepth) + { + MyHelpers::CheckValueInArray('relation code', $sRelCode, MetaModel::EnumRelations()); + $this->m_aRelatedTo[] = array('flt'=>$oFilter, 'relcode'=>$sRelCode, 'maxdepth'=>$iMaxDepth); + } + + public function MergeWith($oFilter) + { + $aAliasTranslation = array(); + $res = $this->MergeWith_InNamespace($oFilter, $this->m_aClasses, $aAliasTranslation); + $this->TransferConditionExpression($oFilter, $aAliasTranslation); + return $res; + } + + protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation) + { + if ($this->GetClass() != $oFilter->GetClass()) + { + throw new CoreException("Attempting to merge a filter of class '{$this->GetClass()}' with a filter of class '{$oFilter->GetClass()}'"); + } + + // Translate search condition into our aliasing scheme + $aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias(); + + $this->m_aFullText = array_merge($this->m_aFullText, $oFilter->m_aFullText); + $this->m_aRelatedTo = array_merge($this->m_aRelatedTo, $oFilter->m_aRelatedTo); + + foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$oExtFilter) + { + $this->AddCondition_PointingTo_InNamespace($oExtFilter, $sExtKeyAttCode, $aClassAliases, $aAliasTranslation); + } + foreach($oFilter->m_aReferencedBy as $sForeignClass => $aReferences) + { + foreach($aReferences as $sForeignExtKeyAttCode => $oForeignFilter) + { + $this->AddCondition_ReferencedBy_InNamespace($oForeignFilter, $sForeignExtKeyAttCode, $aClassAliases, $aAliasTranslation); + } + } + } + + + public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];} + public function GetClasses() {return $this->m_aClasses;} + + public function GetClass() {return $this->m_sClass;} + public function GetClassAlias() {return $this->m_sClassAlias;} + public function GetCriteria() {return $this->m_oSearchCondition;} + public function GetCriteria_FullText() {return $this->m_aFullText;} + public function GetCriteria_PointingTo($sKeyAttCode = "") + { + if (empty($sKeyAttCode)) + { + return $this->m_aPointingTo; + } + if (!array_key_exists($sKeyAttCode, $this->m_aPointingTo)) return null; + return $this->m_aPointingTo[$sKeyAttCode]; + } + public function GetCriteria_ReferencedBy($sRemoteClass = "", $sForeignExtKeyAttCode = "") + { + if (empty($sRemoteClass)) + { + return $this->m_aReferencedBy; + } + if (!array_key_exists($sRemoteClass, $this->m_aReferencedBy)) return null; + if (empty($sForeignExtKeyAttCode)) + { + return $this->m_aReferencedBy[$sRemoteClass]; + } + if (!array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sRemoteClass])) return null; + return $this->m_aReferencedBy[$sRemoteClass][$sForeignExtKeyAttCode]; + } + public function GetCriteria_RelatedTo() + { + return $this->m_aRelatedTo; + } + + public function RenderCondition() + { + return $this->m_oSearchCondition->Render(); + } + + public function serialize() + { + // Efficient but resulting in long strings: + // -> return (base64_encode(serialize($this))); + + $sValue = $this->GetClass()."\n"; + $sValue .= $this->GetClassAlias()."\n"; + + foreach($this->m_aClasses as $sClassAlias => $sClass) + { + // A stands for "Aliases" + $sValue .= "A:$sClassAlias:$sClass\n"; + } + foreach($this->m_aFullText as $sFullText) + { + // F stands for "Full text" + $sValue .= "F:".$sFullText."\n"; + } + $sValue .= "C:".$this->m_oSearchCondition->serialize()."\n"; + + foreach($this->m_aPointingTo as $sExtKey=>$oFilter) + { + // P stands for "Pointing to" + $sValue .= "P:".$sExtKey.":".$oFilter->serialize()."\n"; + } + foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) + { + foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter) + { + // R stands for "Referenced by" + $sValue .= "R:".$sForeignExtKeyAttCode.":".$oForeignFilter->serialize()."\n"; + } + } + foreach($this->m_aRelatedTo as $aRelatedTo) + { + $oFilter = $aRelatedTo['flt']; + $sRelCode = $aRelatedTo['relcode']; + $iMaxDepth = $aRelatedTo['maxdepth']; + + $sValue .= "T:".$oFilter->serialize().":$sRelCode:$iMaxDepth"; + } + return base64_encode($sValue); + } + + static public function unserialize($sValue) + { + // See comment above... + // -> return (unserialize(base64_decode($sValue))); + + $sClearText = base64_decode($sValue); + $aValues = split("\n", $sClearText); + $i = 0; + $sClass = $aValues[$i++]; + $sClassAlias = $aValues[$i++]; + $oFilter = new DBObjectSearch($sClass, $sClassAlias); + while($i < count($aValues) && !empty($aValues[$i])) + { + $aCondition = split(":", $aValues[$i++]); + switch ($aCondition[0]) + { + case "A": + $oFilter->m_aClasses[$aCondition[1]] = $aCondition[2]; + break; + case "F": + $oFilter->AddCondition_FullText($aCondition[1]); + break; + case "C": + $oFilter->m_oSearchCondition = Expression::unserialize($aCondition[1]); + break; + case "P": + //$oAtt = DBObject::GetAttributeDef($sClass, $aCondition[1]); + //$sRemoteClass = $oAtt->GetTargetClass(); + $oSubFilter = self::unserialize($aCondition[2]); + $sExtKeyAttCode = $aCondition[1]; + $oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode); + break; + case "R": + $oRemoteFilter = self::unserialize($aCondition[2]); + $sExtKeyAttCodeToMe = $aCondition[1]; + $oFilter->AddCondition_ReferencedBy($oRemoteFilter, $sExtKeyAttCodeToMe); + break; + case "T": + $oSubFilter = self::unserialize($aCondition[1]); + $sRelCode = $aCondition[2]; + $iMaxDepth = $aCondition[3]; + $oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth); + default: + throw new CoreException("invalid filter definition (cannot unserialize the data, clear text = '$sClearText')"); + } + } + return $oFilter; + } + + // SImple BUt Structured Query Languag - SubuSQL + // + static private function Value2Expression($value) + { + $sRet = $value; + if (is_array($value)) + { + $sRet = VS_START.implode(', ', $value).VS_END; + } + else if (!is_numeric($value)) + { + $sRet = "'".addslashes($value)."'"; + } + return $sRet; + } + static private function Expression2Value($sExpr) + { + $retValue = $sExpr; + if ((substr($sExpr, 0, 1) == "'") && (substr($sExpr, -1, 1) == "'")) + { + $sNoQuotes = substr($sExpr, 1, -1); + return stripslashes($sNoQuotes); + } + if ((substr($sExpr, 0, 1) == VS_START) && (substr($sExpr, -1, 1) == VS_END)) + { + $sNoBracket = substr($sExpr, 1, -1); + $aRetValue = array(); + foreach (explode(",", $sNoBracket) as $sItem) + { + $aRetValue[] = self::Expression2Value(trim($sItem)); + } + return $aRetValue; + } + return $retValue; + } + + public function ToOQL() + { + $sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias(); + $sRes .= $this->ToOQL_Joins(); + $sRes .= " WHERE ".$this->m_oSearchCondition->Render(); + return $sRes; + } + + protected function ToOQL_Joins() + { + $sRes = ''; + foreach($this->m_aPointingTo as $sExtKey=>$oFilter) + { + $sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.' = '.$oFilter->GetClassAlias().'.id'; + $sRes .= $oFilter->ToOQL_Joins(); + } + foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences) + { + foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter) + { + $sRes .= ' JOIN '.$oForeignFilter->GetClass().' AS '.$oForeignFilter->GetClassAlias().' ON '.$oForeignFilter->GetClassAlias().'.'.$sForeignExtKeyAttCode.' = '.$this->GetClassAlias().'.id'; + $sRes .= $oForeignFilter->ToOQL_Joins(); + } + } + return $sRes; + } + + public function ToSibusQL() + { + return "NONONO"; + } + + static private function privProcessParams($sQuery, array $aParams, $oDbObject) + { + $iPlaceHoldersCount = preg_match_all(SIBUSQLPARAMREGEXP, $sQuery, $aMatches, PREG_SET_ORDER); + if ($iPlaceHoldersCount > 0) + { + foreach($aMatches as $aMatch) + { + $sStringToSearch = $aMatch[0]; + $sParameterName = $aMatch[1]; + $sDefaultValue = $aMatch[2]; + $sDescription = $aMatch[3]; + + $sValue = $sDefaultValue; + if (array_key_exists($sParameterName, $aParams)) + { + $sValue = $aParams[$sParameterName]; + unset($aParams[$sParameterName]); + } + else if (is_object($oDbObject)) + { + if (strpos($sParameterName, "this.") === 0) + { + $sAttCode = substr($sParameterName, strlen("this.")); + if ($sAttCode == 'id') + { + $sValue = $oDbObject->GetKey(); + } + else if ($sAttCode == 'class') + { + $sValue = get_class($oDbObject); + } + else if (MetaModel::IsValidAttCode(get_class($oDbObject), $sAttCode)) + { + $sValue = $oDbObject->Get($sAttCode); + } + } + } + $sQuery = str_replace($sStringToSearch, $sValue, $sQuery); + } + } + if (count($aParams) > 0) + { + throw new CoreException("Unused parameter(s) for this SibusQL expression: (".implode(', ', array_keys($aParams)).")"); + } + return $sQuery; + } + + static public function ListSibusQLParams($sQuery) + { + $aRet = array(); + $iPlaceHoldersCount = preg_match_all(SIBUSQLPARAMREGEXP, $sQuery, $aMatches, PREG_SET_ORDER); + if ($iPlaceHoldersCount > 0) + { + foreach($aMatches as $aMatch) + { + $sStringToSearch = $aMatch[0]; + $sParameterName = $aMatch[1]; + $sDefaultValue = $aMatch[2]; + $sDescription = $aMatch[3]; + $aRet[$sParameterName]["description"] = $sDescription; + $aRet[$sParameterName]["default"] = $sDefaultValue; + } + } + return $aRet; + } + + protected function OQLExpressionToCondition($sQuery, $oExpression, $aClassAliases) + { + if ($oExpression instanceof BinaryOqlExpression) + { + $sOperator = $oExpression->GetOperator(); + $oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases); + $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); + return new BinaryExpression($oLeft, $sOperator, $oRight); + } + elseif ($oExpression instanceof FieldOqlExpression) + { + $sClassAlias = $oExpression->GetParent(); + $sFltCode = $oExpression->GetName(); + if (empty($sClassAlias)) + { + // Try to find an alias + // Build an array of field => array of aliases + $aFieldClasses = array(); + foreach($aClassAliases as $sAlias => $sReal) + { + foreach(MetaModel::GetFiltersList($sReal) as $sAnFltCode) + { + $aFieldClasses[$sAnFltCode][] = $sAlias; + } + } + if (!array_key_exists($sFltCode, $aFieldClasses)) + { + throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), array_keys($aFieldClasses)); + } + if (count($aFieldClasses[$sFltCode]) > 1) + { + throw new OqlNormalizeException('Ambiguous filter code', $sQuery, $oExpression->GetNameDetails()); + } + $sClassAlias = $aFieldClasses[$sFltCode][0]; + } + else + { + if (!array_key_exists($sClassAlias, $aClassAliases)) + { + throw new OqlNormalizeException('Unknown class [alias]', $sQuery, $oExpression->GetParentDetails(), array_keys($aClassAliases)); + } + $sClass = $aClassAliases[$sClassAlias]; + if (!MetaModel::IsValidFilterCode($sClass, $sFltCode)) + { + throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), MetaModel::GetFiltersList($sClass)); + } + } + + return new FieldExpression($sFltCode, $sClassAlias); + } + elseif ($oExpression instanceof TrueOqlExpression) + { + return new TrueExpression; + } + elseif ($oExpression instanceof ScalarOqlExpression) + { + return new ScalarExpression($oExpression->GetValue()); + } + elseif ($oExpression instanceof ListOqlExpression) + { + return new ListExpression($oExpression->GetItems()); + } + elseif ($oExpression instanceof FunctionOqlExpression) + { + return new FunctionExpression($oExpression->GetVerb(), $oExpression->GetArgs()); + } + else + { + throw new CoreException('Unknown expression type', array('class'=>get_class($oExpression), 'query'=>$sQuery)); + } + } + + static public function FromOQL($sQuery, array $aParams = array(), $oObject = null) + { + if (empty($sQuery)) return null; + + $oOql = new OqlInterpreter($sQuery); + $oOqlQuery = $oOql->ParseQuery(); + + $sClass = $oOqlQuery->GetClass(); + $sClassAlias = $oOqlQuery->GetClassAlias(); + + if (!MetaModel::IsValidClass($sClass)) + { + throw new OqlNormalizeException('Unknown class', $sQuery, $oOqlQuery->GetClassDetails(), MetaModel::GetClasses()); + } + + $oResultFilter = new DBObjectSearch($sClass, $sClassAlias); + $aAliases = array($sClassAlias => $sClass); + + // Maintain an array of filters, because the flat list is in fact referring to a tree + // And this will be an easy way to dispatch the conditions + // $oResultFilter will be referenced by the other filters, or the other way around... + $aJoinItems = array($sClassAlias => $oResultFilter); + + $aJoinSpecs = $oOqlQuery->GetJoins(); + if (is_array($aJoinSpecs)) + { + foreach ($aJoinSpecs as $oJoinSpec) + { + $sJoinClass = $oJoinSpec->GetClass(); + $sJoinClassAlias = $oJoinSpec->GetClassAlias(); + if (!MetaModel::IsValidClass($sJoinClass)) + { + throw new OqlNormalizeException('Unknown class', $sQuery, $oJoinSpec->GetClassDetails(), MetaModel::GetClasses()); + } + if (array_key_exists($sJoinClassAlias, $aAliases)) + { + if ($sJoinClassAlias != $sJoinClass) + { + throw new OqlNormalizeException('Duplicate class alias', $sQuery, $oJoinSpec->GetClassAliasDetails()); + } + else + { + throw new OqlNormalizeException('Duplicate class name', $sQuery, $oJoinSpec->GetClassDetails()); + } + } + + // Assumption: ext key on the left only !!! + // normalization should take care of this + $oLeftField = $oJoinSpec->GetLeftField(); + $sFromClass = $oLeftField->GetParent(); + $sExtKeyAttCode = $oLeftField->GetName(); + + $oRightField = $oJoinSpec->GetRightField(); + $sToClass = $oRightField->GetParent(); + $sPKeyDescriptor = $oRightField->GetName(); + if ($sPKeyDescriptor != 'id') + { + throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sQuery, $oRightField->GetNameDetails(), array('id')); + } + + $aAliases[$sJoinClassAlias] = $sJoinClass; + $aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias); + + if (!array_key_exists($sFromClass, $aJoinItems)) + { + throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sQuery, $oLeftField->GetParentDetails(), array_keys($aJoinItems)); + } + if (!array_key_exists($sToClass, $aJoinItems)) + { + throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sQuery, $oRightField->GetParentDetails(), array_keys($aJoinItems)); + } + $aExtKeys = array_keys(MetaModel::GetExternalKeys($aAliases[$sFromClass])); + if (!in_array($sExtKeyAttCode, $aExtKeys)) + { + throw new OqlNormalizeException('Unknown external key in join condition (left expression)', $sQuery, $oLeftField->GetNameDetails(), $aExtKeys); + } + + if ($sFromClass == $sJoinClassAlias) + { + $aJoinItems[$sToClass]->AddCondition_ReferencedBy($aJoinItems[$sFromClass], $sExtKeyAttCode); + } + else + { + $aJoinItems[$sFromClass]->AddCondition_PointingTo($aJoinItems[$sToClass], $sExtKeyAttCode); + } + } + } + + $oConditionTree = $oOqlQuery->GetCondition(); + if ($oConditionTree instanceof Expression) + { + $oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases); + } + + return $oResultFilter; + } + + static public function FromSibusQL($sQuery, array $aParams = array(), $oObject = null) + { + if (empty($sQuery)) return null; + $sQuery = self::privProcessParams($sQuery, $aParams, $oObject); + + $iSepPos = strpos($sQuery, ":"); + if ($iSepPos === false) + { + if (preg_match('@^\\s*SELECT@', $sQuery)) + { + return self::FromOQL($sQuery, $aParams, $oObject); + } + // Only the class was specified -> all rows are required + $sClass = trim($sQuery); + $oFilter = new DBObjectSearch($sClass); + } + else + { + $sClass = trim(substr($sQuery, 0, $iSepPos)); + $sConds = trim(substr($sQuery, $iSepPos + 1)); + $aValues = split(" AND ", $sConds); + + $oFilter = new DBObjectSearch($sClass); + + foreach ($aValues as $sCond) + { + $sCond = trim($sCond); + + if (strpos($sCond, "* HAS ") === 0) + { + $sValue = self::Expression2Value(substr($sCond, strlen("* HAS "))); + $oFilter->AddCondition_FullText($sValue); + } + else if (preg_match("@^(\S+) IN \\((.+)\\)$@", $sCond, $aMatches)) + { + $sExtKeyAttCode = $aMatches[1]; + $sFilterExp = $aMatches[2]; + + $oSubFilter = self::FromSibuSQL($sFilterExp); + $oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode); + } + else if (strpos($sCond, "PKEY IS ") === 0) + { + if (preg_match("@^PKEY IS (\S+) IN \\((.+)\\)$@", $sCond, $aMatches)) + { + $sExtKeyAttCodeToMe = $aMatches[1]; + $sFilterExp = $aMatches[2]; + $oRemoteFilter = self::FromSibuSQL($sFilterExp); + $oFilter->AddCondition_ReferencedBy($oRemoteFilter, $sExtKeyAttCodeToMe); + } + } + else if (strpos($sCond, "RELATED") === 0) + { + if (preg_match("@^RELATED\s*\\((.+)\\)\s*TO\s*\\((.+)\\)@", trim($sCond), $aMatches)) + { + $aRelation = explode(',', trim($aMatches[1])); + $sRelCode = trim($aRelation[0]); + $iMaxDepth = intval(trim($aRelation[1])); + $sFilterExp = trim($aMatches[2]); + + $oSubFilter = self::FromSibuSQL($sFilterExp); + $oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth); + } + } + else + { + $sOperandExpr = "'.*'|\d+|-\d+|".VS_START.".+".VS_END; + if (preg_match("@^(\S+)\s+(.*)\s+($sOperandExpr)$@", $sCond, $aMatches)) + { + $sFltCode = trim($aMatches[1]); + $sOpCode = trim($aMatches[2]); + $value = self::Expression2Value($aMatches[3]); + $oFilter->AddCondition($sFltCode, $value, $sOpCode); + } + else + { + throw new CoreException("Wrong format for filter definition: '$sQuery'"); + } + } + } + } + +// #@# todo - obsolete smoothly, first give the OQL version ! +// throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL())); + + return $oFilter; + } + + // Sexy display of a SibuSQL expression + static public function SibuSQLAsHtml($sQuery) + { + $sQuery = htmlentities($sQuery); + $aParams = self::ListSibusQLParams($sQuery); + $aParamValues = array(); + foreach ($aParams as $sParamName => $aParamInfo) + { + $sDescription = $aParamInfo["description"]; + $sDefaultValue = $aParamInfo["default"]; + $aParamValues[$sParamName] = "$sParamName"; + } + $sQuery = self::privProcessParams($sQuery, $aParamValues, null); + return $sQuery; + } + + public function toxpath() + { + // #@# a voir... + } + static public function fromxpath() + { + // #@# a voir... + } +} + + +?> diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php new file mode 100644 index 0000000000..d2719c5cd7 --- /dev/null +++ b/core/dbobjectset.class.php @@ -0,0 +1,250 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class DBObjectSet +{ + private $m_oFilter; + private $m_aOrderBy; + public $m_bLoaded; + private $m_aData; + private $m_aId2Row; + private $m_iCurrRow; + + public function __construct($oFilter, $aOrderBy = array()) + { + $this->m_oFilter = $oFilter; + $this->m_aOrderBy = $aOrderBy; + + $this->m_bLoaded = false; + $this->m_aData = array(); + $this->m_aId2Row = array(); + $this->m_iCurrRow = 0; + } + + public function __destruct() + { + } + + public function __toString() + { + $sRet = ''; + $this->Rewind(); + $sRet .= "Set (".$this->m_oFilter->ToSibuSQL().")
    \n"; + $sRet .= "Query:
    ".MetaModel::MakeSelectQuery($this->m_oFilter, array()).")
    \n"; + + $sRet .= $this->Count()." records
    \n"; + if ($this->Count() > 0) + { + $sRet .= "
      \n"; + while ($oObj = $this->Fetch()) + { + $sRet .= "
    • ".$oObj->__toString()."
    • \n"; + } + $sRet .= "
    \n"; + } + return $sRet; + } + + static public function FromScratch($sClass) + { + $oFilter = new CMDBSearchFilter($sClass); + $oRetSet = new self($oFilter); + $oRetSet->m_bLoaded = true; // no DB load + return $oRetSet; + } + + static public function FromArray($sClass, $aObjects) + { + $oFilter = new CMDBSearchFilter($sClass); + $oRetSet = new self($oFilter); + $oRetSet->m_bLoaded = true; // no DB load + $oRetSet->AddObjectArray($aObjects); + return $oRetSet; + } + + public function ToArray($bWithId = true) + { + $aRet = array(); + $this->Rewind(); + while ($oObject = $this->Fetch()) + { + if ($bWithId) + { + $aRet[$oObject->GetKey()] = $oObject; + } + else + { + $aRet[] = $oObject; + } + } + return $aRet; + } + + public function GetFilter() + { + return $this->m_oFilter; + } + + public function GetClass() + { + return $this->m_oFilter->GetClass(); + } + + public function GetRootClass() + { + return MetaModel::GetRootClass($this->GetClass()); + } + + public function Load() + { + if ($this->m_bLoaded) return; +// #@# debug - echo "Loading (".$this->m_oFilter->ToSibuSQL().")....
    \n"; + $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy); + $resQuery = CMDBSource::Query($sSQL); + if (!$resQuery) return; + + while ($aRow = CMDBSource::FetchArray($resQuery)) + { + $sClass = $this->m_oFilter->GetClass(); + $oObject = MetaModel::GetObjectByRow($sClass, $aRow); + $this->AddObject($oObject); + } + CMDBSource::FreeResult($resQuery); + + $this->m_bLoaded = true; + } + + public function Count() + { + if (!$this->m_bLoaded) $this->Load(); + return count($this->m_aData); + } + + public function Fetch() + { + if (!$this->m_bLoaded) $this->Load(); + + if ($this->m_iCurrRow >= count($this->m_aData)) + { + return null; + } + $oRetObj = $this->m_aData[$this->m_iCurrRow]; + $this->m_iCurrRow++; + return $oRetObj; + } + + public function Rewind() + { + $this->Seek(0); + } + + public function Seek($iRow) + { + if (!$this->m_bLoaded) $this->Load(); + + $this->m_iCurrRow = min($iRow, count($this->m_aData)); + return $this->m_iCurrRow; + } + + public function AddObject($oObject) + { + // ?usefull? if ($oObject->GetClass() != $this->GetClass()) return; + + // it is mandatory to avoid duplicates + if (array_key_exists($oObject->GetKey(), $this->m_aId2Row)) return; + + // Do not load here, because the load uses that method too + $iNextPos = count($this->m_aData); + $this->m_aData[$iNextPos] = $oObject; + $this->m_aId2Row[$oObject->GetKey()] = $iNextPos; + } + + public function AddObjectArray($aObjects) + { + foreach ($aObjects as $oObj) + { + $this->AddObject($oObj); + } + } + + public function Merge($oObjectSet) + { + if ($this->GetRootClass() != $oObjectSet->GetRootClass()) + { + throw new CoreException("Could not merge two objects sets if they don't have the same root class"); + } + if (!$this->m_bLoaded) $this->Load(); + + $oObjectSet->Seek(0); + while ($oObject = $oObjectSet->Fetch()) + { + $this->AddObject($oObject); + } + } + + public function CreateIntersect($oObjectSet) + { + if ($this->GetRootClass() != $oObjectSet->GetRootClass()) + { + throw new CoreException("Could not 'intersect' two objects sets if they don't have the same root class"); + } + if (!$this->m_bLoaded) $this->Load(); + + $oNewSet = DBObjectSet::FromScratch($this->GetClass()); + + $oObjectSet->Seek(0); + while ($oObject = $oObjectSet->Fetch()) + { + if (array_key_exists($oObject->GetKey(), $this->m_aId2Row)) + { + $oNewSet->AddObject($oObject); + } + } + return $oNewSet; + } + + public function CreateDelta($oObjectSet) + { + if ($this->GetRootClass() != $oObjectSet->GetRootClass()) + { + throw new CoreException("Could not 'delta' two objects sets if they don't have the same root class"); + } + if (!$this->m_bLoaded) $this->Load(); + + $oNewSet = DBObjectSet::FromScratch($this->GetClass()); + + $oObjectSet->Seek(0); + while ($oObject = $oObjectSet->Fetch()) + { + if (!array_key_exists($oObject->GetKey(), $this->m_aId2Row)) + { + $oNewSet->AddObject($oObject); + } + } + return $oNewSet; + } + + public function GetRelatedObjects($sRelCode, $iMaxDepth = 99) + { + $aVisited = array(); // optimization for consecutive calls of MetaModel::GetRelatedObjects + $this->Seek(0); + while ($oObject = $this->Fetch()) + { + $aRelatedObjs = $oObject->GetRelatedObjects($sRelCode, $iMaxDepth, $aVisited); + } + return $aRelatedObjs; + } +} + +?> diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php new file mode 100644 index 0000000000..76409cdc15 --- /dev/null +++ b/core/expression.class.inc.php @@ -0,0 +1,485 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class Expression +{ + // recursive translation of identifiers + abstract public function Translate($aTranslationData, $bMatchAll = true); + + // recursive rendering + abstract public function Render(); + + // recursively builds an array of class => fieldname + abstract public function ListRequiredFields(); + + abstract public function IsTrue(); + + public function RequiresField($sClass, $sFieldName) + { + // #@# todo - optimize : this is called quite often when building a single query ! + $aRequired = $this->ListRequiredFields(); + if (!in_array($sClass.'.'.$sFieldName, $aRequired)) return false; + return true; + } + + public function serialize() + { + return base64_encode($this->Render()); + } + + static public function unserialize($sValue) + { + return self::FromOQL(base64_decode($sValue)); + } + + static public function FromOQL($sConditionExpr) + { + $oOql = new OqlInterpreter($sConditionExpr); + $oExpression = $oOql->ParseExpression(); + + return $oExpression; + } + + public function LogAnd($oExpr) + { + if ($this->IsTrue()) return clone $oExpr; + if ($oExpr->IsTrue()) return clone $this; + return new BinaryExpression($this, 'AND', $oExpr); + } + + public function LogOr($oExpr) + { + return new BinaryExpression($this, 'OR', $oExpr); + } +} + + +class BinaryExpression extends Expression +{ + protected $m_oLeftExpr; // filter code or an SQL expression (later?) + protected $m_oRightExpr; + protected $m_sOperator; + + public function __construct($oLeftExpr, $sOperator, $oRightExpr) + { + if (!is_object($oLeftExpr)) + { + throw new CoreException('Expecting an Expression object on the left hand', array('found_type' => gettype($oLeftExpr))); + } + if (!is_object($oRightExpr)) + { + throw new CoreException('Expecting an Expression object on the right hand', array('found_type' => gettype($oRightExpr))); + } + if (!$oLeftExpr instanceof Expression) + { + throw new CoreException('Expecting an Expression object on the left hand', array('found_class' => get_class($oLeftExpr))); + } + if (!$oRightExpr instanceof Expression) + { + throw new CoreException('Expecting an Expression object on the right hand', array('found_class' => get_class($oRightExpr))); + } + $this->m_oLeftExpr = $oLeftExpr; + $this->m_oRightExpr = $oRightExpr; + $this->m_sOperator = $sOperator; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + if ($this->m_sOperator == 'AND') + { + if ($this->m_oLeftExpr->IsTrue() && $this->m_oLeftExpr->IsTrue()) return true; + } + return false; + } + + public function GetLeftExpr() + { + return $this->m_oLeftExpr; + } + + public function GetRightExpr() + { + return $this->m_oRightExpr; + } + + public function GetOperator() + { + return $this->m_sOperator; + } + + // recursive rendering + public function Render() + { + $sOperator = $this->GetOperator(); + $sLeft = $this->GetLeftExpr()->Render(); + $sRight = $this->GetRightExpr()->Render(); + return "($sLeft $sOperator $sRight)"; + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + $oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll); + $oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll); + return new BinaryExpression($oLeft, $this->GetOperator(), $oRight); + } + + public function ListRequiredFields() + { + $aLeft = $this->GetLeftExpr()->ListRequiredFields(); + $aRight = $this->GetRightExpr()->ListRequiredFields(); + return array_merge($aLeft, $aRight); + } +} + + +class UnaryExpression extends Expression +{ + protected $m_value; + + public function __construct($value) + { + $this->m_value = $value; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return ($this->m_value == 1); + } + + public function GetValue() + { + return $this->m_value; + } + + // recursive rendering + public function Render() + { + return CMDBSource::Quote($this->m_value); + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + return clone $this; + } + + public function ListRequiredFields() + { + return array(); + } +} + +class ScalarExpression extends UnaryExpression +{ + public function __construct($value) + { + if (!is_scalar($value)) + { + throw new CoreException('Attempt to create a scalar expression from a non scalar', array('var_type'=>gettype($value))); + } + parent::__construct($value); + } +} + +class TrueExpression extends ScalarExpression +{ + public function __construct() + { + parent::__construct(1); + } + + public function IsTrue() + { + return true; + } +} + +class FieldExpression extends UnaryExpression +{ + protected $m_sParent; + protected $m_sName; + + public function __construct($sName, $sParent = '') + { + parent::__construct("$sParent.$sName"); + + $this->m_sParent = $sParent; + $this->m_sName = $sName; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return false; + } + + public function GetParent() {return $this->m_sParent;} + public function GetName() {return $this->m_sName;} + + // recursive rendering + public function Render() + { + if (empty($this->m_sParent)) + { + return "`{$this->m_sName}`"; + } + return "`{$this->m_sParent}`.`{$this->m_sName}`"; + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + if (!array_key_exists($this->m_sParent, $aTranslationData)) + { + if ($bMatchAll) throw new CoreException('Unknown parent id in translation table', array('parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData))); + return clone $this; + } + if (!array_key_exists($this->m_sName, $aTranslationData[$this->m_sParent])) + { + if (!array_key_exists('*', $aTranslationData[$this->m_sParent])) + { + // #@# debug - if ($bMatchAll) MyHelpers::var_dump_html($aTranslationData, true); + if ($bMatchAll) throw new CoreException('Unknown name in translation table', array('name' => $this->m_sName, 'parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData[$this->m_sParent]))); + return clone $this; + } + $sNewParent = $aTranslationData[$this->m_sParent]['*']; + $sNewName = $this->m_sName; + } + else + { + $sNewParent = $aTranslationData[$this->m_sParent][$this->m_sName][0]; + $sNewName = $aTranslationData[$this->m_sParent][$this->m_sName][1]; + } + return new FieldExpression($sNewName, $sNewParent); + } + + public function ListRequiredFields() + { + return array($this->m_sParent.'.'.$this->m_sName); + } +} + + +// Temporary, until we implement functions and expression casting! +// ... or until we implement a real full text search based in the MATCH() expression +class ListExpression extends Expression +{ + protected $m_aExpressions; + + public function __construct($aExpressions) + { + $this->m_aExpressions = $aExpressions; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return false; + } + + public function GetItems() + { + return $this->m_aExpressions; + } + + // recursive rendering + public function Render() + { + $aRes = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aRes[] = $oExpr->Render(); + } + return '('.implode(', ', $aRes).')'; + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + $aRes = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll); + } + return new ListExpression($aRes); + } + + public function ListRequiredFields() + { + $aRes = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aRes = array_merge($aRes, $oExpr->ListRequiredFields()); + } + return $aRes; + } +} + + +class FunctionExpression extends Expression +{ + protected $m_sVerb; + protected $m_aArgs; // array of expressions + + public function __construct($sVerb, $aArgExpressions) + { + $this->m_sVerb = $sVerb; + $this->m_aArgs = $aArgExpressions; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return false; + } + + public function GetVerb() + { + return $this->m_sVerb; + } + + public function GetArgs() + { + return $this->m_aArgs; + } + + // recursive rendering + public function Render() + { + $aRes = array(); + foreach ($this->m_aArgs as $oExpr) + { + $aRes[] = $oExpr->Render(); + } + return $this->m_sVerb.'('.implode(', ', $aRes).')'; + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + $aRes = array(); + foreach ($this->m_aArgs as $oExpr) + { + $aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll); + } + return new FunctionExpression($this->m_sVerb, $aRes); + } + + public function ListRequiredFields() + { + $aRes = array(); + foreach ($this->m_aArgs as $oExpr) + { + $aRes = array_merge($aRes, $oExpr->ListRequiredFields()); + } + return $aRes; + } +} + +class IntervalExpression extends Expression +{ + protected $m_oValue; // expression + protected $m_sUnit; + + public function __construct($oValue, $sUnit) + { + $this->m_oValue = $oValue; + $this->m_sUnit = $sUnit; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return false; + } + + public function GetValue() + { + return $this->m_oValue; + } + + public function GetUnit() + { + return $this->m_sUnit; + } + + // recursive rendering + public function Render() + { + return 'INTERVAL '.$this->m_oValue->Render().' '.$this->m_sUnit; + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + return new IntervalExpression($this->m_oValue->Translate($aTranslationData, $bMatchAll), $this->m_sUnit); + } + + public function ListRequiredFields() + { + return array(); + } +} + +class CharConcatExpression extends Expression +{ + protected $m_aExpressions; + + public function __construct($aExpressions) + { + $this->m_aExpressions = $aExpressions; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return false; + } + + public function GetItems() + { + return $this->m_aExpressions; + } + + // recursive rendering + public function Render() + { + $aRes = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $sCol = $oExpr->Render(); + // Concat will be globally NULL if one single argument is null ! + $aRes[] = "COALESCE($sCol, '')"; + } + return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)"; + } + + public function Translate($aTranslationData, $bMatchAll = true) + { + $aRes = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll); + } + return new CharConcatExpression($aRes); + } + + public function ListRequiredFields() + { + $aRes = array(); + foreach ($this->m_aExpressions as $oExpr) + { + $aRes = array_merge($aRes, $oExpr->ListRequiredFields()); + } + return $aRes; + } +} + +?> diff --git a/core/filterdef.class.inc.php b/core/filterdef.class.inc.php new file mode 100644 index 0000000000..371eadbd59 --- /dev/null +++ b/core/filterdef.class.inc.php @@ -0,0 +1,296 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class FilterDefinition +{ + abstract public function GetType(); + abstract public function GetTypeDesc(); + + protected $m_sCode; + private $m_aParams = array(); + protected function Get($sParamName) {return $this->m_aParams[$sParamName];} + + public function __construct($sCode, $aParams = array()) + { + $this->m_sCode = $sCode; + $this->m_aParams = $aParams; + $this->ConsistencyCheck(); + } + + public function OverloadParams($aParams) + { + foreach ($aParams as $sParam => $value) + { + if (!array_key_exists($sParam, $this->m_aParams)) + { + throw new CoreException("Unknown attribute definition parameter '$sParam', please select a value in {".implode(", ", $this->m_aParams)."}"); + } + else + { + $this->m_aParams[$sParam] = $value; + } + } + } + + // to be overloaded + static protected function ListExpectedParams() + { + return array(); + } + + private function ConsistencyCheck() + { + // Check that any mandatory param has been specified + // + $aExpectedParams = $this->ListExpectedParams(); + foreach($aExpectedParams as $sParamName) + { + if (!array_key_exists($sParamName, $this->m_aParams)) + { + $aBacktrace = debug_backtrace(); + $sTargetClass = $aBacktrace[2]["class"]; + $sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"]; + throw new CoreException("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)"); + } + } + } + + public function GetCode() {return $this->m_sCode;} + abstract public function GetLabel(); + abstract public function GetValuesDef(); + + // returns an array of opcode=>oplabel (e.g. "differs from") + abstract public function GetOperators(); + // returns an opcode + abstract public function GetLooseOperator(); + abstract public function GetFilterSQLExpr($sOpCode, $value); + abstract public function TemporaryGetSQLCol(); + + // Wrapper - no need for overloading this one + public function GetOpDescription($sOpCode) + { + $aOperators = $this->GetOperators(); + if (!array_key_exists($sOpCode, $aOperators)) + { + throw new CoreException("Unknown operator '$sOpCode'"); + } + + return $aOperators[$sOpCode]; + } +} + +/** + * Match against the object unique identifier + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class FilterPrivateKey extends FilterDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("id_field")); + } + + public function GetType() {return "PrivateKey";} + public function GetTypeDesc() {return "Match against object identifier";} + + public function GetLabel() + { + return "Object Private Key"; + } + + public function GetValuesDef() + { + return null; + } + + public function GetOperators() + { + return array( + "="=>"equals", + "!="=>"differs from", + "IN"=>"in", + "NOTIN"=>"not in" + ); + } + public function GetLooseOperator() + { + return "IN"; + } + + public function GetFilterSQLExpr($sOpCode, $value) + { + $sFieldName = $this->Get("id_field"); + // #@# not obliged to quote... these are numbers !!! + $sQValue = CMDBSource::Quote($value); + switch($sOpCode) + { + case "IN": + if (!is_array($sQValue)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + return "$sFieldName IN (".implode(", ", $sQValue).")"; + + case "NOTIN": + if (!is_array($sQValue)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + return "$sFieldName NOT IN (".implode(", ", $sQValue).")"; + + case "!=": + return $sFieldName." != ".$sQValue; + + case "=": + default: + return $sFieldName." = ".$sQValue; + } + } + public function TemporaryGetSQLCol() + { + return $this->Get("id_field"); + } +} + +/** + * Match against an existing attribute (the attribute type will determine the available operators) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class FilterFromAttribute extends FilterDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("refattribute")); + } + + public function __construct($oRefAttribute, $aParam = array()) + { + // In this very specific case, the code is the one of the attribute + // (this to get a very very simple syntax upon declaration) + $aParam["refattribute"] = $oRefAttribute; + parent::__construct($oRefAttribute->GetCode(), $aParam); + } + + public function GetType() {return "Basic";} + public function GetTypeDesc() {return "Match against field contents";} + + public function __GetRefAttribute() // for checking purposes only !!! + { + return $oAttDef = $this->Get("refattribute"); + } + + public function GetLabel() + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetLabel(); + } + + public function GetValuesDef() + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetValuesDef(); + } + + public function GetOperators() + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetBasicFilterOperators(); + } + public function GetLooseOperator() + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetBasicFilterLooseOperator(); + } + + public function GetFilterSQLExpr($sOpCode, $value) + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetBasicFilterSQLExpr($sOpCode, $value); + } + + public function TemporaryGetSQLCol() + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetSQLExpr(); + } +} + +/** + * Match against a given column (experimental -to be cleaned up later) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class FilterDBValues extends FilterDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("dbfield")); + } + + public function GetType() {return "Values from DB";} + public function GetTypeDesc() {return "Match against the existing values in a field";} + + public function GetLabel() + { + return "enum de valeurs DB"; + } + + public function GetValuesDef() + { + return null; + } + + public function GetOperators() + { + return array( + "IN"=>"in", + ); + } + public function GetLooseOperator() + { + return "IN"; + } + + public function GetFilterSQLExpr($sOpCode, $value) + { + $sFieldName = $this->Get("dbfield"); + if (is_array($value) && !empty($value)) + { + $sValueList = "'".implode("', '", $value)."'"; + return "$sFieldName IN ($sValueList)"; + } + return "1=1"; + } + + public function TemporaryGetSQLCol() + { + return $this->Get("dbfield"); + } +} + +?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php new file mode 100644 index 0000000000..275ad454ad --- /dev/null +++ b/core/metamodel.class.php @@ -0,0 +1,2643 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class MetaModel +{ + /////////////////////////////////////////////////////////////////////////// + // + // STATIC Members + // + /////////////////////////////////////////////////////////////////////////// + + // Purpose: workaround the following limitation = PHP5 does not allow to know the class (derived from the current one) + // from which a static function is called (__CLASS__ and self are interpreted during parsing) + private static function GetCallersPHPClass($sExpectedFunctionName = null) + { + //var_dump(debug_backtrace()); + $aBacktrace = debug_backtrace(); + // $aBacktrace[0] is where we are + // $aBacktrace[1] is the caller of GetCallersPHPClass + // $aBacktrace[1] is the info we want + if (!empty($sExpectedFunctionName)) + { + assert('$aBacktrace[2]["function"] == $sExpectedFunctionName'); + } + return $aBacktrace[2]["class"]; + } + + // Static init -why and how it works + // + // We found the following limitations: + //- it is not possible to define non scalar constants + //- it is not possible to declare a static variable as '= new myclass()' + // Then we had do propose this model, in which a derived (non abstract) + // class should implement Init(), to call InheritAttributes or AddAttribute. + + private static function _check_subclass($sClass) + { + // See also IsValidClass()... ???? #@# + // class is mandatory + // (it is not possible to guess it when called as myderived::...) + if (!array_key_exists($sClass, self::$m_aClassParams)) + { + throw new CoreException("Unknown class '$sClass', expected a value in {".implode(', ', array_keys(self::$m_aClassParams))."}"); + } + } + + public static function static_var_dump() + { + var_dump(get_class_vars(__CLASS__)); + } + + private static $m_bDebugQuery = false; + private static $m_iStackDepthRef = 0; + + public static function StartDebugQuery() + { + $aBacktrace = debug_backtrace(); + self::$m_iStackDepthRef = count($aBacktrace); + self::$m_bDebugQuery = true; + } + public static function StopDebugQuery() + { + self::$m_bDebugQuery = false; + } + public static function DbgTrace($value) + { + if (!self::$m_bDebugQuery) return; + $aBacktrace = debug_backtrace(); + $iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery; + $sIndent = ""; + for ($i = 0 ; $i < $iCallStackPos ; $i++) + { + $sIndent .= " .-=^=-. "; + } + $aCallers = array(); + foreach($aBacktrace as $aStackInfo) + { + $aCallers[] = $aStackInfo["function"]; + } + $sCallers = "Callstack: ".implode(', ', $aCallers); + $sFunction = "".$aBacktrace[1]["function"].""; + + if (is_string($value)) + { + echo "$sIndent$sFunction: $value
    \n"; + } + else if (is_object($value)) + { + echo "$sIndent$sFunction:\n
    \n";
    +			print_r($value);
    +			echo "
    \n"; + } + else + { + echo "$sIndent$sFunction: $value
    \n"; + } + } + + private static $m_sDBName = ""; + private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB) + private static $m_Category2Class = array(); + private static $m_aRootClasses = array(); // array of "classname" => "rootclass" + private static $m_aParentClasses = array(); // array of ("classname" => array of "parentclass") + private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass") + + private static $m_aClassParams = array(); // array of ("classname" => array of class information) + + static public function GetParentPersistentClass($sRefClass) + { + $sClass = get_parent_class($sRefClass); + if (!$sClass) return ''; + + if ($sClass == 'DBObject') return ''; // Warning: __CLASS__ is lower case in my version of PHP + + // Note: the UI/business model may implement pure PHP classes (intermediate layers) + if (array_key_exists($sClass, self::$m_aClassParams)) + { + return $sClass; + } + return self::GetParentPersistentClass($sClass); + } + + final static public function GetName($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["name"]; + } + final static public function GetCategory($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["category"]; + } + final static public function HasCategory($sClass, $sCategory) + { + self::_check_subclass($sClass); + return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); + } + final static public function GetClassDescription($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["description"]; + } + final static public function IsAutoIncrementKey($sClass) + { + self::_check_subclass($sClass); + return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement"); + } + final static public function GetKeyLabel($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["key_label"]; + } + final static public function GetNameAttributeCode($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["name_attcode"]; + } + final static public function GetStateAttributeCode($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["state_attcode"]; + } + final static public function GetDefaultState($sClass) + { + $sDefaultState = ''; + $sStateAttrCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttrCode)) + { + $oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode); + $sDefaultState = $oStateAttrDef->GetDefaultValue(); + } + return $sDefaultState; + } + final static public function GetReconcKeys($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["reconc_keys"]; + } + final static public function GetDisplayTemplate($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["display_template"]; + } + final static public function GetAttributeOrigin($sClass, $sAttCode) + { + self::_check_subclass($sClass); + return self::$m_aAttribOrigins[$sClass][$sAttCode]; + } + final static public function GetPrequisiteAttributes($sClass, $sAttCode) + { + self::_check_subclass($sClass); + $oAtt = self::GetAttributeDef($sClass, $sAttCode); + // Temporary implementation: later, we might be able to compute + // the dependencies, based on the attributes definition + // (allowed values and default values) + if ($oAtt->IsWritable()) + { + return $oAtt->GetPrerequisiteAttributes(); + } + else + { + return array(); + } + } + // #@# restore to private ? + final static public function DBGetTable($sClass, $sAttCode = null) + { + self::_check_subclass($sClass); + if (empty($sAttCode) || ($sAttCode == "id")) + { + $sTableRaw = self::$m_aClassParams[$sClass]["db_table"]; + if (empty($sTableRaw)) + { + // return an empty string whenever the table is undefined, meaning that there is no table associated to this 'abstract' class + return ''; + } + else + { + return self::$m_sTablePrefix.$sTableRaw; + } + } + // This attribute has been inherited (compound objects) + return self::DBGetTable(self::$m_aAttribOrigins[$sClass][$sAttCode]); + } + final static public function DBGetKey($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["db_key_field"]; + } + final static public function DBGetClassField($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["db_finalclass_field"]; + } + final static public function HasFinalClassField($sClass) + { + self::_check_subclass($sClass); + if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false; + return (self::$m_aClassParams[$sClass]["db_finalclass_field"]); + } + final static public function IsStandaloneClass($sClass) + { + self::_check_subclass($sClass); + + $sRootClass = self::GetRootClass($sClass); + return (!self::HasFinalClassField($sRootClass)); + } + final static public function IsSameFamilyBranch($sClassA, $sClassB) + { + self::_check_subclass($sClassA); + self::_check_subclass($sClassB); + if (in_array($sClassA, self::$m_aParentClasses[$sClassB])) return true; + if (in_array($sClassB, self::$m_aParentClasses[$sClassA])) return true; + if ($sClassA == $sClassB) return true; + return false; + } + final static public function IsSameFamily($sClassA, $sClassB) + { + self::_check_subclass($sClassA); + self::_check_subclass($sClassB); + return (self::GetRootClass($sClassA) == self::GetRootClass($sClassB)); + } + + // Attributes of a given class may contain attributes defined in a parent class + // - Some attributes are a copy of the definition + // - Some attributes correspond to the upper class table definition (compound objects) + // (see also filters definition) + private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes) + private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) + private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field"))) + final static public function ListAttributeDefs($sClass) + { + self::_check_subclass($sClass); + return self::$m_aAttribDefs[$sClass]; + } + + final public static function GetAttributesList($sClass) + { + self::_check_subclass($sClass); + return array_keys(self::$m_aAttribDefs[$sClass]); + } + + final public static function GetFiltersList($sClass) + { + self::_check_subclass($sClass); + return array_keys(self::$m_aFilterDefs[$sClass]); + } + + final public static function GetKeysList($sClass) + { + self::_check_subclass($sClass); + $aExtKeys = array(); + foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aExtKeys[] = $sAttCode; + } + } + return $aExtKeys; + } + + final static public function IsValidKeyAttCode($sClass, $sAttCode) + { + if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; + if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return false; + return (self::$m_aAttribDefs[$sClass][$sAttCode]->IsExternalKey()); + } + final static public function IsValidAttCode($sClass, $sAttCode) + { + if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; + return (array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])); + } + final static public function IsAttributeOrigin($sClass, $sAttCode) + { + return (self::$m_aAttribOrigins[$sClass][$sAttCode] == $sClass); + } + + final static public function IsValidFilterCode($sClass, $sFilterCode) + { + if (!array_key_exists($sClass, self::$m_aFilterDefs)) return false; + return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])); + } + public static function IsValidClass($sClass) + { + return (array_key_exists($sClass, self::$m_aAttribDefs)); + } + + public static function IsReconcKey($sClass, $sAttCode) + { + return (in_array($sAttCode, self::GetReconcKeys($sClass))); + } + + final static public function GetAttributeDef($sClass, $sAttCode) + { + self::_check_subclass($sClass); + return self::$m_aAttribDefs[$sClass][$sAttCode]; + } + + final static public function GetExternalKeys($sClass) + { + $aExtKeys = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if ($oAtt->IsExternalKey()) + { + $aExtKeys[$sAttCode] = $oAtt; + } + } + return $aExtKeys; + } + + final static public function GetExternalFields($sClass, $sKeyAttCode) + { + $aExtFields = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if ($oAtt->IsExternalField() && ($oAtt->GetKeyAttCode() == $sKeyAttCode)) + { + $aExtFields[] = $oAtt; + } + } + return $aExtFields; + } + + final static public function GetExtKeyFriends($sClass, $sExtKeyAttCode) + { + if (array_key_exists($sExtKeyAttCode, self::$m_aExtKeyFriends[$sClass])) + { + return self::$m_aExtKeyFriends[$sClass][$sExtKeyAttCode]; + } + else + { + return array(); + } + } + + public static function GetLabel($sClass, $sAttCode) + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef) return $oAttDef->GetLabel(); + return ""; + } + + public static function GetDescription($sClass, $sAttCode) + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef) return $oAttDef->GetDescription(); + return ""; + } + + // Filters of a given class may contain filters defined in a parent class + // - Some filters are a copy of the definition + // - Some filters correspond to the upper class table definition (compound objects) + // (see also attributes definition) + private static $m_aFilterDefs = array(); // array of ("classname" => array filterdef) + private static $m_aFilterOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) + + public static function GetClassFilterDefs($sClass) + { + self::_check_subclass($sClass); + return self::$m_aFilterDefs[$sClass]; + } + + final static public function GetClassFilterDef($sClass, $sFilterCode) + { + self::_check_subclass($sClass); + return self::$m_aFilterDefs[$sClass][$sFilterCode]; + } + + public static function GetFilterLabel($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetLabel(); + return ""; + } + + public static function GetFilterDescription($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetDescription(); + return ""; + } + + // returns an array of opcode=>oplabel (e.g. "differs from") + public static function GetFilterOperators($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetOperators(); + return array(); + } + + // returns an opcode + public static function GetFilterLooseOperator($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetLooseOperator(); + return array(); + } + + public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetOpDescription($sOpCode); + return ""; + } + + public static function GetFilterHTMLInput($sFilterCode) + { + return ""; + } + + // Lists of attributes/search filters + // + private static $m_aListInfos = array(); // array of ("listcode" => various info on the list, common to every classes) + private static $m_aListData = array(); // array of ("classname" => array of "listcode" => list) + // list may be an array of attcode / fltcode + // list may be an array of "groupname" => (array of attcode / fltcode) + + public static function EnumZLists() + { + return array_keys(self::$m_aListInfos); + } + + final static public function GetZListInfo($sListCode) + { + return self::$m_aListInfos[$sListCode]; + } + + public static function GetZListItems($sClass, $sListCode) + { + if (array_key_exists($sClass, self::$m_aListData)) + { + if (array_key_exists($sListCode, self::$m_aListData[$sClass])) + { + return self::$m_aListData[$sClass][$sListCode]; + } + } + $sParentClass = self::GetParentPersistentClass($sClass); + if (empty($sParentClass)) return array(); // nothing for the mother of all classes + // Dig recursively + return self::GetZListItems($sParentClass, $sListCode); + } + + public static function IsAttributeInZList($sClass, $sListCode, $sAttCodeOrFltCode, $sGroup = null) + { + $aZList = self::GetZListItems($sClass, $sListCode); + if (!$sGroup) + { + return (in_array($sAttCodeOrFltCode, $aZList)); + } + return (in_array($sAttCodeOrFltCode, $aZList[$sGroup])); + } + + // + // Relations + // + private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes) + + public static function EnumRelations() + { + return array_keys(self::$m_aRelationInfos); + } + + public static function EnumRelationProperties($sRelCode) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + return self::$m_aRelationInfos[$sRelCode]; + } + + final static public function GetRelationProperty($sRelCode, $sProperty) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]); + + return self::$m_aRelationInfos[$sRelCode][$sProperty]; + } + + public static function EnumRelationQueries($sClass, $sRelCode) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); + } + + // + // Object lifecycle model + // + private static $m_aStates = array(); // array of ("classname" => array of "statecode"=>array('label'=>..., 'description'=>..., attribute_inherit=> attribute_list=>...)) + private static $m_aStimuli = array(); // array of ("classname" => array of ("stimuluscode"=>array('label'=>..., 'description'=>...))) + private static $m_aTransitions = array(); // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD))) + + public static function EnumStates($sClass) + { + if (array_key_exists($sClass, self::$m_aStates)) + { + return self::$m_aStates[$sClass]; + } + else + { + return array(); + } + } + + public static function EnumStimuli($sClass) + { + if (array_key_exists($sClass, self::$m_aStimuli)) + { + return self::$m_aStimuli[$sClass]; + } + else + { + return array(); + } + } + + public static function EnumTransitions($sClass, $sStateCode) + { + if (array_key_exists($sClass, self::$m_aTransitions)) + { + if (array_key_exists($sStateCode, self::$m_aTransitions[$sClass])) + { + return self::$m_aTransitions[$sClass][$sStateCode]; + } + } + return array(); + } + public static function GetAttributeFlags($sClass, $sState, $sAttCode) + { + $iFlags = 0; // By default (if no life cycle) no flag at all + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttCode)) + { + $aStates = MetaModel::EnumStates($sClass); + $aCurrentState = $aStates[$sState]; + if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) ) + { + $iFlags = $aCurrentState['attribute_list'][$sAttCode]; + } + } + return $iFlags; + } + + // + // Allowed values + // + + public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if (!$oAttDef) return null; + $oValSetDef = $oAttDef->GetValuesDef(); + if (!$oValSetDef) return null; + return $oValSetDef->GetValues($aArgs, $sBeginsWith); + } + + public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') + { + $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); + if (!$oFltDef) return null; + $oValSetDef = $oFltDef->GetValuesDef(); + if (!$oValSetDef) return null; + return $oValSetDef->GetValues($aArgs, $sBeginsWith); + } + + // + // Businezz model declaration verbs (should be static) + // + + public static function RegisterZList($sListCode, $aListInfo) + { + // Check mandatory params + $aMandatParams = array( + "description" => "detailed (though one line) description of the list", + "type" => "attributes | filters", + ); + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aListInfo)) + { + throw new CoreException("Declaration of list $sListCode - missing parameter $sParamName"); + } + } + + self::$m_aListInfos[$sListCode] = $aListInfo; + } + + public static function RegisterRelation($sRelCode, $aRelationInfo) + { + // Check mandatory params + $aMandatParams = array( + "description" => "detailed (though one line) description of the list", + "verb_down" => "e.g.: 'impacts'", + "verb_up" => "e.g.: 'is impacted by'", + ); + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aRelationInfo)) + { + throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName"); + } + } + + self::$m_aRelationInfos[$sRelCode] = $aRelationInfo; + } + + // Must be called once and only once... + public static function InitClasses($sTablePrefix) + { + if (count(self::GetClasses()) > 0) + { + throw new CoreException("InitClasses should not be called more than once -skipped"); + return; + } + + self::$m_sTablePrefix = $sTablePrefix; + + foreach(get_declared_classes() as $sPHPClass) { + if (is_subclass_of($sPHPClass, 'DBObject')) + { + if (method_exists($sPHPClass, 'Init')) + { + call_user_func(array($sPHPClass, 'Init')); + } + } + } + foreach (self::GetClasses() as $sClass) + { + // Compute the fields that will be used to display a pointer to another object + // + self::$m_aExtKeyFriends[$sClass] = array(); + foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) + { + // oAttDef is either + // - an external KEY / FIELD (direct), + // - an external field pointing to an external KEY / FIELD + // - an external field pointing to an external field pointing to.... + + // Get the real external key attribute + // It will be our reference to determine the other ext fields related to the same ext key + $oFinalKeyAttDef = $oAttDef->GetKeyAttDef(EXTKEY_ABSOLUTE); + + self::$m_aExtKeyFriends[$sClass][$sAttCode] = array(); + foreach (self::GetExternalFields($sClass, $oAttDef->GetKeyAttCode($sAttCode)) as $oExtField) + { + // skip : those extfields will be processed as external keys + if ($oExtField->IsExternalKey(EXTKEY_ABSOLUTE)) continue; + + // Note: I could not compare the objects by the mean of '===' + // because they are copied for the inheritance, and the internal references are NOT updated + if ($oExtField->GetKeyAttDef(EXTKEY_ABSOLUTE) == $oFinalKeyAttDef) + { + self::$m_aExtKeyFriends[$sClass][$sAttCode][$oExtField->GetCode()] = $oExtField; + } + } + } + } + + // Add a 'id' filter + // + if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('id', self::$m_aFilterDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code"); + } + $oFilter = new FilterPrivateKey('id', array('id_field' => self::DBGetKey($sClass))); + self::$m_aFilterDefs[$sClass]['id'] = $oFilter; + self::$m_aFilterOrigins[$sClass]['id'] = $sClass; + + // Add a 'class' attribute/filter to the root classes and their children + // + if (!self::IsStandaloneClass($sClass)) + { + if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); + } + $sClassAttCode = 'finalclass'; + $sRootClass = self::GetRootClass($sClass); + $sDbFinalClassField = self::DBGetClassField($sRootClass); + $oClassAtt = new AttributeString($sClassAttCode, array( + "label"=>"Class", + "description"=>"Real (final) object class", + "allowed_values"=>null, + "sql"=>$sDbFinalClassField, + "default_value"=>$sClass, + "is_null_allowed"=>false, + "depends_on"=>array() + )); + self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt; + self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass; + + $oClassFlt = new FilterFromAttribute($oClassAtt); + self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; + self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); + } + } + + } + + // To be overriden, must be called for any object class (optimization) + public static function Init() + { + // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) + } + // To be overloaded by biz model declarations + public static function GetRelationQueries($sRelCode) + { + // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) + return array(); + } + + public static function Init_Params($aParams) + { + // Check mandatory params + $aMandatParams = array( + "category" => "group classes by modules defining their visibility in the UI", + "name" => "internal class name, may be different than the PHP class name", + "description" => "detailed (though one line) description of the class", + "key_type" => "autoincrement | string", + "key_label" => "if set, then display the key as an attribute", + "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", + "state_attcode" => "define wich attribute is representing the state (object lifecycle)", + "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", + "db_table" => "database table", + "db_key_field" => "database field which is the key", + "db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class", + ); + + $sClass = self::GetCallersPHPClass("Init"); + if (!array_key_exists("name", $aParams)) + { + throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})"); + } + + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aParams)) + { + throw new CoreException("Declaration of class $sClass - missing parameter $sParamName"); + } + } + + $aCategories = explode(',', $aParams['category']); + foreach ($aCategories as $sCategory) + { + self::$m_Category2Class[$sCategory][] = $sClass; + } + self::$m_Category2Class[''][] = $sClass; // all categories, include this one + + + self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance + self::$m_aParentClasses[$sClass] = array(); + self::$m_aChildClasses[$sClass] = array(); + + self::$m_aClassParams[$sClass]= $aParams; + + self::$m_aAttribDefs[$sClass] = array(); + self::$m_aAttribOrigins[$sClass] = array(); + self::$m_aExtKeyFriends[$sClass] = array(); + self::$m_aFilterDefs[$sClass] = array(); + self::$m_aFilterOrigins[$sClass] = array(); + } + + protected static function object_array_mergeclone($aSource1, $aSource2) + { + $aRes = array(); + foreach ($aSource1 as $key=>$object) + { + $aRes[$key] = clone $object; + } + foreach ($aSource2 as $key=>$object) + { + $aRes[$key] = clone $object; + } + return $aRes; + } + + public static function Init_InheritAttributes($sSourceClass = null) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no attributes for the mother of all classes + } + if (isset(self::$m_aAttribDefs[$sSourceClass])) + { + if (!isset(self::$m_aAttribDefs[$sTargetClass])) + { + self::$m_aAttribDefs[$sTargetClass] = array(); + self::$m_aAttribOrigins[$sTargetClass] = array(); + } + self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); + self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); + } + // later on, we might consider inheritance in different ways !!! + //if (strlen(self::DBGetTable($sSourceClass)) != 0) + if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass])) + { + // Inherit the root class + self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; + } + else + { + // I am a root class, standalone as well ! + // ???? + //self::$m_aRootClasses[$sTargetClass] = $sTargetClass; + } + self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass]; + self::$m_aParentClasses[$sTargetClass][] = $sSourceClass; + // I am the child of each and every parent... + foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass) + { + self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass; + } + } + public static function Init_OverloadAttributeParams($sAttCode, $aParams) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + if (!self::IsValidAttCode($sTargetClass, $sAttCode)) + { + throw new CoreException("Could not overload '$sAttCode', expecting a code from {".implode(", ", self::GetAttributesList($sTargetClass))."}"); + } + self::$m_aAttribDefs[$sTargetClass][$sAttCode]->OverloadParams($aParams); + } + public static function Init_AddAttribute(AttributeDefinition $oAtt) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; + self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + + // Specific case of external fields: + // I wanted to simplify the syntax of the declaration of objects in the biz model + // Therefore, the reference to the host class is set there + $oAtt->SetHostClass($sTargetClass); + } + + public static function Init_InheritFilters($sSourceClass = null) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no filters for the mother of all classes + } + if (isset(self::$m_aFilterDefs[$sSourceClass])) + { + if (!isset(self::$m_aFilterDefs[$sTargetClass])) + { + self::$m_aFilterDefs[$sTargetClass] = array(); + self::$m_aFilterOrigins[$sTargetClass] = array(); + } + + foreach (self::$m_aFilterDefs[$sSourceClass] as $sFltCode=>$oFilter) + { + if ($oFilter instanceof FilterFromAttribute) + { + // In that case, cloning is not enough: + // we must ensure that we will point to the correct + // attribute definition (in case some properties are overloaded) + $oAttDef1 = $oFilter->__GetRefAttribute(); + $oAttDef2 = self::GetAttributeDef($sTargetClass, $oAttDef1->GetCode()); + $oNewFilter = new FilterFromAttribute($oAttDef2); + } + else + { + $oNewFilter = clone $oFilter; + } + self::$m_aFilterDefs[$sTargetClass][$sFltCode] = $oNewFilter; + } + + self::$m_aFilterOrigins[$sTargetClass] = array_merge(self::$m_aFilterOrigins[$sTargetClass], self::$m_aFilterOrigins[$sSourceClass]); + } + } + + public static function Init_OverloadFilterParams($sFltCode, $aParams) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + if (!self::IsValidFilterCode($sTargetClass, $sFltCode)) + { + throw new CoreException("Could not overload '$sFltCode', expecting a code from {".implode(", ", self::GetFiltersList($sTargetClass))."}"); + } + self::$m_aFilterDefs[$sTargetClass][$sFltCode]->OverloadParams($aParams); + } + + public static function Init_AddFilter(FilterDefinition $oFilter) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aFilterDefs[$sTargetClass][$oFilter->GetCode()] = $oFilter; + self::$m_aFilterOrigins[$sTargetClass][$oFilter->GetCode()] = $sTargetClass; + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + } + public static function Init_AddFilterFromAttribute($sAttCode) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + $oAttDef = self::GetAttributeDef($sTargetClass, $sAttCode); + + $sFilterCode = $sAttCode; + $oNewFilter = new FilterFromAttribute($oAttDef); + self::$m_aFilterDefs[$sTargetClass][$sFilterCode] = $oNewFilter; + + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $oKeyDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); + self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $oKeyDef->GetTargetClass(); + } + else + { + self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $sTargetClass; + } + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + } + + public static function Init_SetZListItems($sListCode, $aItems) + { + MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); + + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aListData[$sTargetClass][$sListCode] = $aItems; + } + + public static function Init_DefineState($sStateCode, $aStateDef) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); + + $sParentState = $aStateDef['attribute_inherit']; + if (!empty($sParentState)) + { + // Inherit from the given state (must be defined !) + $aToInherit = self::$m_aStates[$sTargetClass][$sParentState]; + // The inherited configuration could be overriden + $aStateDef['attribute_list'] = array_merge($aToInherit, $aStateDef['attribute_list']); + } + self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef; + + // by default, create an empty set of transitions associated to that state + self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); + } + + public static function Init_DefineStimulus($sStimulusCode, $oStimulus) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; + } + + public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); + self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef; + } + + public static function Init_InheritLifecycle($sSourceClass = '') + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no attributes for the mother of all classes + } + + self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"]; + self::$m_aStates[$sTargetClass] = clone self::$m_aStates[$sSourceClass]; + self::$m_aStimuli[$sTargetClass] = clone self::$m_aStimuli[$sSourceClass]; + self::$m_aTransitions[$sTargetClass] = clone self::$m_aTransitions[$sSourceClass]; + } + + // + // Static API + // + + public static function GetRootClass($sClass = null) + { + self::_check_subclass($sClass); + return self::$m_aRootClasses[$sClass]; + } + public static function IsRootClass($sClass) + { + self::_check_subclass($sClass); + return (self::GetRootClass($sClass) == $sClass); + } + public static function EnumParentClasses($sClass) + { + self::_check_subclass($sClass); + return self::$m_aParentClasses[$sClass]; + } + public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) + { + self::_check_subclass($sClass); + + $aRes = self::$m_aChildClasses[$sClass]; + if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP) + { + // Add it to the list + $aRes[] = $sClass; + } + return $aRes; + } + + public static function EnumCategories() + { + return array_keys(self::$m_Category2Class); + } + + // Note: use EnumChildClasses to take the compound objects into account + public static function GetSubclasses($sClass) + { + self::_check_subclass($sClass); + $aSubClasses = array(); + foreach(get_declared_classes() as $sSubClass) { + if (is_subclass_of($sSubClass, $sClass)) + { + $aSubClasses[] = $sSubClass; + } + } + return $aSubClasses; + } + public static function GetClasses($sCategory = '') + { + if (array_key_exists($sCategory, self::$m_Category2Class)) + { + return self::$m_Category2Class[$sCategory]; + } + + if (count(self::$m_Category2Class) > 0) + { + throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); + } + return array(); + } + + public static function IsAbstract($sClass) + { + if (strlen(self::DBGetTable($sClass)) == 0) return true; + return false; + } + + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array()) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + + // Check the order by specification + foreach ($aOrderBy as $sFieldAlias => $bAscending) + { + MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); + if (!is_bool($bAscending)) + { + throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); + } + } + if (empty($aOrderBy)) + { + $sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass()); + if (!empty($sNameAttCode)) + { + // By default, simply order on the "name" attribute, ascending + $aOrderBy = array($sNameAttCode => true); + } + } + + //MyHelpers::var_dump_html($oSelect->RenderSelect($aOrderBy)); + return $oSelect->RenderSelect($aOrderBy); + } + + public static function MakeDeleteQuery(DBObjectSearch $oFilter) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + return $oSelect->RenderDelete(); + } + + public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues) + { + // $aValues is an array of $sAttCode => $value + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues); + return $oSelect->RenderUpdate(); + } + + private static function MakeQuery($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array()) + { + // Note: query class might be different than the class of the filter + // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) + // $aExpectedAtts is an array of sAttCode=>sAlias + $sClass = $oFilter->GetClass(); + $sClassAlias = $oFilter->GetClassAlias(); + + $bIsOnQueriedClass = ($sClassAlias == $sGlobalTargetAlias); + if ($bIsOnQueriedClass) + { + $aClassAliases = array_merge($aClassAliases, $oFilter->GetClasses()); + } + + self::DbgTrace("Entering: ".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts)); + + $sRootClass = self::GetRootClass($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (empty($aExpectedAtts) && $bIsOnQueriedClass) + { + // default to the whole list of attributes + the very std id/finalclass + $aExpectedAtts['id'] = 'id'; + foreach (self::GetAttributesList($sClass) as $sAttCode) + { + $aExpectedAtts[$sAttCode] = $sAttCode; // alias == attcode + } + } + + // Compute a clear view of external keys, and external attributes + // Build the list of external keys: + // -> ext keys required by a closed join ??? + // -> ext keys mentionned in a 'pointing to' condition + // -> ext keys required for an external field + // + $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) + // + // Optimization: could be computed once for all (cached) + // Could be done in MakeQuerySingleTable ??? + // + + if ($bIsOnQueriedClass) + { + // Get all Ext keys for the queried class (??) + foreach(self::GetKeysList($sClass) as $sKeyAttCode) + { + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + } + // Get all Ext keys used by the filter + foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $trash) + { + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + // Add the ext fields used in the select (eventually adds an external key) + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if (array_key_exists($sAttCode, $aExpectedAtts) || $oConditionTree->RequiresField($sClassAlias, $sAttCode)) + { + // Add the external attribute + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; + } + } + } + + // First query built upon on the leaf (ie current) class + // + self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); + $oSelectBase = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sClass, $aExpectedAtts, $aExtKeys, $aValues); + + // Then we join the queries of the eventual parent classes (compound model) + foreach(self::EnumParentClasses($sClass) as $sParentClass) + { + if (self::DBGetTable($sParentClass) == "") continue; + self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); + $oSelectParentTable = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sParentClass, $aExpectedAtts, $aExtKeys, $aValues); + $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass)); + } + + // Filter on objects referencing me + foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) + { + foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) + { + $oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); + + // We don't want any attribute from the foreign class, just filter on an inner join + $aExpAtts = array(); + + self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()"); + //self::DbgTrace($oForeignFilter); + //self::DbgTrace($oForeignFilter->ToSibuSQL()); + //self::DbgTrace($oSelectForeign); + //self::DbgTrace($oSelectForeign->RenderSelect(array())); + $oSelectForeign = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts); + + $sForeignClassAlias = $oForeignFilter->GetClassAlias(); + $sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0]; + $sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1]; + $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); + } + } + + // Filter on related objects + // + foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo) + { + $oSubFilter = $aCritInfo['flt']; + $sRelCode = $aCritInfo['relcode']; + $iMaxDepth = $aCritInfo['maxdepth']; + + // Get the starting point objects + $oStartSet = new CMDBObjectSet($oSubFilter); + + // Get the objects related to those objects... recursively... + $aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth); + $aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array(); + + // #@# todo - related objects and expressions... + // Create condition + if (count($aRestriction) > 0) + { + $oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')'); + } + else + { + // Quick N'dirty -> generate an empty set + $oSelectBase->AddCondition('false'); + } + } + + // Translate the conditions... and go + // + if ($bIsOnQueriedClass) + { + $oConditionTranslated = $oConditionTree->Translate($aTranslation); + $oSelectBase->SetCondition($oConditionTranslated); + } + + // That's all... cross fingers and we'll get some working query + + //MyHelpers::var_dump_html($oSelectBase, true); + //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); + if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); + return $oSelectBase; + } + + protected static function MakeQuerySingleTable($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, $oFilter, $sTableClass, $aExpectedAtts, $aExtKeys, $aValues) + { + // $aExpectedAtts is an array of sAttCode=>sAlias + // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) + + // Prepare the query for a single table (compound objects) + // Ignores the items (attributes/filters) that are not on the target table + // Perform an (inner or left) join for every external key (and specify the expected fields) + // + // Returns an SQLQuery + // + $sTargetClass = $oFilter->GetClass(); + $sTargetAlias = $oFilter->GetClassAlias(); + $sTable = self::DBGetTable($sTableClass); + $sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable); + + $bIsOnQueriedClass = ($sTargetAlias == $sGlobalTargetAlias); + + self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts)); + + // 1 - SELECT and UPDATE + // + // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) + // + $aSelect = array(); + $aUpdateValues = array(); + + // 1/a - Get the key + // + if ($bIsOnQueriedClass) + { + $aSelect[$aExpectedAtts['id']] = new FieldExpression(self::DBGetKey($sTableClass), $sTableAlias); + } + // We need one pkey to be the key, let's take the one corresponding to the leaf + if ($sTableClass == $sTargetClass) + { + $aTranslation[$sTargetAlias]['id'] = array($sTableAlias, self::DBGetKey($sTableClass)); + } + + // 1/b - Get the other attributes + // + foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; + + // Skip this attribute if not writable (means that it does not correspond + if (count($oAttDef->DBGetUsedFields()) == 0) continue; + + // Update... + // + if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) + { + assert ($oAttDef->IsDirectField()); + // Later, we'll have to use $oAttDef->GetDBField(); + $aUpdateValues[$oAttDef->GetSQLExpr()] = $oAttDef->RealValueToSQLValue($aValues[$sAttCode]); + } + + // Select... + // + // Skip, if a list of fields has been specified and it is not there + if (!array_key_exists($sAttCode, $aExpectedAtts)) continue; + $sAttAlias = $aExpectedAtts[$sAttCode]; + + if ($oAttDef->IsExternalField()) + { + // skip, this will be handled in the joined tables + } + else + { + // standard field, or external key + // add it to the output + $aSelect[$sAttAlias] = new FieldExpression($oAttDef->GetSQLExpr(), $sTableAlias); + } + } + + // 2 - WHERE + // + foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt) + { + // Skip this filter if not defined in this table + if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue; + + // #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression" + // non-sens: $aTranslation[$sTargetAlias][$sFltCode] = array($sTableAlias, $oFltAtt->GetFilterSQLExpr(opcode, operand)); + $aTranslation[$sTargetAlias][$sFltCode] = array($sTableAlias, $oFltAtt->TemporaryGetSQLCol()); + } + + // #@# todo - See what a full text search condition should be + // 2' - WHERE / Full text search condition + // + if ($bIsOnQueriedClass) + { + $aFullText = $oFilter->GetCriteria_FullText(); + } + else + { + // Pourquoi ??? + $aFullText = array(); + } + + // 3 - The whole stuff, for this table only + // + $oSelectBase = new SQLQuery($sTable, $sTableAlias, $aSelect, null, $aFullText, $bIsOnQueriedClass, $aUpdateValues); + + // 4 - The external keys -> joins... + // + if (array_key_exists($sTableClass, $aExtKeys)) + { + foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) + { + $oKeyAttDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); + + $oExtFilter = $oFilter->GetCriteria_PointingTo($sKeyAttCode); + + // In case the join was not explicitely defined in the filter, + // we need to do it now + if (empty($oExtFilter)) + { + $sKeyClass = $oKeyAttDef->GetTargetClass(); + $sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass); + $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); + } + else + { + // The aliases should not conflict because normalization occured while building the filter + $sKeyClass = $oExtFilter->GetClass(); + $sKeyClassAlias = $oExtFilter->GetClassAlias(); + + // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree + } + + // Specify expected attributes for the target class query + // ... and use the current alias ! + $aExpAtts = array(); + $aIntermediateTranslation = array(); + foreach($aExtFields as $sAttCode => $oAtt) + { + + $sExtAttCode = $oAtt->GetExtAttCode(); + if (array_key_exists($sAttCode, $aExpectedAtts)) + { + // Request this attribute... transmit the alias ! + $aExpAtts[$sExtAttCode] = $aExpectedAtts[$sAttCode]; + } + // Translate mainclass.extfield => remoteclassalias.remotefieldcode + $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); + $sRemoteAttExpr = $oRemoteAttDef->GetSQLExpr(); + $aIntermediateTranslation[$sTargetAlias][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); + //#@# debug - echo "

    $sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

    \n"; + } + $oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false); + + self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); + $oSelectExtKey = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); + + $sLocalKeyField = $oKeyAttDef->GetSQLExpr(); + $sExternalKeyField = self::DBGetKey($sKeyClass); + self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); + if ($oKeyAttDef->IsNullAllowed()) + { + $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); + } + else + { + $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); + } + } + } + + //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); + return $oSelectBase; + } + + public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) + { + if (!array_key_exists($sNewName, $aAliases)) + { + $aAliases[$sNewName] = $sRealName; + return $sNewName; + } + + for ($i = 1 ; $i < 100 ; $i++) + { + $sAnAlias = $sNewName.$i; + if (!array_key_exists($sAnAlias, $aAliases)) + { + // Create that new alias + $aAliases[$sAnAlias] = $sRealName; + return $sAnAlias; + } + } + throw new CoreException('Failed to create an alias', array('aliases' => $aAliases, 'new'=>$sNewName)); + } + + public static function CheckDefinitions() + { + if (count(self::GetClasses()) == 0) + { + throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!"); + } + + $aErrors = array(); + $aSugFix = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sNameAttCode = self::GetNameAttributeCode($sClass); + if (empty($sNameAttCode)) + { + // let's try this !!! + // $aErrors[$sClass][] = "Missing value for name definition: the framework will (should...) replace it by the id"; + // $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + else if(!self::IsValidAttCode($sClass, $sNameAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sNameAttCode."' for the name definition"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + + foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode) + if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // It makes no sense to check the attributes again and again in the subclasses + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + if ($oAttDef->IsExternalKey()) + { + if (!self::IsValidClass($oAttDef->GetTargetClass())) + { + $aErrors[$sClass][] = "Unkown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}"; + } + } + elseif ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode)) + { + $aErrors[$sClass][] = "Unkown key attribute code '".$sKeyAttCode."' for the external field $sAttCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}"; + } + else + { + $oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode); + $sTargetClass = $oKeyAttDef->GetTargetClass(); + $sExtAttCode = $oAttDef->GetExtAttCode(); + if (!self::IsValidAttCode($sTargetClass, $sExtAttCode)) + { + $aErrors[$sClass][] = "Unkown key attribute code '".$sExtAttCode."' for the external field $sAttCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}"; + } + } + } + else // standard attributes + { + // Check that the default values definition is a valid object! + $oValSetDef = $oAttDef->GetValuesDef(); + if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition) + { + $aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type"; + $aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object."; + } + else + { + // Default value must be listed in the allowed values (if defined) + $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); + if (!is_null($aAllowedValues)) + { + $sDefaultValue = $oAttDef->GetDefaultValue(); + if (!array_key_exists($sDefaultValue, $aAllowedValues)) + { + $aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value"; + $aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}"; + } + } + } + } + // Check dependencies + if ($oAttDef->IsWritable()) + { + foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode) + { + if (!self::IsValidAttCode($sClass, $sDependOnAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + } + } + } + foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef) + { + if (method_exists($oFilterDef, '__GetRefAttribute')) + { + $oAttDef = $oFilterDef->__GetRefAttribute(); + if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) + { + $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + } + } + + // Lifecycle + // + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (strlen($sStateAttCode) > 0) + { + // Lifecycle - check that the state attribute does exist as an attribute + if (!self::IsValidAttCode($sClass, $sStateAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sStateAttCode."' for the state definition"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + else + { + // Lifecycle - check that there is a value set constraint on the state attribute + $aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode); + $aStates = array_keys(self::EnumStates($sClass)); + if (is_null($aAllowedValuesRaw)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values"; + $aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]"; + } + else + { + $aAllowedValues = array_keys($aAllowedValuesRaw); + + // Lifecycle - check the the state attribute allowed values are defined states + foreach($aAllowedValues as $sValue) + { + if (!in_array($sValue, $aStates)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state"; + $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; + } + } + + // Lifecycle - check that defined states are allowed values + foreach($aStates as $sStateValue) + { + if (!in_array($sStateValue, $aAllowedValues)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value"; + $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; + } + } + } + + // Lifcycle - check that the action handlers are defined + foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef) + { + foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) + { + foreach ($aTransitionDef['actions'] as $sActionHandler) + { + if (!method_exists($sClass, $sActionHandler)) + { + $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; + $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; + } + } + } + } + } + } + + // ZList + // + foreach(self::EnumZLists() as $sListCode) + { + foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode) + { + if (!self::IsValidAttCode($sClass, $sMyAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sMyAttCode."' from ZList '$sListCode'"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + } + } + } + if (count($aErrors) > 0) + { + echo "
    "; + echo "

    Business model inconsistencies have been found

    \n"; + // #@# later -> this is the responsibility of the caller to format the output + foreach ($aErrors as $sClass => $aMessages) + { + echo "

    Wrong declaration for class $sClass

    \n"; + echo "
      \n"; + $i = 0; + foreach ($aMessages as $sMsg) + { + echo "
    • $sMsg ({$aSugFix[$sClass][$i]})
    • \n"; + $i++; + } + echo "
    \n"; + } + echo "

    Aborting...

    \n"; + echo "
    \n"; + exit; + } + } + + public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) + { + if (empty($sRepairUrl)) return; + if (count($aSQLFixes) == 0) return; + + echo "
    \n"; + echo " \n"; + echo " \n"; + echo "
    \n"; + } + + public static function DBExists() + { + // returns true if at least one table exists (taking into account the DB sharing) + // then some tables might be missing, but that is made in DBCheckFormat + // + if (empty(self::$m_sTablePrefix)) + { + return CMDBSource::IsDB(self::$m_sDBName); + } + + // DB sharing + // Check if there is at least one table with the prefix + // + if (!CMDBSource::IsDB(self::$m_sDBName)) + { + return false; + } + CMDBSource::SelectDB(self::$m_sDBName); + + // If a table matches the prefix, then consider that the database already exists + $sSQL = "SHOW TABLES LIKE '".strtolower(self::$m_sTablePrefix)."%' "; + $result = CMDBSource::Query($sSQL); + return (CMDBSource::NbRows($result) > 0); + } + + public static function DBDrop() + { + $bDropEntireDB = true; + + if (!empty(self::$m_sTablePrefix)) + { + // Do drop only tables corresponding to the sub-database (table prefix) + // then possibly drop the DB itself (if no table remain) + foreach (CMDBSource::EnumTables() as $sTable) + { + // perform a case insensitive test because on Windows the table names become lowercase :-( + if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix)) + { + CMDBSource::DropTable($sTable); + } + else + { + // There is at least one table which is out of the scope of the current application + $bDropEntireDB = false; + } + } + } + + if ($bDropEntireDB) + { + CMDBSource::DropDB(self::$m_sDBName); + } + } + + + public static function DBCreate() + { + // Note: we have to check if the DB does exist, because we may share the DB + // with other applications (in which case the DB does exist, not the tables with the given prefix) + if (!CMDBSource::IsDB(self::$m_sDBName)) + { + CMDBSource::CreateDB(self::$m_sDBName); + } + self::DBCreateTables(); + } + + protected static function DBCreateTables() + { + list($aErrors, $aSugFix) = self::DBCheckFormat(); + + $aSQL = array(); + foreach ($aSugFix as $sClass => $aQueries) + { + foreach ($aQueries as $sQuery) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } + } + // does not work -how to have multiple statements in a single query? + // $sDoCreateAll = implode(" ; ", $aSQL); + } + + public static function DBCheckFormat() + { + $aErrors = array(); + $aSugFix = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + // Check that the table exists + // + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + $sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : ""); + if (!CMDBSource::IsTable($sTable)) + { + $aErrors[$sClass][] = "table '$sTable' could not be found into the DB"; + $aSugFix[$sClass][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb"; + } + // Check that the key field exists + // + elseif (!CMDBSource::IsField($sTable, $sKeyField)) + { + $aErrors[$sClass][] = "key '$sKeyField' (table $sTable) could not be found"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; + } + else + { + // Check the key field properties + // + if (!CMDBSource::IsKey($sTable, $sKeyField)) + { + $aErrors[$sClass][] = "key '$sKeyField' is not a key for table '$sTable'"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; + } + if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField)) + { + $aErrors[$sClass][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; + } + } + + // Check that any defined field exists + // + $aTableInfo = CMDBSource::GetTableInfo($sTable); + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not originaly defined in this class + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + foreach($oAttDef->DBGetUsedFields() as $sField) + { + $sDBFieldType = $oAttDef->GetDBFieldType(); + $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; + if (!CMDBSource::IsField($sTable, $sField)) + { + $aErrors[$sClass][] = "field '$sField' could not be found in table '$sTable'"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; + } + elseif ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) + { + if ($oAttDef->IsNullAllowed()) + { + $aErrors[$sClass][] = "field '$sField' in table '$sTable' could be NULL"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + else + { + $aErrors[$sClass][] = "field '$sField' in table '$sTable' could NOT be NULL"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + } + } + } + } + return array($aErrors, $aSugFix); + } + + + private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false) + { + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) + { + $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; + } + $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); + if (count($aWrongRecords) == 0) return; + + if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); + if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); + + foreach ($aWrongRecords as $iRecordId) + { + if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) + { + switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) + { + case 'Delete': + // Already planned for a deletion + // Let's concatenate the errors description together + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; + break; + + case 'Update': + // Let's plan a deletion + break; + } + } + else + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; + } + + if (!$bProcessingFriends) + { + if (!array_key_exists($sTable, $aPlannedDel) || !in_array($iRecordId, $aPlannedDel[$sTable])) + { + // Something new to be deleted... + $iNewDelCount++; + } + } + + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Delete'; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; + $aPlannedDel[$sTable][] = $iRecordId; + } + + // Now make sure that we would delete the records of the other tables for that class + // + if (!$bProcessingFriends) + { + $sDeleteKeys = "'".implode("', '", $aWrongRecords)."'"; + foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sFriendClass) + { + $sFriendTable = self::DBGetTable($sFriendClass); + $sFriendKey = self::DBGetKey($sFriendClass); + + // skip the current table + if ($sFriendTable == $sTable) continue; + + $sFindRelatedRec = "SELECT DISTINCT maintable.`$sFriendKey` AS id FROM `$sFriendTable` AS maintable WHERE maintable.`$sFriendKey` IN ($sDeleteKeys)"; + self::DBCheckIntegrity_Check2Delete($sFindRelatedRec, "Cascading deletion of record in friend table `$sTable`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true); + } + } + } + + private static function DBCheckIntegrity_Check2Update($sSelWrongRecs, $sErrorDesc, $sColumn, $sNewValue, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) + { + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) + { + $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; + } + $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); + if (count($aWrongRecords) == 0) return; + + if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); + if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); + + foreach ($aWrongRecords as $iRecordId) + { + if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) + { + switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) + { + case 'Delete': + // No need to update, the record will be deleted! + break; + + case 'Update': + // Already planned for an update + // Add this new update spec to the list + $bFoundSameSpec = false; + foreach ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] as $aUpdateSpec) + { + if (($sColumn == $aUpdateSpec['column']) && ($sNewValue == $aUpdateSpec['newvalue'])) + { + $bFoundSameSpec = true; + } + } + if (!$bFoundSameSpec) + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'][] = (array('column' => $sColumn, 'newvalue'=>$sNewValue)); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; + } + break; + } + } + else + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Update'; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(array('column' => $sColumn, 'newvalue'=>$sNewValue)); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; + } + + } + } + + // returns the count of records found for deletion + public static function DBCheckIntegrity_SinglePass(&$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) + { + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + // Check that the final class field contains the name of a class which inherited from the current class + // + if (self::HasFinalClassField($sClass)) + { + $sFinalClassField = self::DBGetClassField($sClass); + + $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); + + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + // Compound objects model - node/leaf classes (not the root itself) + // + if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass)) + { + $sRootTable = self::DBGetTable($sRootClass); + $sRootKey = self::DBGetKey($sRootClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + + $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); + + // Check that any record found here has its counterpart in the root table + // and which refers to a child class + // + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `$sTable`, but no counterpart in root table `$sRootTable` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + + // Check that any record found in the root table and referring to a child class + // has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy) + // + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + if ($oAttDef->IsExternalKey()) + { + // Check that any external field is pointing to an existing object + // + $sRemoteClass = $oAttDef->GetTargetClass(); + $sRemoteTable = self::DBGetTable($sRemoteClass); + $sRemoteKey = self::DBGetKey($sRemoteClass); + + $sExtKeyField = $oAttDef->GetSQLExpr(); + + // Note: a class/table may have an external key on itself + $sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`"; + + $sSelWrongRecs = $sSelBase." WHERE `$sRemoteTable`.`$sRemoteKey` IS NULL"; + if ($oAttDef->IsNullAllowed()) + { + // Exclude the records pointing to 0/null from the errors + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + // Do almost the same, taking into account the records planned for deletion + if (array_key_exists($sRemoteTable, $aPlannedDel) && count($aPlannedDel[$sRemoteTable]) > 0) + { + // This could be done by the mean of a 'OR ... IN (aIgnoreRecords) + // but in that case you won't be able to track the root cause (cascading) + $sSelWrongRecs = $sSelBase." WHERE maintable.`$sExtKeyField` IN ('".implode("', '", $aPlannedDel[$sRemoteTable])."')"; + if ($oAttDef->IsNullAllowed()) + { + // Exclude the records pointing to 0/null from the errors + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + } + } + else if ($oAttDef->IsDirectField()) + { + // Check that the values fit the allowed values + // + $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); + if (!is_null($aAllowedValues) && count($aAllowedValues) > 0) + { + $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); + + $sMyAttributeField = $oAttDef->GetSQLExpr(); + $sDefaultValue = $oAttDef->GetDefaultValue(); + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + } + } + } + } + + public static function DBCheckIntegrity($sRepairUrl = "", $sSQLStatementArgName = "") + { + // Records in error, and action to be taken: delete or update + // by RootClass/Table/Record + $aErrorsAndFixes = array(); + + // Records to be ignored in the current/next pass + // by Table = array of RecordId + $aPlannedDel = array(); + + // Count of errors in the next pass: no error means that we can leave... + $iErrorCount = 0; + // Limit in case of a bug in the algorythm + $iLoopCount = 0; + + $iNewDelCount = 1; // startup... + while ($iNewDelCount > 0) + { + $iNewDelCount = 0; + self::DBCheckIntegrity_SinglePass($aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + $iErrorCount += $iNewDelCount; + + // Safety net #1 - limit the planned deletions + // + $iMaxDel = 1000; + $iPlannedDel = 0; + foreach ($aPlannedDel as $sTable => $aPlannedDelOnTable) + { + $iPlannedDel += count($aPlannedDelOnTable); + } + if ($iPlannedDel > $iMaxDel) + { + throw new CoreWarning("DB Integrity Check safety net - Exceeding the limit of $iMaxDel planned record deletion"); + break; + } + // Safety net #2 - limit the iterations + // + $iLoopCount++; + $iMaxLoops = 10; + if ($iLoopCount > $iMaxLoops) + { + throw new CoreWarning("DB Integrity Check safety net - Reached the limit of $iMaxLoops loops"); + break; + } + } + + // Display the results + // + $iIssueCount = 0; + $aFixesDelete = array(); + $aFixesUpdate = array(); + + foreach ($aErrorsAndFixes as $sRootClass => $aTables) + { + foreach ($aTables as $sTable => $aRecords) + { + foreach ($aRecords as $iRecord => $aError) + { + $sAction = $aError['Action']; + $sReason = $aError['Reason']; + $iPass = $aError['Pass']; + + switch ($sAction) + { + case 'Delete': + $sActionDetails = ""; + $aFixesDelete[$sTable][] = $iRecord; + break; + + case 'Update': + $aUpdateDesc = array(); + foreach($aError['Action_Details'] as $aUpdateSpec) + { + $aUpdateDesc[] = $aUpdateSpec['column']." -> ".$aUpdateSpec['newvalue']; + $aFixesUpdate[$sTable][$aUpdateSpec['column']][$aUpdateSpec['newvalue']][] = $iRecord; + } + $sActionDetails = "Set ".implode(", ", $aUpdateDesc); + + break; + + default: + $sActionDetails = "bug: unknown action '$sAction'"; + } + $aIssues[] = "$sRootClass / $sTable / $iRecord / $sReason / $sAction / $sActionDetails"; + $iIssueCount++; + } + } + } + + if ($iIssueCount > 0) + { + // Build the queries to fix in the database + // + // First step, be able to get class data out of the table name + // Could be optimized, because we've made the job earlier... but few benefits, so... + $aTable2ClassProp = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + $aErrorsAndFixes[$sRootClass][$sTable] = array(); + $aTable2ClassProp[$sTable] = array('rootclass'=>$sRootClass, 'class'=>$sClass, 'keyfield'=>$sKeyField); + } + // Second step, build a flat list of SQL queries + $aSQLFixes = array(); + $iPlannedUpdate = 0; + foreach ($aFixesUpdate as $sTable => $aColumns) + { + foreach ($aColumns as $sColumn => $aNewValues) + { + foreach ($aNewValues as $sNewValue => $aRecords) + { + $iPlannedUpdate += count($aRecords); + $sWrongRecords = "'".implode("', '", $aRecords)."'"; + $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; + + $aSQLFixes[] = "UPDATE `$sTable` SET `$sColumn` = $sNewValue WHERE `$sKeyField` IN ($sWrongRecords)"; + } + } + } + $iPlannedDel = 0; + foreach ($aFixesDelete as $sTable => $aRecords) + { + $iPlannedDel += count($aRecords); + $sWrongRecords = "'".implode("', '", $aRecords)."'"; + $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; + + $aSQLFixes[] = "DELETE FROM `$sTable` WHERE `$sKeyField` IN ($sWrongRecords)"; + } + + // Report the results + // + echo "
    "; + echo "

    Database corruption error(s): $iErrorCount issues have been encountered. $iPlannedDel records will be deleted, $iPlannedUpdate records will be updated:

    \n"; + // #@# later -> this is the responsibility of the caller to format the output + echo "
      \n"; + foreach ($aIssues as $sIssueDesc) + { + echo "
    • $sIssueDesc
    • \n"; + } + echo "
    \n"; + self::DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes); + echo "

    Aborting...

    \n"; + echo "
    \n"; + exit; + } + } + + public static function Startup($sConfigFile, $bAllowMissingDB = false) + { + self::LoadConfig($sConfigFile); + if (self::DBExists()) + { + CMDBSource::SelectDB(self::$m_sDBName); + } + else + { + if (!$bAllowMissingDB) + { + throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); + } + } + } + + public static function LoadConfig($sConfigFile) + { + $oConfig = new Config($sConfigFile); + + foreach ($oConfig->GetAppModules() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'application', $sToInclude); + } + foreach ($oConfig->GetDataModels() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'business', $sToInclude); + } + foreach ($oConfig->GetAddons() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'addons', $sToInclude); + } + + $sServer = $oConfig->GetDBHost(); + $sUser = $oConfig->GetDBUser(); + $sPwd = $oConfig->GetDBPwd(); + $sSource = $oConfig->GetDBName(); + $sTablePrefix = $oConfig->GetDBSubname(); + + // The include have been included, let's browse the existing classes and + // develop some data based on the proposed model + self::InitClasses($sTablePrefix); + + self::$m_sDBName = $sSource; + self::$m_sTablePrefix = $sTablePrefix; + + CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) + } + + protected static function Plugin($sConfigFile, $sModuleType, $sToInclude) + { + if (!file_exists($sToInclude)) + { + throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sToInclude)); + } + require_once($sToInclude); + } + + // Building an object + // + // + private static $aQueryCacheGetObject = array(); + private static $aQueryCacheGetObjectHits = array(); + public static function GetQueryCacheStatus() + { + $aRes = array(); + $iTotalHits = 0; + foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits) + { + $aRes[] = "$sClass: $iHits"; + $iTotalHits += $iHits; + } + return $iTotalHits.' ('.implode(', ', $aRes).')'; + } + + public static function MakeSingleRow($sClass, $iKey) + { + if (!array_key_exists($sClass, self::$aQueryCacheGetObject)) + { + // NOTE: Quick and VERY dirty caching mechanism which relies on + // the fact that the string '987654321' will never appear in the + // standard query + // This will be replaced for sure with a prepared statement + // or a view... next optimization to come! + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', 987654321, '='); + + $sSQL = self::MakeSelectQuery($oFilter); + self::$aQueryCacheGetObject[$sClass] = $sSQL; + self::$aQueryCacheGetObjectHits[$sClass] = 0; + } + else + { + $sSQL = self::$aQueryCacheGetObject[$sClass]; + self::$aQueryCacheGetObjectHits[$sClass] += 1; +// echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."
    \n"; + } + $sSQL = str_replace('987654321', CMDBSource::Quote($iKey), $sSQL); + $res = CMDBSource::Query($sSQL); + + $aRow = CMDBSource::FetchArray($res); + CMDBSource::FreeResult($res); + if (empty($aRow)) + { + throw new CoreException("No result for the single row query: '$sSQL'"); + } + return $aRow; + } + + public static function GetObjectByRow($sClass, $aRow) + { + self::_check_subclass($sClass); + + // Compound objects: if available, get the final object class + // + if (!array_key_exists("finalclass", $aRow)) + { + // Either this is a bug (forgot to specify a root class with a finalclass field + // Or this is the expected behavior, because the object is not made of several tables + } + elseif (empty($aRow["finalclass"])) + { + // The data is missing in the DB + // @#@ possible improvement: check that the class is valid ! + $sRootClass = self::GetRootClass($sClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + throw new CoreException("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)"); + } + else + { + // do the job for the real target class + $sClass = $aRow["finalclass"]; + } + return new $sClass($aRow); + } + + public static function GetObject($sClass, $iKey) + { + self::_check_subclass($sClass); + $aRow = self::MakeSingleRow($sClass, $iKey); + if (empty($aRow)) + { + return null; + } + return self::GetObjectByRow($sClass, $aRow); + } + + public static function NewObject($sClass) + { + self::_check_subclass($sClass); + return new $sClass(); + } + + public static function BulkDelete(DBObjectSearch $oFilter) + { + $sSQL = self::MakeDeleteQuery($oFilter); + CMDBSource::Query($sSQL); + } + + public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) + { + // $aValues is an array of $sAttCode => $value + $sSQL = self::MakeUpdateQuery($oFilter, $aValues); + CMDBSource::Query($sSQL); + } + + // Links + // + // + public static function EnumReferencedClasses($sClass) + { + self::_check_subclass($sClass); + + // 1-N links (referenced by my class), returns an array of sAttCode=>sClass + $aResult = array(); + foreach(self::$m_aAttribDefs[$sClass] as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aResult[$sAttCode] = $oAttDef->GetTargetClass(); + } + } + return $aResult; + } + public static function EnumReferencingClasses($sClass, $bSkipLinkingClasses = false) + { + self::_check_subclass($sClass); + + $aLinksClasses = self::EnumLinksClasses(); + + // 1-N links (referencing my class), array of sClass => array of sAttcode + $aResult = array(); + foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) + { + if ($bSkipLinkingClasses && in_array($sSomeClass, $aLinksClasses)) continue; + + $aExtKeys = array(); + foreach ($aClassAttributes as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey() && ($oAttDef->GetTargetClass() == $sClass)) + { + $aExtKeys[] = $sAttCode; + } + } + if (count($aExtKeys) != 0) + { + $aResult[$sSomeClass] = $aExtKeys; + } + } + return $aResult; + } + public static function EnumLinksClasses() + { + // Returns a flat array of classes having at least two external keys + $aResult = array(); + foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) + { + $iExtKeyCount = 0; + foreach ($aClassAttributes as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey()) + { + $iExtKeyCount++; + } + } + if ($iExtKeyCount >= 2) + { + $aResult[] = $sSomeClass; + } + } + return $aResult; + } + public static function EnumLinkingClasses($sClass = "") + { + // N-N links, array of sLinkClass => (array of sAttCode=>sClass) + $aResult = array(); + foreach (self::EnumLinksClasses() as $sSomeClass) + { + $aTargets = array(); + $bFoundClass = false; + foreach (self::ListAttributeDefs($sSomeClass) as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey()) + { + $sRemoteClass = $oAttDef->GetTargetClass(); + if (empty($sClass)) + { + $aTargets[$sAttCode] = $sRemoteClass; + } + elseif ($sClass == $sRemoteClass) + { + $bFoundClass = true; + } + else + { + $aTargets[$sAttCode] = $sRemoteClass; + } + } + } + if (empty($sClass) || $bFoundClass) + { + $aResult[$sSomeClass] = $aTargets; + } + } + return $aResult; + } + + public static function GetLinkLabel($sLinkClass, $sAttCode) + { + self::_check_subclass($sLinkClass); + + // e.g. "supported by" (later: $this->GetLinkLabel(), computed on link data!) + return self::GetLabel($sLinkClass, $sAttCode); + } + +} // class MetaModel + + +// Standard attribute lists +MetaModel::RegisterZList("noneditable", array("description"=>"non editable fields", "type"=>"attributes")); + +MetaModel::RegisterZList("details", array("description"=>"All attributes to be displayed for the 'details' of an object", "type"=>"attributes")); +MetaModel::RegisterZList("list", array("description"=>"All attributes to be displayed for a list of objects", "type"=>"attributes")); +MetaModel::RegisterZList("preview", array("description"=>"All attributes visible in preview mode", "type"=>"attributes")); + +MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters")); +MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters")); + + +?> diff --git a/core/oql/build.cmd b/core/oql/build.cmd new file mode 100644 index 0000000000..2187cec66f --- /dev/null +++ b/core/oql/build.cmd @@ -0,0 +1,3 @@ +c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\LexerGenerator\cli.php" oql-lexer.plex +c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\ParserGenerator\cli.php" oql-parser.y +pause \ No newline at end of file diff --git a/core/oql/oql-lexer.php b/core/oql/oql-lexer.php new file mode 100644 index 0000000000..dede6ca1f4 --- /dev/null +++ b/core/oql/oql-lexer.php @@ -0,0 +1,522 @@ +data = $data; + $this->count = 0; + $this->line = 1; + } + + + private $_yy_state = 1; + private $_yy_stack = array(); + + function yylex() + { + return $this->{'yylex' . $this->_yy_state}(); + } + + function yypushstate($state) + { + array_push($this->_yy_stack, $this->_yy_state); + $this->_yy_state = $state; + } + + function yypopstate() + { + $this->_yy_state = array_pop($this->_yy_stack); + } + + function yybegin($state) + { + $this->_yy_state = $state; + } + + + + + function yylex1() + { + if ($this->count >= strlen($this->data)) { + return false; // end of input + } + do { + $rules = array( + '/^[ \t\n]+/', + '/^SELECT/', + '/^AS/', + '/^WHERE/', + '/^JOIN/', + '/^ON/', + '/^\//', + '/^\\*/', + '/^\\+/', + '/^-/', + '/^AND/', + '/^OR/', + '/^,/', + '/^\\(/', + '/^\\)/', + '/^=/', + '/^!=/', + '/^>/', + '/^=/', + '/^<=/', + '/^LIKE/', + '/^NOT LIKE/', + '/^IN/', + '/^NOT IN/', + '/^INTERVAL/', + '/^IF/', + '/^ELT/', + '/^COALESCE/', + '/^CONCAT/', + '/^SUBSTR/', + '/^TRIM/', + '/^DATE/', + '/^DATE_FORMAT/', + '/^CURRENT_DATE/', + '/^NOW/', + '/^TIME/', + '/^TO_DAYS/', + '/^FROM_DAYS/', + '/^YEAR/', + '/^MONTH/', + '/^DAY/', + '/^DATE_ADD/', + '/^DATE_SUB/', + '/^ROUND/', + '/^FLOOR/', + '/^[0-9]+|0x[0-9a-fA-F]+/', + '/^\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/', + '/^([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/', + '/^\\./', + ); + $match = false; + foreach ($rules as $index => $rule) { + if (preg_match($rule, substr($this->data, $this->count), $yymatches)) { + if ($match) { + if (strlen($yymatches[0]) > strlen($match[0][0])) { + $match = array($yymatches, $index); // matches, token + } + } else { + $match = array($yymatches, $index); + } + } + } + if (!$match) { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->count]); + } + $this->token = $match[1]; + $this->value = $match[0][0]; + $yysubmatches = $match[0]; + array_shift($yysubmatches); + if (!$yysubmatches) { + $yysubmatches = array(); + } + $r = $this->{'yy_r1_' . $this->token}($yysubmatches); + if ($r === null) { + $this->count += strlen($this->value); + $this->line += substr_count($this->value, "\n"); + // accept this token + return true; + } elseif ($r === true) { + // we have changed state + // process this token in the new state + return $this->yylex(); + } elseif ($r === false) { + $this->count += strlen($this->value); + $this->line += substr_count($this->value, "\n"); + if ($this->count >= strlen($this->data)) { + return false; // end of input + } + // skip this token + continue; + } else { + $yy_yymore_patterns = array_slice($rules, $this->token, true); + // yymore is needed + do { + if (!isset($yy_yymore_patterns[$this->token])) { + throw new Exception('cannot do yymore for the last token'); + } + $match = false; + foreach ($yy_yymore_patterns[$this->token] as $index => $rule) { + if (preg_match('/' . $rule . '/', + substr($this->data, $this->count), $yymatches)) { + $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns + if ($match) { + if (strlen($yymatches[0]) > strlen($match[0][0])) { + $match = array($yymatches, $index); // matches, token + } + } else { + $match = array($yymatches, $index); + } + } + } + if (!$match) { + throw new Exception('Unexpected input at line' . $this->line . + ': ' . $this->data[$this->count]); + } + $this->token = $match[1]; + $this->value = $match[0][0]; + $yysubmatches = $match[0]; + array_shift($yysubmatches); + if (!$yysubmatches) { + $yysubmatches = array(); + } + $this->line = substr_count($this->value, "\n"); + $r = $this->{'yy_r1_' . $this->token}(); + } while ($r !== null || !$r); + if ($r === true) { + // we have changed state + // process this token in the new state + return $this->yylex(); + } else { + // accept + $this->count += strlen($this->value); + $this->line += substr_count($this->value, "\n"); + return true; + } + } + } while (true); + + } // end function + + function yy_r1_0($yy_subpatterns) + { + + return false; + } + function yy_r1_1($yy_subpatterns) + { + + $this->token = OQLParser::SELECT; + } + function yy_r1_2($yy_subpatterns) + { + + $this->token = OQLParser::AS_ALIAS; + } + function yy_r1_3($yy_subpatterns) + { + + $this->token = OQLParser::WHERE; + } + function yy_r1_4($yy_subpatterns) + { + + $this->token = OQLParser::JOIN; + } + function yy_r1_5($yy_subpatterns) + { + + $this->token = OQLParser::ON; + } + function yy_r1_6($yy_subpatterns) + { + + $this->token = OQLParser::MATH_DIV; + } + function yy_r1_7($yy_subpatterns) + { + + $this->token = OQLParser::MATH_MULT; + } + function yy_r1_8($yy_subpatterns) + { + + $this->token = OQLParser::MATH_PLUS; + } + function yy_r1_9($yy_subpatterns) + { + + $this->token = OQLParser::MATH_MINUS; + } + function yy_r1_10($yy_subpatterns) + { + + $this->token = OQLParser::LOG_AND; + } + function yy_r1_11($yy_subpatterns) + { + + $this->token = OQLParser::LOG_OR; + } + function yy_r1_12($yy_subpatterns) + { + + $this->token = OQLParser::COMA; + } + function yy_r1_13($yy_subpatterns) + { + + $this->token = OQLParser::PAR_OPEN; + } + function yy_r1_14($yy_subpatterns) + { + + $this->token = OQLParser::PAR_CLOSE; + } + function yy_r1_15($yy_subpatterns) + { + + $this->token = OQLParser::EQ; + } + function yy_r1_16($yy_subpatterns) + { + + $this->token = OQLParser::NOT_EQ; + } + function yy_r1_17($yy_subpatterns) + { + + $this->token = OQLParser::GT; + } + function yy_r1_18($yy_subpatterns) + { + + $this->token = OQLParser::LT; + } + function yy_r1_19($yy_subpatterns) + { + + $this->token = OQLParser::GE; + } + function yy_r1_20($yy_subpatterns) + { + + $this->token = OQLParser::LE; + } + function yy_r1_21($yy_subpatterns) + { + + $this->token = OQLParser::LIKE; + } + function yy_r1_22($yy_subpatterns) + { + + $this->token = OQLParser::NOT_LIKE; + } + function yy_r1_23($yy_subpatterns) + { + + $this->token = OQLParser::IN; + } + function yy_r1_24($yy_subpatterns) + { + + $this->token = OQLParser::NOT_IN; + } + function yy_r1_25($yy_subpatterns) + { + + $this->token = OQLParser::INTERVAL; + } + function yy_r1_26($yy_subpatterns) + { + + $this->token = OQLParser::F_IF; + } + function yy_r1_27($yy_subpatterns) + { + + $this->token = OQLParser::F_ELT; + } + function yy_r1_28($yy_subpatterns) + { + + $this->token = OQLParser::F_COALESCE; + } + function yy_r1_29($yy_subpatterns) + { + + $this->token = OQLParser::F_CONCAT; + } + function yy_r1_30($yy_subpatterns) + { + + $this->token = OQLParser::F_SUBSTR; + } + function yy_r1_31($yy_subpatterns) + { + + $this->token = OQLParser::F_TRIM; + } + function yy_r1_32($yy_subpatterns) + { + + $this->token = OQLParser::F_DATE; + } + function yy_r1_33($yy_subpatterns) + { + + $this->token = OQLParser::F_DATE_FORMAT; + } + function yy_r1_34($yy_subpatterns) + { + + $this->token = OQLParser::F_CURRENT_DATE; + } + function yy_r1_35($yy_subpatterns) + { + + $this->token = OQLParser::F_NOW; + } + function yy_r1_36($yy_subpatterns) + { + + $this->token = OQLParser::F_TIME; + } + function yy_r1_37($yy_subpatterns) + { + + $this->token = OQLParser::F_TO_DAYS; + } + function yy_r1_38($yy_subpatterns) + { + + $this->token = OQLParser::F_FROM_DAYS; + } + function yy_r1_39($yy_subpatterns) + { + + $this->token = OQLParser::F_YEAR; + } + function yy_r1_40($yy_subpatterns) + { + + $this->token = OQLParser::F_MONTH; + } + function yy_r1_41($yy_subpatterns) + { + + $this->token = OQLParser::F_DAY; + } + function yy_r1_42($yy_subpatterns) + { + + $this->token = OQLParser::F_DATE_ADD; + } + function yy_r1_43($yy_subpatterns) + { + + $this->token = OQLParser::F_DATE_SUB; + } + function yy_r1_44($yy_subpatterns) + { + + $this->token = OQLParser::F_ROUND; + } + function yy_r1_45($yy_subpatterns) + { + + $this->token = OQLParser::F_FLOOR; + } + function yy_r1_46($yy_subpatterns) + { + + $this->token = OQLParser::NUMVAL; + } + function yy_r1_47($yy_subpatterns) + { + + $this->token = OQLParser::STRVAL; + } + function yy_r1_48($yy_subpatterns) + { + + $this->token = OQLParser::NAME; + } + function yy_r1_49($yy_subpatterns) + { + + $this->token = OQLParser::DOT; + } + + +} + +define('UNEXPECTED_INPUT_AT_LINE', 'Unexpected input at line'); + +class OQLLexerException extends OQLException +{ + public function __construct($sInput, $iLine, $iCol, $sUnexpected) + { + parent::__construct("Syntax error", $sInput, $iLine, $iCol, $sUnexpected); + } +} + +class OQLLexer extends OQLLexerRaw +{ + public function getTokenPos() + { + return max(0, $this->count - strlen($this->value)); + } + + function yylex() + { + try + { + return parent::yylex(); + } + catch (Exception $e) + { + $sMessage = $e->getMessage(); + if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) + { + $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); + if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) + { + $iLine = $aMatches[1]; + $sUnexpected = $aMatches[2]; + throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); + } + } + // Default: forward the exception + throw $e; + } + } +} +?> diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex new file mode 100644 index 0000000000..8196f540a5 --- /dev/null +++ b/core/oql/oql-lexer.plex @@ -0,0 +1,305 @@ +data = $data; + $this->count = 0; + $this->line = 1; + } + +/*!lex2php +%input $this->data +%counter $this->count +%token $this->token +%value $this->value +%line $this->line +%matchlongest 1 +whitespace = /[ \t\n]+/ +select = "SELECT" +as_alias = "AS" +where = "WHERE" +join = "JOIN" +on = "ON" +coma = "," +par_open = "(" +par_close = ")" +math_div = "/" +math_mult = "*" +math_plus = "+" +math_minus = "-" +log_and = "AND" +log_or = "OR" +eq = "=" +not_eq = "!=" +gt = ">" +lt = "<" +ge = ">=" +le = "<=" +like = "LIKE" +not_like = "NOT LIKE" +in = "IN" +not_in = "NOT IN" +interval = "INTERVAL" +f_if = "IF" +f_elt = "ELT" +f_coalesce = "COALESCE" +f_concat = "CONCAT" +f_substr = "SUBSTR" +f_trim = "TRIM" +f_date = "DATE" +f_date_format = "DATE_FORMAT" +f_current_date = "CURRENT_DATE" +f_now = "NOW" +f_time = "TIME" +f_to_days = "TO_DAYS" +f_from_days = "FROM_DAYS" +f_year = "YEAR" +f_month = "MONTH" +f_day = "DAY" +f_date_add = "DATE_ADD" +f_date_sub = "DATE_SUB" +f_round = "ROUND" +f_floor = "FLOOR" +numval = /[0-9]+|0x[0-9a-fA-F]+/ +strval = /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/ +name = /([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/ +dot = "." +*/ + +/*!lex2php +whitespace { + return false; +} +select { + $this->token = OQLParser::SELECT; +} +as_alias { + $this->token = OQLParser::AS_ALIAS; +} +where { + $this->token = OQLParser::WHERE; +} +join { + $this->token = OQLParser::JOIN; +} +on { + $this->token = OQLParser::ON; +} +math_div { + $this->token = OQLParser::MATH_DIV; +} +math_mult { + $this->token = OQLParser::MATH_MULT; +} +math_plus { + $this->token = OQLParser::MATH_PLUS; +} +math_minus { + $this->token = OQLParser::MATH_MINUS; +} +log_and { + $this->token = OQLParser::LOG_AND; +} +log_or { + $this->token = OQLParser::LOG_OR; +} +coma { + $this->token = OQLParser::COMA; +} +par_open { + $this->token = OQLParser::PAR_OPEN; +} +par_close { + $this->token = OQLParser::PAR_CLOSE; +} +eq { + $this->token = OQLParser::EQ; +} +not_eq { + $this->token = OQLParser::NOT_EQ; +} +gt { + $this->token = OQLParser::GT; +} +lt { + $this->token = OQLParser::LT; +} +ge { + $this->token = OQLParser::GE; +} +le { + $this->token = OQLParser::LE; +} +like { + $this->token = OQLParser::LIKE; +} +not_like { + $this->token = OQLParser::NOT_LIKE; +} +in { + $this->token = OQLParser::IN; +} +not_in { + $this->token = OQLParser::NOT_IN; +} +interval { + $this->token = OQLParser::INTERVAL; +} +f_if { + $this->token = OQLParser::F_IF; +} +f_elt { + $this->token = OQLParser::F_ELT; +} +f_coalesce { + $this->token = OQLParser::F_COALESCE; +} +f_concat { + $this->token = OQLParser::F_CONCAT; +} +f_substr { + $this->token = OQLParser::F_SUBSTR; +} +f_trim { + $this->token = OQLParser::F_TRIM; +} +f_date { + $this->token = OQLParser::F_DATE; +} +f_date_format { + $this->token = OQLParser::F_DATE_FORMAT; +} +f_current_date { + $this->token = OQLParser::F_CURRENT_DATE; +} +f_now { + $this->token = OQLParser::F_NOW; +} +f_time { + $this->token = OQLParser::F_TIME; +} +f_to_days { + $this->token = OQLParser::F_TO_DAYS; +} +f_from_days { + $this->token = OQLParser::F_FROM_DAYS; +} +f_year { + $this->token = OQLParser::F_YEAR; +} +f_month { + $this->token = OQLParser::F_MONTH; +} +f_day { + $this->token = OQLParser::F_DAY; +} +f_date_add { + $this->token = OQLParser::F_DATE_ADD; +} +f_date_sub { + $this->token = OQLParser::F_DATE_SUB; +} +f_round { + $this->token = OQLParser::F_ROUND; +} +f_floor { + $this->token = OQLParser::F_FLOOR; +} +numval { + $this->token = OQLParser::NUMVAL; +} +strval { + $this->token = OQLParser::STRVAL; +} +name { + $this->token = OQLParser::NAME; +} +dot { + $this->token = OQLParser::DOT; +} +*/ + +} + +define('UNEXPECTED_INPUT_AT_LINE', 'Unexpected input at line'); + +class OQLLexerException extends OQLException +{ + public function __construct($sInput, $iLine, $iCol, $sUnexpected) + { + parent::__construct("Syntax error", $sInput, $iLine, $iCol, $sUnexpected); + } +} + +class OQLLexer extends OQLLexerRaw +{ + public function getTokenPos() + { + return max(0, $this->count - strlen($this->value)); + } + + function yylex() + { + try + { + return parent::yylex(); + } + catch (Exception $e) + { + $sMessage = $e->getMessage(); + if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) + { + $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); + if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) + { + $iLine = $aMatches[1]; + $sUnexpected = $aMatches[2]; + throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); + } + } + // Default: forward the exception + throw $e; + } + } +} +?> diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php new file mode 100644 index 0000000000..c3995650c9 --- /dev/null +++ b/core/oql/oql-parser.php @@ -0,0 +1,1669 @@ +string = $s->string; + $this->metadata = $s->metadata; + } else { + $this->string = (string) $s; + if ($m instanceof OQLParser_yyToken) { + $this->metadata = $m->metadata; + } elseif (is_array($m)) { + $this->metadata = $m; + } + } + } + + function __toString() + { + return $this->_string; + } + + function offsetExists($offset) + { + return isset($this->metadata[$offset]); + } + + function offsetGet($offset) + { + return $this->metadata[$offset]; + } + + function offsetSet($offset, $value) + { + if ($offset === null) { + if (isset($value[0])) { + $x = ($value instanceof OQLParser_yyToken) ? + $value->metadata : $value; + $this->metadata = array_merge($this->metadata, $x); + return; + } + $offset = count($this->metadata); + } + if ($value === null) { + return; + } + if ($value instanceof OQLParser_yyToken) { + if ($value->metadata) { + $this->metadata[$offset] = $value->metadata; + } + } elseif ($value) { + $this->metadata[$offset] = $value; + } + } + + function offsetUnset($offset) + { + unset($this->metadata[$offset]); + } +} + +/** The following structure represents a single element of the + * parser's stack. Information stored includes: + * + * + The state number for the parser at this level of the stack. + * + * + The value of the token stored at this level of the stack. + * (In other words, the "major" token.) + * + * + The semantic value stored at this level of the stack. This is + * the information used by the action routines in the grammar. + * It is sometimes called the "minor" token. + */ +class OQLParser_yyStackEntry +{ + public $stateno; /* The state-number */ + public $major; /* The major token value. This is the code + ** number for the token at this stack level */ + public $minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; + +// code external to the class is included here + +// declare_class is output here +#line 24 "oql-parser.y" +class OQLParserRaw#line 102 "oql-parser.php" +{ +/* First off, code is included which follows the "include_class" declaration +** in the input file. */ + +/* Next is all token values, as class constants +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ + const SELECT = 1; + const AS_ALIAS = 2; + const WHERE = 3; + const JOIN = 4; + const ON = 5; + const EQ = 6; + const PAR_OPEN = 7; + const PAR_CLOSE = 8; + const COMA = 9; + const INTERVAL = 10; + const F_DAY = 11; + const F_MONTH = 12; + const F_YEAR = 13; + const DOT = 14; + const NAME = 15; + const NUMVAL = 16; + const STRVAL = 17; + const NOT_EQ = 18; + const LOG_AND = 19; + const LOG_OR = 20; + const MATH_DIV = 21; + const MATH_MULT = 22; + const MATH_PLUS = 23; + const MATH_MINUS = 24; + const GT = 25; + const LT = 26; + const GE = 27; + const LE = 28; + const LIKE = 29; + const NOT_LIKE = 30; + const IN = 31; + const NOT_IN = 32; + const F_IF = 33; + const F_ELT = 34; + const F_COALESCE = 35; + const F_CONCAT = 36; + const F_SUBSTR = 37; + const F_TRIM = 38; + const F_DATE = 39; + const F_DATE_FORMAT = 40; + const F_CURRENT_DATE = 41; + const F_NOW = 42; + const F_TIME = 43; + const F_TO_DAYS = 44; + const F_FROM_DAYS = 45; + const F_DATE_ADD = 46; + const F_DATE_SUB = 47; + const F_ROUND = 48; + const F_FLOOR = 49; + const YY_NO_ACTION = 205; + const YY_ACCEPT_ACTION = 204; + const YY_ERROR_ACTION = 203; + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < self::YYNSTATE Shift N. That is, +** push the lookahead +** token onto the stack +** and goto state N. +** +** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE. +** +** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred. +** +** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its +** input. (and concludes parsing) +** +** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large static array $yy_action. +** Given state S and lookahead X, the action is computed as +** +** self::$yy_action[self::$yy_shift_ofst[S] + X ] +** +** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value +** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if +** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that +** the action is not in the table and that self::$yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the static $yy_reduce_ofst array is used in place of +** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of +** self::YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** self::$yy_action A single table containing all actions. +** self::$yy_lookahead A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** self::$yy_shift_ofst For each state, the offset into self::$yy_action for +** shifting terminals. +** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for +** shifting non-terminals after a reduce. +** self::$yy_default Default action for each state. +*/ + const YY_SZ_ACTTAB = 419; +static public $yy_action = array( + /* 0 */ 5, 57, 8, 4, 95, 96, 97, 6, 93, 76, + /* 10 */ 77, 89, 2, 53, 86, 50, 54, 25, 52, 55, + /* 20 */ 51, 46, 47, 49, 56, 70, 94, 110, 109, 108, + /* 30 */ 107, 111, 112, 115, 114, 113, 106, 71, 98, 99, + /* 40 */ 100, 104, 103, 26, 66, 38, 42, 9, 81, 5, + /* 50 */ 62, 44, 82, 95, 96, 97, 3, 93, 76, 77, + /* 60 */ 39, 102, 92, 75, 74, 73, 72, 75, 74, 73, + /* 70 */ 72, 10, 66, 41, 91, 94, 110, 109, 108, 107, + /* 80 */ 111, 112, 115, 114, 113, 106, 71, 98, 99, 100, + /* 90 */ 104, 103, 5, 63, 90, 22, 95, 96, 97, 61, + /* 100 */ 93, 76, 77, 65, 64, 60, 83, 11, 80, 79, + /* 110 */ 91, 33, 91, 22, 21, 18, 16, 12, 94, 110, + /* 120 */ 109, 108, 107, 111, 112, 115, 114, 113, 106, 71, + /* 130 */ 98, 99, 100, 104, 103, 204, 105, 87, 42, 23, + /* 140 */ 43, 24, 66, 88, 30, 28, 84, 45, 36, 6, + /* 150 */ 22, 20, 58, 15, 32, 37, 1, 76, 77, 101, + /* 160 */ 116, 75, 74, 73, 72, 41, 13, 66, 7, 160, + /* 170 */ 67, 93, 24, 42, 35, 78, 173, 173, 88, 34, + /* 180 */ 28, 84, 45, 40, 42, 173, 20, 173, 15, 69, + /* 190 */ 37, 173, 173, 173, 59, 173, 75, 74, 73, 72, + /* 200 */ 41, 42, 173, 173, 173, 173, 88, 34, 28, 84, + /* 210 */ 45, 41, 173, 173, 20, 173, 15, 173, 37, 173, + /* 220 */ 173, 173, 68, 173, 75, 74, 73, 72, 41, 173, + /* 230 */ 173, 173, 85, 42, 173, 173, 173, 173, 88, 30, + /* 240 */ 28, 84, 45, 173, 173, 173, 20, 173, 15, 173, + /* 250 */ 37, 173, 173, 173, 173, 173, 75, 74, 73, 72, + /* 260 */ 41, 42, 173, 173, 173, 173, 88, 17, 28, 84, + /* 270 */ 45, 173, 173, 173, 20, 173, 15, 42, 37, 173, + /* 280 */ 173, 48, 44, 173, 75, 74, 73, 72, 41, 173, + /* 290 */ 173, 173, 173, 42, 173, 173, 173, 173, 88, 27, + /* 300 */ 28, 84, 45, 173, 41, 173, 20, 173, 15, 173, + /* 310 */ 37, 173, 173, 173, 173, 173, 75, 74, 73, 72, + /* 320 */ 41, 42, 173, 173, 173, 173, 88, 173, 28, 84, + /* 330 */ 45, 173, 173, 173, 20, 173, 15, 173, 31, 173, + /* 340 */ 173, 173, 173, 173, 75, 74, 73, 72, 41, 173, + /* 350 */ 173, 173, 173, 42, 173, 173, 173, 173, 88, 173, + /* 360 */ 28, 84, 45, 173, 173, 173, 20, 173, 14, 173, + /* 370 */ 173, 173, 173, 173, 173, 173, 75, 74, 73, 72, + /* 380 */ 41, 42, 173, 173, 173, 173, 88, 173, 28, 84, + /* 390 */ 45, 42, 173, 173, 19, 173, 88, 173, 29, 84, + /* 400 */ 45, 173, 173, 173, 75, 74, 73, 72, 41, 173, + /* 410 */ 173, 173, 173, 173, 75, 74, 73, 72, 41, + ); + static public $yy_lookahead = array( + /* 0 */ 7, 6, 70, 10, 11, 12, 13, 73, 15, 16, + /* 10 */ 17, 8, 9, 18, 56, 83, 84, 54, 23, 24, + /* 20 */ 25, 26, 27, 28, 29, 30, 33, 34, 35, 36, + /* 30 */ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + /* 40 */ 47, 48, 49, 1, 81, 54, 54, 68, 62, 7, + /* 50 */ 58, 59, 62, 11, 12, 13, 3, 15, 16, 17, + /* 60 */ 74, 82, 8, 77, 78, 79, 80, 77, 78, 79, + /* 70 */ 80, 7, 81, 81, 20, 33, 34, 35, 36, 37, + /* 80 */ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + /* 90 */ 48, 49, 7, 55, 66, 57, 11, 12, 13, 56, + /* 100 */ 15, 16, 17, 11, 12, 13, 8, 9, 31, 32, + /* 110 */ 20, 55, 20, 57, 2, 54, 6, 5, 33, 34, + /* 120 */ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + /* 130 */ 45, 46, 47, 48, 49, 51, 52, 53, 54, 2, + /* 140 */ 54, 4, 81, 59, 60, 61, 62, 63, 55, 73, + /* 150 */ 57, 67, 76, 69, 14, 71, 7, 16, 17, 21, + /* 160 */ 22, 77, 78, 79, 80, 81, 5, 81, 72, 14, + /* 170 */ 19, 15, 4, 54, 65, 81, 85, 85, 59, 60, + /* 180 */ 61, 62, 63, 64, 54, 85, 67, 85, 69, 59, + /* 190 */ 71, 85, 85, 85, 75, 85, 77, 78, 79, 80, + /* 200 */ 81, 54, 85, 85, 85, 85, 59, 60, 61, 62, + /* 210 */ 63, 81, 85, 85, 67, 85, 69, 85, 71, 85, + /* 220 */ 85, 85, 75, 85, 77, 78, 79, 80, 81, 85, + /* 230 */ 85, 85, 53, 54, 85, 85, 85, 85, 59, 60, + /* 240 */ 61, 62, 63, 85, 85, 85, 67, 85, 69, 85, + /* 250 */ 71, 85, 85, 85, 85, 85, 77, 78, 79, 80, + /* 260 */ 81, 54, 85, 85, 85, 85, 59, 60, 61, 62, + /* 270 */ 63, 85, 85, 85, 67, 85, 69, 54, 71, 85, + /* 280 */ 85, 58, 59, 85, 77, 78, 79, 80, 81, 85, + /* 290 */ 85, 85, 85, 54, 85, 85, 85, 85, 59, 60, + /* 300 */ 61, 62, 63, 85, 81, 85, 67, 85, 69, 85, + /* 310 */ 71, 85, 85, 85, 85, 85, 77, 78, 79, 80, + /* 320 */ 81, 54, 85, 85, 85, 85, 59, 85, 61, 62, + /* 330 */ 63, 85, 85, 85, 67, 85, 69, 85, 71, 85, + /* 340 */ 85, 85, 85, 85, 77, 78, 79, 80, 81, 85, + /* 350 */ 85, 85, 85, 54, 85, 85, 85, 85, 59, 85, + /* 360 */ 61, 62, 63, 85, 85, 85, 67, 85, 69, 85, + /* 370 */ 85, 85, 85, 85, 85, 85, 77, 78, 79, 80, + /* 380 */ 81, 54, 85, 85, 85, 85, 59, 85, 61, 62, + /* 390 */ 63, 54, 85, 85, 67, 85, 59, 85, 61, 62, + /* 400 */ 63, 85, 85, 85, 77, 78, 79, 80, 81, 85, + /* 410 */ 85, 85, 85, 85, 77, 78, 79, 80, 81, +); + const YY_SHIFT_USE_DFLT = -8; + const YY_SHIFT_MAX = 45; + static public $yy_shift_ofst = array( + /* 0 */ 42, -7, -7, 85, 85, 85, 85, 85, 85, 85, + /* 10 */ 141, 141, 156, 156, -5, -5, 156, 92, 137, 138, + /* 20 */ 138, 156, 168, 156, 156, 168, 156, 54, 77, 77, + /* 30 */ 90, 151, 156, 53, 90, 64, 53, 151, 112, 98, + /* 40 */ 3, 155, 140, 161, 110, 149, +); + const YY_REDUCE_USE_DFLT = -69; + const YY_REDUCE_MAX = 37; + static public $yy_reduce_ofst = array( + /* 0 */ 84, 119, 147, 179, 207, 239, 267, 299, 327, 337, + /* 10 */ -14, -10, 223, -8, -68, -68, 130, 76, 93, -21, + /* 20 */ -21, 86, 38, -37, -9, 56, 61, -66, 109, 109, + /* 30 */ -66, 96, 94, 43, -66, 28, -42, 96, +); + static public $yyExpectedTokens = array( + /* 0 */ array(1, 7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 1 */ array(7, 10, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 2 */ array(7, 10, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 3 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 4 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 5 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 6 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 7 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 8 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 9 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), + /* 10 */ array(16, 17, ), + /* 11 */ array(16, 17, ), + /* 12 */ array(15, ), + /* 13 */ array(15, ), + /* 14 */ array(6, 18, 23, 24, 25, 26, 27, 28, 29, 30, ), + /* 15 */ array(6, 18, 23, 24, 25, 26, 27, 28, 29, 30, ), + /* 16 */ array(15, ), + /* 17 */ array(11, 12, 13, 20, ), + /* 18 */ array(2, 4, ), + /* 19 */ array(21, 22, ), + /* 20 */ array(21, 22, ), + /* 21 */ array(15, ), + /* 22 */ array(4, ), + /* 23 */ array(15, ), + /* 24 */ array(15, ), + /* 25 */ array(4, ), + /* 26 */ array(15, ), + /* 27 */ array(8, 20, ), + /* 28 */ array(31, 32, ), + /* 29 */ array(31, 32, ), + /* 30 */ array(20, ), + /* 31 */ array(19, ), + /* 32 */ array(15, ), + /* 33 */ array(3, ), + /* 34 */ array(20, ), + /* 35 */ array(7, ), + /* 36 */ array(3, ), + /* 37 */ array(19, ), + /* 38 */ array(2, 5, ), + /* 39 */ array(8, 9, ), + /* 40 */ array(8, 9, ), + /* 41 */ array(14, ), + /* 42 */ array(14, ), + /* 43 */ array(5, ), + /* 44 */ array(6, ), + /* 45 */ array(7, ), + /* 46 */ array(), + /* 47 */ array(), + /* 48 */ array(), + /* 49 */ array(), + /* 50 */ array(), + /* 51 */ array(), + /* 52 */ array(), + /* 53 */ array(), + /* 54 */ array(), + /* 55 */ array(), + /* 56 */ array(), + /* 57 */ array(), + /* 58 */ array(), + /* 59 */ array(), + /* 60 */ array(), + /* 61 */ array(), + /* 62 */ array(), + /* 63 */ array(), + /* 64 */ array(), + /* 65 */ array(), + /* 66 */ array(), + /* 67 */ array(), + /* 68 */ array(), + /* 69 */ array(), + /* 70 */ array(), + /* 71 */ array(), + /* 72 */ array(), + /* 73 */ array(), + /* 74 */ array(), + /* 75 */ array(), + /* 76 */ array(), + /* 77 */ array(), + /* 78 */ array(), + /* 79 */ array(), + /* 80 */ array(), + /* 81 */ array(), + /* 82 */ array(), + /* 83 */ array(), + /* 84 */ array(), + /* 85 */ array(), + /* 86 */ array(), + /* 87 */ array(), + /* 88 */ array(), + /* 89 */ array(), + /* 90 */ array(), + /* 91 */ array(), + /* 92 */ array(), + /* 93 */ array(), + /* 94 */ array(), + /* 95 */ array(), + /* 96 */ array(), + /* 97 */ array(), + /* 98 */ array(), + /* 99 */ array(), + /* 100 */ array(), + /* 101 */ array(), + /* 102 */ array(), + /* 103 */ array(), + /* 104 */ array(), + /* 105 */ array(), + /* 106 */ array(), + /* 107 */ array(), + /* 108 */ array(), + /* 109 */ array(), + /* 110 */ array(), + /* 111 */ array(), + /* 112 */ array(), + /* 113 */ array(), + /* 114 */ array(), + /* 115 */ array(), + /* 116 */ array(), +); + static public $yy_default = array( + /* 0 */ 203, 146, 203, 203, 203, 203, 203, 203, 203, 203, + /* 10 */ 203, 203, 203, 203, 140, 139, 203, 203, 125, 138, + /* 20 */ 137, 203, 124, 203, 203, 125, 203, 203, 135, 136, + /* 30 */ 129, 142, 203, 122, 149, 203, 122, 141, 203, 203, + /* 40 */ 203, 158, 203, 203, 203, 203, 176, 177, 127, 178, + /* 50 */ 165, 175, 173, 168, 166, 174, 179, 167, 150, 147, + /* 60 */ 153, 120, 126, 123, 152, 151, 160, 169, 148, 128, + /* 70 */ 180, 194, 157, 156, 155, 154, 162, 163, 159, 182, + /* 80 */ 181, 144, 145, 143, 130, 121, 119, 118, 131, 132, + /* 90 */ 134, 170, 133, 161, 183, 198, 197, 196, 195, 199, + /* 100 */ 200, 171, 164, 202, 201, 117, 193, 187, 186, 185, + /* 110 */ 184, 188, 189, 192, 191, 190, 172, +); +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** self::YYNOCODE is a number which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** self::YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** self::YYSTACKDEPTH is the maximum depth of the parser's stack. +** self::YYNSTATE the combined number of states. +** self::YYNRULE the number of rules in the grammar +** self::YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ + const YYNOCODE = 86; + const YYSTACKDEPTH = 100; + const YYNSTATE = 117; + const YYNRULE = 86; + const YYERRORSYMBOL = 50; + const YYERRSYMDT = 'yy0'; + const YYFALLBACK = 0; + /** The next table maps tokens into fallback tokens. If a construct + * like the following: + * + * %fallback ID X Y Z. + * + * appears in the grammer, then ID becomes a fallback token for X, Y, + * and Z. Whenever one of the tokens X, Y, or Z is input to the parser + * but it does not parse, the type of the token is changed to ID and + * the parse is retried before an error is thrown. + */ + static public $yyFallback = array( + ); + /** + * Turn parser tracing on by giving a stream to which to write the trace + * and a prompt to preface each trace message. Tracing is turned off + * by making either argument NULL + * + * Inputs: + * + * - A stream resource to which trace output should be written. + * If NULL, then tracing is turned off. + * - A prefix string written at the beginning of every + * line of trace output. If NULL, then tracing is + * turned off. + * + * Outputs: + * + * - None. + * @param resource + * @param string + */ + static function Trace($TraceFILE, $zTracePrompt) + { + if (!$TraceFILE) { + $zTracePrompt = 0; + } elseif (!$zTracePrompt) { + $TraceFILE = 0; + } + self::$yyTraceFILE = $TraceFILE; + self::$yyTracePrompt = $zTracePrompt; + } + + /** + * Output debug information to output (php://output stream) + */ + static function PrintTrace() + { + self::$yyTraceFILE = fopen('php://output', 'w'); + self::$yyTracePrompt = ''; + } + + /** + * @var resource|0 + */ + static public $yyTraceFILE; + /** + * String to prepend to debug output + * @var string|0 + */ + static public $yyTracePrompt; + /** + * @var int + */ + public $yyidx; /* Index of top element in stack */ + /** + * @var int + */ + public $yyerrcnt; /* Shifts left before out of the error */ + /** + * @var array + */ + public $yystack = array(); /* The parser's stack */ + + /** + * For tracing shifts, the names of all terminals and nonterminals + * are required. The following table supplies these names + * @var array + */ + static public $yyTokenName = array( + '$', 'SELECT', 'AS_ALIAS', 'WHERE', + 'JOIN', 'ON', 'EQ', 'PAR_OPEN', + 'PAR_CLOSE', 'COMA', 'INTERVAL', 'F_DAY', + 'F_MONTH', 'F_YEAR', 'DOT', 'NAME', + 'NUMVAL', 'STRVAL', 'NOT_EQ', 'LOG_AND', + 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', + 'MATH_MINUS', 'GT', 'LT', 'GE', + 'LE', 'LIKE', 'NOT_LIKE', 'IN', + 'NOT_IN', 'F_IF', 'F_ELT', 'F_COALESCE', + 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', 'F_DATE', + 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', 'F_TIME', + 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', 'F_DATE_SUB', + 'F_ROUND', 'F_FLOOR', 'error', 'result', + 'query', 'condition', 'class_name', 'join_statement', + 'where_statement', 'join_item', 'join_condition', 'field_id', + 'expression_prio4', 'expression_basic', 'scalar', 'func_name', + 'arg_list', 'list_operator', 'list', 'expression_prio1', + 'operator1', 'expression_prio2', 'operator2', 'expression_prio3', + 'operator3', 'operator4', 'scalar_list', 'argument', + 'interval_unit', 'num_scalar', 'str_scalar', 'num_value', + 'str_value', 'name', 'num_operator1', 'num_operator2', + 'str_operator', + ); + + /** + * For tracing reduce actions, the names of all rules are required. + * @var array + */ + static public $yyRuleName = array( + /* 0 */ "result ::= query", + /* 1 */ "result ::= condition", + /* 2 */ "query ::= SELECT class_name join_statement where_statement", + /* 3 */ "query ::= SELECT class_name AS_ALIAS class_name join_statement where_statement", + /* 4 */ "where_statement ::= WHERE condition", + /* 5 */ "where_statement ::=", + /* 6 */ "join_statement ::= join_item join_statement", + /* 7 */ "join_statement ::= join_item", + /* 8 */ "join_statement ::=", + /* 9 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", + /* 10 */ "join_item ::= JOIN class_name ON join_condition", + /* 11 */ "join_condition ::= field_id EQ field_id", + /* 12 */ "condition ::= expression_prio4", + /* 13 */ "expression_basic ::= scalar", + /* 14 */ "expression_basic ::= field_id", + /* 15 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", + /* 16 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", + /* 17 */ "expression_basic ::= expression_basic list_operator list", + /* 18 */ "expression_prio1 ::= expression_basic", + /* 19 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", + /* 20 */ "expression_prio2 ::= expression_prio1", + /* 21 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", + /* 22 */ "expression_prio3 ::= expression_prio2", + /* 23 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", + /* 24 */ "expression_prio4 ::= expression_prio3", + /* 25 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", + /* 26 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", + /* 27 */ "scalar_list ::= scalar", + /* 28 */ "scalar_list ::= scalar_list COMA scalar", + /* 29 */ "arg_list ::=", + /* 30 */ "arg_list ::= argument", + /* 31 */ "arg_list ::= arg_list COMA argument", + /* 32 */ "argument ::= expression_prio4", + /* 33 */ "argument ::= INTERVAL expression_prio4 interval_unit", + /* 34 */ "interval_unit ::= F_DAY", + /* 35 */ "interval_unit ::= F_MONTH", + /* 36 */ "interval_unit ::= F_YEAR", + /* 37 */ "scalar ::= num_scalar", + /* 38 */ "scalar ::= str_scalar", + /* 39 */ "num_scalar ::= num_value", + /* 40 */ "str_scalar ::= str_value", + /* 41 */ "field_id ::= name", + /* 42 */ "field_id ::= class_name DOT name", + /* 43 */ "class_name ::= name", + /* 44 */ "name ::= NAME", + /* 45 */ "num_value ::= NUMVAL", + /* 46 */ "str_value ::= STRVAL", + /* 47 */ "operator1 ::= num_operator1", + /* 48 */ "operator2 ::= num_operator2", + /* 49 */ "operator2 ::= str_operator", + /* 50 */ "operator2 ::= EQ", + /* 51 */ "operator2 ::= NOT_EQ", + /* 52 */ "operator3 ::= LOG_AND", + /* 53 */ "operator4 ::= LOG_OR", + /* 54 */ "num_operator1 ::= MATH_DIV", + /* 55 */ "num_operator1 ::= MATH_MULT", + /* 56 */ "num_operator2 ::= MATH_PLUS", + /* 57 */ "num_operator2 ::= MATH_MINUS", + /* 58 */ "num_operator2 ::= GT", + /* 59 */ "num_operator2 ::= LT", + /* 60 */ "num_operator2 ::= GE", + /* 61 */ "num_operator2 ::= LE", + /* 62 */ "str_operator ::= LIKE", + /* 63 */ "str_operator ::= NOT_LIKE", + /* 64 */ "list_operator ::= IN", + /* 65 */ "list_operator ::= NOT_IN", + /* 66 */ "func_name ::= F_IF", + /* 67 */ "func_name ::= F_ELT", + /* 68 */ "func_name ::= F_COALESCE", + /* 69 */ "func_name ::= F_CONCAT", + /* 70 */ "func_name ::= F_SUBSTR", + /* 71 */ "func_name ::= F_TRIM", + /* 72 */ "func_name ::= F_DATE", + /* 73 */ "func_name ::= F_DATE_FORMAT", + /* 74 */ "func_name ::= F_CURRENT_DATE", + /* 75 */ "func_name ::= F_NOW", + /* 76 */ "func_name ::= F_TIME", + /* 77 */ "func_name ::= F_TO_DAYS", + /* 78 */ "func_name ::= F_FROM_DAYS", + /* 79 */ "func_name ::= F_YEAR", + /* 80 */ "func_name ::= F_MONTH", + /* 81 */ "func_name ::= F_DAY", + /* 82 */ "func_name ::= F_DATE_ADD", + /* 83 */ "func_name ::= F_DATE_SUB", + /* 84 */ "func_name ::= F_ROUND", + /* 85 */ "func_name ::= F_FLOOR", + ); + + /** + * This function returns the symbolic name associated with a token + * value. + * @param int + * @return string + */ + function tokenName($tokenType) + { + if ($tokenType === 0) { + return 'End of Input'; + } + if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) { + return self::$yyTokenName[$tokenType]; + } else { + return "Unknown"; + } + } + + /** + * The following function deletes the value associated with a + * symbol. The symbol can be either a terminal or nonterminal. + * @param int the symbol code + * @param mixed the symbol's value + */ + static function yy_destructor($yymajor, $yypminor) + { + switch ($yymajor) { + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + default: break; /* If no destructor action specified: do nothing */ + } + } + + /** + * Pop the parser's stack once. + * + * If there is a destructor routine associated with the token which + * is popped from the stack, then call it. + * + * Return the major token number for the symbol popped. + * @param OQLParser_yyParser + * @return int + */ + function yy_pop_parser_stack() + { + if (!count($this->yystack)) { + return; + } + $yytos = array_pop($this->yystack); + if (self::$yyTraceFILE && $this->yyidx >= 0) { + fwrite(self::$yyTraceFILE, + self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] . + "\n"); + } + $yymajor = $yytos->major; + self::yy_destructor($yymajor, $yytos->minor); + $this->yyidx--; + return $yymajor; + } + + /** + * Deallocate and destroy a parser. Destructors are all called for + * all stack elements before shutting the parser down. + */ + function __destruct() + { + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + if (is_resource(self::$yyTraceFILE)) { + fclose(self::$yyTraceFILE); + } + } + + /** + * Based on the current state and parser stack, get a list of all + * possible lookahead tokens + * @param int + * @return array + */ + function yy_get_expected_tokens($token) + { + $state = $this->yystack[$this->yyidx]->stateno; + $expected = self::$yyExpectedTokens[$state]; + if (in_array($token, self::$yyExpectedTokens[$state], true)) { + return $expected; + } + $stack = $this->yystack; + $yyidx = $this->yyidx; + do { + $yyact = $this->yy_find_shift_action($token); + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { + // reduce action + $done = 0; + do { + if ($done++ == 100) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // too much recursion prevents proper detection + // so give up + return array_unique($expected); + } + $yyruleno = $yyact - self::YYNSTATE; + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; + $nextstate = $this->yy_find_reduce_action( + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno]['lhs']); + if (isset(self::$yyExpectedTokens[$nextstate])) { + $expected += self::$yyExpectedTokens[$nextstate]; + if (in_array($token, + self::$yyExpectedTokens[$nextstate], true)) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + return array_unique($expected); + } + } + if ($nextstate < self::YYNSTATE) { + // we need to shift a non-terminal + $this->yyidx++; + $x = new OQLParser_yyStackEntry; + $x->stateno = $nextstate; + $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; + $this->yystack[$this->yyidx] = $x; + continue 2; + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // the last token was just ignored, we can't accept + // by ignoring input, this is in essence ignoring a + // syntax error! + return array_unique($expected); + } elseif ($nextstate === self::YY_NO_ACTION) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // input accepted, but not shifted (I guess) + return $expected; + } else { + $yyact = $nextstate; + } + } while (true); + } + break; + } while (true); + return array_unique($expected); + } + + /** + * Based on the parser state and current parser stack, determine whether + * the lookahead token is possible. + * + * The parser will convert the token value to an error token if not. This + * catches some unusual edge cases where the parser would fail. + * @param int + * @return bool + */ + function yy_is_expected_token($token) + { + if ($token === 0) { + return true; // 0 is not part of this + } + $state = $this->yystack[$this->yyidx]->stateno; + if (in_array($token, self::$yyExpectedTokens[$state], true)) { + return true; + } + $stack = $this->yystack; + $yyidx = $this->yyidx; + do { + $yyact = $this->yy_find_shift_action($token); + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { + // reduce action + $done = 0; + do { + if ($done++ == 100) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // too much recursion prevents proper detection + // so give up + return true; + } + $yyruleno = $yyact - self::YYNSTATE; + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; + $nextstate = $this->yy_find_reduce_action( + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno]['lhs']); + if (isset(self::$yyExpectedTokens[$nextstate]) && + in_array($token, self::$yyExpectedTokens[$nextstate], true)) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + return true; + } + if ($nextstate < self::YYNSTATE) { + // we need to shift a non-terminal + $this->yyidx++; + $x = new OQLParser_yyStackEntry; + $x->stateno = $nextstate; + $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; + $this->yystack[$this->yyidx] = $x; + continue 2; + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + if (!$token) { + // end of input: this is valid + return true; + } + // the last token was just ignored, we can't accept + // by ignoring input, this is in essence ignoring a + // syntax error! + return false; + } elseif ($nextstate === self::YY_NO_ACTION) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // input accepted, but not shifted (I guess) + return true; + } else { + $yyact = $nextstate; + } + } while (true); + } + break; + } while (true); + $this->yyidx = $yyidx; + $this->yystack = $stack; + return true; + } + + /** + * Find the appropriate action for a parser given the terminal + * look-ahead token iLookAhead. + * + * If the look-ahead token is YYNOCODE, then check to see if the action is + * independent of the look-ahead. If it is, return the action, otherwise + * return YY_NO_ACTION. + * @param int The look-ahead token + */ + function yy_find_shift_action($iLookAhead) + { + $stateno = $this->yystack[$this->yyidx]->stateno; + + /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */ + if (!isset(self::$yy_shift_ofst[$stateno])) { + // no shift actions + return self::$yy_default[$stateno]; + } + $i = self::$yy_shift_ofst[$stateno]; + if ($i === self::YY_SHIFT_USE_DFLT) { + return self::$yy_default[$stateno]; + } + if ($iLookAhead == self::YYNOCODE) { + return self::YY_NO_ACTION; + } + $i += $iLookAhead; + if ($i < 0 || $i >= self::YY_SZ_ACTTAB || + self::$yy_lookahead[$i] != $iLookAhead) { + if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback) + && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) { + if (self::$yyTraceFILE) { + fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " . + self::$yyTokenName[$iLookAhead] . " => " . + self::$yyTokenName[$iFallback] . "\n"); + } + return $this->yy_find_shift_action($iFallback); + } + return self::$yy_default[$stateno]; + } else { + return self::$yy_action[$i]; + } + } + + /** + * Find the appropriate action for a parser given the non-terminal + * look-ahead token $iLookAhead. + * + * If the look-ahead token is self::YYNOCODE, then check to see if the action is + * independent of the look-ahead. If it is, return the action, otherwise + * return self::YY_NO_ACTION. + * @param int Current state number + * @param int The look-ahead token + */ + function yy_find_reduce_action($stateno, $iLookAhead) + { + /* $stateno = $this->yystack[$this->yyidx]->stateno; */ + + if (!isset(self::$yy_reduce_ofst[$stateno])) { + return self::$yy_default[$stateno]; + } + $i = self::$yy_reduce_ofst[$stateno]; + if ($i == self::YY_REDUCE_USE_DFLT) { + return self::$yy_default[$stateno]; + } + if ($iLookAhead == self::YYNOCODE) { + return self::YY_NO_ACTION; + } + $i += $iLookAhead; + if ($i < 0 || $i >= self::YY_SZ_ACTTAB || + self::$yy_lookahead[$i] != $iLookAhead) { + return self::$yy_default[$stateno]; + } else { + return self::$yy_action[$i]; + } + } + + /** + * Perform a shift action. + * @param int The new state to shift in + * @param int The major token to shift in + * @param mixed the minor token to shift in + */ + function yy_shift($yyNewState, $yyMajor, $yypMinor) + { + $this->yyidx++; + if ($this->yyidx >= self::YYSTACKDEPTH) { + $this->yyidx--; + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will execute if the parser + ** stack ever overflows */ + return; + } + $yytos = new OQLParser_yyStackEntry; + $yytos->stateno = $yyNewState; + $yytos->major = $yyMajor; + $yytos->minor = $yypMinor; + array_push($this->yystack, $yytos); + if (self::$yyTraceFILE && $this->yyidx > 0) { + fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt, + $yyNewState); + fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt); + for($i = 1; $i <= $this->yyidx; $i++) { + fprintf(self::$yyTraceFILE, " %s", + self::$yyTokenName[$this->yystack[$i]->major]); + } + fwrite(self::$yyTraceFILE,"\n"); + } + } + + /** + * The following table contains information about every rule that + * is used during the reduce. + * + *
    +     * array(
    +     *  array(
    +     *   int $lhs;         Symbol on the left-hand side of the rule
    +     *   int $nrhs;     Number of right-hand side symbols in the rule
    +     *  ),...
    +     * );
    +     * 
    + */ + static public $yyRuleInfo = array( + array( 'lhs' => 51, 'rhs' => 1 ), + array( 'lhs' => 51, 'rhs' => 1 ), + array( 'lhs' => 52, 'rhs' => 4 ), + array( 'lhs' => 52, 'rhs' => 6 ), + array( 'lhs' => 56, 'rhs' => 2 ), + array( 'lhs' => 56, 'rhs' => 0 ), + array( 'lhs' => 55, 'rhs' => 2 ), + array( 'lhs' => 55, 'rhs' => 1 ), + array( 'lhs' => 55, 'rhs' => 0 ), + array( 'lhs' => 57, 'rhs' => 6 ), + array( 'lhs' => 57, 'rhs' => 4 ), + array( 'lhs' => 58, 'rhs' => 3 ), + array( 'lhs' => 53, 'rhs' => 1 ), + array( 'lhs' => 61, 'rhs' => 1 ), + array( 'lhs' => 61, 'rhs' => 1 ), + array( 'lhs' => 61, 'rhs' => 4 ), + array( 'lhs' => 61, 'rhs' => 3 ), + array( 'lhs' => 61, 'rhs' => 3 ), + array( 'lhs' => 67, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 3 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 3 ), + array( 'lhs' => 71, 'rhs' => 1 ), + array( 'lhs' => 71, 'rhs' => 3 ), + array( 'lhs' => 60, 'rhs' => 1 ), + array( 'lhs' => 60, 'rhs' => 3 ), + array( 'lhs' => 66, 'rhs' => 3 ), + array( 'lhs' => 74, 'rhs' => 1 ), + array( 'lhs' => 74, 'rhs' => 3 ), + array( 'lhs' => 64, 'rhs' => 0 ), + array( 'lhs' => 64, 'rhs' => 1 ), + array( 'lhs' => 64, 'rhs' => 3 ), + array( 'lhs' => 75, 'rhs' => 1 ), + array( 'lhs' => 75, 'rhs' => 3 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 77, 'rhs' => 1 ), + array( 'lhs' => 78, 'rhs' => 1 ), + array( 'lhs' => 59, 'rhs' => 1 ), + array( 'lhs' => 59, 'rhs' => 3 ), + array( 'lhs' => 54, 'rhs' => 1 ), + array( 'lhs' => 81, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 80, 'rhs' => 1 ), + array( 'lhs' => 68, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 73, 'rhs' => 1 ), + array( 'lhs' => 82, 'rhs' => 1 ), + array( 'lhs' => 82, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 84, 'rhs' => 1 ), + array( 'lhs' => 84, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + ); + + /** + * The following table contains a mapping of reduce action to method name + * that handles the reduction. + * + * If a rule is not set, it has no handler. + */ + static public $yyReduceMap = array( + 0 => 0, + 1 => 0, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 5, + 8 => 5, + 6 => 6, + 7 => 7, + 9 => 9, + 10 => 10, + 11 => 11, + 12 => 12, + 13 => 12, + 14 => 12, + 18 => 12, + 20 => 12, + 22 => 12, + 24 => 12, + 32 => 12, + 34 => 12, + 35 => 12, + 36 => 12, + 37 => 12, + 38 => 12, + 15 => 15, + 16 => 16, + 17 => 17, + 19 => 17, + 21 => 17, + 23 => 17, + 25 => 17, + 26 => 26, + 27 => 27, + 30 => 27, + 28 => 28, + 31 => 28, + 29 => 29, + 33 => 33, + 39 => 39, + 40 => 39, + 41 => 41, + 42 => 42, + 43 => 43, + 66 => 43, + 67 => 43, + 68 => 43, + 69 => 43, + 70 => 43, + 71 => 43, + 72 => 43, + 73 => 43, + 74 => 43, + 75 => 43, + 76 => 43, + 77 => 43, + 78 => 43, + 79 => 43, + 80 => 43, + 81 => 43, + 82 => 43, + 83 => 43, + 84 => 43, + 85 => 43, + 44 => 44, + 45 => 45, + 47 => 45, + 48 => 45, + 49 => 45, + 50 => 45, + 51 => 45, + 52 => 45, + 53 => 45, + 54 => 45, + 55 => 45, + 56 => 45, + 57 => 45, + 58 => 45, + 59 => 45, + 60 => 45, + 61 => 45, + 62 => 45, + 63 => 45, + 64 => 45, + 65 => 45, + 46 => 46, + ); + /* Beginning here are the reduction cases. A typical example + ** follows: + ** #line + ** function yy_r0($yymsp){ ... } // User supplied code + ** #line + */ +#line 29 "oql-parser.y" + function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } +#line 1230 "oql-parser.php" +#line 32 "oql-parser.y" + function yy_r2(){ + $this->_retvalue = new OqlQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + } +#line 1235 "oql-parser.php" +#line 35 "oql-parser.y" + function yy_r3(){ + $this->_retvalue = new OqlQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + } +#line 1240 "oql-parser.php" +#line 39 "oql-parser.y" + function yy_r4(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1243 "oql-parser.php" +#line 40 "oql-parser.y" + function yy_r5(){ $this->_retvalue = null; } +#line 1246 "oql-parser.php" +#line 42 "oql-parser.y" + function yy_r6(){ + // insert the join statement on top of the existing list + array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + // and return the updated array + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; + } +#line 1254 "oql-parser.php" +#line 48 "oql-parser.y" + function yy_r7(){ + $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); + } +#line 1259 "oql-parser.php" +#line 54 "oql-parser.y" + function yy_r9(){ + // create an array with one single item + $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + } +#line 1265 "oql-parser.php" +#line 59 "oql-parser.y" + function yy_r10(){ + // create an array with one single item + $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + } +#line 1271 "oql-parser.php" +#line 64 "oql-parser.y" + function yy_r11(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } +#line 1274 "oql-parser.php" +#line 66 "oql-parser.y" + function yy_r12(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1277 "oql-parser.php" +#line 70 "oql-parser.y" + function yy_r15(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } +#line 1280 "oql-parser.php" +#line 71 "oql-parser.y" + function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } +#line 1283 "oql-parser.php" +#line 72 "oql-parser.y" + function yy_r17(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1286 "oql-parser.php" +#line 87 "oql-parser.y" + function yy_r26(){ + $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); + } +#line 1291 "oql-parser.php" +#line 90 "oql-parser.y" + function yy_r27(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); + } +#line 1296 "oql-parser.php" +#line 93 "oql-parser.y" + function yy_r28(){ + array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; + } +#line 1302 "oql-parser.php" +#line 98 "oql-parser.y" + function yy_r29(){ + $this->_retvalue = array(); + } +#line 1307 "oql-parser.php" +#line 109 "oql-parser.y" + function yy_r33(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1310 "oql-parser.php" +#line 118 "oql-parser.y" + function yy_r39(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1313 "oql-parser.php" +#line 121 "oql-parser.y" + function yy_r41(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1316 "oql-parser.php" +#line 122 "oql-parser.y" + function yy_r42(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } +#line 1319 "oql-parser.php" +#line 123 "oql-parser.y" + function yy_r43(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1322 "oql-parser.php" +#line 125 "oql-parser.y" + function yy_r44(){ + if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') + { + $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); + } + else + { + $name = $this->yystack[$this->yyidx + 0]->minor; + } + $this->_retvalue = new OqlName($name, $this->m_iColPrev); + } +#line 1335 "oql-parser.php" +#line 137 "oql-parser.y" + function yy_r45(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1338 "oql-parser.php" +#line 138 "oql-parser.y" + function yy_r46(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } +#line 1341 "oql-parser.php" + + /** + * placeholder for the left hand side in a reduce operation. + * + * For a parser with a rule like this: + *
    +     * rule(A) ::= B. { A = 1; }
    +     * 
    + * + * The parser will translate to something like: + * + * + * function yy_r0(){$this->_retvalue = 1;} + * + */ + private $_retvalue; + + /** + * Perform a reduce action and the shift that must immediately + * follow the reduce. + * + * For a rule such as: + * + *
    +     * A ::= B blah C. { dosomething(); }
    +     * 
    + * + * This function will first call the action, if any, ("dosomething();" in our + * example), and then it will pop three states from the stack, + * one for each entry on the right-hand side of the expression + * (B, blah, and C in our example rule), and then push the result of the action + * back on to the stack with the resulting state reduced to (as described in the .out + * file) + * @param int Number of the rule by which to reduce + */ + function yy_reduce($yyruleno) + { + //int $yygoto; /* The next state */ + //int $yyact; /* The next action */ + //mixed $yygotominor; /* The LHS of the rule reduced */ + //OQLParser_yyStackEntry $yymsp; /* The top of the parser's stack */ + //int $yysize; /* Amount to pop the stack */ + $yymsp = $this->yystack[$this->yyidx]; + if (self::$yyTraceFILE && $yyruleno >= 0 + && $yyruleno < count(self::$yyRuleName)) { + fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n", + self::$yyTracePrompt, $yyruleno, + self::$yyRuleName[$yyruleno]); + } + + $this->_retvalue = $yy_lefthand_side = null; + if (array_key_exists($yyruleno, self::$yyReduceMap)) { + // call the action + $this->_retvalue = null; + $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}(); + $yy_lefthand_side = $this->_retvalue; + } + $yygoto = self::$yyRuleInfo[$yyruleno]['lhs']; + $yysize = self::$yyRuleInfo[$yyruleno]['rhs']; + $this->yyidx -= $yysize; + for($i = $yysize; $i; $i--) { + // pop all of the right-hand side parameters + array_pop($this->yystack); + } + $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto); + if ($yyact < self::YYNSTATE) { + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if (!self::$yyTraceFILE && $yysize) { + $this->yyidx++; + $x = new OQLParser_yyStackEntry; + $x->stateno = $yyact; + $x->major = $yygoto; + $x->minor = $yy_lefthand_side; + $this->yystack[$this->yyidx] = $x; + } else { + $this->yy_shift($yyact, $yygoto, $yy_lefthand_side); + } + } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) { + $this->yy_accept(); + } + } + + /** + * The following code executes when the parse fails + * + * Code from %parse_fail is inserted here + */ + function yy_parse_failed() + { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will be executed whenever the + ** parser fails */ + } + + /** + * The following code executes when a syntax error first occurs. + * + * %syntax_error code is inserted here + * @param int The major type of the error token + * @param mixed The minor type of the error token + */ + function yy_syntax_error($yymajor, $TOKEN) + { +#line 25 "oql-parser.y" + +throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); +#line 1457 "oql-parser.php" + } + + /** + * The following is executed when the parser accepts + * + * %parse_accept code is inserted here + */ + function yy_accept() + { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $stack = $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + } + + /** + * The main parser program. + * + * The first argument is the major token number. The second is + * the token value string as scanned from the input. + * + * @param int the token number + * @param mixed the token value + * @param mixed any extra arguments that should be passed to handlers + */ + function doParse($yymajor, $yytokenvalue) + { +// $yyact; /* The parser action. */ +// $yyendofinput; /* True if we are at the end of input */ + $yyerrorhit = 0; /* True if yymajor has invoked an error */ + + /* (re)initialize the parser, if necessary */ + if ($this->yyidx === null || $this->yyidx < 0) { + /* if ($yymajor == 0) return; // not sure why this was here... */ + $this->yyidx = 0; + $this->yyerrcnt = -1; + $x = new OQLParser_yyStackEntry; + $x->stateno = 0; + $x->major = 0; + $this->yystack = array(); + array_push($this->yystack, $x); + } + $yyendofinput = ($yymajor==0); + + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sInput %s\n", + self::$yyTracePrompt, self::$yyTokenName[$yymajor]); + } + + do { + $yyact = $this->yy_find_shift_action($yymajor); + if ($yymajor < self::YYERRORSYMBOL && + !$this->yy_is_expected_token($yymajor)) { + // force a syntax error + $yyact = self::YY_ERROR_ACTION; + } + if ($yyact < self::YYNSTATE) { + $this->yy_shift($yyact, $yymajor, $yytokenvalue); + $this->yyerrcnt--; + if ($yyendofinput && $this->yyidx >= 0) { + $yymajor = 0; + } else { + $yymajor = self::YYNOCODE; + } + } elseif ($yyact < self::YYNSTATE + self::YYNRULE) { + $this->yy_reduce($yyact - self::YYNSTATE); + } elseif ($yyact == self::YY_ERROR_ACTION) { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sSyntax Error!\n", + self::$yyTracePrompt); + } + if (self::YYERRORSYMBOL) { + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if ($this->yyerrcnt < 0) { + $this->yy_syntax_error($yymajor, $yytokenvalue); + } + $yymx = $this->yystack[$this->yyidx]->major; + if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ){ + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sDiscard input token %s\n", + self::$yyTracePrompt, self::$yyTokenName[$yymajor]); + } + $this->yy_destructor($yymajor, $yytokenvalue); + $yymajor = self::YYNOCODE; + } else { + while ($this->yyidx >= 0 && + $yymx != self::YYERRORSYMBOL && + ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE + ){ + $this->yy_pop_parser_stack(); + } + if ($this->yyidx < 0 || $yymajor==0) { + $this->yy_destructor($yymajor, $yytokenvalue); + $this->yy_parse_failed(); + $yymajor = self::YYNOCODE; + } elseif ($yymx != self::YYERRORSYMBOL) { + $u2 = 0; + $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2); + } + } + $this->yyerrcnt = 3; + $yyerrorhit = 1; + } else { + /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if ($this->yyerrcnt <= 0) { + $this->yy_syntax_error($yymajor, $yytokenvalue); + } + $this->yyerrcnt = 3; + $this->yy_destructor($yymajor, $yytokenvalue); + if ($yyendofinput) { + $this->yy_parse_failed(); + } + $yymajor = self::YYNOCODE; + } + } else { + $this->yy_accept(); + $yymajor = self::YYNOCODE; + } + } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); + } +}#line 186 "oql-parser.y" + + +class OQLParserException extends OQLException +{ + public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue) + { + $sIssue = "Unexpected token $sTokenName"; + + parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue); + } +} + +class OQLParser extends OQLParserRaw +{ + // dirty, but working for us (no other mean to get the final result :-( + protected $my_result; + + public function GetResult() + { + return $this->my_result; + } + + // More info on the source query and the current position while parsing it + // Data used when an exception is raised + protected $m_iLine; // still not used + protected $m_iCol; + protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token + protected $m_sSourceQuery; + + public function __construct($sQuery) + { + $this->m_iLine = 0; + $this->m_iCol = 0; + $this->m_iColPrev = 0; + $this->m_sSourceQuery = $sQuery; + // no constructor - parent::__construct(); + } + + public function doParse($token, $value, $iCurrPosition = 0) + { + $this->m_iColPrev = $this->m_iCol; + $this->m_iCol = $iCurrPosition; + + return parent::DoParse($token, $value); + } + + public function doFinish() + { + $this->doParse(0, 0); + return $this->my_result; + } + + public function __destruct() + { + // Bug in the original destructor, causing an infinite loop ! + // This is a real issue when a fatal error occurs on the first token (the error could not be seen) + if (is_null($this->yyidx)) + { + $this->yyidx = -1; + } + parent::__destruct(); + } +} + +#line 1676 "oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y new file mode 100644 index 0000000000..551a82ada6 --- /dev/null +++ b/core/oql/oql-parser.y @@ -0,0 +1,250 @@ + +/* + +This is a LALR(1) grammar +(seek for Lemon grammar to get some documentation from the Net) +That doc was helpful: http://www.hwaci.com/sw/lemon/lemon.html + +To handle operators precedence we could have used the %left directive +(we took another option, because that one was discovered right after... +which option is the best for us?) +Example: +%left LOG_AND. +%left LOG_OR. +%nonassoc EQ NE GT GE LT LE. +%left PLUS MINUS. +%left TIMES DIVIDE MOD. +%right EXP NOT. + +TODO : solve the 2 remaining shift-reduce conflicts (JOIN) + +*/ + +%name OQLParser_ +%declare_class {class OQLParserRaw} +%syntax_error { +throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); +} + +result ::= query(X). { $this->my_result = X; } +result ::= condition(X). { $this->my_result = X; } + +query(A) ::= SELECT class_name(X) join_statement(J) where_statement(W). { + A = new OqlQuery(X, X, W, J); +} +query(A) ::= SELECT class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). { + A = new OqlQuery(X, Y, W, J); +} + +where_statement(A) ::= WHERE condition(C). { A = C;} +where_statement(A) ::= . { A = null;} + +join_statement(A) ::= join_item(J) join_statement(S). { + // insert the join statement on top of the existing list + array_unshift(S, J); + // and return the updated array + A = S; +} +join_statement(A) ::= join_item(J). { + A = Array(J); +} +join_statement(A) ::= . { A = null;} + +join_item(A) ::= JOIN class_name(X) AS_ALIAS class_name(Y) ON join_condition(C). +{ + // create an array with one single item + A = new OqlJoinSpec(X, Y, C); +} +join_item(A) ::= JOIN class_name(X) ON join_condition(C). +{ + // create an array with one single item + A = new OqlJoinSpec(X, X, C); +} + +join_condition(A) ::= field_id(X) EQ field_id(Y). { A = new BinaryOqlExpression(X, '=', Y); } + +condition(A) ::= expression_prio4(X). { A = X; } + +expression_basic(A) ::= scalar(X). { A = X; } +expression_basic(A) ::= field_id(X). { A = X; } +expression_basic(A) ::= func_name(X) PAR_OPEN arg_list(Y) PAR_CLOSE. { A = new FunctionOqlExpression(X, Y); } +expression_basic(A) ::= PAR_OPEN expression_prio4(X) PAR_CLOSE. { A = X; } +expression_basic(A) ::= expression_basic(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); } + +expression_prio1(A) ::= expression_basic(X). { A = X; } +expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); } + +expression_prio2(A) ::= expression_prio1(X). { A = X; } +expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z). { A = new BinaryOqlExpression(X, Y, Z); } + +expression_prio3(A) ::= expression_prio2(X). { A = X; } +expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). { A = new BinaryOqlExpression(X, Y, Z); } + +expression_prio4(A) ::= expression_prio3(X). { A = X; } +expression_prio4(A) ::= expression_prio4(X) operator4(Y) expression_prio3(Z). { A = new BinaryOqlExpression(X, Y, Z); } + + +list(A) ::= PAR_OPEN scalar_list(X) PAR_CLOSE. { + A = new ListOqlExpression(X); +} +scalar_list(A) ::= scalar(X). { + A = array(X); +} +scalar_list(A) ::= scalar_list(L) COMA scalar(X). { + array_push(L, X); + A = L; +} + +arg_list(A) ::= . { + A = array(); +} +arg_list(A) ::= argument(X). { + A = array(X); +} +arg_list(A) ::= arg_list(L) COMA argument(X). { + array_push(L, X); + A = L; +} +argument(A) ::= expression_prio4(X). { A = X; } +argument(A) ::= INTERVAL expression_prio4(X) interval_unit(Y). { A = new IntervalOqlExpression(X, Y); } + +interval_unit(A) ::= F_DAY(X). { A = X; } +interval_unit(A) ::= F_MONTH(X). { A = X; } +interval_unit(A) ::= F_YEAR(X). { A = X; } + +scalar(A) ::= num_scalar(X). { A = X; } +scalar(A) ::= str_scalar(X). { A = X; } + +num_scalar(A) ::= num_value(X). { A = new ScalarOqlExpression(X); } +str_scalar(A) ::= str_value(X). { A = new ScalarOqlExpression(X); } + +field_id(A) ::= name(X). { A = new FieldOqlExpression(X); } +field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); } +class_name(A) ::= name(X). { A=X; } + +name(A) ::= NAME(X). { + if (X[0] == '`') + { + $name = substr(X, 1, strlen(X) - 2); + } + else + { + $name = X; + } + A = new OqlName($name, $this->m_iColPrev); +} + +num_value(A) ::= NUMVAL(X). {A=X;} +str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));} + + +operator1(A) ::= num_operator1(X). {A=X;} +operator2(A) ::= num_operator2(X). {A=X;} +operator2(A) ::= str_operator(X). {A=X;} +operator2(A) ::= EQ(X). {A=X;} +operator2(A) ::= NOT_EQ(X). {A=X;} +operator3(A) ::= LOG_AND(X). {A=X;} +operator4(A) ::= LOG_OR(X). {A=X;} + +num_operator1(A) ::= MATH_DIV(X). {A=X;} +num_operator1(A) ::= MATH_MULT(X). {A=X;} +num_operator2(A) ::= MATH_PLUS(X). {A=X;} +num_operator2(A) ::= MATH_MINUS(X). {A=X;} +num_operator2(A) ::= GT(X). {A=X;} +num_operator2(A) ::= LT(X). {A=X;} +num_operator2(A) ::= GE(X). {A=X;} +num_operator2(A) ::= LE(X). {A=X;} + +str_operator(A) ::= LIKE(X). {A=X;} +str_operator(A) ::= NOT_LIKE(X). {A=X;} + +list_operator(A) ::= IN(X). {A=X;} +list_operator(A) ::= NOT_IN(X). {A=X;} + +func_name(A) ::= F_IF(X). { A=X; } +func_name(A) ::= F_ELT(X). { A=X; } +func_name(A) ::= F_COALESCE(X). { A=X; } +func_name(A) ::= F_CONCAT(X). { A=X; } +func_name(A) ::= F_SUBSTR(X). { A=X; } +func_name(A) ::= F_TRIM(X). { A=X; } +func_name(A) ::= F_DATE(X). { A=X; } +func_name(A) ::= F_DATE_FORMAT(X). { A=X; } +func_name(A) ::= F_CURRENT_DATE(X). { A=X; } +func_name(A) ::= F_NOW(X). { A=X; } +func_name(A) ::= F_TIME(X). { A=X; } +func_name(A) ::= F_TO_DAYS(X). { A=X; } +func_name(A) ::= F_FROM_DAYS(X). { A=X; } +func_name(A) ::= F_YEAR(X). { A=X; } +func_name(A) ::= F_MONTH(X). { A=X; } +func_name(A) ::= F_DAY(X). { A=X; } +func_name(A) ::= F_DATE_ADD(X). { A=X; } +func_name(A) ::= F_DATE_SUB(X). { A=X; } +func_name(A) ::= F_ROUND(X). { A=X; } +func_name(A) ::= F_FLOOR(X). { A=X; } + + +%code { + +class OQLParserException extends OQLException +{ + public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue) + { + $sIssue = "Unexpected token $sTokenName"; + + parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue); + } +} + +class OQLParser extends OQLParserRaw +{ + // dirty, but working for us (no other mean to get the final result :-( + protected $my_result; + + public function GetResult() + { + return $this->my_result; + } + + // More info on the source query and the current position while parsing it + // Data used when an exception is raised + protected $m_iLine; // still not used + protected $m_iCol; + protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token + protected $m_sSourceQuery; + + public function __construct($sQuery) + { + $this->m_iLine = 0; + $this->m_iCol = 0; + $this->m_iColPrev = 0; + $this->m_sSourceQuery = $sQuery; + // no constructor - parent::__construct(); + } + + public function doParse($token, $value, $iCurrPosition = 0) + { + $this->m_iColPrev = $this->m_iCol; + $this->m_iCol = $iCurrPosition; + + return parent::DoParse($token, $value); + } + + public function doFinish() + { + $this->doParse(0, 0); + return $this->my_result; + } + + public function __destruct() + { + // Bug in the original destructor, causing an infinite loop ! + // This is a real issue when a fatal error occurs on the first token (the error could not be seen) + if (is_null($this->yyidx)) + { + $this->yyidx = -1; + } + parent::__destruct(); + } +} + +} diff --git a/core/oql/oqlexception.class.inc.php b/core/oql/oqlexception.class.inc.php new file mode 100644 index 0000000000..512a3077fd --- /dev/null +++ b/core/oql/oqlexception.class.inc.php @@ -0,0 +1,78 @@ +m_MyIssue = $sIssue; + $this->m_sInput = $sInput; + $this->m_iLine = $iLine; + $this->m_iCol = $iCol; + $this->m_sUnexpected = $sUnexpected; + $this->m_aExpecting = $aExpecting; + + if (is_null($this->m_aExpecting) || (count($this->m_aExpecting) == 0)) + { + $sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput'"; + } + else + { + $sExpectations = '{'.implode(', ', $this->m_aExpecting).'}'; + $sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting); + $sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput', expecting $sExpectations, I would suggest to use '$sSuggest'"; + } + + // make sure everything is assigned properly + parent::__construct($sMessage, 0); + } + + public function getHtmlDesc($sHighlightHtmlBegin = '', $sHighlightHtmlEnd = '') + { + $sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: "); + $sRet .= htmlentities(substr($this->m_sInput, 0, $this->m_iCol)); + $sRet .= $sHighlightHtmlBegin.htmlentities(substr($this->m_sInput, $this->m_iCol, strlen($this->m_sUnexpected))).$sHighlightHtmlEnd; + $sRet .= htmlentities(substr($this->m_sInput, $this->m_iCol + strlen($this->m_sUnexpected))); + + if (!is_null($this->m_aExpecting) && (count($this->m_aExpecting) > 0)) + { + $sExpectations = '{'.implode(', ', $this->m_aExpecting).'}'; + $sRet .= ", expecting ".htmlentities($sExpectations); + $sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting); + if (strlen($sSuggest) > 0) + { + $sRet .= ", I would suggest to use '$sHighlightHtmlBegin".htmlentities($sSuggest)."$sHighlightHtmlEnd'"; + } + } + + return $sRet; + } + + static protected function FindClosestString($sInput, $aDictionary) + { + // no shortest distance found, yet + $fShortest = -1; + $sRet = ''; + + // loop through words to find the closest + foreach ($aDictionary as $sSuggestion) + { + // calculate the distance between the input string and the suggested one + $fDist = levenshtein($sInput, $sSuggestion); + if ($fDist == 0) + { + // Exact match + return $sSuggestion; + } + + if ($fShortest < 0 || ($fDist < 4 && $fDist <= $fShortest)) + { + // set the closest match, and shortest distance + $sRet = $sSuggestion; + $fShortest = $fDist; + } + } + return $sRet; + } +} + +?> diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php new file mode 100644 index 0000000000..a464413da2 --- /dev/null +++ b/core/oql/oqlinterpreter.class.inc.php @@ -0,0 +1,59 @@ +GetPos(), $oName->GetValue(), $aExpecting); + } +} + +class OqlInterpreterException extends OQLException +{ +} + + +class OqlInterpreter +{ + public $m_sQuery; + + public function __construct($sQuery) + { + $this->m_sQuery = $sQuery; + } + + protected function Parse() + { + $oLexer = new OQLLexer($this->m_sQuery); + $oParser = new OQLParser($this->m_sQuery); + + while($oLexer->yylex()) + { + $oParser->doParse($oLexer->token, $oLexer->value, $oLexer->getTokenPos()); + } + $res = $oParser->doFinish(); + return $res; + } + + public function ParseQuery() + { + $oRes = $this->Parse(); + if (!$oRes instanceof OqlQuery) + { + throw new OqlException('Expecting an OQL query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlQuery')); + } + return $oRes; + } + + public function ParseExpression() + { + $oRes = $this->Parse(); + if (!$oRes instanceof Expression) + { + throw new OqlException('Expecting an OQL expression', $this->m_sQuery, 0, 0, get_class($oRes), array('Expression')); + } + return $oRes; + } +} + +?> diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php new file mode 100644 index 0000000000..ad71950b3f --- /dev/null +++ b/core/oql/oqlquery.class.inc.php @@ -0,0 +1,168 @@ +m_iPos = $iPos; + $this->m_sValue = $sValue; + } + + public function GetValue() + { + return $this->m_sValue; + } + + public function GetPos() + { + return $this->m_iPos; + } + + public function __toString() + { + return $this->m_sValue; + } +} + +class OqlJoinSpec +{ + protected $m_oClass; + protected $m_oClassAlias; + protected $m_oLeftField; + protected $m_oRightField; + + protected $m_oNextJoinspec; + + public function __construct($oClass, $oClassAlias, BinaryExpression $oExpression) + { + $this->m_oClass = $oClass; + $this->m_oClassAlias = $oClassAlias; + $this->m_oLeftField = $oExpression->GetLeftExpr(); + $this->m_oRightField = $oExpression->GetRightExpr(); + } + + public function GetClass() + { + return $this->m_oClass->GetValue(); + } + public function GetClassAlias() + { + return $this->m_oClassAlias->GetValue(); + } + + public function GetClassDetails() + { + return $this->m_oClass; + } + public function GetClassAliasDetails() + { + return $this->m_oClassAlias; + } + + public function GetLeftField() + { + return $this->m_oLeftField; + } + public function GetRightField() + { + return $this->m_oRightField; + } +} + +class BinaryOqlExpression extends BinaryExpression +{ +} + +class ScalarOqlExpression extends ScalarExpression +{ +} + +class FieldOqlExpression extends FieldExpression +{ + protected $m_oParent; + protected $m_oName; + + public function __construct($oName, $oParent = null) + { + if (is_null($oParent)) + { + $oParent = new OqlName('', 0); + } + $this->m_oParent = $oParent; + $this->m_oName = $oName; + + parent::__construct($oName->GetValue(), $oParent->GetValue()); + } + + public function GetParentDetails() + { + return $this->m_oParent; + } + + public function GetNameDetails() + { + return $this->m_oName; + } +} + +class ListOqlExpression extends ListExpression +{ +} + +class FunctionOqlExpression extends FunctionExpression +{ +} + +class IntervalOqlExpression extends IntervalExpression +{ +} +class OqlQuery +{ + protected $m_oClass; + protected $m_oClassAlias; + protected $m_aJoins; // array of OqlJoinSpec + protected $m_oCondition; // condition tree (expressions) + + public function __construct($oClass, $oClassAlias = '', $oCondition = null, $aJoins = null) + { + $this->m_oClass = $oClass; + $this->m_oClassAlias = $oClassAlias; + $this->m_aJoins = $aJoins; + $this->m_oCondition = $oCondition; + } + + public function GetClass() + { + return $this->m_oClass->GetValue(); + } + public function GetClassAlias() + { + return $this->m_oClassAlias->GetValue(); + } + + public function GetClassDetails() + { + return $this->m_oClass; + } + public function GetClassAliasDetails() + { + return $this->m_oClassAlias; + } + + public function GetJoins() + { + return $this->m_aJoins; + } + public function GetCondition() + { + return $this->m_oCondition; + } +} + +?> diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php new file mode 100644 index 0000000000..8a55d33c8e --- /dev/null +++ b/core/sqlquery.class.inc.php @@ -0,0 +1,413 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +require_once('cmdbsource.class.inc.php'); + + +class SQLExpression extends BinaryExpression +{ +} +class ScalarSQLExpression extends ScalarExpression +{ +} +class TrueSQLExpression extends TrueExpression +{ +} +class FieldSQLExpression extends FieldExpression +{ +} + + + +class SQLQuery +{ + private $m_sTable = ''; + private $m_sTableAlias = ''; + private $m_aFields = array(); + private $m_oConditionExpr = null; + private $m_aFullTextNeedles = array(); + private $m_bToDelete = true; // The current table must be listed for deletion ? + private $m_aValues = array(); // Values to set in case of an update query + private $m_aJoinSelects = array(); + + public function __construct($sTable, $sTableAlias, $aFields, $oConditionExpr, $aFullTextNeedles, $bToDelete = true, $aValues = array()) + { + if (!CMDBSource::IsTable($sTable)) + { + throw new CoreException("Unknown table '$sTable'"); + } + // $aFields must be an array of "alias"=>"expr" + // $oConditionExpr must be a condition tree + // $aValues is an array of "alias"=>value + + $this->m_sTable = $sTable; + $this->m_sTableAlias = $sTableAlias; + $this->m_aFields = $aFields; + $this->m_oConditionExpr = $oConditionExpr; + if (is_null($oConditionExpr)) + { + $this->m_oConditionExpr = new TrueExpression; + } + else if (!$oConditionExpr instanceof Expression) + { + throw new CoreException('Invalid type for condition, expecting an Expression', array('class' => get_class($oConditionExpr))); + } + $this->m_aFullTextNeedles = $aFullTextNeedles; + $this->m_bToDelete = $bToDelete; + $this->m_aValues = $aValues; + } + + public function DisplayHtml() + { + if (count($this->m_aFields) == 0) $sFields = ""; + else + { + $aFieldDesc = array(); + foreach ($this->m_aFields as $sAlias => $oExpression) + { + $aFieldDesc[] = $oExpression->Render()." as $sAlias"; + } + $sFields = " => ".implode(', ', $aFieldDesc); + } + echo "$this->m_sTable$sFields
    \n"; + // #@# todo - display html of an expression tree + //$this->m_oConditionExpr->DisplayHtml() + if (count($this->m_aFullTextNeedles) > 0) + { + echo "Full text criteria...
    \n"; + echo "
      \n"; + foreach ($this->m_aFullTextNeedles as $sFTNeedle) + { + echo "
    • $sFTNeedle
    • \n"; + } + echo "
    "; + } + if (count($this->m_aJoinSelects) > 0) + { + echo "Joined to...
    \n"; + echo "
      \n"; + foreach ($this->m_aJoinSelects as $aJoinInfo) + { + $sJoinType = $aJoinInfo["jointype"]; + $oSQLQuery = $aJoinInfo["select"]; + $sLeftField = $aJoinInfo["leftfield"]; + $sRightField = $aJoinInfo["rightfield"]; + $sRightTableAlias = $aJoinInfo["righttablealias"]; + + echo "
    • Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."
    • \n"; + } + echo "
    "; + } + $aFrom = array(); + $aFields = array(); + $oCondition = null; + $aDelTables = array(); + $aSetValues = array(); + $this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues); + echo "From ...
    \n"; + echo "
    \n";
    +		print_r($aFrom);
    +		echo "
    "; + } + + public function SetCondition($oConditionExpr) + { + $this->m_oConditionExpr = $oConditionExpr; + } + + public function AddCondition($oConditionExpr) + { + $this->m_oConditionExpr->LogAnd($oConditionExpr); + } + + private function AddJoin($sJoinType, $oSQLQuery, $sLeftField, $sRightField, $sRightTableAlias = '') + { + assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__)); + if (!CMDBSource::IsField($this->m_sTable, $sLeftField)) + { + throw new CoreException("Unknown field '$sLeftField' in table '".$this->m_sTable); + } + if (empty($sRightTableAlias)) + { + $sRightTableAlias = $oSQLQuery->m_sTableAlias; + } +// #@# Could not be verified here because the namespace is unknown - do we need to check it there? +// +// if (!CMDBSource::IsField($sRightTable, $sRightField)) +// { +// throw new CoreException("Unknown field '$sRightField' in table '".$sRightTable."'"); +// } + + $this->m_aJoinSelects[] = array( + "jointype" => $sJoinType, + "select" => $oSQLQuery, + "leftfield" => $sLeftField, + "rightfield" => $sRightField, + "righttablealias" => $sRightTableAlias + ); + } + public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRigthtTable = '') + { + $this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRigthtTable); + } + public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField) + { + return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField); + } + + // Interface, build the SQL query + public function RenderDelete() + { + // The goal will be to complete the list as we build the Joins + $aFrom = array(); + $aFields = array(); + $oCondition = null; + $aDelTables = array(); + $aSetValues = array(); + $this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues); + + // Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ... + + $sDelete = self::ClauseDelete($aDelTables); + $sFrom = self::ClauseFrom($aFrom); + // #@# safety net to redo ? + /* + if ($this->m_oConditionExpr->IsAny()) + -- if (count($aConditions) == 0) -- + { + throw new CoreException("Building a request wich will delete every object of a given table -looks suspicious- please use truncate instead..."); + } + */ + $sWhere = self::ClauseWhere($oCondition); + return "DELETE $sDelete FROM $sFrom WHERE $sWhere"; + } + + // Interface, build the SQL query + public function RenderUpdate() + { + // The goal will be to complete the list as we build the Joins + $aFrom = array(); + $aFields = array(); + $oCondition = null; + $aDelTables = array(); + $aSetValues = array(); + $this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues); + + $sFrom = self::ClauseFrom($aFrom); + $sValues = self::ClauseValues($aSetValues); + $sWhere = self::ClauseWhere($oCondition); + return "UPDATE $sFrom SET $sValues WHERE $sWhere"; + } + + // Interface, build the SQL query + public function RenderSelect($aOrderBy = array()) + { + // The goal will be to complete the lists as we build the Joins + $aFrom = array(); + $aFields = array(); + $oCondition = null; + $aDelTables = array(); + $aSetValues = array(); + $this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues); + + $sSelect = self::ClauseSelect($aFields); + $sFrom = self::ClauseFrom($aFrom); + $sWhere = self::ClauseWhere($oCondition); + $sOrderBy = self::ClauseOrderBy($aOrderBy); + if (!empty($sOrderBy)) + { + $sOrderBy = "ORDER BY $sOrderBy"; + } + return "SELECT DISTINCT $sSelect FROM $sFrom WHERE $sWhere $sOrderBy"; + } + + private static function ClauseSelect($aFields) + { + $aSelect = array(); + foreach ($aFields as $sFieldAlias => $sSQLExpr) + { + $aSelect[] = "$sSQLExpr AS $sFieldAlias"; + } + $sSelect = implode(', ', $aSelect); + return $sSelect; + } + + private static function ClauseDelete($aDelTableAliases) + { + $aDelTables = array(); + foreach ($aDelTableAliases as $sTableAlias) + { + $aDelTables[] = "$sTableAlias"; + } + $sDelTables = implode(', ', $aDelTables); + return $sDelTables; + } + + private static function ClauseFrom($aFrom) + { + $sFrom = ""; + foreach ($aFrom as $sTableAlias => $aJoinInfo) + { + switch ($aJoinInfo["jointype"]) + { + case "first": + $sFrom .= "`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]); + break; + case "inner": + $sFrom .= " INNER JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]); + $sFrom .= ") ON ".$aJoinInfo["joincondition"]; + break; + case "left": + $sFrom .= " LEFT JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`"; + $sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]); + $sFrom .= ") ON ".$aJoinInfo["joincondition"]; + break; + default: + throw new CoreException("Unknown jointype: '".$aJoinInfo["jointype"]."'"); + } + } + return $sFrom; + } + + private static function ClauseValues($aValues) + { + $aSetValues = array(); + foreach ($aValues as $sFieldSpec => $value) + { + $aSetValues[] = "$sFieldSpec = ".CMDBSource::Quote($value); + } + $sSetValues = implode(', ', $aSetValues); + return $sSetValues; + } + + private static function ClauseWhere($oConditionExpr) + { + return $oConditionExpr->Render(); + } + + private static function ClauseOrderBy($aOrderBy) + { + $aOrderBySpec = array(); + foreach($aOrderBy as $sFieldAlias => $bAscending) + { + $aOrderBySpec[] = '`'.$sFieldAlias.'`'.($bAscending ? " ASC" : " DESC"); + } + $sOrderBy = implode(", ", $aOrderBySpec); + return $sOrderBy; + } + + // Purpose: prepare the query data, once for all + private function privRender(&$aFrom, &$aFields, &$oCondition, &$aDelTables, &$aSetValues) + { + $sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues); + + // Add the full text search condition, based on each and every requested field + // + // To be updated with a real full text search based on the mySQL settings + // (then it might move somewhere else !) + // + $oCondition = $this->m_oConditionExpr; + if ((count($aFields) > 0) && (count($this->m_aFullTextNeedles) > 0)) + { + $aFieldExp = array(); + foreach ($aFields as $sField) + { + // This is TEMPORARY (that's why it is weird, actually) + // Full text match will be done as an expression in the filter condition + + // $sField is already a string `table`.`column` + // Let's make an expression out of it (again !) + $aFieldExp[] = Expression::FromOQL($sField); + } + $oFullTextExpr = new CharConcatExpression($aFieldExp); + // The cast is necessary because the CONCAT result in a binary string: + // if any of the field is a binary string => case sensitive comparison + // + foreach($this->m_aFullTextNeedles as $sFTNeedle) + { + $oNewCond = new BinaryExpression($oFullTextExpr, 'LIKE', new ScalarExpression("%$sFTNeedle%")); + $oCondition = $oCondition->LogAnd($oNewCond); + } + } + + return $sTableAlias; + } + + private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, $sJoinType = 'first', $sCallerAlias = '', $sLeftField = '', $sRightField = '', $sRightTableAlias = '') + { + $aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable); + + $aTranslationTable[$this->m_sTable]['*'] = $this->m_sTableAlias; + + // Handle the various kinds of join (or first table in the list) + // + if (empty($sRightTableAlias)) + { + $sRightTableAlias = $this->m_sTableAlias; + } + $sJoinCond = "`$sCallerAlias`.`$sLeftField` = `$sRightTableAlias`.`$sRightField`"; + switch ($sJoinType) + { + case "first": + $aFrom[$this->m_sTableAlias] = array("jointype"=>"first", "tablename"=>$this->m_sTable, "joincondition"=>""); + break; + case "inner": + case "left": + // table or tablealias ??? + $aFrom[$this->m_sTableAlias] = array("jointype"=>$sJoinType, "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond"); + break; + } + + // Given the alias, modify the fields and conditions + // before adding them into the current lists + // + foreach($this->m_aFields as $sAlias => $oExpression) + { + $sTable = $oExpression->GetParent(); + $sColumn = $oExpression->GetName(); + $aFields["`$sAlias`"] = $oExpression->Render(); + } + if ($this->m_bToDelete) + { + $aDelTables[] = "`{$this->m_sTableAlias}`"; + } + foreach($this->m_aValues as $sFieldName=>$value) + { + $aSetValues["`{$this->m_sTableAlias}`.`$sFieldName`"] = $value; // quoted further! + } + + // loop on joins, to complete the list of tables/fields/conditions + // + $aTempFrom = array(); // temporary subset of 'from' specs, to be grouped in the final query + foreach ($this->m_aJoinSelects as $aJoinData) + { + $sJoinType = $aJoinData["jointype"]; + $oRightSelect = $aJoinData["select"]; + $sLeftField = $aJoinData["leftfield"]; + $sRightField = $aJoinData["rightfield"]; + $sRightTableAlias = $aJoinData["righttablealias"]; + + $sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $sJoinType, $this->m_sTableAlias, $sLeftField, $sRightField, $sRightTableAlias); + } + $aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom; + + return $this->m_sTableAlias; + } + +} + +?> diff --git a/core/stimulus.class.inc.php b/core/stimulus.class.inc.php new file mode 100644 index 0000000000..d2326f6fc2 --- /dev/null +++ b/core/stimulus.class.inc.php @@ -0,0 +1,59 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class ObjectStimulus +{ + private $m_aParams = array(); + + public function __construct($aParams) + { + $this->m_aParams = $aParams; + $this->ConsistencyCheck(); + } + + public function Get($sParamName) {return $this->m_aParams[$sParamName];} + + // Note: I could factorize this code with the parameter management made for the AttributeDef class + // to be overloaded + static protected function ListExpectedParams() + { + return array("label", "description"); + } + + private function ConsistencyCheck() + { + + // Check that any mandatory param has been specified + // + $aExpectedParams = $this->ListExpectedParams(); + foreach($aExpectedParams as $sParamName) + { + if (!array_key_exists($sParamName, $this->m_aParams)) + { + $aBacktrace = debug_backtrace(); + $sTargetClass = $aBacktrace[2]["class"]; + $sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"]; + throw new CoreException("missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)"); + } + } + } +} + + + +class StimulusUserAction extends ObjectStimulus +{ +} + +?> diff --git a/core/test.class.inc.php b/core/test.class.inc.php new file mode 100644 index 0000000000..f20ebec079 --- /dev/null +++ b/core/test.class.inc.php @@ -0,0 +1,504 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class ExceptionFromError extends Exception +{ + public function getTraceAsHtml() + { + $aBackTrace = $this->getTrace(); + return MyHelpers::get_callstack_html(0, $this->getTrace()); + // return "
    \n".$this->getTraceAsString()."
    \n"; + } +} + + +/** + * Test handler API and basic helpers + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class TestHandler +{ + protected $m_aSuccesses; + protected $m_aWarnings; + protected $m_aErrors; + protected $m_sOutput; + + public function __construct() + { + $this->m_aSuccesses = array(); + $this->m_aWarnings = array(); + $this->m_aErrors = array(); + } + + abstract static public function GetName(); + abstract static public function GetDescription(); + + protected function DoPrepare() {return true;} + abstract protected function DoExecute(); + protected function DoCleanup() {return true;} + + protected function ReportSuccess($sMessage, $sSubtestId = '') + { + $this->m_aSuccesses[] = $sMessage; + } + + protected function ReportWarning($sMessage, $sSubtestId = '') + { + $this->m_aWarnings[] = $sMessage; + } + + protected function ReportError($sMessage, $sSubtestId = '') + { + $this->m_aErrors[] = $sMessage; + } + + public function GetResults() + { + return $this->m_aSuccesses; + } + + public function GetWarnings() + { + return $this->m_aWarnings; + } + + public function GetErrors() + { + return $this->m_aErrors; + } + + public function GetOutput() + { + return $this->m_sOutput; + } + + public function error_handler($errno, $errstr, $errfile, $errline) + { + // Note: return false to call the default handler (stop the program if an error) + + switch ($errno) + { + case E_USER_ERROR: + $this->ReportError($errstr); + //throw new ExceptionFromError("Fatal error in line $errline of file $errfile: $errstr"); + break; + case E_USER_WARNING: + $this->ReportWarning($errstr); + break; + case E_USER_NOTICE: + $this->ReportWarning($errstr); + break; + default: + throw new ExceptionFromError("Fatal warning in line $errline of file $errfile: $errstr"); + $this->ReportWarning("Unknown error type: [$errno] $errstr"); + echo "Unknown error type: [$errno] $errstr
    \n"; + break; + } + return true; // do not call the default handler + } + + public function Execute() + { + ob_start(); + set_error_handler(array($this, 'error_handler')); + try + { + $this->DoPrepare(); + $this->DoExecute(); + } + catch (ExceptionFromError $e) + { + $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); + } + catch (CoreException $e) + { + //$this->ReportError($e->getMessage()); + //$this->ReportError($e->__tostring()); + $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); + } + catch (Exception $e) + { + //$this->ReportError($e->getMessage()); + //$this->ReportError($e->__tostring()); + $this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString()); + } + restore_error_handler(); + $this->m_sOutput = ob_get_clean(); + return (count($this->GetErrors()) == 0); + } +} + + + + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestFunction extends TestHandler +{ + // simply overload DoExecute (temporary) +} + + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestWebServices extends TestHandler +{ + // simply overload DoExecute (temporary) + + static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = '', $sOptionnalHeaders = null) + { + $aDataAndAuth = $aData; + $aDataAndAuth['operation'] = 'login'; + $aDataAndAuth['auth_user'] = $sLogin; + $aDataAndAuth['auth_pwd'] = $sPassword; + + $sHost = $GLOBALS['_SERVER']['HTTP_HOST']; + $sUrl = "http://$sHost/$sRelativeUrl"; + + return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders); + } + + // Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl + // originaly named after do_post_request + // Partially adapted to our coding conventions + static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null) + { + // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. + + $sData = http_build_query($aData); + + $aParams = array('http' => array( + 'method' => 'POST', + 'content' => $sData, + 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", + )); + if ($sOptionnalHeaders !== null) + { + $aParams['http']['header'] .= $sOptionnalHeaders; + } + $ctx = stream_context_create($aParams); + + $fp = @fopen($sUrl, 'rb', false, $ctx); + if (!$fp) + { + throw new Exception("Problem with $sUrl, $php_errormsg"); + } + $response = @stream_get_contents($fp); + if ($response === false) + { + throw new Exception("Problem reading data from $sUrl, $php_errormsg"); + } + return $response; + } +} + +/** + * Test to check that a function outputs some values depending on its input + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestFunctionInOut extends TestFunction +{ + abstract static public function GetCallSpec(); // parameters to call_user_func + abstract static public function GetInOut(); // array of input => output + + protected function DoExecute() + { + $aTests = $this->GetInOut(); + if (is_array($aTests)) + { + foreach ($aTests as $iTestId => $aTest) + { + $ret = call_user_func_array($this->GetCallSpec(), $aTest['args']); + if ($ret != $aTest['output']) + { + // Note: to be improved to cope with non string parameters + $this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId); + } + else + { + $this->ReportSuccess("Found the expected output '$ret'", $iTestId); + } + } + } + else + { + $ret = call_user_func($this->GetCallSpec()); + $this->ReportSuccess('Finished successfully'); + } + } +} + + +/** + * Test to check an URL (Searches for Error/Warning/Etc keywords) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestUrl extends TestHandler +{ + abstract static public function GetUrl(); + abstract static public function GetErrorKeywords(); + abstract static public function GetWarningKeywords(); + + protected function DoExecute() + { + return true; + } +} + + +/** + * Test to check a user management module + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestUserRights extends TestHandler +{ + protected function DoExecute() + { + return true; + } +} + + +/** + * Test to execute a scenario on a given DB + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestScenarioOnDB extends TestHandler +{ + abstract static public function GetDBHost(); + abstract static public function GetDBUser(); + abstract static public function GetDBPwd(); + abstract static public function GetDBName(); + + protected function DoPrepare() + { + $sDBHost = $this->GetDBHost(); + $sDBUser = $this->GetDBUser(); + $sDBPwd = $this->GetDBPwd(); + $sDBName = $this->GetDBName(); + + CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd); + if (CMDBSource::IsDB($sDBName)) + { + CMDBSource::DropDB($sDBName); + } + CMDBSource::CreateDB($sDBName); + } + + protected function DoCleanup() + { + // CMDBSource::DropDB($this->GetDBName()); + } +} + + +/** + * Test to use a business model on a given DB + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestBizModel extends TestHandler +{ +// abstract static public function GetDBSubName(); +// abstract static public function GetBusinessModelFile(); + abstract static public function GetConfigFile(); + + protected function DoPrepare() + { + MetaModel::Startup($this->GetConfigFile(), true); // allow missing DB + MetaModel::CheckDefinitions(); + + // something here to create records... but that's another story + } + + protected function ResetDB() + { + if (MetaModel::DBExists()) + { + MetaModel::DBDrop(); + } + MetaModel::DBCreate(); + } + + static protected function show_list($oObjectSet) + { + $oObjectSet->Rewind(); + $aData = array(); + while ($oItem = $oObjectSet->Fetch()) + { + $aValues = array(); + foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode) + { + $aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode); + } + //echo $oItem->GetKey()." => ".implode(", ", $aValues)."
    \n"; + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + } + + static protected function search_and_show_list(DBObjectSearch $oMyFilter) + { + $oObjSet = new CMDBObjectSet($oMyFilter); + echo $oMyFilter->__DescribeHTML()."' - Found ".$oObjSet->Count()." items.
    \n"; + self::show_list($oObjSet); + } + + static protected function search_and_show_list_from_sibusql($sSibuSQL) + { + echo $sSibuSQL."...
    \n"; + $oNewFilter = DBObjectSearch::FromSibuSQL($sSibuSQL); + self::search_and_show_list($oNewFilter); + } +} + + +/** + * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestBizModelGeneric extends TestBizModel +{ + static public function GetName() + { + return 'Full test on a given business model'; + } + + static public function GetDescription() + { + return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.'; + } + + protected function DoPrepare() + { + parent::DoPrepare(); + + if (!MetaModel::DBExists()) + { + MetaModel::DBCreate(); + } + // something here to create records... but that's another story + } + + protected function DoExecute() + { + foreach(MetaModel::GetClasses() as $sClassName) + { + if (MetaModel::IsAbstract($sClassName)) continue; + + $oNobody = MetaModel::GetObject($sClassName, 123); + $oBaby = new $sClassName; + $oFilter = new DBObjectSearch($sClassName); + + // Challenge reversibility of SibusQL / filter object + // + $sExpr1 = $oFilter->ToSibuSQL(); + $oNewFilter = DBObjectSearch::FromSibuSQL($sExpr1); + $sExpr2 = $oNewFilter->ToSibuSQL(); + if ($sExpr1 != $sExpr2) + { + $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); + } + + // Use the filter (perform the query) + // + $oSet = new CMDBObjectSet($oFilter); + $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + } + return true; + } +} + + +?> diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php new file mode 100644 index 0000000000..b62c8a0424 --- /dev/null +++ b/core/userrights.class.inc.php @@ -0,0 +1,211 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class UserRightException extends CoreException +{ +} + + +define('UR_ALLOWED_NO', 0); +define('UR_ALLOWED_YES', 1); +define('UR_ALLOWED_DEPENDS', 2); + +define('UR_ACTION_READ', 1); // View an object +define('UR_ACTION_MODIFY', 2); // Create/modify an object/attribute +define('UR_ACTION_DELETE', 3); // Delete an object + +define('UR_ACTION_BULK_READ', 4); // Export multiple objects +define('UR_ACTION_BULK_MODIFY', 5); // Create/modify multiple objects +define('UR_ACTION_BULK_DELETE', 6); // Delete multiple objects + +define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions (CSV import, View schema...) + +/** + * User management module API + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class UserRightsAddOnAPI +{ + abstract public function Setup(); // initial installation + abstract public function Init(); // loads data (possible optimizations) + abstract public function CheckCredentials($iUserId, $sPassword); // returns the id of the user or false + abstract public function GetFilter($iUserId, $sClass); // returns a filter object + abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances); + abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances); + abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances); +} + + + +/** + * User management core API + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class UserRights +{ + protected static $m_oAddOn; + protected static $m_sUser; + protected static $m_sRealUser; + protected static $m_iUserId; + protected static $m_iRealUserId; + + public static function SelectModule($sModuleName) + { + if (!class_exists($sModuleName)) + { + throw new CoreException("Could not select this module, '$sModuleName' in not a valid class name"); + return; + } + if (!is_subclass_of($sModuleName, 'UserRightsAddOnAPI')) + { + throw new CoreException("Could not select this module, the class '$sModuleName' is not derived from UserRightsAddOnAPI"); + return; + } + self::$m_oAddOn = new $sModuleName; + self::$m_oAddOn->Init(); + self::$m_sUser = ''; + self::$m_sRealUser = ''; + self::$m_iUserId = 0; + self::$m_iRealUserId = 0; + } + + // Installation: create the very first user + public static function CreateAdministrator($sAdminUser, $sAdminPwd) + { + return self::$m_oAddOn->CreateAdministrator($sAdminUser, $sAdminPwd); + } + + // Installation (e.g: give default values for users) + public static function Setup() + { + // to be discussed... + return self::$m_oAddOn->Setup(); + } + + protected static function IsLoggedIn() + { + return (!empty(self::$m_sUser)); + } + + public static function Login($sName, $sPassword) + { + self::$m_iUserId = self::$m_oAddOn->CheckCredentials($sName, $sPassword); + if ( self::$m_iUserId !== false ) + { + self::$m_sUser = $sName; + self::$m_iRealUserId = self::$m_iUserId; + self::$m_sRealUser = $sName; + return true; + } + else + { + return false; + } + } + + public static function Impersonate($sName, $sPassword) + { + if (!self::CheckLogin()) return false; + + self::$m_iRealUserId = self::$m_oAddOn->CheckCredentials($sName, $sPassword); + if ( self::$m_iRealUserId !== false) + { + self::$m_sUser = $sName; + return true; + } + else + { + return false; + } + } + + public static function GetUser() + { + return self::$m_sUser; + } + + public static function GetUserId() + { + return self::$m_iUserId; + } + + public static function GetRealUser() + { + return self::$m_sRealUser; + } + + public static function GetRealUserId() + { + return self::$m_iRealUserId; + } + + protected static function CheckLogin() + { + if (!self::IsLoggedIn()) + { + //throw new UserRightException('No user logged in', array()); + return false; + } + return true; + } + + + public static function GetFilter($sClass) + { + if (!MetaModel::HasCategory($sClass, 'bizModel')) return new DBObjectSearch($sClass); + if (!self::CheckLogin()) return false; + + return self::$m_oAddOn->GetFilter(self::$m_iUserId, $sClass); + } + + public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $aInstances) + { + if (!MetaModel::HasCategory($sClass, 'bizModel')) return true; + if (!self::CheckLogin()) return false; + + return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $aInstances); + } + + public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $aInstances) + { + if (!MetaModel::HasCategory($sClass, 'bizModel')) return true; + if (!self::CheckLogin()) return false; + + return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $aInstances); + } + + public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + { + if (!MetaModel::HasCategory($sClass, 'bizModel')) return true; + if (!self::CheckLogin()) return false; + + return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $aInstances); + } +} + + +?> diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php new file mode 100644 index 0000000000..c541683cce --- /dev/null +++ b/core/valuesetdef.class.inc.php @@ -0,0 +1,238 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +require_once('MyHelpers.class.inc.php'); + +abstract class ValueSetDefinition +{ + protected $m_bIsLoaded = false; + protected $m_aValues = array(); + protected $m_aArgsObj = array(); + protected $m_aArgsApp = array(); + + + // Displayable description that could be computed out of the std usage context + public function GetValuesDescription() + { + $aValues = $this->GetValues(array(), ''); + $aDisplayedValues = array(); + foreach($aValues as $key => $value) + { + $aDisplayedValues[] = "$key => $value"; + } + $sAllowedValues = implode(', ', $aDisplayedValues); + return $sAllowedValues; + } + + + public function GetValues($aArgs, $sBeginsWith) + { + if (!$this->m_bIsLoaded) + { + $this->LoadValues($aArgs); + $this->m_bIsLoaded = true; + } + if (strlen($sBeginsWith) == 0) + { + $aRet = $this->m_aValues; + } + else + { + $iCheckedLen = strlen($sBeginsWith); + $sBeginsWith = strtolower($sBeginsWith); + $aRet = array(); + foreach ($this->m_aValues as $sKey=>$sValue) + { + if (strtolower(substr($sValue, 0, $iCheckedLen)) == $sBeginsWith) + { + $aRet[$sKey] = $sValue; + } + } + } + return $aRet; + } + + public function ListArgsFromContextApp() + { + return $this->m_aArgsObj; + } + public function ListArgsFromContextObj() + { + return $this->m_aArgsApp; + } + + abstract protected function LoadValues($aArgs); +} + + +/** + * Set of existing values for an attribute, given a search filter + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class ValueSetObjects extends ValueSetDefinition +{ + protected $m_sFilterExpr; // in SibuSQL + protected $m_sValueAttCode; + protected $m_aOrderBy; + + public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array()) + { + $this->m_sFilterExpr = $sFilterExp; + $this->m_sValueAttCode = $sValueAttCode; + $this->m_aOrderBy = $aOrderBy; + } + + protected function LoadValues($aArgs) + { + $this->m_aValues = array(); + + $oFilter = DBObjectSearch::FromSibuSQL($this->m_sFilterExpr, $aArgs); + if (!$oFilter) return false; + + if (empty($this->m_sValueAttCode)) + { + $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); + } + $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy); + while ($oObject = $oObjects->Fetch()) + { + $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($this->m_sValueAttCode); + } + return true; + } + + public function GetValuesDescription() + { + return 'Filter: '.$this->m_sFilterExpr; + } +} + + +/** + * Set of existing values for an attribute, given a search filter and a relation id + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class ValueSetRelatedObjects extends ValueSetObjects +{ + public function __construct($sFilterExp, $sRelCode, $sClass, $sValueAttCode = '', $aOrderBy = array()) + { + $sFullFilterExp = "$sClass: RELATED ($sRelCode, 1) TO ($sFilterExp)"; + parent::__construct($sFullFilterExp, $sValueAttCode, $aOrderBy); + } +} + + +/** + * Set oof existing values for an attribute, given a set of objects (AttributeLinkedSet) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class ValueSetRelatedObjectsFromLinkedSet extends ValueSetDefinition +{ + protected $m_sLinkedSetAttCode; + protected $m_sRelCode; + protected $m_sValueAttCode; + protected $m_aOrderBy; + + public function __construct($sLinkedSetAttCode, $sRelCode, $sValueAttCode = '', $aOrderBy = array()) + { + $this->m_sLinkedSetAttCode = $sLinkedSetAttCode; + $this->m_sRelCode = $sRelCode; + $this->m_sValueAttCode = $sValueAttCode; + $this->m_aOrderBy = $aOrderBy; + } + + protected function LoadValues($aArgs) + { + $this->m_aValues = array(); + + if (empty($this->m_sValueAttCode)) + { + $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); + } + + $oCurrentObject = @$aArgs['*this*']; + if (!is_object($oCurrentObject)) return false; + + $oObjects = $oCurrentObject->Get($this->m_sLinkedSetAttCode); + while ($oObject = $oObjects->Fetch()) + { + $this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode); + } + return true; + } + + public function GetValuesDescription() + { + return 'Objects related ('.$this->m_sRelCode.') to objects linked through '.$this->m_sLinkedSetAttCode; + } +} + + +/** + * Fixed set values (could be hardcoded in the business model) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class ValueSetEnum extends ValueSetDefinition +{ + public function __construct($Values) + { + if (is_array($Values)) + { + $aValues = $Values; + } + else + { + $aValues = array(); + foreach (explode(",", $Values) as $sVal) + { + $sVal = trim($sVal); + $sKey = $sVal; + $aValues[$sKey] = $sVal; + } + } + $this->m_aValues = $aValues; + } + + protected function LoadValues($aArgs) + { + return true; + } +} + +?> diff --git a/css/blue_green.css b/css/blue_green.css new file mode 100644 index 0000000000..1e51162c33 --- /dev/null +++ b/css/blue_green.css @@ -0,0 +1,222 @@ +/* CSS Document */ +body { + font-family: Verdana, Arial, Helevtica; + font-size: smaller; + background-color: #68a; + color:#000000; + margin: 0; /* Remove body margin/padding */ + padding: 0; + overflow: hidden; /* Remove scroll bars on browser window */ +} + +table { + border: 1px solid #000000; +} + +.raw_output { + font-family: Courier-New, Courier, Arial, Helevtica; + font-size: smaller; + background-color: #eeeeee; + color: #000000; + border: 1px dashed #000000; + padding: 0.25em; + margin-top: 1em; +} + +th { + font-family: Verdana, Arial, Helvetica; + font-size: smaller; + color: #000000; + background-color:#ace27d; +} + +td { + font-family: Verdana, Arial, Helvetica; + font-size: smaller; + background-color: #b7cfe8; +} + +tr.clicked td { + font-family: Verdana, Arial, Helvetica; + font-size: smaller; + background-color: #ffcfe8; +} + +td.label { + font-family: Verdana, Arial, Helvetica; + font-size: smaller; + color: #000000; + background-color:#ace27d; + padding: 0.2em; +} + +td a, td a:visited { + text-decoration:none; + color:#000000; +} +td a:hover { + text-decoration:underline; + color:#FFFFFF; +} + +a.small_action { + font-family: Verdana, Arial, Helvetica; + font-size: smaller; + color: #000000; + text-decoration:none; +} + +.display_block { + noborder: 1px dashed #CCC; + background: #79b; + padding:0.25em; +} +div#TopPane .display_block { + background: #f0eee0; + padding:0.25em; + text-align:center; +} +div#TopPane label { + color:#000; + background: #f0eee0; +} + +div#TopPane td { + color:#000; + background: #f0eee0; +} + +.loading { + noborder: 1px dashed #CCC; + background: #b9c1c8; + padding:0.25em; +} + +label { + font-family:Georgia, "Times New Roman", Times, serif; + color:#FFFFFF; + text-align:right; +} + +input.textSearch { + border:1px solid #333; + noheight:1.2em; + font-size:0.8em; + font-family:Verdana, Arial, Helvetica, sans-serif; + color:#000000; +} + +/* By Rom */ +.csvimport_createobj { + color: #AA0000; + background-color:#EEEEEE; +} +.csvimport_error { + font-weight: bold; + color: #FF0000; + background-color:#EEEEEE; +} +.csvimport_warning { + color: #CC8888; + background-color:#EEEEEE; +} +.csvimport_ok { + color: #00000; + background-color:#BBFFBB; +} +.csvimport_reconkey { + font-style: italic; + color: #888888; + background-color:#FFFFF; +} +.csvimport_extreconkey { + color: #888888; + background-color:#FFFFFF; +} + +.treeview, .treeview ul { + padding: 0; + margin: 0; + list-style: none; +} + +.treeview li { + margin: 0; + padding: 3px 0pt 3px 16px; + font-size:0.9em; +} + +ul.dir li { + padding: 2px 0 0 16px; +} + +.treeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; } +.treeview .collapsable { background-image: url(/images/tv-collapsable.gif); } +.treeview .expandable { background-image: url(/images/tv-expandable.gif); } +.treeview .last { background-image: url(/images/tv-item-last.gif); } +.treeview .lastCollapsable { background-image: url(/images/tv-collapsable-last.gif); } +.treeview .lastExpandable { background-image: url(/images/tv-expandable-last.gif); } + +#Header { padding: 0; background:#ccc url(/images/bandeau2.gif) repeat-x center;} +div.iTopLogo { + background:url(/images/iTop.gif) no-repeat center; + width:100px; + height:56px; +} +div.iTopLogo span { + display:none; +} + +#MySplitter { + /* Height is set to match window size in $().ready() below */ + border:0px; + margin:4px; + padding:0px; + min-width: 100px; /* Splitter can't be too thin ... */ + min-height: 100px; /* ... or too flat */ +} +#LeftPane { + background: #f0eee0; + padding: 4px; + overflow: auto; /* Scroll bars appear as needed */ + color:#666; +} +#TopPane { /* Top nested in right pane */ + background: #f0eee0; + padding: 4px; + height: 150px; /* Initial height */ + min-height: 75px; /* Minimum height */ + overflow: auto; + color:#666; +} +#RightPane { /* Bottom nested in right pane */ + background: #79b; + height:150px; /* Initial height */ + min-height:130px; + no.padding:15px; + no.margin:10px; + overflow:auto; + color:#fff; +} + +#BottomPane { /* Bottom nested in right pane */ + background: #79b; + padding: 4px; + overflow: auto; + color:#fff; +} +#MySplitter .vsplitbar { + width: 7px; + height: 50px; + background: #68a url(/images/vgrabber2.gif) no-repeat center; +} +#MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover { + background: #68a url(/images/vgrabber2_active.gif) no-repeat center; +} +#MySplitter .hsplitbar { + height: 8px; + background: #68a url(/images/hgrabber2.gif) no-repeat center; +} +#MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover { + background: #68a url(/images/hgrabber2_active.gif) no-repeat center; +} diff --git a/css/date.picker.css b/css/date.picker.css new file mode 100644 index 0000000000..a394482a08 --- /dev/null +++ b/css/date.picker.css @@ -0,0 +1,117 @@ + + +table.jCalendar { + border: 1px solid #000; + background: #aaa; + border-collapse: separate; + border-spacing: 2px; +} +table.jCalendar th { + background: #333; + color: #fff; + font-weight: bold; + padding: 3px 5px; +} +table.jCalendar td { + background: #ccc; + color: #000; + padding: 3px 5px; + text-align: center; +} +table.jCalendar td.other-month { + background: #ddd; + color: #aaa; +} +table.jCalendar td.today { + background: #666; + color: #fff; +} +table.jCalendar td.selected { + background: #f66; + color: #fff; +} +table.jCalendar td.selected:hover { + background: #f33; + color: #fff; +} +table.jCalendar td:hover, table.jCalendar td.dp-hover { + background: #fff; + color: #000; +} +table.jCalendar td.disabled, table.jCalendar td.disabled:hover { + background: #bbb; + color: #888; +} + +/* For the popup */ + +/* NOTE - you will probably want to style a.dp-choose-date - see how I did it in demo.css */ + +div.dp-popup { + position: relative; + background: #ccc; + font-size: 10px; + font-family: arial, sans-serif; + padding: 2px; + width: 171px; + line-height: 1.2em; +} +div#dp-popup { + position: absolute; + z-index: 199; +} +div.dp-popup h2 { + font-size: 12px; + text-align: center; + margin: 2px 0; + padding: 0; +} +a#dp-close { + font-size: 11px; + padding: 4px 0; + text-align: center; + display: block; +} +a#dp-close:hover { + text-decoration: underline; +} +div.dp-popup a { + color: #000; + text-decoration: none; + padding: 3px 2px 0; +} +div.dp-popup div.dp-nav-prev { + position: absolute; + top: 2px; + left: 4px; + width: 100px; +} +div.dp-popup div.dp-nav-prev a { + float: left; +} +/* Opera needs the rules to be this specific otherwise it doesn't change the cursor back to pointer after you have disabled and re-enabled a link */ +div.dp-popup div.dp-nav-prev a, div.dp-popup div.dp-nav-next a { + cursor: pointer; +} +div.dp-popup div.dp-nav-prev a.disabled, div.dp-popup div.dp-nav-next a.disabled { + cursor: default; +} +div.dp-popup div.dp-nav-next { + position: absolute; + top: 2px; + right: 4px; + width: 100px; +} +div.dp-popup div.dp-nav-next a { + float: right; +} +div.dp-popup a.disabled { + cursor: default; + color: #aaa; +} +div.dp-popup td { + cursor: pointer; +} +div.dp-popup td.disabled { + cursor: default; +} \ No newline at end of file diff --git a/css/default.css b/css/default.css new file mode 100644 index 0000000000..eaabed838f --- /dev/null +++ b/css/default.css @@ -0,0 +1,165 @@ +/* CSS Document */ +body { + font-family: Verdana, Arial, Helevtica; + font-size: smaller; + background-color: #ffffff; + color:#000000; + margin: 0; /* Remove body margin/padding */ + padding: 0; + overflow: hidden; /* Remove scroll bars on browser window */ +} + +table { + border: 1px solid #000000; +} + +.raw_output { + font-family: Courier-New, Courier, Arial, Helevtica; + font-size: smaller; + background-color: #eeeeee; + color: #000000; + border: 1px dashed #000000; + padding: 0.25em; + margin-top: 1em; +} + +th { + font-family: Verdana, Arial, Helevtica; + font-size: smaller; + color: #000000; + background-color:#E1DEB5; +} + +td { + font-family: Verdana, Arial, Helevtica; + font-size: smaller; +} + +td.label { + font-family: Verdana, Arial, Helevtica; + font-size: smaller; + color: #000000; + background-color:#E1DEB5; + padding: 0.2em; +} + +a.small_action { + font-family: Verdana, Arial, Helvetica; + font-size: smaller; + color: #000000; + text-decoration:none; +} + +.display_block { + border: 1px dashed #CCC; + background: #CFC; + padding:0.25em; +} + +.loading { + border: 1px dashed #CCC; + background: #FCC; + padding:0.25em; +} + +/* By Rom */ +.csvimport_createobj { + color: #AA0000; + background-color:#EEEEEE; +} +.csvimport_error { + font-weight: bold; + color: #FF0000; + background-color:#EEEEEE; +} +.csvimport_warning { + color: #CC8888; + background-color:#EEEEEE; +} +.csvimport_ok { + color: #00000; + background-color:#BBFFBB; +} +.csvimport_reconkey { + font-style: italic; + color: #888888; + background-color:#FFFFF; +} +.csvimport_extreconkey { + color: #888888; + background-color:#FFFFFF; +} + +.treeview, .treeview ul { + padding: 0; + margin: 0; + list-style: none; +} + +.treeview li { + margin: 0; + padding: 3px 0pt 3px 16px; +} + +ul.dir li { padding: 2px 0 0 16px; } + +.treeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; } +.treeview .collapsable { background-image: url(../images/tv-collapsable.gif); } +.treeview .expandable { background-image: url(../images/tv-expandable.gif); } +.treeview .last { background-image: url(../images/tv-item-last.gif); } +.treeview .lastCollapsable { background-image: url(../images/tv-collapsable-last.gif); } +.treeview .lastExpandable { background-image: url(../images/tv-expandable-last.gif); } + +#MySplitter { + /* Height is set to match window size in $().ready() below */ + border:0px; + margin:4px; + padding:0px; + min-width: 100px; /* Splitter can't be too thin ... */ + min-height: 100px; /* ... or too flat */ +} +#LeftPane { + background: #f0eee0; + padding: 4px; + overflow: auto; /* Scroll bars appear as needed */ + color:#666; +} +#TopPane { /* Top nested in right pane */ + background: #f0eee0; + padding: 4px; + height: 150px; /* Initial height */ + min-height: 75px; /* Minimum height */ + overflow: auto; + color:#666; +} +#RightPane { /* Bottom nested in right pane */ + background: #79b; + height:150px; /* Initial height */ + min-height:130px; + no.padding:15px; + no.margin:10px; + overflow:auto; + color:#fff; +} + +#BottomPane { /* Bottom nested in right pane */ + background: #79b; + padding: 4px; + overflow: auto; + color:#fff; +} +#MySplitter .vsplitbar { + width: 7px; + height: 50px; + background: #68a url(../images/vgrabber2.gif) no-repeat center; +} +#MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover { + background: #68a url(../images/vgrabber2_active.gif) no-repeat center; +} +#MySplitter .hsplitbar { + height: 8px; + background: #68a url(../images/hgrabber2.gif) no-repeat center; +} +#MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover { + background: #68a url(../images/hgrabber2_active.gif) no-repeat center; +} diff --git a/css/jqModal.css b/css/jqModal.css new file mode 100644 index 0000000000..db45b7039b --- /dev/null +++ b/css/jqModal.css @@ -0,0 +1,40 @@ +/* jqModal base Styling courtesy of; + Brice Burgess */ + +/* The Window's CSS z-index value is respected (takes priority). If none is supplied, + the Window's z-index value will be set to 3000 by default (in jqModal.js). You + can change this value by either; + a) supplying one via CSS + b) passing the "zIndex" parameter. E.g. (window).jqm({zIndex: 500}); */ + +.jqmWindow { + display: none; + + position: fixed; + top: 10%; + left: 20%; + + margin-left: -100px; + width: 800px; + + background-color: #FFF; + color: #333; + border: 1px solid black; + padding: 12px; +} + +.jqmOverlay { background-color: #333; } + +/* Background iframe styling for IE6. Prevents ActiveX bleed-through (" ] || + + !tags.indexOf("", "" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "", "
    " ] || + + !tags.indexOf("", "" ] || + + // matched above + (!tags.indexOf("", "" ] || + + !tags.indexOf("", "" ] || + + // IE can't serialize and \n"); + } + break; + + case 'apply_modify': + $sClass = utils::ReadPostedParam('class', ''); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class' and 'id' parameters must be specifed for this operation.

    \n"); + } + else if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already be updated!\n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $oP->set_title("iTop - ".$oObj->GetName()." - $sClass modification"); + $oP->add("

    ".$oObj->GetName()." - $sClass modification

    \n"); + $bObjectModified = false; + foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) + { + $iFlags = $oObj->GetAttributeFlags($sAttCode); + if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) + { + // Non-visible, or read-only attribute, do nothing + } + else if ($oAttDef->IsLinkSet()) + { + // Link set, the data is a set of link objects, encoded in JSON + $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); + if (!empty($aAttributes[$sAttCode])) + { + $oLinkSet = WizardHelper::ParseJsonSet($oObj, $oAttDef->GetLinkedClass(), $oAttDef->GetExtKeyToMe(), $aAttributes[$sAttCode]); + $oObj->Set($sAttCode, $oLinkSet); + // TO DO: detect a real modification, for now always update !! + $bObjectModified = true; + } + } + else if (!$oAttDef->IsExternalField()) + { + $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); + $previousValue = $oObj->Get($sAttCode); + if (!empty($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) + { + $oObj->Set($sAttCode, $aAttributes[$sAttCode]); + $bObjectModified = true; + } + } + } + if (!$bObjectModified) + { + $oP->p("No modification detected. ".get_class($oObj)." has not been updated.\n"); + } + else if ($oObj->CheckToUpdate()) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + + $oP->p(get_class($oObj)." updated.\n"); + } + else + { + $oP->p("Error: object can not be updated!\n"); + //$oObj->Reload(); // restore default values! + } + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); + } + } + $oObj->DisplayDetails($oP); + break; + + case 'delete': + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + $oObj = $oContext->GetObject($sClass, $id); + $sName = $oObj->GetName(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $oMyChange->DBInsert(); + $oObj->DBDeleteTracked($oMyChange); + $oP->add("

    ".$sName." - $sClass deleted

    \n"); + break; + + case 'apply_new': + $oP->p('Creation of the object'); + $oP->p('Obsolete, should now go through the wizard...'); + break; + + case 'apply_clone': + $sClass = utils::ReadPostedParam('class', ''); + $iCloneId = utils::ReadPostedParam('clone_id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already be cloned!\n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $iCloneId); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($oObj)); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ( ('finalclass' != $sAttCode) && // finalclass is a reserved word, hardcoded ! + ($sStateAttCode != $sAttCode) && + (!$oAttDef->IsExternalField()) ) + { + $value = utils::ReadPostedParam('attr_'.$sAttCode, ''); + $oObj->Set($sAttCode, $value); + } + } + $oObj->DBCloneTracked($oMyChange); + $oP->add("

    ".$oObj->GetName()." - $sClass created

    \n"); + $oObj->DisplayDetails($oP); + } + + break; + + case 'wizard_apply_new': + $sJson = utils::ReadPostedParam('json_obj', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already be created!\n"); + } + else + { + $oObj = $oWizardHelper->GetTargetObject(); + if (is_object($oObj)) + { + $sClass = get_class($oObj); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBInsertTracked($oMyChange); + $oP->set_title("iTop - ".$oObj->GetName()." - $sClass created"); + $oP->add("

    ".$oObj->GetName()." - $sClass created

    \n"); + $oObj->DisplayDetails($oP); + } + } + break; + + case 'stimulus': + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + $sStimulus = utils::ReadParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class', 'id' and 'stimulus' parameters must be specifed for this operation.

    \n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) + { + $oP->add("

    Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

    \n"); + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->Get('label'); + $sActionDetails = $aStimuli[$sStimulus]->Get('description'); + $aTransition = $aTransitions[$sStimulus]; + $sTargetState = $aTransition['target_state']; + $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add("
    \n"); + $oP->add("

    $sActionLabel - {$oObj->GetName()}

    \n"); + //$oP->add("

    Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

    \n"); + $oP->add("
    \n"); + $oObj->DisplayBareDetails($oP); + $aTargetState = $aTargetStates[$sTargetState]; + //print_r($aTransitions[$sStimulus]); + //print_r($aTargetState); + $aExpectedAttributes = $aTargetState['attribute_list']; + $oP->add("
    \n"); + $oP->add("

    $sActionDetails

    \n"); + $oP->add("
    \n"); + $oP->add("
    \n"); + $aDetails = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) + { + $aAttributesDef = MetaModel::ListAttributeDefs($sClass); + $oAttDef = $aAttributesDef[$sAttCode]; + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetDisplayValue($sAttCode)); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + } + $oP->details($aDetails); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add($oAppContext->GetForForm()); + $oP->add("    \n"); + $oP->add("\n"); + $oP->add("
    \n"); + $oP->add("
    \n"); + $oP->add("
    \n"); + } + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); + } + } + break; + + case 'apply_stimulus': + $sClass = utils::ReadPostedParam('class', ''); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + $sStimulus = utils::ReadPostedParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class', 'id' and 'stimulus' parameters must be specifed for this operation.

    \n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) + { + $oP->add("

    Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

    \n"); + } + else if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already be updated!\n"); + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->Get('label'); + $sActionDetails = $aStimuli[$sStimulus]->Get('description'); + $aTransition = $aTransitions[$sStimulus]; + $sTargetState = $aTransition['target_state']; + $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add("
    \n"); + $oP->add("

    $sActionLabel - {$oObj->GetName()}

    \n"); + $oP->add("

    $sActionDetails

    \n"); + $oP->add("

    Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

    \n"); + $oP->add("
    \n"); + $aTargetState = $aTargetStates[$sTargetState]; + //print_r($aTransitions[$sStimulus]); + //print_r($aTargetState); + $aExpectedAttributes = $aTargetState['attribute_list']; + $aDetails = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + if (($iExpectCode & OPT_ATT_MUSTCHANGE) || ($oObj->Get($sAttCode) == '') ) + { + $paramValue = utils::ReadPostedParam("attr_$sAttCode", ''); + $oObj->Set($sAttCode, $paramValue); + } + } + if ($oObj->ApplyStimulus($sStimulus) && $oObj->CheckToUpdate()) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + + $oP->p(get_class($oObj)." updated.\n"); + } + $oObj->DisplayDetails($oP); + } + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); + } + } + break; + + + default: + $oActiveNode->RenderContent($oP, $oAppContext->GetAsHash()); +} +$oP->output(); +?> diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php new file mode 100644 index 0000000000..ebe762fb36 --- /dev/null +++ b/pages/UniversalSearch.php @@ -0,0 +1,142 @@ +add("
    "); +$oP->add("
    "); +$oP->add(""); +$oP->add("Select the class to search:
    "); + +// Second part: advanced search form: +$oFilter = null; +if (!empty($sFilter)) +{ + $oFilter = CMDBSearchFilter::unserialize($sFilter); +} +else if (!empty($sClassName)) +{ + $oFilter = new CMDBSearchFilter($sClassName); +} + +if ($oFilter != null) +{ + $oSet =new CMDBObjectSet($oFilter); + cmdbAbstractObject::DisplaySearchForm($oP, $oSet, array('org_id' => $currentOrganization, 'class' => $sClassName)); + $oP->add("
    \n"); + + // Search results + $oP->add("
    "); + $oResultBlock = new DisplayBlock($oFilter, 'list', false); + $oResultBlock->RenderContent($oP); + + // Menu node + $sFilter = $oFilter->ToSibusQL(); + $sMenuNodeContent = << +$sFilter +
    +
    +

    +$sFilter +
    +EOF; + + + if ($sOperation == "add_menu") + { + $oMenuNode = MetaModel::NewObject('menuNode'); + $sClass = utils::ReadPostedParam('class', ''); + $sLabel = utils::ReadPostedParam('label', ''); + $sDescription = utils::ReadPostedParam('description', ''); + $iPreviousNodeId = utils::ReadPostedParam('previous_node_id', 1); + $bChildItem = utils::ReadPostedParam('child_item', false); + $oMenuNode->Set('label', $sDescription); + $oMenuNode->Set('name', $sLabel); + $oMenuNode->Set('icon_path', '/images/std_view.gif'); + $oMenuNode->Set('template', $sMenuNodeContent); + $oMenuNode->Set('hyperlink', 'UI.php'); + $oMenuNode->Set('type', 'user'); + $oMenuNode->Set('user_id', UserRights::GetUserId()); + $oPreviousNode = MetaModel::GetObject('menuNode', $iPreviousNodeId); + if ($bChildItem) + { + // Insert the new item as a child of the previous one + $oMenuNode->Set('parent_id', $iPreviousNodeId); + $oMenuNode->Set('rank', 1); // A new child item is the first one, so let's start the numbering at 1 + // If there are already child nodes, shift their rank by one + // to make room for the newly inserted child node + $oNextNodeSet = $oPreviousNode->GetChildNodesSet(null); // null => don't limit ourselves to the user context + // since we need to update all children in order to keep + // the database consistent + while($oNextNode = $oNextNodeSet->Fetch()) + { + $oNextNode->Set('rank', 1 + $oNextNode->Get('rank')); + $oNextNode->DBUpdate(); + } + } + else + { + // Insert the new item as the next sibling of the previous one + $oMenuNode->Set('parent_id', $oPreviousNode->Get('parent_id')); + $oMenuNode->Set('rank', 1 + $oPreviousNode->Get('rank')); // the new item comes immediatly after the selected one + // Add 1 to the rank of all the nodes currently following the 'selected' one + // to make room for the newly inserted node + $oNextNodeSet = $oPreviousNode->GetNextNodesSet(null); // null => don't limit ourselves to the user context + // since we need to update all children in order to keep + // the database consistent + while($oNextNode = $oNextNodeSet->Fetch()) + { + $oNextNode->Set('rank', 1 + $oNextNode->Get('rank')); + $oNextNode->DBUpdate(); + } + + } + if ($oMenuNode->CheckToInsert()) + { + $oMenuNode->DBInsert(); + $oP->add("
    "); + $oP->add("

    Menu item created !

    "); + $oP->add(""); + $oP->add(""); + $oP->add(""); + $oP->add(""); + } + } + + $oP->add("\n"); +} +else +{ + $oP->add("\n"); +} + +$oP->output(); +?> diff --git a/pages/advanced_search.php b/pages/advanced_search.php new file mode 100644 index 0000000000..e10a2c0956 --- /dev/null +++ b/pages/advanced_search.php @@ -0,0 +1,310 @@ +no_cache(); + + +MetaModel::CheckDefinitions(); +// new API - MetaModel::DBCheckFormat(); +// not necessary, and time consuming! +// MetaModel::DBCheckIntegrity(); + + +function ReadParam($sName, $defaultValue = "") +{ + return isset($_REQUEST[$sName]) ? $_REQUEST[$sName] : $defaultValue; +} + + + +function Page1_AskClass($oPage) +{ + $oPage->add("\n"); + //$oPage->add(""); + $oPage->p("Please select the type of object that you want to look for:"); + $oPage->MakeClassesSelect("class", "", 50); + $oPage->add("\n"); + $oPage->add("
    \n"); +} + + +function Page2_ConfigFilters($oPage, $oFilter) +{ + $sClass = $oFilter->GetClass(); + + $oPage->p("Objects of class $sClass"); + $oPage->add("
    \n"); + $oPage->add("\n"); + + // Full text input + // + $oPage->add("
    \n"); + $oPage->add("Full text: "); + $sFullText = ""; + foreach($oFilter->GetCriteria_FullText() as $sFullText) + { + // #@# Known limitation: do not consider other full text conditions... + continue; + } + + $oPage->add("\n"); + $oPage->add("
    \n"); + + // Attribute-related criteria + // + foreach (MetaModel::GetClassFilterDefs($sClass) as $sFltCode => $oFltDef) + { + // Set its current values + $sOpCode = "__none__"; + $sValue = ""; + foreach($oFilter->GetCriteria() as $aCritInfo) + { + if ($aCritInfo["filtercode"] == "pkey") + { + // ??? + } + elseif ($aCritInfo["filtercode"] == $sFltCode) + { + $sOpCode = $aCritInfo["opcode"]; + $sValue = $aCritInfo["value"]; + break; + } + } + + $oPage->add("
    \n"); + //$oPage->add($oFltDef->GetType()." (".$oFltDef->GetTypeDesc().")"); + $oPage->add(" ".$oFltDef->GetLabel()." "); + + $aOperators = array_merge(array("__none__" => ""), $oFltDef->GetOperators()); + $oPage->add_select($aOperators, "flt_ops[$sFltCode]", $sOpCode, 100); + $oPage->add("\n"); + + $oPage->add("\n"); + $oPage->add("
    \n"); + } + + // Ext key criteria + // + foreach (MetaModel::EnumReferencedClasses($sClass) as $sExtKeyAttCode => $sRemoteClass) + { + // Set its current values + $oSubFilter = $oFilter->GetCriteria_PointingTo($sExtKeyAttCode); + if (!$oSubFilter) + { + $oSubFilter = new CMDBSearchFilter($sRemoteClass); + } + + $oPage->add("
    \n"); + $oAtt = MetaModel::GetAttributeDef($oFilter->GetClass(), $sExtKeyAttCode); + $oPage->add($oAtt->GetLabel()." having ({$oSubFilter->DescribeConditions()})"); + //$oPage->add("having $oFilter->DescribeConditionPointTo($sExtKeyAttCode)); + $oPage->add("\n"); + + $oPage->add(dialogstack::RenderEditableField("Edit...", "flt_pointto[$sExtKeyAttCode]", $oSubFilter->serialize(), true)); + $oPage->add("
    \n"); + } + + // Ext key criteria, the other way + // + foreach (MetaModel::EnumReferencingClasses($sClass, true) as $sRemoteClass => $aRemoteKeys) + { + foreach ($aRemoteKeys as $sExtKeyAttCode) + { + // Set its current values + $oSubFilter = $oFilter->GetCriteria_ReferencedBy($sRemoteClass, $sExtKeyAttCode); + if (!$oSubFilter) + { + $oSubFilter = new CMDBSearchFilter($sRemoteClass); + } + + $oPage->add("
    \n"); + //$oPage->add($oFilter->DescribeConditionRefBy($sRemoteClass, $sExtKeyAttCode)); + $oAtt = MetaModel::GetAttributeDef($sRemoteClass, $sExtKeyAttCode); + $oPage->add("being ".$oAtt->GetLabel()." for ".$sRemoteClass."(e)s in ({$oSubFilter->DescribeConditions()})"); + $oPage->add("\n"); + + $oPage->add(dialogstack::RenderEditableField("Edit...", "flt_refedby[$sRemoteClass][$sExtKeyAttCode]", $oSubFilter->serialize(), true)); + $oPage->add("
    \n"); + } + } + + // Ext key criteria -> link objects + // + foreach (MetaModel::EnumLinkingClasses($sClass) as $sLinkClass => $aRemoteClasses) + { + foreach($aRemoteClasses as $sExtKeyAttCode => $sRemoteClass) + { + // Set its current values + //$oSubFilter = $oFilter->GetCriteria_PointingTo($sExtKeyAttCode); + $oSubFilter = null; + if (!$oSubFilter) + { + $oSubFilter = new CMDBSearchFilter($sRemoteClass); + } + $oPage->add("
    \n"); + //$oPage->add(" ".MetaModel::GetClassLabel($sRemoteClass)." "); + $oPage->add(" Linked to '".MetaModel::GetLinkLabel($sLinkClass, $sExtKeyAttCode)."' by "); + $oSubFilter = new CMDBSearchFilter($sRemoteClass); + $oPage->add($oSubFilter->__DescribeHTML()); + $oPage->add("\n"); + + $oPage->add(dialogstack::RenderEditableField("Edit...", "flt_linkedwith[$sRemoteClass][$sExtKeyAttCode]", $oSubFilter->serialize(), true)); + $oPage->add("
    \n"); + } + } + + $oPage->add("\n"); + $oPage->add("
    \n"); +} + +function MakeFilterFromArgs() +{ + $sClass = ReadParam("class"); + $sFilterFullText = ReadParam("flt_fulltext", ""); + $aFilterOps = ReadParam("flt_ops", array()); + $aFilterValues = ReadParam("flt_values", array()); + $aPointTo = ReadParam("flt_pointto", array()); + $aRefedBy = ReadParam("flt_refedby", array()); + $aLinkedWith = ReadParam("flt_linkedwith", array()); + + $oFilter = new CMDBSearchFilter($sClass); + + if (!empty($sFilterFullText)) + { + $oFilter->AddCondition_FullText($sFilterFullText); + } + + foreach($aFilterOps as $sFltCode=>$sOpCode) + { + if ($sOpCode == "__none__") continue; + $oFilter->AddCondition($sFltCode, $aFilterValues[$sFltCode], $sOpCode); + } + + foreach($aPointTo as $sExtKeyAttCode=>$sFilterShortcut) + { + $oSubFilter = CMDBSearchFilter::unserialize($sFilterShortcut); + $oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode); + } + + foreach($aRefedBy as $sForeignClass=>$aExtKeys) + { + foreach($aExtKeys as $sForeignExtKey=>$sFilterShortcut) + { + //MyHelpers::var_dump_html("$sForeignClass / $sForeignExtKey / $sFilterShortcut"); + $oSubFilter = CMDBSearchFilter::unserialize($sFilterShortcut); + //MyHelpers::var_dump_html($oSubFilter); + $oFilter->AddCondition_ReferencedBy($oSubFilter, $sForeignExtKey); + } + } + +// $oFilter->AddCondition_LinkedTo(DBObjectSearch $oLinkFilter, $sExtKeyAttCodeToMe, $sExtKeyAttCodeTarget, DBObjectSearch $oFilterTarget); + + return $oFilter; +} + +function Page3_ViewResults($oPage, $oFilter) +{ + // Output results in various forms... + // + if ($oFilter->IsAny()) + { + $oPage->p("You are considering the ENTIRE set of objects..."); + } + else + { + $oPage->p($oFilter->__DescribeHTML()); + + $oSet = new CMDBObjectSet($oFilter); + $oPage->p("Found ".$oSet->Count()." items"); + + $sFilterPhrase = $oFilter->serialize(); + $oPage->p("See detailed results"); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +// M a i n P r o g r a m +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +$oPage->p("

    Advanced search

    "); + + +// Page 1 - Ask class +// +// Page 2 - Class is given, enum existing filters/possible links +// +// Page 3 - Interpret user choices, create a filter and render its string representation +// + +//MyHelpers::arg_dump_html(); +//MyHelpers::var_dump_html($_SESSION); + +if (ReadParam('userconfig', false)) +{ + $sTodo = 'userconfig'; +} +if (ReadParam('makeit', false)) +{ + $sTodo = 'makeit'; +} +else +{ + if (dialogstack::IsDialogStartup()) + { + $sInit = dialogstack::StartDialog(); + $oFilter = CMDBSearchFilter::unserialize($sInit); + $sTodo = 'userconfig'; + } + else + { + $sClass = ReadParam('class', ''); + if (empty($sClass)) + { + $sTodo = 'selectclass'; + } + else + { + $oFilter = MakeFilterFromArgs(); + $sTodo = 'userconfig'; + } + } +} + +switch ($sTodo) +{ +case "selectclass": + Page1_AskClass($oPage); + break; + +case "userconfig": + dialogstack::DeclareCaller("Define filter for ".$oFilter->GetClass()); + $oPage->add(implode(" / ", dialogstack::GetCurrentStack())); + Page2_ConfigFilters($oPage, $oFilter); + break; + +case "makeit": + $oFilter = MakeFilterFromArgs(); + Page3_ViewResults($oPage, $oFilter); + $oPage->add(dialogstack::RenderEndDialogForm(DLGSTACK_OK, "Use filter", $oFilter->serialize())); + $oPage->add(dialogstack::RenderEndDialogForm(DLGSTACK_CANCEL, "Annuler")); + break; + +default: + trigger_error("Wrong value for arg todo ($sTodo)", E_USER_ERROR); +} + +$oPage->output(); + +?> diff --git a/pages/ajax.php b/pages/ajax.php new file mode 100644 index 0000000000..1173ddd212 --- /dev/null +++ b/pages/ajax.php @@ -0,0 +1,40 @@ +no_cache(); +$oPage->add("

    Asynchronous versus asynchronous DisplayBlocks

    \n"); + +$oContext = new UserContext(); +$operation = ReadParam('operation', ''); +$sClassName = ReadParam('class', 'bizContact'); +$sOrganizationCode = ReadParam('org', 'ITOP'); + +$oPage->p("[Synchronous] Count of all $sClassName objects for organization '$sOrganizationCode'"); +$oFilter = $oContext->NewFilter($sClassName); +$oFilter ->AddCondition('organization', $sOrganizationCode, '='); +$oBlock = new DisplayBlock($oFilter, 'count', false); +$oBlock->Display($oPage, "block1"); + +$oPage->p("[Asynchronous] All $sClassName objects for organization '$sOrganizationCode'"); +$oFilter = $oContext->NewFilter($sClassName); +$oFilter ->AddCondition('organization', $sOrganizationCode, '='); +$oBlock = new DisplayBlock($oFilter, 'list', true); +$oBlock->Display($oPage, "block2"); + +$oPage->p("[Asynchronous] Details of all $sClassName objects for organization '$sOrganizationCode'"); +$oFilter = $oContext->NewFilter($sClassName); +$oFilter ->AddCondition('organization', $sOrganizationCode, '='); +$oBlock = new DisplayBlock($oFilter, 'details', true); +$oBlock->Display($oPage, "block3"); + +$oPage->output(); +?> diff --git a/pages/ajax.render.php b/pages/ajax.render.php new file mode 100644 index 0000000000..176a32128f --- /dev/null +++ b/pages/ajax.render.php @@ -0,0 +1,218 @@ +no_cache(); + +$oContext = new UserContext(); +$operation = utils::ReadParam('operation', ''); +$sFilter = stripslashes(utils::ReadParam('filter', '')); +$sEncoding = utils::ReadParam('encoding', 'serialize'); +$sClass = utils::ReadParam('class', 'bizContact'); +$sStyle = utils::ReadParam('style', 'list'); + +switch($operation) +{ + case 'wizard_helper_preview': + $sJson = utils::ReadParam('json_obj', '', 'post'); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $oObj->DisplayBareDetails($oPage); + break; + + case 'wizard_helper': + $sJson = utils::ReadParam('json_obj', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + foreach($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $oWizardHelper->SetDefaultValue($sAttCode, $oAttDef->GetDefaultValue()); + } + foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) + { + $oWizardHelper->SetAllowedValuesHtml($sAttCode, "Possible values ($sAttCode)"); + } + $oPage->add($oWizardHelper->ToJSON()); + break; + + case 'ajax': + if ($sFilter != "") + { + if ($sEncoding == 'sibusql') + { + $oFilter = CMDBSearchFilter::FromSibusQL($sFilter); + } + else + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + } + $oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false); + $oDisplayBlock->RenderContent($oPage); + } + else + { + $oPage->p("Invalid query (empty filter)."); + } + break; + + case 'details': + $key = utils::ReadParam('id', 0); + $oFilter = $oContext->NewFilter($sClass); + $oFilter->AddCondition('pkey', $key, '='); + $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); + $oDisplayBlock->RenderContent($oPage); + break; + + case 'preview': + $key = utils::ReadParam('id', 0); + $oFilter = $oContext->NewFilter($sClass); + $oFilter->AddCondition('pkey', $key, '='); + $oDisplayBlock = new DisplayBlock($oFilter, 'preview', false); + $oDisplayBlock->RenderContent($oPage); + break; + + case 'pie_chart': + $sGroupBy = utils::ReadParam('group_by', ''); + if ($sFilter != '') + { + if ($sEncoding == 'oql') + { + $oFilter = CMDBSearchFilter::FromOQL($sFilter); + } + else + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + } + $oDisplayBlock = new DisplayBlock($oFilter, 'pie_chart_ajax', false); + $oDisplayBlock->RenderContent($oPage, array('group_by' => $sGroupBy)); + } + else + { + + $oPage->add("\n3d pie\n."); + } + break; + + case 'open_flash_chart': + $aParams = utils::ReadParam('params', array()); + if ($sFilter != '') + { + if ($sEncoding == 'oql') + { + $oFilter = CMDBSearchFilter::FromOQL($sFilter); + } + else + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + } + $oDisplayBlock = new DisplayBlock($oFilter, 'open_flash_chart_ajax', false); + $oDisplayBlock->RenderContent($oPage, $aParams); + } + else + { + + $oPage->add("\n3d pie\n."); + } + break; + + case 'modal_details': + $key = utils::ReadParam('id', 0); + $oFilter = $oContext->NewFilter($sClass); + $oFilter->AddCondition('pkey', $key, '='); + $oPage->Add("

    Object Details

    \n"); + $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); + $oDisplayBlock->RenderContent($oPage); + $oPage->Add("\n"); + break; + + case 'ui.linkswidget': + $sClass = utils::ReadParam('sclass', 'bizContact'); + $sAttCode = utils::ReadParam('attCode', 'name'); + $sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + UILinksWidget::Autocomplete($oPage, $oContext, $sClass, $sAttCode, $sName, $iMaxCount); + break; + + case 'ui.linkswidget.linkedset': + $sClass = utils::ReadParam('sclass', 'bizContact'); + $sJSONSet = stripslashes(utils::ReadParam('sset', '')); + $sExtKeyToMe = utils::ReadParam('sextkeytome', ''); + UILinksWidget::RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe); + break; + + case 'autocomplete': + $key = utils::ReadParam('id', 0); + $sClass = utils::ReadParam('sclass', 'bizContact'); + $sAttCode = utils::ReadParam('attCode', 'name'); + $sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + $aArgs = array(); + if (!empty($key)) + { + if ($oThis = MetaModel::GetObject($sClass, $key)) + { + $aArgs['*this*'] = $oThis; + } + } + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs, $sName); + $iCount = 0; + foreach($aAllowedValues as $key => $value) + { + $oPage->add($value."|".$key."\n"); + if ($iCount++) break; + } + break; + + case 'link': + $sClass = utils::ReadParam('sclass', 'logInfra'); + $sAttCode = utils::ReadParam('attCode', 'name'); + //$sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + $iCount = 0; + $oFilter = $oContext->NewFilter($sClass); + $oFilter->AddCondition($sAttCode, $sName, 'Begins with'); + //$oFilter->AddCondition('org_id', $sOrg); + $oSet = new CMDBObjectSet($oFilter, array($sAttCode => true)); + while( ($iCount < $iMaxCount) && ($oObj = $oSet->fetch()) ) + { + $oPage->add($oObj->GetAsHTML($sAttCode)."|".$oObj->GetKey()."\n"); + $iCount++; + } + break; + + case 'create': + case 'create_menu': + $sClass = utils::ReadParam('class', ''); + $sFilter = utils::ReadParam('filter', ''); + menuNode::DisplayCreationForm($oPage, $sClass, $sFilter); + break; + + case 'combo_options': + $oFilter = CMDBSearchFilter::FromSibusQL($sFilter); + $oSet = new CMDBObjectSet($oFilter); + while( $oObj = $oSet->fetch()) + { + $oPage->add(''); + } + break; + + default: + $oPage->p("Invalid query."); +} +$oPage->output(); +?> diff --git a/pages/audit.php b/pages/audit.php new file mode 100644 index 0000000000..6f69cfdbb8 --- /dev/null +++ b/pages/audit.php @@ -0,0 +1,149 @@ +GetObject('AuditRule', $iRuleId); + $sSibusql = $oRule->Get('query'); + $oRuleFilter = DBObjectSearch::FromSibusQL($sSibusql); + if ($oRule->Get('valid_flag') == 'false') + { + // The query returns directly the invalid elements + $oFilter = $oRuleFilter; + $oFilter->MergeWith($oDefinitionFilter); + $oErrorObjectSet = new CMDBObjectSet($oFilter); + } + else + { + // The query returns only the valid elements, all the others are invalid + $oFilter = $oRuleFilter; + $oErrorObjectSet = new CMDBObjectSet($oFilter); + $aValidIds = array(0); // Make sure that we have at least one value in the list + while($oObj = $oErrorObjectSet->Fetch()) + { + $aValidIds[] = $oObj->GetKey(); + } + $oFilter = $oDefinitionFilter; + $oFilter->AddCondition('pkey', $aValidIds, 'NOTIN'); + $oErrorObjectSet = new CMDBObjectSet($oFilter); + } + return $oErrorObjectSet; +} + +function GetReportColor($iTotal, $iErrors) +{ + $sResult = 'red'; + if ( ($iTotal == 0) || ($iErrors / $iTotal) <= 0.05 ) + { + $sResult = 'green'; + } + else if ( ($iErrors / $iTotal) <= 0.25 ) + { + $sResult = 'orange'; + } + return $sResult; +} + +switch($operation) +{ + case 'errors': + $iCategory = utils::ReadParam('category', ''); + $iRuleIndex = utils::ReadParam('rule', 0); + + $oContext = new UserContext(); + $oAuditCategory = $oContext->GetObject('AuditCategory', $iCategory); + $oDefinitionFilter = DBObjectSearch::FromSibusQL($oAuditCategory->Get('definition_set')); + if (!empty($currentOrganization)) + { + $oDefinitionFilter->AddCondition('org_id', $currentOrganization); + } + $oDefinitionSet = new CMDBObjectSet($oDefinitionFilter); + $oErrorObjectSet = GetRuleResultSet($iRuleIndex, $oDefinitionFilter); + $oAuditRule = $oContext->GetObject('AuditRule', $iRuleIndex); + $oP->add(''); + $oP->p('[Back to audit results]'); + cmdbAbstractObject::DisplaySet($oP, $oErrorObjectSet); + break; + + case 'audit': + default: + $oP->add(''); + $oAuditFilter = new CMDBSearchFilter('AuditCategory'); + $oCategoriesSet = new DBObjectSet($oAuditFilter); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
    \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + while($oAuditCategory = $oCategoriesSet->fetch()) + { + $oDefinitionFilter = DBObjectSearch::FromSibusQL($oAuditCategory->Get('definition_set')); + $aObjectsWithErrors = array(); + if (!empty($currentOrganization)) + { + $oDefinitionFilter->AddCondition('org_id', $currentOrganization); + } + $aResults = array(); + $oDefinitionSet = new CMDBObjectSet($oDefinitionFilter); + $iCount = $oDefinitionSet->Count(); + $oRulesFilter = new CMDBSearchFilter('AuditRule'); + $oRulesFilter->AddCondition('category_id', $oAuditCategory->GetKey()); + $oRulesSet = new DBObjectSet($oRulesFilter); + while($oAuditRule = $oRulesSet->fetch() ) + { + $aRow = array(); + $aRow['description'] = $oAuditRule->Get('name'); + if ($iCount == 0) + { + // nothing to check, really ! + $aRow['nb_errors'] = "GetKey()."&rule=".$oAuditRule->GetKey()."\">0"; + $aRow['percent_ok'] = '100.00'; + $aRow['class'] = GetReportColor($iCount, 0); + } + else + { + $oRuleFilter = DBObjectSearch::FromSibusQL($oAuditRule->Get('query')); + $oErrorObjectSet = GetRuleResultSet($oAuditRule->GetKey(), $oDefinitionFilter); + $iErrorsCount = $oErrorObjectSet->Count(); + while($oObj = $oErrorObjectSet->Fetch()) + { + $aObjectsWithErrors[$oObj->GetKey()] = true; + } + $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCount"; + $aRow['percent_ok'] = sprintf('%.2f', 100.0 * (($iCount - $iErrorsCount) / $iCount)); + $aRow['class'] = GetReportColor($iCount, $iErrorsCount); + } + $aResults[] = $aRow; + $iTotalErrors = count($aObjectsWithErrors); + $sOverallPercentOk = ($iCount == 0) ? '100.00' : sprintf('%.2f', 100.0 * (($iCount - $iTotalErrors) / $iCount)); + $sClass = GetReportColor($iCount, $iTotalErrors); + + } + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + foreach($aResults as $aRow) + { + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + } + } + $oP->add("
    Audit Rule# Objects# Errors% Ok
    ".$oAuditCategory->GetName()."$iCount$iTotalErrors$sOverallPercentOk %
     ".$aRow['description']."".$aRow['nb_errors']."".$aRow['percent_ok']." %
    \n"); + $oP->add("
    \n"); +} + +$oP->output(); +?> diff --git a/pages/csvimport.php b/pages/csvimport.php new file mode 100644 index 0000000000..0110e1f53b --- /dev/null +++ b/pages/csvimport.php @@ -0,0 +1,527 @@ + '); + +/////////////////////////////////////////////////////////////////////////////// +// External key/field naming conventions (sharing the naming space with std attributes +/////////////////////////////////////////////////////////////////////////////// + +function IsExtKeyField($sColDesc) +{ + return ($iPos = strpos($sColDesc, EXTKEY_SEP)); +} + +function GetExtKeyFieldCodes($sColDesc) +{ + $iPos = strpos($sColDesc, EXTKEY_SEP); + return array( + substr($sColDesc, 0, $iPos), + substr($sColDesc, $iPos + strlen(EXTKEY_SEP)) + ); +} + +function MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode) +{ + $oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); + $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); + + return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$oForeignAtt->GetLabel(); +} + +function MakeExtFieldSelectValue($sAttCode, $sExtAttCode) +{ + return $sAttCode.EXTKEY_SEP.$sExtAttCode; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + + +function ShowTableForm($oPage, $oCSVParser, $sClass) +{ + $aData = $oCSVParser->ToArray(null, 3); + $aColToRow = array(); + foreach($aData as $aRow) + { + foreach ($aRow as $sFieldId=>$sValue) + { + $aColToRow[$sFieldId][] = $sValue; + } + } + + $aFields = array(); + foreach($oCSVParser->ListFields() as $iFieldIndex=>$sFieldName) + { + $sSelField = ""; + + $sCHECKED = ($sFieldName == "pkey" || MetaModel::IsReconcKey($sClass, $sFoundAttCode)) ? " CHECKED" : ""; + $sSelField .= " "; + + $aFields["field$iFieldIndex"]["label"] = $sSelField; + $aFields["field$iFieldIndex"]["value"] = $aColToRow[$iFieldIndex]; + } + $oPage->details($aFields); +} + + +function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CMDBChange $oChange = null) +{ + // Note: $oChange can be null, in which case the aim is to check what would be done + + // Setup field mapping: sort out between values and other specific columns + // + $iPKeyId = null; + $aReconcilKeys = array(); + $aAttList = array(); + $aExtKeys = array(); + foreach($aFieldMap as $sFieldId=>$sColDesc) + { + $iFieldId = (int) substr($sFieldId, strlen("field")); + if ($sColDesc == "pkey") + { + // Skip ! + $iPKeyId = $iFieldId; + } + elseif ($sColDesc == "__none__") + { + // Skip ! + } + elseif (IsExtKeyField($sColDesc)) + { + list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); + $aExtKeys[$sExtKeyAttCode][$sExtReconcKeyAttCode] = $iFieldId; + } + elseif (array_key_exists($sFieldId, $aIsReconcKey)) + { + $aReconcilKeys[$sColDesc] = $iFieldId; + $aAttList[$sColDesc] = $iFieldId; // A reconciliation key is also a field + } + else + { + // $sColDesc is an attribute code + // + $aAttList[$sColDesc] = $iFieldId; + } + } + + // Setup result presentation + // + $aDisplayConfig = array(); + $aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>""); + $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); + if (isset($iPKeyId)) + { + $aDisplayConfig["col$iPKeyId"] = array("label"=>"pkey", "description"=>""); + } + foreach($aReconcilKeys as $sAttCode => $iCol) + { + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + foreach($aExtKeys as $sAttCode=>$aKeyConfig) + { + $oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sLabel = $oExtKeyAtt->GetLabel(); + $aDisplayConfig[$sAttCode] = array("label"=>"$sLabel", "description"=>""); + foreach ($aKeyConfig as $sForeignAttCode => $iCol) + { + // The foreign attribute is one of our reconciliation key + + $sLabel = MakeExtFieldLabel($sClass, $sAttCode, $sForeignAttCode); + $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + } + foreach ($aAttList as $sAttCode => $iCol) + { + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + + // Compute the results + // + $aData = $oCSVParser->ToArray(); + + $oBulk = new BulkChange( + $sClass, + $aData, + $aAttList, + array_keys($aReconcilKeys), + $aExtKeys + ); + $aRes = $oBulk->Process($oChange); + + $aResultDisp = array(); // to be displayed + foreach($aRes as $iRow => $aRowData) + { + $aRowDisp = array(); + $aRowDisp["__RECONCILIATION__"] = $aRowData["__RECONCILIATION__"]; + $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); + foreach($aRowData as $sKey => $value) + { + if ($sKey == '__RECONCILIATION__') continue; + if ($sKey == '__STATUS__') continue; + + switch (get_class($value)) + { + case 'CellChangeSpec_Void': + $sClass = ''; + break; + case 'CellChangeSpec_Unchanged': + $sClass = ''; + break; + case 'CellChangeSpec_Modify': + $sClass = 'csvimport_ok'; + break; + case 'CellChangeSpec_Init': + $sClass = 'csvimport_init'; + break; + case 'CellChangeSpec_Issue': + $sClass = 'csvimport_error'; + break; + } + if (empty($sClass)) + { + $aRowDisp[$sKey] = $value->GetDescription(); + } + else + { + $aRowDisp[$sKey] = "
    ".$value->GetDescription()."
    "; + } + } + $aResultDisp[$iRow] = $aRowDisp; + } + $oPage->table($aDisplayConfig, $aResultDisp); +} + +/////////////////////////////////////////////////////////////////////////////// +// Wizard entry points +/////////////////////////////////////////////////////////////////////////////// + +function Do_Welcome($oPage, $sClass) +{ + $sWiztep = "1_welcome"; + $oPage->p("

    Bulk load from CSV data / step 1

    "); + + $sCSVData = utils::ReadPostedParam('csvdata'); + + $oPage->add("
    "); + $oPage->MakeClassesSelect("class", $sClass, 50); + //$oPage->Add(""); + $oPage->add("
    "); + $oPage->add(""); + $oPage->add("
    "); + $oPage->add(""); + $oPage->add("
    \n"); + $oPage->add("
    "); + + // As a help to the end-user, let's display the list of possible fields + // for a class, that can be copied/pasted into the CSV area. + $sCurrentList = ""; + $aHeadersList = array(); + foreach(MetaModel::GetClasses('bizmodel') as $sClassName) + { + $aList = MetaModel::GetZListItems($sClassName, 'details'); + $aHeader = array(); + // $aHeader[] = MetaModel::GetKeyLabel($sClassName); + $aHeader[] = 'pkey'; // Should be what's coded on the line above... but there is a bug + foreach($aList as $sAttCode) + { + $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); + } + + $sAttributes = implode(",", $aHeader); + $aHeadersList[$sClassName] = $sAttributes; + + if($sClassName == $sClass) + { + // this class is currently selected + $sCurrentList = $sAttributes; + } + } + // Store all the values in a variable client-side + $aScript = array(); + foreach($aHeadersList as $sClassName => $sAttributes) + { + $aScript[] = "'$sClassName':'$sAttributes'"; + } + $oPage->add("\n"); + + $oPage->add_ready_script("$('#select_class').change( function() {DisplayFields(this.value);} );"); + + $oPage->add("Fields for this object: "); + +} + +function Do_Format($oPage, $sClass) +{ + $oPage->p("

    Bulk load from CSV data / step 2

    "); + $sWiztep = "2_format"; + + $sCSVData = utils::ReadPostedParam('csvdata'); + $oCSVParser = new CSVParser($sCSVData); + $sSep = $oCSVParser->GuessSeparator(); + $iSkip = $oCSVParser->GuessSkipLines(); + + // No data ? + $aData = $oCSVParser->ToArray(null); + $iTarget = count($aData); + if ($iTarget == 0) + { + $oPage->add("Empty data set..."); + $oPage->add("
    "); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add("
    "); + } + + // Guess the format : + $oPage->p("Guessed separator: '$sSep' (ASCII=".ord($sSep).")"); + $oPage->p("Guessed # of lines to skip: $iSkip"); + + $oPage->p("Target: $iTarget rows"); + + $oPage->Add("
    "); + ShowTableForm($oPage, $oCSVParser, $sClass); + $oPage->Add(""); + $oPage->Add(""); + $oPage->Add(""); + $oPage->Add(""); + + $oPage->Add(""); + $oPage->add(""); + $oPage->Add(""); + $oPage->Add("
    "); +} + +function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) +{ + $sCSVData = utils::ReadPostedParam('csvdata'); + $sSep = utils::ReadPostedParam('separator'); + $iSkip = utils::ReadPostedParam('skiplines'); + $aFieldMap = utils::ReadPostedParam('fmap'); + $aIsReconcKey = utils::ReadPostedParam('iskey'); + + $oCSVParser = new CSVParser($sCSVData); + $oCSVParser->SetSeparator($sSep); + $oCSVParser->SetSkipLines($iSkip); + $aData = $oCSVParser->ToArray(null); + $iTarget = count($aData); + + $oPage->p("

    Goal summary

    "); + $oPage->p("Target: $iTarget rows"); + + $aSampleData = $oCSVParser->ToArray(array_keys($aFieldMap), 5); + $aDisplayConfig = array(); + foreach ($aFieldMap as $sFieldId=>$sColDesc) + { + if (array_key_exists($sFieldId, $aIsReconcKey)) + { + $sReconcKey = " [search]"; + } + else + { + $sReconcKey = ""; + } + + if ($sColDesc == "pkey") + { + $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>"blah pkey"); + } + elseif ($sColDesc == "__none__") + { + // Skip ! + } + else if (MetaModel::IsValidAttCode($sClass, $sColDesc)) + { + $sAttCode = $sColDesc; + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig[$sFieldId] = array("label"=>"$sLabel$sReconcKey", "description"=>""); + } + elseif (IsExtKeyField($sColDesc)) + { + list($sExtKeyAttCode, $sForeignAttCode) = GetExtKeyFieldCodes($sColDesc); + $aDisplayConfig[$sFieldId] = array("label"=>MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode), "description"=>""); + } + else + { + // ??? + $aDisplayConfig[$sFieldId] = array("label"=>"-?-?-$sColDesc-?-?-", "description"=>""); + } + } + $oPage->table($aDisplayConfig, $aSampleData); + + if ($oChange) + { + $oPage->p("

    Processing...

    "); + } + else + { + $oPage->p("

    Check...

    "); + } + ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, $oChange); + + $oPage->add("
    "); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add_input_hidden("fmap", $aFieldMap); + $oPage->add_input_hidden("iskey", $aIsReconcKey); +} + +function Do_Verify($oPage, $sClass) +{ + $oPage->p("

    Bulk load from CSV data / step 3

    "); + $sWiztep = "3_verify"; + + DoProcessOrVerify($oPage, $sClass, null); + // FORM started by DoProcessOrVerify... + + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add("
    "); +} + +function Do_Execute($oPage, $sClass) +{ + $oPage->p("

    Bulk load from CSV data / step 4

    "); + $sWiztep = "4_execute"; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "CSV Import"); + $iChangeId = $oMyChange->DBInsert(); + + DoProcessOrVerify($oPage, $sClass, $oMyChange); + + // FORM started by DoProcessOrVerify... + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +// M a i n P r o g r a m +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +$sFromWiztep = utils::ReadPostedParam('fromwiztep', ''); +$sClass = utils::ReadPostedParam('class', ''); +$sTodo = utils::ReadPostedParam('todo', ''); + +switch($sFromWiztep) +{ + case '': + Do_Welcome($oPage, $sClass); + break; + + case '1_welcome': + if ($sTodo == "Next") Do_Format($oPage, $sClass); + else trigger_error("Wrong argument todo='$sTodo'", E_USER_ERROR); + break; + + case '2_format': + if ($sTodo == "Next") Do_Verify($oPage, $sClass); + else Do_Welcome($oPage, $sClass); + break; + + case '3_verify': + if ($sTodo == "Next") Do_Execute($oPage, $sClass); + else Do_Format($oPage, $sClass); + break; + + case '4_execute': + if ($sTodo == "Next") trigger_error("Wrong argument todo='$sTodo'", E_USER_ERROR); + else Do_Verify($oPage, $sClass); + break; + + default: + trigger_error("Wrong argument fromwiztep='$sFromWiztep'", E_USER_ERROR); +} + +$oPage->output(); +?> diff --git a/pages/data_generator.php b/pages/data_generator.php new file mode 100644 index 0000000000..351269d180 --- /dev/null +++ b/pages/data_generator.php @@ -0,0 +1,321 @@ +\n"; + echo "DEBUG: \$_SERVER\n"; + print_r($_SERVER); + echo "\n"; + exit; +} +if (!UserRights::Login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) +{ + header('WWW-Authenticate: Basic realm="iTop"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'Authentication failed !'; + exit; +} + +$oPage = new iTopWebPage("iTop Data Generator", $currentOrganization); +$oPage->no_cache(); + +// From now on the context is limited to the the selected organization ?? + +// Retrieve the root node for the menu +$oSearchFilter = $oContext->NewFilter("menuNode"); +$oSearchFilter->AddCondition('parent_id', 0, '='); +// There may be more criteria added later to have a specific menu based on the user's profile +$oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); +while ($oRootMenuNode = $oSet->Fetch()) +{ + $oRootMenuNode->DisplayMenu($oPage, $oContext, $iActiveNodeId, null, $oAppContext->GetAsHash()); +} +/** + * The (ordered) list of classes for which to generate objects + * + * Each class in this list must implement a non-static Generate(cmdbDataGenerator $oGenerator) method + */ +//$aClassesToGenerate = array('bizOrganization' /* This one is special and must go first */, 'bizService', 'bizContact', 'bizPC', 'bizNetworkDevice'); +$aClassesToGenerate = array('bizOrganization' /* This one is special and must go first */, 'bizLocation', 'bizPC', 'bizNetworkDevice', 'bizPerson', 'bizIncidentTicket', 'bizInfraGroup', 'bizInfraInfra'); + +///////////////////////////////////////////////////////////////////////////////////// +// Actual actions of the page +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Populate an organization with objects of each class listed in the (global) $aClassesToGenerate array + * + * @param web_page $oPage The object used for the HTML output + * @param cmdbGenerator $oGenerator The object used for the generation of the objects + * @param string $sSize An enum specifying (roughly) how many objects of each class to create: one of 'small', 'medium', 'big', 'huge' or 'max' + */ +function PopulateOrganization(CMDBChange $oMyChange, web_page $oPage, cmdbDataGenerator $oGenerator, $sSize = 'small') +{ + global $aClassesToGenerate; + + for ($i=1 /* skip the first one (i=0) which is the org itself */; $iGenerate($oGenerator); + // By rom + // $oObject->DBInsert(); + $oObject->DBInsertTracked($oMyChange); + + //$oObject->DisplayDetails($oPage); + } + } + $oPage->p("$nbObjects $sClass objects created."); + } +} + +/** + * Delete an organization and all the instances of 'Object' belonging to this organization + * + * @param web_page $oPage The object used for the HTML output + * @param string $sOrganizationCode The code (pkey) of the organization to delete + */ +function DeleteOrganization($oMyChange, $oPage, $sOrganizationCode) +{ + $oOrg = MetaModel::GetObject('bizOrganization', $sOrganizationCode); + if ($oOrg == null) + { + $oPage->p("Organization '$sOrganizationCode' already deleted!!"); + } + else + { + // Delete all the objects linked to this organization + $oFilter = new CMDBSearchFilter('logRealObject'); + $oFilter->AddCondition('organization', $sOrganizationCode, '='); + $oSet = new CMDBObjectSet($oFilter); + $countDeleted = $oSet->Count(); + MetaModel::BulkDeleteTracked($oMyChange, $oFilter); // Should do a one by one delete to let the objects do their own cleanup ! + $oPage->p("$countDeleted object(s) deleted!!"); + + $oOrg->DBDeleteTracked($oMyChange); + $oPage->p("Ok, organization '$sOrganizationCode' deleted!"); + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// M a i n P r o g r a m +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +$operation = utils::ReadParam('operation', ''); + +$oPage->add("
    \n"); +$oPage->add("
    \n"); + +switch($operation) +{ + + case 'specify_generate': + // Display a form to specify what to generate + $oPage->p("iTop Data Generator\n"); + $oPage->add("
    \n"); + $oPage->add("Number of organizations to generate: \n"); + $oPage->add(" (max ".count($aCompanies).") \n"); + $oPage->add("Size of the organizations\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("
    \n"); + break; + + case 'generate': + // perform the actual generation + $iOrgCount = utils::ReadParam('org_count', 0); + $sOrgSize = utils::ReadParam('org_size', 'small'); + // By rom + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by data generator ($iOrgCount orgs of size '$sOrgSize')"); + $oMyChange->DBInsert(); + while($iOrgCount > 0) + { + set_time_limit(5*60); // let it run for 5 minutes for each organization + $oGenerator = new cmdbDataGenerator(); + // Create the new organization + $oOrg = MetaModel::NewObject('bizOrganization'); + $oOrg->Generate($oGenerator); + + // By rom + // $oOrg->DBInsert(); + $oOrg->DBInsertTracked($oMyChange); + $oGenerator->SetOrganizationId($oOrg->GetKey()); + + $oPage->p("Organization '".$oOrg->GetKey()."' created\n"); + $oOrg->DisplayDetails($oPage); + $oPage->add("
    \n"); + PopulateOrganization($oMyChange, $oPage, $oGenerator, $sOrgSize); + $oPage->add("
    \n"); + unset($oGenerator); + $iOrgCount--; + } + break; + + case 'specify_update': + // Specify which organization to update + $oPage->add("
    \n"); + $oPage->add("Select the organization to update: \n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->p(""); + $oPage->add("Quantity of of objects to add in each class: \n"); + $oPage->add("\n"); + $oPage->p(""); + $oPage->add("  \n"); + $oPage->add("\n"); + $oPage->add("
    \n"); + break; + + case 'update': + // perform the actual update + set_time_limit(5*60); // let it run for 5 minutes + $sOrganizationCode = utils::ReadParam('org', ''); + $sOrgSize = utils::ReadParam('org_size', 'small'); + if ($sOrganizationCode == '') + { + $oPage->p("Error: please specify an organization (org)."); + } + else + { + // By rom + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by data generator (update org '$sOrganizationCode', size '$sOrgSize')"); + $oMyChange->DBInsert(); + + $oPage->p("Organization '$sOrganizationCode' updated."); + $oGenerator = new cmdbDataGenerator($sOrganizationCode); + PopulateOrganization($oMyChange, $oPage, $oGenerator, $sOrgSize); + } + break; + + case 'specify_delete': + // Select an organization to be deleted + $oPage->add("
    \n"); + $oPage->add("Select the organization to delete: \n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->p(""); + $oPage->add("  \n"); + $oPage->add("\n"); + $oPage->add("
    \n"); + break; + + case 'confirm_delete': + // confirm the dangerous action + $sOrganizationCode = ReadParam('org', ''); + $oPage->p("iTop Data Generator\n"); + $oPage->p("Warning: you are about to delete the organization '$sOrganizationCode' and all its related objects\n"); + $oPage->add("
    \n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("  \n"); + $oPage->add("\n"); + $oPage->add("
    \n"); + break; + + case 'delete': + // perform the actual deletion + $sOrganizationCode = ReadParam('org', ''); + if ($sOrganizationCode == '') + { + $oPage->p("Error: please specify an organization (org)."); + } + else + { + // By rom + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by data generator (delete org '$sOrganizationCode'"); + $oMyChange->DBInsert(); + + $oPage->p("Deleting '$sOrganizationCode'\n"); + DeleteOrganization($oMyChange, $oPage, $sOrganizationCode); + } + break; + + // display the menu of actions + case 'menu': + default: + $oPage->p("Data Generator Menu"); + $oPage->p("Generate one or more organizations"); + $oPage->p("Add more objects into an organization"); + $oPage->p("Delete an organization"); +} +$oPage->add("
    \n"); +$oPage->add("
    \n"); + +$oPage->p("Return to the data generator menu"); + +$oPage->output(); +?> diff --git a/pages/db_importer.php b/pages/db_importer.php new file mode 100644 index 0000000000..7d7df98944 --- /dev/null +++ b/pages/db_importer.php @@ -0,0 +1,171 @@ +add('
    '); + $oP->add('

    Creation of an archive for the business model: '.$sBizModel.'

    '); + $oP->p('Title of the archive: '); + $oP->p('Description of this archive: '); + $oP->p('Select the packages you want to include into this archive (When restoring the archive you will prompted to pick a package):'); + $oP->p(' The full database (schema + data).'); + $oP->p(' Only the schema but of the complete database.'); + $oP->p(' The complete business model (all the tables used by the business model, schema + data).'); + $oP->p(' Only the schema of the business model.'); + $oP->add(''); + $oP->add(''); + $oP->add(''); + $oP->add($oAppContext->GetForForm()); + $oP->add('
    '); + $oP->p('Back to menu'); + break; + + case 'do_create': + $sTitle = utils::ReadParam('title', 'Unknown archive'); + $sDescription = utils::ReadParam('description', 'No description provided for this archive.'); + $bfullDump = utils::ReadParam('full', false); + $bfullSchemaDump = utils::ReadParam('full_schema', false); + $bBizDump = utils::ReadParam('biz', false); + $bBizSchemaDump = utils::ReadParam('biz_schema', false); + $sArchiveFile = '../tmp/archive1.zip'; + + $oArchive = new iTopArchive($sArchiveFile, iTopArchive::create); + $oArchive->SetTitle($sTitle); + $oArchive->SetDescription($sDescription); + if ($bfullDump) + { + $oArchive->AddDatabaseDump("Full Database Dump", "Choose this option to completely reload your database. All current data will be deleted and replaced by the backup", "full-db.sql", 'itop', array(), false); + } + + if ($bfullSchemaDump) + { + $oArchive->AddDatabaseDump("Full Schema Dump", "Choose this option to completely wipe out your database and start from an empty database", "full-schema.sql", 'itop', array(), true); + } + + if ($bBizDump || $bBizSchemaDump) + { + // compute the list of the tables involved in the business model + $aBizTables = array(); + foreach(MetaModel::GetClasses('bizmodel') as $sClass) + { + $sTable = MetaModel::DBGetTable($sClass); + $aBizTables[$sTable] = $sTable; + } + unset($aBizTables['']); + if ($bfullDump) + { + $oArchive->AddDatabaseDump("Full Business Model Dump", "Choose this option to completely reload the business model part of your database. All current business data will be deleted and replaced by the backup. Application data (like menus...) are preserved.", "biz-db.sql", 'itop', $aBizTables, false); + } + + if ($bfullSchemaDump) + { + $oArchive->AddDatabaseDump("Full Business Model Schema Dump", "Choose this option to wipe out the business data and start from an empty database. All current business data will be deleted. Application data (like menus...) are preserved.", "biz-schema.sql", 'itop', $aBizTables, true); + } + } + $oArchive->WriteCatalog(); + $oP->p("The archive '$sTitle' has been created in $sArchiveFile."); + $oP->p('Back to menu'); + break; + + case 'select_archive': + $sArchivesDir = '../tmp'; + $oP->add('
    '); + $oP->add('

    Importation of an archive

    '); + $oP->p('Select the archive you want to import:'); + $aArchives = array(); + if ($handle = opendir($sArchivesDir)) + { + while (false !== ($sFileName = readdir($handle))) + { + if (strtolower(substr($sFileName, -3, 3)) == 'zip') + { + $oArchive = new iTopArchive('../tmp/'.$sFileName, iTopArchive::read); + if ($oArchive->IsValid()) + { + $oArchive->ReadCatalog(); + $aArchives['../tmp/'.$sFileName] = $oArchive->GetTitle(); + } + } + } + closedir($handle); + } + foreach($aArchives as $sFileName => $sTitle) + { + $oP->p(''.$sTitle); + } + $oP->add(''); + $oP->add(''); + $oP->add(''); + $oP->add($oAppContext->GetForForm()); + $oP->add('
    '); + $oP->p("(Archives are searched into the directory: $sArchivesDir.)"); + $oP->p('Cancel'); + break; + + case 'select_package': + $sArchiveFile = utils::ReadParam('archive_file', ''); + $oArchive = new iTopArchive($sArchiveFile, iTopArchive::read); + $oArchive->ReadCatalog(); + $oP->add('
    '); + $oP->add('

    Selection of a package inside '.$oArchive->GetTitle().'

    '); + $oP->p('Select the package you want to apply:'); + $aPackages = $oArchive->GetPackages(); + foreach($aPackages as $sPackageName => $aPackage) + { + $oP->p(''.$aPackage['title']); + $oP->p($aPackage['description']); + } + $oP->add(''); + $oP->add(''); + $oP->add(''); + $oP->add(''); + $oP->add($oAppContext->GetForForm()); + $oP->add('
    '); + $oP->p('Cancel'); + break; + + case 'import_package': + $sArchiveFile = utils::ReadParam('archive_file', ''); + $sPackageName = utils::ReadParam('package_name', ''); + $oArchive = new iTopArchive($sArchiveFile, iTopArchive::read); + $oArchive->ReadCatalog(); + $oP->add('

    Applying the package '.$sPackageName.'

    '); + if($oArchive->ImportSQL($sPackageName)) + { + $oP->p('Done.'); + } + else + { + $oP->p('Sorry, an error occured while applying the package...'); + } + $oP->p('Apply another package from the same archive'); + $oP->p('Select another archive'); + $oP->p('Back to the menu'); + break; + + case 'menu': + default: + $oP->add('

    Database backup & restore

    '); + $oP->add('

    Select one of the actions below:

    '); + $oP->add('

    Export the database to an archive

    '); + $oP->add('

    Reload the database from an archive

    '); +} + +$oP->output(); +?> diff --git a/pages/graphviz.php b/pages/graphviz.php new file mode 100644 index 0000000000..32181c4ce5 --- /dev/null +++ b/pages/graphviz.php @@ -0,0 +1,82 @@ +p("no lifecycle for this class"); + } + else + { + $aStates = MetaModel::EnumStates($sClass); + $aStimuli = MetaModel::EnumStimuli($sClass); + $sDotFileContent .= "digraph finite_state_machine { + rankdir=LR; + size=\"12,12\" + node [ fontname=Verdana ]; + edge [ fontname=Verdana ]; +"; + $aStatesLinks = array(); + foreach ($aStates as $sStateCode => $aStateDef) + { + $aStatesLinks[$sStateCode] = array('in' => 0, 'out' => 0); + } + + foreach ($aStates as $sStateCode => $aStateDef) + { + $sStateLabel = $aStates[$sStateCode]['label']; + $sStateDescription = $aStates[$sStateCode]['description']; + foreach(MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) + { + $aStatesLinks[$sStateCode]['out']++; + $aStatesLinks[$aTransitionDef['target_state']]['in']++; + $sStimulusLabel = $aStimuli[$sStimulusCode]->Get('label'); + $sTargetStateLabel = $aStates[$aTransitionDef['target_state']]['label']; + $sDotFileContent .= "\t$sStateCode -> {$aTransitionDef['target_state']} [ label=\"$sStimulusLabel\"];\n"; + } + } + foreach($aStates as $sStateCode => $aStateDef) + { + $sStateLabel = str_replace(' ', '\n', $aStates[$sStateCode]['label']); + if ( ($aStatesLinks[$sStateCode]['in'] == 0) || ($aStatesLinks[$sStateCode]['out'] == 0)) + { + $sDotFileContent .= "\t$sStateCode [ shape=doublecircle,label=\"$sStateLabel\"];\n"; + } + else + { + $sDotFileContent .= "\t$sStateCode [ shape=circle,label=\"$sStateLabel\"];\n"; + } + } + $sDotFileContent .= "}\n"; + } + return $sDotFileContent; +} + +$sClass = utils::ReadParam('class', 'bizIncidentTicket'); +$sDir = dirname(__FILE__); +$sImageFilePath = realpath($sDir."/../images/lifecycle/$sClass.png"); +if (!file_exists($sImageFilePath)) +{ + // create the file with Graphviz + $sDotDescription = GraphvizLifecycle($sClass); + $sDotFilePath = $sDir."/tmp-lifecycle.dot"; + $rFile = fopen($sDotFilePath, "w"); + fwrite($rFile, $sDotDescription); + fclose($rFile); + exec("/iTop/Graphviz/bin/dot.exe -Tpng < $sDotFilePath > $sImageFilePath"); +} + +header('Content-type: image/png'); +echo file_get_contents($sImageFilePath); +?> diff --git a/pages/incident.php b/pages/incident.php new file mode 100644 index 0000000000..bea0a89511 --- /dev/null +++ b/pages/incident.php @@ -0,0 +1,757 @@ +m_sCurrentStep = $sStep; + } + + protected function GetFields($sStep = '') + { + if ($sStep == '') + { + $sStep = $this->m_sCurrentStep; + } + return $this->m_aSteps[$sStep]; + } + + protected function AddContextToForm(web_page $oPage) + { + // Store as hidden fields in the page all the variables from the previous steps + foreach($this->m_aSteps as $sStep => $aFields) + { + if ($sStep == $this->m_sCurrentStep) continue; + foreach($aFields as $sAttName => $sFieldName) + { + $oPage->add("\n"); + } + } + } + + function GetObjectPicker(web_page $oPage, $sTitle, $sFieldName, $sClass) + { + $sScript = +<< 0) + { + aRelatedObjectIds = sRelatedObjectIds.split(' '); + } + else + { + aRelatedObjectIds = new Array(); + aRelatedObjectIds[0] = 0; + } + var sibusql = sClass+": pkey IN {" + aRelatedObjectIds.join(", ") + "}"; + $.get("ajax.render.php?filter=" + sibusql + "&style=list&encoding=sibusql", + { operation: "ajax" }, + function(data){ + $("#related_objects").empty(); + $("#related_objects").append(data); + $("#related_objects").removeClass("loading"); + }); + } + + function AddObject(sClass) + { + var sRelatedObjectIds = new String($('#related_object_ids').val()); + var sCurrentObjectId = new String($('#ac_current_object_id').val()); + if (sRelatedObjectIds.length > 0) + { + aRelatedObjectIds = sRelatedObjectIds.split(' '); + } + else + { + aRelatedObjectIds = new Array(); + } + // To do: check if the ID is not already in the list... + aRelatedObjectIds[aRelatedObjectIds.length] = sCurrentObjectId; + // Update the form & reload the list + $('#related_object_ids').val(aRelatedObjectIds.join(' ')); + UpdateObjectList(sClass); + } + + function ManageObjects(sTitle, sClass, sInputId) + { + $('#Manage_DlgTitle').text(sTitle); + sObjList = new String($('#'+sInputId).val()); + if (sObjList == '') + { + sObjList = new String('0'); + } + var aObjList = sObjList.split(' '); + Manage_LoadSelect('selected_objects', sClass+': pkey IN {' + aObjList.join(', ') + '}'); + Manage_LoadSelect('available_objects', sClass); + $('#ManageObjectsDlg').jqmShow(); + } + + function Manage_LoadSelect(sSelectedId, sFilter) + { + $('#'+sSelectedId).addClass('loading'); + $.get('ajax.render.php?filter=' + sFilter, + { operation: 'combo_options' }, + function(data){ + $('#'+sSelectedId).empty(); + $('#'+sSelectedId).append(data); + $('#'+sSelectedId).removeClass('loading'); + } + ); + } + + function Manage_SwapSelectedObjects(oSourceSelect, oDestinationSelect) + { + for (i=oSourceSelect.length-1;i>=0;i--) // Count down because we are removing the indexes from the combo + { + if (oSourceSelect.options[i].selected) + { + var newOption = document.createElement('option'); + newOption.text = oSourceSelect.options[i].text; + newOption.value = oSourceSelect.options[i].value; + oDestinationSelect.add(newOption, null); + oSourceSelect.remove(i); + } + } + Manage_UpdateButtons(); + } + + function Manage_UpdateButtons() + { + var oSrc = document.getElementById('available_objects'); + var oAddBtn = document.getElementById('btn_add_objects') + var oDst = document.getElementById('selected_objects'); + var oRemoveBtn = document.getElementById('btn_remove_objects') + if (oSrc.selectedIndex == -1) + { + oAddBtn.disabled = true; + } + else + { + oAddBtn.disabled = false; + } + if (oDst.selectedIndex == -1) + { + oRemoveBtn.disabled = true; + } + else + { + oRemoveBtn.disabled = false; + } + } + + function Manage_AddObjects() + { + var oSrc = document.getElementById('available_objects'); + var oDst = document.getElementById('selected_objects'); + Manage_SwapSelectedObjects(oSrc, oDst); + } + + function Manage_RemoveObjects() + { + var oSrc = document.getElementById('selected_objects'); + var oDst = document.getElementById('available_objects'); + Manage_SwapSelectedObjects(oSrc, oDst); + } + + function Manage_Ok(sClass) + { + var objectsToAdd = document.getElementById('selected_objects'); + var aSelectedObjects = new Array(); + for (i=0; i

    Selected Objects

    + + + + + + + + + +
    +

    Selected objects:

    + +

    +
    +

    +

    +
    +

    Available objects:

    + +

    +
    +       +
    +EOF; + $sHTML = ' + +   +  '; + $sHTML .= ''; + $sHTML .= '
    '.$sManageObjectsDlg.'
    '; + $oPage->add_script($sScript); + $oPage->add_ready_script("\$('#current_object_id').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#ac_current_object_id', extraParams:{operation:'link', sclass:'$sClass', attCode:'name'}});"); + $oPage->add_ready_script("$('#ManageObjectsDlg').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window + $oPage->add_ready_script("UpdateObjectList('$sClass');"); + return $sHTML; + } + + function DisplayObjectPickerList(web_page $oPage, $sClass) + { + $oFilter = new CMDBSearchFilter($sClass); + $oFilter->AddCondition('pkey', array(0), 'IN'); + //$oPage->p($oFilter->__DescribeHTML()); + $oBlock = new DisplayBlock($oFilter, 'list', true /* Asynchronous */); + $oBlock->Display($oPage, 'related_objects'); + } +} + +class IncidentCreationWizard extends DialogWizard +{ + public function __construct($sStep) + { + parent::__construct($sStep); + $this->m_aSteps = + array( + '1' => array('title' => 'attr_title', 'customer_id' => 'attr_customer_id', 'initial_situation' => 'attr_initial_situation', 'severity' => 'attr_severity', 'impact' => 'attr_impact', 'workgroup_id' => 'attr_workgroup_id', 'action_log' => 'attr_action_log'), + '2' => array('impacted_infra_ids' => 'impacted_infra_ids'), + '3' => array('additional_impacted_object_ids' => 'additional_impacted_object_ids'), + '4' => array('related_incident_ids' => 'related_incident_ids'), + '5' => array('contact_ids' => 'contact_ids'), + ); + } + + protected function AddContextToForm($oPage) + { + parent::AddContextToForm($oPage); + $oPage->add("\n"); + $oPage->add("m_sNextStep."\" />\n"); + } + + public function DisplayNewTicketForm(web_page $oPage) + { + assert($this->m_sCurrentStep == '1'); + $this->m_sNextStep = '2'; + $aFields = $this->GetFields(); + + $oPage->add('
    '); + $aDetails = array(); + $aAttributesDef = MetaModel::ListAttributeDefs('bizIncidentTicket'); + foreach($aFields as $sAttCode => $sFieldName) + { + $oAttDef = $aAttributesDef[$sAttCode]; + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, 'bizIncidentTicket', $sAttCode, $oAttDef); + $aDetails[] = array('label' => $oAttDef->GetLabel().' *', 'value' => $sHTMLValue); + } + $oPage->details($aDetails); + $this->AddContextToForm($oPage); + $oPage->add("    \n"); + $oPage->add("\n"); + $oPage->add('
    '); + } + + public function DisplayImpactedInfraForm(web_page $oPage) + { + assert($this->m_sCurrentStep == '2'); + $this->m_sNextStep = '3'; + $oPage->add('
    '); + $aDetails = array(); + $sHTML = $this->GetObjectPicker($oPage, 'Impacted Infrastructure', 'impacted_infra_ids', 'logInfra'); + $aDetails[] = array('label' => 'Impacted element:', 'value' => $sHTML); + $oPage->details($aDetails); + $this->DisplayObjectPickerList($oPage, 'logInfra'); + $this->AddContextToForm($oPage); + $oPage->add("    \n"); + $oPage->add("\n"); + $oPage->add('
    '); + } + + public function DisplayAdditionalImpactedObjectForm(web_page $oPage) + { + assert($this->m_sCurrentStep == '3'); + $this->m_sNextStep = '4'; + $sImpactedInfraIds = Utils::ReadParam('impacted_infra_ids'); + + $sImpactedInfraIds = Utils::ReadParam('impacted_infra_ids', ''); + if (!empty($sImpactedInfraIds)) + { + $oPage->p('Impacted Infrastructure:'); + $oFilter = new CMDBSearchFilter('logRealObject'); + $oFilter->AddCondition('pkey', explode(' ', $sImpactedInfraIds), 'IN'); + $oBlock = new DisplayBlock($oFilter, 'list', false /* Synchronous */); + $oBlock->Display($oPage, 'impacted_infra'); + } + + $aImpactedInfraIds = explode(' ', $sImpactedInfraIds); + $oInfraSet = CMDBObjectSet::FromScratch('logRealObject'); + foreach($aImpactedInfraIds as $id) + { + $oObj = MetaModel::GetObject('logRealObject', $id); + $oInfraSet->AddObject($oObj); + } + $aImpactedObject = $oInfraSet->GetRelatedObjects('impacts'); + $aAdditionalIds = array(); + foreach($aImpactedObject as $sRootClass => $aObjects) + { + foreach($aObjects as $oObj) + { + $aAdditionalIds[] = $oObj->GetKey(); + } + } + $sAdditionalIds = implode(' ', $aAdditionalIds); + $oPage->add_ready_script('$("#related_object_ids").val("'.$sAdditionalIds.'");'); + + $oPage->p('Additional Impact Computed:'); + $this->DisplayObjectPickerList($oPage, 'logRealObject'); + $oPage->add('
    '); + $aDetails = array(); + $sHTML = $this->GetObjectPicker($oPage, 'Additional Impacted Infrastructure', 'additional_impacted_object_ids', 'logRealObject'); + $aDetails[] = array('label' => 'Impacted element:', 'value' => $sHTML); + $oPage->details($aDetails); + $this->AddContextToForm($oPage); + $oPage->add("    \n"); + $oPage->add("\n"); + $oPage->add('
    '); + } + + public function DisplayRelatedTicketsForm(web_page $oPage) + { + assert($this->m_sCurrentStep == '4'); + $this->m_sNextStep = '5'; + $oRelatedTicketsFilter = new DBObjectSearch('bizIncidentTicket'); + $sImpactedInfraIds = Utils::ReadParam('impacted_infra_ids', ''); + $sAdditionalImpactedObjectIds = Utils::ReadParam('additional_impacted_object_ids', ''); + $sIds = trim($sImpactedInfraIds.' '.$sAdditionalImpactedObjectIds); + $aTicketIds = array(); + if (!empty($sIds)) + { + $aIds = explode(' ', $sIds); + $sSibusQL = "bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id IN (logRealObject: pkey IN {".implode(',', $aIds)."}))"; + $oTicketSearch = DBObjectSearch::FromSibusQL($sSibusQL); + $oRelatedTicketSet = new DBObjectSet($oTicketSearch); + while ($oTicket = $oRelatedTicketSet->Fetch()) + { + $aTicketIds[] = $oTicket->GetKey(); + } + } + + $sTicketIds = implode(' ', $aTicketIds); + $oPage->add_ready_script('$("#related_object_ids").val("'.$sTicketIds.'");'); + $oPage->p('Potentially related incidents:'); + $this->DisplayObjectPickerList($oPage, 'bizIncidentTicket'); + + $oPage->add('
    '); + $sHTML = $this->GetObjectPicker($oPage, 'Related Incidents', 'related_incident_ids', 'bizIncidentTicket'); + $aDetails[] = array('label' => 'Related Incident:', 'value' => $sHTML); + $oPage->details($aDetails); + $this->AddContextToForm($oPage); + $oPage->add("    \n"); + $oPage->add("\n"); + $oPage->add('
    '); + } + + public function DisplayContactsToNotifyForm(web_page $oPage) + { + assert($this->m_sCurrentStep == '5'); + $this->m_sNextStep = '6'; + $oPage->add('
    '); + $sHTML = $this->GetObjectPicker($oPage, 'Contacts to notify', 'contact_ids', 'bizContact'); + $aDetails[] = array('label' => 'Additional contact:', 'value' => $sHTML); + $oPage->details($aDetails); + $this->DisplayObjectPickerList($oPage, 'bizContact'); + $this->AddContextToForm($oPage); + $oPage->add("    \n"); + $oPage->add("\n"); + $oPage->add('
    '); + } + + function DisplayFinalForm(web_page $oPage) + { + $oAppContext = new ApplicationContext(); + assert($this->m_sCurrentStep == '6'); + $this->m_sNextStep = '7'; + + $aDetails = array(); + $aAttributesDef = MetaModel::ListAttributeDefs('bizIncidentTicket'); + $aFields = $this->GetFields('1'); + foreach($aFields as $sAttCode => $sFieldName) + { + $oAttDef = $aAttributesDef[$sAttCode]; + $sValue = Utils::ReadParam($sFieldName, ''); + if ($oAttDef->IsExternalKey() && isset($sValue) && ($sValue != 0)) + { + $oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $sValue); + if (!is_object($oTargetObj)) + { + trigger_error("Houston: could not find ".$oAttDef->GetTargetClass()."::$sValue"); + } + $sPage = cmdbAbstractObject::ComputeUIPage($oAttDef->GetTargetClass()); + $sHint = htmlentities($oAttDef->GetTargetClass()."::".$sValue); + $sHTMLValue = "GetTargetClass()."&id=$sValue&".$oAppContext->GetForLink()."\" title=\"$sHint\">".$oTargetObj->GetName().""; + } + else + { + $sHTMLValue = $oAttDef->GetAsHTML($sValue); + } + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + $oPage->details($aDetails); + + $oPage->AddTabContainer('LinkedObjects'); + $oPage->SetCurrentTabContainer('LinkedObjects'); + + $sImpactedInfraIds = Utils::ReadParam('impacted_infra_ids', ''); + $sImpactedInfraIds .= ' '.Utils::ReadParam('additional_impacted_object_ids', ''); + $sImpactedInfraIds = trim($sImpactedInfraIds); + $oPage->SetCurrentTab("Infrastructure impacted"); + if (!empty($sImpactedInfraIds)) + { + $oFilter = new CMDBSearchFilter('logRealObject'); + $oFilter->AddCondition('pkey', explode(' ', $sImpactedInfraIds), 'IN'); + $oBlock = new DisplayBlock($oFilter, 'list', false /* Synchronous */); + $oBlock->Display($oPage, 'related_objects'); + } + else + { + $oPage->p("There is no infrastructure impacted by this incident"); + } + + $sRelatedIncidentIds = Utils::ReadParam('related_incident_ids', ''); + $oPage->SetCurrentTab("Related tickets"); + if (!empty($sRelatedIncidentIds)) + { + $oFilter = new CMDBSearchFilter('bizIncidentTicket'); + $oFilter->AddCondition('pkey', explode(' ', $sRelatedIncidentIds), 'IN'); + $oBlock = new DisplayBlock($oFilter, 'list', false /* Synchronous */); + $oBlock->Display($oPage, 'related_incidents'); + } + else + { + $oPage->p("There is no other incident related to this one"); + } + + $oPage->SetCurrentTab("Contacts to notify"); + $sContactIds = Utils::ReadParam('contact_ids', ''); + if (!empty($sContactIds)) + { + $oFilter = new CMDBSearchFilter('bizContact'); + $oFilter->AddCondition('pkey', explode(' ', $sContactIds), 'IN'); + $oBlock = new DisplayBlock($oFilter, 'list', false /* Synchronous */); + $oBlock->Display($oPage, 'contacts'); + } + else + { + $oPage->p("There is no contact to notify"); + } + $oPage->SetCurrentTab(); + + $oPage->add('
    '); + $this->AddContextToForm($oPage); + $oPage->add("    \n"); + $oPage->add("    \n"); + $oPage->add("\n"); + $oPage->add('
    '); + } + + public function CreateIncident(web_page $oPage) + { + $oAppContext = new ApplicationContext(); + assert($this->m_sCurrentStep == '7'); + $this->m_sNextStep = '1'; + + $oIncident = MetaModel::NewObject('bizIncidentTicket'); + $oPage->p("Creation of Incident Ticket."); + + $aFields = $this->GetFields('1'); + foreach($aFields as $sAttCode => $sFieldName) + { + $sValue = Utils::ReadPostedParam($sFieldName, ''); + $oIncident->Set($sAttCode, $sValue); + } + $oIncident->Set('ticket_status', 'New'); + $oIncident->Set('start_date', time()); + $oIncident->Set('name', 'ID not set'); + + if ($oIncident->CheckToInsert()) + { + // Create the ticket itself + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Administrator"); + $iChangeId = $oMyChange->DBInsert(); + $oIncident->DBInsertTracked($oMyChange); + + $sName = sprintf('I-%06d', $oIncident->GetKey()); + $oIncident->Set('name', $sName); + $oIncident->DBUpdateTracked($oMyChange); + $oPage->p("Incident $sName created.\n"); + + // Now link the objects to the Incident: + // 1) the impacted infra + $sImpactedInfraIds = Utils::ReadParam('impacted_infra_ids', ''); + $sImpactedInfraIds .= ' '.Utils::ReadParam('additional_impacted_object_ids', ''); + $sImpactedInfraIds = trim($sImpactedInfraIds); + if (!empty($sImpactedInfraIds)) + { + $aImpactedInfra = explode(' ', $sImpactedInfraIds); + foreach($aImpactedInfra as $iInfraId) + { + $oLink = MetaModel::NewObject('lnkInfraTicket'); + $oLink->Set('infra_id', $iInfraId); + $oLink->Set('ticket_id', $oIncident->GetKey()); + $oLink->Set('impact', 'automatic'); + $oLink->DBInsertTracked($oMyChange); + } + } + // 2) the related incidents + $sRelatedIncidentsIds = Utils::ReadPostedParam('related_incident_ids'); + if (!empty($sRelatedIncidentsIds)) + { + $aRelatedIncidents = explode(' ', $sRelatedIncidentsIds); + foreach($aRelatedIncidents as $iIncidentId) + { + $oLink = MetaModel::NewObject('lnkRelatedTicket'); + $oLink->Set('rel_ticket_id', $iIncidentId); + $oLink->Set('ticket_id', $oIncident->GetKey()); + $oLink->Set('impact', 'automatic'); + $oLink->DBInsertTracked($oMyChange); + } + } + // 3) the contacts to notify + $sContactsIds = Utils::ReadPostedParam('contact_ids'); + if (!empty($sContactsIds)) + { + $aContactsToNotify = explode(' ', $sContactsIds); + foreach($aContactsToNotify as $iContactId) + { + $oLink = MetaModel::NewObject('lnkContactRealObject'); + $oLink->Set('contact_id', $iContactId); + $oLink->Set('object_id', $oIncident->GetKey()); + $oLink->Set('role', 'notification'); + $oLink->DBInsertTracked($oMyChange); + } + } + $oIncident->DisplayDetails($oPage, 'bizIncidentTicket', $oIncident->GetKey()); + } + else + { + $oPage->p("Error: object can not be created!\n"); + } + } +} + + +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +$currentOrganization = utils::ReadParam('org_id', ''); +$operation = utils::ReadParam('operation', ''); +$oP = new iTopWebPage("ITop - Incident Management", $currentOrganization); + +switch($operation) +{ + case 'details': + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class' and 'id' parameters must be specifed for this operation.

    \n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $oP->set_title("iTop - ".$oObj->GetName()." - $sClass details"); + $oObj->DisplayDetails($oP); + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to view it).

    \n"); + } + } + break; + + case 'new': + $step = utils::ReadParam('step', '1'); + $aSteps = array( + 'Ticket Information', + 'Impacted Infrastructure', + 'Additional Impact', + 'Related Tickets', + 'Contacts to Notify', + 'Confirmation', + 'Ticket Creation' + ); + $oWizard = new IncidentCreationWizard($step); + $oP = new iTopWizardWebPage("ITop - Incident Management", $currentOrganization, $step, $aSteps); + + switch($step) + { + case 1: + default: + //$oP->add(''); + $oWizard->DisplayNewTicketForm($oP); + break; + + case 2: + //$oP->add(''); + $oWizard->DisplayImpactedInfraForm($oP); + break; + + case 3: + //$oP->add(''); + $oWizard->DisplayAdditionalImpactedObjectForm($oP); + break; + + case 4: + //$oP->add(''); + $oWizard->DisplayRelatedTicketsForm($oP); + break; + + case 5: + //$oP->add(''); + $oWizard->DisplayContactsToNotifyForm($oP); + break; + + case 6: + //$oP->add(''); + $oWizard->DisplayFinalForm($oP); + break; + + case 7: + $oWizard->CreateIncident($oP); + break; + } + break; + + case 'modify': + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class' and 'id' parameters must be specifed for this operation.

    \n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $oP->set_title("iTop - ".$oObj->GetName()." - $sClass modification"); + $oP->add("

    ".$oObj->GetName()." - $sClass modification

    \n"); + $oObj->DisplayModifyForm($oP); + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to view it).

    \n"); + } + } + break; + + case 'apply_modify': + $sClass = utils::ReadPostedParam('class', ''); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class' and 'id' parameters must be specifed for this operation.

    \n"); + } + else if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already be updated!\n"); + } + else + { + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $oP->set_title("iTop - ".$oObj->GetName()." - $sClass modification"); + $oP->add("

    ".$oObj->GetName()." - $sClass modification

    \n"); + $bObjectModified = false; + foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) + { + $iFlags = $oObj->GetAttributeFlags($sAttCode); + if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) + { + // Non-visible, or read-only attribute, do nothing + } + else if (!$oAttDef->IsExternalField()) + { + $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); + $previousValue = $oObj->Get($sAttCode); + if (!empty($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) + { + $oObj->Set($sAttCode, $aAttributes[$sAttCode]); + $bObjectModified = true; + } + } + } + if (!$bObjectModified) + { + $oP->p("No modification detected. ".get_class($oObj)." has not been updated.\n"); + } + else if ($oObj->CheckToUpdate()) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by somebody"); // TO DO put the correct user info here + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + + $oP->p(get_class($oObj)." updated.\n"); + } + else + { + $oP->p("Error: object can not be updated!\n"); + //$oObj->Reload(); // restore default values! + } + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); + } + } + $oP->add("

    Alors a roule ?

    "); + $oObj->DisplayDetails($oP); + break; +} +$oP->output(); +?> diff --git a/pages/index.php b/pages/index.php new file mode 100644 index 0000000000..33c4d77c0b --- /dev/null +++ b/pages/index.php @@ -0,0 +1,704 @@ +no_cache(); + + + +MetaModel::CheckDefinitions(); +// new API - MetaModel::DBCheckFormat(); +// not necessary, and time consuming! +// MetaModel::DBCheckIntegrity(); + + +// Comment by Rom: MetaModel::GetSubclasses("logRealObject") retourne la totale +// utiliser IsRootClass pour savoir si une classe obtenue est une classe feuille ou non +$aTopLevelClasses = array('bizService', 'bizLocation', 'bizContact', 'logInfra', 'bizDocument', 'bizObject'); + +function ReadParam($sName, $defaultValue = "") +{ + return isset($_REQUEST[$sName]) ? $_REQUEST[$sName] : $defaultValue; +} + +function DisplaySelectOrg($oPage, $sCurrentOrganization, $iContext) +{ + global $oContext; + + //$oSearchFilter = new CMDBSearchFilter("bizOrganization"); + $oSearchFilter = $oContext->NewFilter("bizOrganization"); + $oPage->p($oSearchFilter->serialize()); + $oSet = new CMDBObjectSet($oSearchFilter); + if ($oSet->Count() == 0) + { + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oPage->p("No organization found.\n"); + $oPage->p($oSearchFilter->__DescribeHTML()); + $oPage->add("
    \n"); + $oPage->add("
    \n"); + } + else + { + $oCurrentOrganization = null; + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oPage->add("Select the context:\n"); + $oPage->add("\n"); + $oPage->p(""); + $oPage->add("Select an organization: \n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("
    \n"); + if ($oCurrentOrganization != null) + { + $oCurrentOrganization->DisplayDetails($oPage); + } + $oPage->add("
    \n"); + $oPage->add("
    \n"); + } +} + +function DisplayDetails(web_page $oPage, $sClassName, $sKey) +{ + global $oContext; + //$oObj = MetaModel::GetObject($sClassName, $sKey); + $oObj = $oContext->GetObject($sClassName, $sKey); + $oPage->p("Details of ".get_class($oObj)." - $sKey"); + + $oObj->DisplayDetails($oPage); + + // Modified by rom + $aLinks = array(); + $aLinks[] = "View changes log"; + $aLinks[] = "Edit this object"; + $aLinks[] = "Delete this object (no confirmation!)"; + // By rom + foreach (MetaModel::EnumLinkingClasses($sClassName) as $sLinkClass => $aRemoteClasses) + { + foreach($aRemoteClasses as $sExtKeyAttCode => $sRemoteClass) + { + // #@# quick and dirty: guess the extkey attcode from link to current class + // later, this information should be part of the biz model + $sExtKeyToMe = ""; + foreach(MetaModel::ListAttributeDefs($sLinkClass) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalKey() && $oAttDef->GetTargetClass() == $sClassName) + { + $sExtKeyToMe = $sAttCode; + break; + } + } + if (empty($sExtKeyToMe)) + { + $oPage->p("Houston... could not find the external key for $sClassName in $sLinkClass"); + } + else + { + $oFilter = new CMDBSearchFilter($sRemoteClass); // just a dummy empty one for edition + + $sButton = "
    \n"; + $sButton .= "
    \n"; + $aOnOKArgs = array("operation"=>"addlinks", "linkclass"=>$sLinkClass, "extkeytome"=>$sExtKeyToMe, "extkeytopartner"=>$sExtKeyAttCode); + $sButton .= dialogstack::RenderEditableField("Add links with $sRemoteClass", "filter", $oFilter->serialize(), true, "", $aOnOKArgs); + $sButton .= "
    \n"; + $sButton .= "
    \n"; + $aLinks[] = $sButton; + } + } + } + $sLinks = implode(" / ", $aLinks); + $oPage->p($sLinks); +} + +// By Rom +function DisplayChangesLog(web_page $oPage, $sClassName, $sKey) +{ + global $oContext; + //$oObj = MetaModel::GetObject($sClassName, $sKey); + $oObj = $oContext->GetObject($sClassName, $sKey); + $oPage->p("Changes log for ".get_class($oObj)." - $sKey"); + + $oObj->DisplayChangesLog($oPage); + + $oPage->p("View details"); + $oPage->p("Edit this object"); + $oPage->p("Delete this object (no confirmation!)"); +} + +function DumpObjectsAsCSV(web_page $oPage, $sClassName, $oSearchFilter = null, $sSeparator = ",") +{ + global $oContext; + + $aHeader = array(); + $aHeader[] = 'pkey'; + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + { + $aHeader[] = $oAttDef->GetLabel(); + } + $oPage->Add(join($sSeparator, $aHeader)."\n"); + + if ($oSearchFilter == null) + { + $oSearchFilter = $oContext->NewFilter($sClassName); + } + $oObjectSet = new CMDBObjectSet($oSearchFilter); + + while ($oObj = $oObjectSet->Fetch()) + { + $aRow = array(); + $aRow[] = $oObj->GetKey(); + foreach($oObj->GetAttributesList($sClassName) as $sAttCode) + { + $aRow[] = $oObj->GetAsCSV($sAttCode); + } + $oPage->Add(join($sSeparator, $aRow)."\n"); + } +} + +function DumpObjects(web_page $oPage, $sClassName, CMDBSearchFilter $oSearchFilter = null) +{ + global $oContext; + + if ($oSearchFilter == null) + { + //$oSearchFilter = new CMDBSearchFilter($sClassName); + $oSearchFilter = $oContext->NewFilter($sClassName); + } + $aAttribs = array(); + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + { + $aAttribs['key'] = array('label' => 'key', 'description' => 'Primary Key'); + $aAttribs[$sAttCode] = array('label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); + } + $oObjectSet = new CMDBObjectSet($oSearchFilter); + + $aValues = array(); + while ($oObj = $oObjectSet->Fetch()) + { + $aRow['key'] = "GetKey()."\">".$oObj->GetKey().""; + foreach($oObj->GetAttributesList($sClassName) as $sAttCode) + { + $aRow[$sAttCode] = $oObj->GetAsHTML($sAttCode); + } + $aValues[] = $aRow; + } + $oPage->table($aAttribs, $aValues); +} + +function DisplayEditForm(web_page $oPage, $sClassName, $sKey) +{ + global $oContext; + //$oObj = MetaModel::GetObject($sClassName, $sKey); + $oObj = $oContext->GetObject($sClassName, $sKey); + if ($oObj == null) + { + $oPage->p("You are not allowed to edit this object."); + return; + } + $oPage->p("Edition of ".get_class($oObj)." - $sKey\n"); + + $aDetails = array(); + $oPage->add("
    \n"); + foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) + { + if (!$oAttDef->IsExternalField()) + { + if ($oAttDef->IsExternalKey()) + { + //External key, display a combo + $sTargetClass = $oAttDef->GetTargetClass(); + //$oFilter = new CMDBSearchFilter($sTargetClass); + $oFilter = $oContext->NewFilter($sTargetClass); + $oSet = new CMDBObjectSet($oFilter); + $sValue = "\n"; + } + else + { + $sValue = "Get($sAttCode))."\">"; + } + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue); + } + } + $oPage->details($aDetails); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); +} + +function DisplayCreationForm(web_page $oPage, $sClassName) +{ + global $oContext; + $oPage->p("New $sClassName\n"); + + $aDetails = array(); + $oPage->add("\n"); + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + { + if (!$oAttDef->IsExternalField()) + { + if ($oAttDef->IsExternalKey()) + { + //External key, display a combo + $sTargetClass = $oAttDef->GetTargetClass(); + //$oFilter = new CMDBSearchFilter($sTargetClass); + $oFilter = $oContext->NewFilter($sTargetClass); + $oSet = new CMDBObjectSet($oFilter); + $sValue = "\n"; + } + else + { + $sValue = "GetDefaultValue()."\">"; + } + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue); + } + } + $oPage->details($aDetails); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); +} + +function UpdateObject(web_page $oPage, $sClassName, $sKey, $aAttributes) +{ + global $oContext; + //$oObj = MetaModel::GetObject($sClassName, $sKey); + $oObj = $oContext->GetObject($sClassName, $sKey); + if ($oObj == null) + { + $oPage->p("You are not allowed to edit this object."); + return; + } + $oPage->p("Update of $sClassName - $sKey"); + + foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) + { + if (isset($aAttributes[$sAttCode])) + { + $oObj->Set($sAttCode, $aAttributes[$sAttCode]); + } + } + if ($oObj->CheckToUpdate()) + { + // By rom + // $oObj->DBUpdate(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by somebody"); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + + $oPage->p(get_class($oObj)." updated\n"); + } + else + { + $oPage->p("Error: object can not be updated!\n"); + $oObj->DBRevert(); // restore default values! + } + // By Rom + // $oObj->DisplayDetails($oPage); + // replaced by... + DisplayDetails($oPage, get_class($oObj), $oObj->GetKey()); + $oPage->p("Return to main page"); +} + +function DeleteObject(web_page $oPage, $sClassName, $sKey) +{ + global $oContext; + //$oObj = MetaModel::GetObject($sClassName, $sKey); + $oObj = $oContext->GetObject($sClassName, $sKey); + if ($oObj == null) + { + $oPage->p("You are not allowed to delete this object."); + return; + } + $oPage->p("Deletion of $sClassName - $sKey"); + + if ($oObj->CheckToDelete()) + { + // By Rom + //$oObj->DBDelete(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by somebody"); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBDeleteTracked($oMyChange); + + $oPage->p("$sClassName deleted\n"); + } + else + { + $oPage->p("Error: object can not be deleted!\n"); + // By Rom + DisplayDetails($oPage, get_class($oObj), $oObj->GetKey()); + } + $oPage->p("Return to main page"); +} + +function CreateObject(web_page $oPage, $sClassName, $aAttributes) +{ + $oObj = MetaModel::NewObject($sClassName); + $oPage->p("Creation of ".get_class($oObj)." object."); + + foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) + { + if (isset($aAttributes[$sAttCode])) + { + $oObj->Set($sAttCode, $aAttributes[$sAttCode]); + } + } + if ($oObj->CheckToInsert()) + { + // By rom + // $oObj->DBInsert(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by somebody"); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBInsertTracked($oMyChange); + + $oPage->p(get_class($oObj)." created\n"); + + // By Rom + // $oObj->DisplayDetails($oPage); + // replaced by... + DisplayDetails($oPage, get_class($oObj), $oObj->GetKey()); + } + else + { + $oPage->p("Error: object can not be created!\n"); + } + $oPage->p("Return to main page"); +} + +// By Rom +function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKeyToPartner, $sFilter) +{ + global $oContext; + //$oObj = MetaModel::GetObject($sClassName, $sKey); + $oObj = $oContext->GetObject($sClassName, $sKey); + if ($oObj == null) + { + $oPage->p("You are not allowed to modify (create links on) this object."); + return; + } + $oPage->p("Creating links for $sClassName - $sKey"); + + $oFilter = CMDBSearchFilter::unserialize($sFilter); + $oPage->p("Linking to ".$oFilter->__DescribeHTML()); + + $oObjSet = new CMDBObjectSet($oFilter); + if ($oObjSet->Count() != 0) + { + while($oPartnerObj = $oObjSet->Fetch()) + { + $oNewLink = MetaModel::NewObject($sLinkClass); + $oNewLink->Set($sExtKeyToMe, $sKey); + $oNewLink->Set($sExtKeyToPartner, $oPartnerObj->GetKey()); + if ($oNewLink->CheckToInsert()) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by somebody"); + $iChangeId = $oMyChange->DBInsert(); + $oNewLink->DBInsertTracked($oMyChange); + + $oPage->p(get_class($oNewLink)." created\n"); + } + else + { + $oPage->p("Error: link can not be created!\n"); + } + } + } + else + {} + +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// +// M a i n P r o g r a m +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +$operation = ReadParam('operation', ''); +$iContext = ReadParam('ctx', 1); + +$oContext = new UserContext(); + +switch($iContext) +{ + case 2: // See only the organization 'ITOP' + $oContext->AddCondition('bizOrganization', 'pkey', 'ITOP', '='); + $oContext->AddCondition('logRealObject', 'organization', 'ITOP', '='); + break; + + case 3: // See only the organization containing 'o' and contacts containing +33 + $oContext->AddCondition('Organization', 'name', 'o', 'Contains'); + //$oContext->AddCondition('Object', 'orgname', 'o', 'Contains'); + $oContext->AddCondition('Contact', 'phone', '+33', 'Contains'); + break; + + case 1: // No limitation + default: + // nothing to do +} + +dialogstack::DeclareCaller("Main navigation menu"); + +switch($operation) +{ + case 'details': + $sClass = ReadParam('class'); + $sKey = ReadParam('key'); + DisplayDetails($oPage, $sClass, $sKey); + break; + + // By rom + case 'changeslog': + $sClass = ReadParam('class'); + $sKey = ReadParam('key'); + DisplayChangesLog($oPage, $sClass, $sKey); + break; + + case 'edit': + $sClass = ReadParam('class'); + $sKey = ReadParam('key'); + DisplayEditForm($oPage, $sClass, $sKey); + break; + + case 'update': + $sClass = ReadParam('class'); + $sKey = ReadParam('key'); + $aAttributes = ReadParam('attr', array()); + UpdateObject($oPage, $sClass, $sKey, $aAttributes); + break; + + case 'new': + $sClass = ReadParam('class'); + DisplayCreationForm($oPage, $sClass); + break; + + case 'create': + $sClass = ReadParam('class'); + $aAttributes = ReadParam('attr', array()); + CreateObject($oPage, $sClass, $aAttributes); + break; + + case 'delete': + $sClass = ReadParam('class'); + $sKey = ReadParam('key'); + DeleteObject($oPage, $sClass, $sKey); + break; + + case 'direct': + $sFilter = ReadParam('filter'); + $sFormat = ReadParam('format', 'html'); + $oSearchFilter = CMDBSearchFilter::unserialize($sFilter); + switch($sFormat) + { + case 'csv': + $oPage->small_p($oSearchFilter->__DescribeHTML()); + $oPage->Add(""); + break; + + case 'xls': + $oPage->add_header('Content-disposition: attachment;filename=served.xls'); // Will fool Excel + $oPage->add_header('Content-Type: application/vnd.ms-excel'); // Will fool Excel + DumpObjects($oPage, $oSearchFilter->GetClass(), $oSearchFilter); + break; + + case 'html': + default: + $oSet = new CMDBObjectSet($oSearchFilter); + cmdbAbstractObject::DisplaySet($oPage, $oSet); + } + break; + + case 'addlinks': + $sClass = ReadParam('class'); + $sKey = ReadParam('key'); + $sLinkClass = ReadParam('linkclass'); + $sExtKeyToMe = ReadParam('extkeytome'); + $sExtKeyToPartner = ReadParam('extkeytopartner'); + $sFilter = ReadParam('filter'); + AddLinks($oPage, $sClass, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKeyToPartner, $sFilter); + break; + + default: + $sCurrentOrganization = ReadParam('org', ''); + $sActiveTab = ReadParam('classname', ''); + DisplaySelectOrg($oPage, $sCurrentOrganization, $iContext); + if ($sCurrentOrganization != "") + { + $oPage->add("
    \n"); + $oPage->add("
      \n"); + $index = 1; + $iActiveTabIndex = 1; // By default the first tab is the active one + foreach( $aTopLevelClasses as $sClassName) + { + if ($sClassName == $sActiveTab) + { + $iActiveTabIndex = $index; + } + $oPage->add("\t
    • $sClassName
    • \n"); + $index++; + } + $oPage->add("
    \n"); + foreach( $aTopLevelClasses as $sClassName) + { + $oPage->add("
    "); + if (count(MetaModel::GetSubclasses($sClassName)) > 0) + { + $sActiveSubclass = ReadParam('subclassname', ''); + foreach(MetaModel::GetSubclasses($sClassName) as $sSubclassName) + { + //$oSearchFilter = new CMDBSearchFilter($sSubclassName); + $oSearchFilter = $oContext->NewFilter($sSubclassName); + $oSearchFilter ->AddCondition('org_id', $sCurrentOrganization, '='); + + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oPage->p("$sSubclassName - ".MetaModel::GetClassDescription($sSubclassName)); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + foreach( MetaModel::GetClassFilterDefs($sSubclassName) as $sFilterCode=>$oFilterDef) + { + $sFilterValue = ""; + if (($sActiveTab == $sClassName) && ($sActiveSubclass == $sSubclassName)) + { + $sFilterValue = ReadParam($sFilterCode, ''); + if (!empty($sFilterValue)) + { + $oSearchFilter->AddCondition($sFilterCode, $sFilterValue, 'Contains'); + } + } + $oPage->add($oFilterDef->GetLabel().":  \n"); + } + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("
    \n"); + + $oSet = new CMDBObjectSet($oSearchFilter); + $iMatchesCount = $oSet->Count(); + if ($iMatchesCount == 0) + { + $oPage->p("No $sSubclassName matches these criteria."); + $oPage->small_p("(".$oSearchFilter->__DescribeHTML().")"); + } + else + { + $oPage->p("$iMatchesCount item(s) found."); + cmdbAbstractObject::DisplaySet($oPage, $oSet); + } + $oPage->p("Create a new $sSubclassName\n"); + $oPage->add("
    \n"); + } + } + else + { + // No subclasses, list the form directly + //$oSearchFilter = new CMDBSearchFilter($sClassName); + $oSearchFilter = $oContext->NewFilter($sClassName); + $oSearchFilter ->AddCondition('org_id', $sCurrentOrganization, '='); + + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oPage->p("$sClassName - ".MetaModel::GetClassDescription($sClassName)); + $oPage->add("
    \n"); + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("\n"); + foreach( MetaModel::GetClassFilterDefs($sClassName) as $sFilterCode=>$oFilterDef) + { + $sFilterValue = ""; + if ($sActiveTab == $sClassName) + { + $sFilterValue = ReadParam($sFilterCode, ''); + if (!empty($sFilterValue)) + { + $oSearchFilter->AddCondition($sFilterCode, $sFilterValue, 'Contains'); + } + } + $oPage->add($oFilterDef->GetLabel().":  \n"); + } + $oPage->add("\n"); + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oPage->add("
    \n"); + $oSet = new CMDBObjectSet($oSearchFilter); + $iMatchesCount = $oSet->Count(); + if ($iMatchesCount == 0) + { + $oPage->p("No $sClassName matches these criteria."); + $oPage->small_p("(".$oSearchFilter->__DescribeHTML().")"); + } + else + { + $oPage->p("$iMatchesCount item(s) found."); + cmdbAbstractObject::DisplaySet($oPage, $oSet); + $oPage->small_p("(".$oSearchFilter->__DescribeHTML().")"); + } + $oPage->p("Create a new $sClassName\n"); + $oPage->add("
    \n"); + $oPage->add("
    \n"); + } + $oPage->add("
    \n"); + } + $oPage->add("
    \n"); + $oPage->add_script('$(function() {$("#classesTabs > ul").tabs( '.$iActiveTabIndex.', { fxFade: true, fxSpeed: \'fast\' } );});'); + } +} +$oPage->output(); +?> diff --git a/pages/opensearch.xml b/pages/opensearch.xml new file mode 100644 index 0000000000..2620b2d1f5 --- /dev/null +++ b/pages/opensearch.xml @@ -0,0 +1,8 @@ + +iTop +webmaster@itop.com +Recherche dans iTop +ISO-8859-1 + +http://localhost:81/pages/UI.php + \ No newline at end of file diff --git a/pages/opensearch.xml.php b/pages/opensearch.xml.php new file mode 100644 index 0000000000..6d57a98db0 --- /dev/null +++ b/pages/opensearch.xml.php @@ -0,0 +1,16 @@ + + +iTop +webmaster@itop.com +Search in iTop +ISO-8859-1 + + + + + diff --git a/pages/php-ofc-library/JSON.php b/pages/php-ofc-library/JSON.php new file mode 100644 index 0000000000..7260001a25 --- /dev/null +++ b/pages/php-ofc-library/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/pages/php-ofc-library/README.txt b/pages/php-ofc-library/README.txt new file mode 100644 index 0000000000..2d8b6297fc --- /dev/null +++ b/pages/php-ofc-library/README.txt @@ -0,0 +1,16 @@ +Open Flash Chart - PHP libraries. These help create data files for Open Flash Chart. +Copyright (C) 2007 + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA \ No newline at end of file diff --git a/pages/php-ofc-library/json_format.php b/pages/php-ofc-library/json_format.php new file mode 100644 index 0000000000..61a842ea14 --- /dev/null +++ b/pages/php-ofc-library/json_format.php @@ -0,0 +1,86 @@ + 0 && $json[$c-1] != '\\') + { + $in_string = !$in_string; + } + default: + $new_json .= $char; + break; + } + } + + return $new_json; +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_area_base.php b/pages/php-ofc-library/ofc_area_base.php new file mode 100644 index 0000000000..8d8f7560ac --- /dev/null +++ b/pages/php-ofc-library/ofc_area_base.php @@ -0,0 +1,66 @@ +$tmp = 0.35; + $this->values = array(); + } + + function set_width( $w ) + { + $this->width = $w; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_fill_colour( $colour ) + { + $this->fill = $colour; + } + + function set_fill_alpha( $alpha ) + { + $tmp = "fill-alpha"; + $this->$tmp = $alpha; + } + + function set_halo_size( $size ) + { + $tmp = 'halo-size'; + $this->$tmp = $size; + } + + function set_values( $v ) + { + $this->values = $v; + } + + function set_dot_size( $size ) + { + $tmp = 'dot-size'; + $this->$tmp = $size; + } + + function set_key( $text, $font_size ) + { + $this->text = $text; + $tmp = 'font-size'; + $this->$tmp = $font_size; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } + + function set_loop() + { + $this->loop = true; + } +} diff --git a/pages/php-ofc-library/ofc_area_hollow.php b/pages/php-ofc-library/ofc_area_hollow.php new file mode 100644 index 0000000000..ba6c204619 --- /dev/null +++ b/pages/php-ofc-library/ofc_area_hollow.php @@ -0,0 +1,10 @@ +type = "area_hollow"; + parent::area_base(); + } +} diff --git a/pages/php-ofc-library/ofc_area_line.php b/pages/php-ofc-library/ofc_area_line.php new file mode 100644 index 0000000000..c4b2b167e9 --- /dev/null +++ b/pages/php-ofc-library/ofc_area_line.php @@ -0,0 +1,10 @@ +type = "area_line"; + parent::area_base(); + } +} diff --git a/pages/php-ofc-library/ofc_bar.php b/pages/php-ofc-library/ofc_bar.php new file mode 100644 index 0000000000..0666939ffb --- /dev/null +++ b/pages/php-ofc-library/ofc_bar.php @@ -0,0 +1,34 @@ +top = $top; + + if( isset( $bottom ) ) + $this->bottom = $bottom; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + +class bar extends bar_base +{ + function bar() + { + $this->type = "bar"; + parent::bar_base(); + } +} + diff --git a/pages/php-ofc-library/ofc_bar_3d.php b/pages/php-ofc-library/ofc_bar_3d.php new file mode 100644 index 0000000000..4b4d8f4fb7 --- /dev/null +++ b/pages/php-ofc-library/ofc_bar_3d.php @@ -0,0 +1,31 @@ +top = $top; +// $this->bottom = $bottom; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + +class bar_3d extends bar_base +{ + function bar_3d() + { + $this->type = "bar_3d"; + parent::bar_base(); + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_bar_base.php b/pages/php-ofc-library/ofc_bar_base.php new file mode 100644 index 0000000000..4ab5c52e58 --- /dev/null +++ b/pages/php-ofc-library/ofc_bar_base.php @@ -0,0 +1,41 @@ +text = $text; + $tmp = 'font-size'; + $this->$tmp = $size; + } + + function set_values( $v ) + { + $this->values = $v; + } + + function append_value( $v ) + { + $this->values[] = $v; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_alpha( $alpha ) + { + $this->alpha = $alpha; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + diff --git a/pages/php-ofc-library/ofc_bar_filled.php b/pages/php-ofc-library/ofc_bar_filled.php new file mode 100644 index 0000000000..2008703cdf --- /dev/null +++ b/pages/php-ofc-library/ofc_bar_filled.php @@ -0,0 +1,39 @@ +$tmp = $outline_colour; + } +} + +class bar_filled extends bar_base +{ + function bar_filled( $colour=null, $outline_colour=null ) + { + $this->type = "bar_filled"; + parent::bar_base(); + + if( isset( $colour ) ) + $this->set_colour( $colour ); + + if( isset( $outline_colour ) ) + $this->set_outline_colour( $outline_colour ); + } + + function set_outline_colour( $outline_colour ) + { + $tmp = 'outline-colour'; + $this->$tmp = $outline_colour; + } +} + diff --git a/pages/php-ofc-library/ofc_bar_glass.php b/pages/php-ofc-library/ofc_bar_glass.php new file mode 100644 index 0000000000..4ca674378c --- /dev/null +++ b/pages/php-ofc-library/ofc_bar_glass.php @@ -0,0 +1,33 @@ +top = $top; +// $this->bottom = $bottom; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + + +class bar_glass extends bar_base +{ + function bar_glass() + { + $this->type = "bar_glass"; + parent::bar_base(); + } +} diff --git a/pages/php-ofc-library/ofc_bar_sketch.php b/pages/php-ofc-library/ofc_bar_sketch.php new file mode 100644 index 0000000000..f39fdcfd9e --- /dev/null +++ b/pages/php-ofc-library/ofc_bar_sketch.php @@ -0,0 +1,23 @@ +type = "bar_sketch"; + parent::bar_base(); + + $this->set_colour( $colour ); + $this->set_outline_colour( $outline_colour ); + $this->offset = $fun_factor; + } + + function set_outline_colour( $outline_colour ) + { + $tmp = 'outline-colour'; + $this->$tmp = $outline_colour; + } +} + diff --git a/pages/php-ofc-library/ofc_bar_stack.php b/pages/php-ofc-library/ofc_bar_stack.php new file mode 100644 index 0000000000..945d3d8aa8 --- /dev/null +++ b/pages/php-ofc-library/ofc_bar_stack.php @@ -0,0 +1,50 @@ +type = "bar_stack"; + parent::bar_base(); + } + + function append_stack( $v ) + { + $this->append_value( $v ); + } + + // an array of HEX colours strings + // e.g. array( '#ff0000', '#00ff00' ); + function set_colours( $colours ) + { + $this->colours = $colours; + } + + // an array of bar_stack_value + function set_keys( $keys ) + { + $this->keys = $keys; + } +} + +class bar_stack_value +{ + function bar_stack_value( $val, $colour ) + { + $this->val = $val; + $this->colour = $colour; + } +} + +class bar_stack_key +{ + function bar_stack_key( $colour, $text, $font_size ) + { + $this->colour = $colour; + $this->text = $text; + $tmp = 'font-size'; + $this->$tmp = $font_size; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_hbar.php b/pages/php-ofc-library/ofc_hbar.php new file mode 100644 index 0000000000..a0fffbb550 --- /dev/null +++ b/pages/php-ofc-library/ofc_hbar.php @@ -0,0 +1,64 @@ +left = $left; + $this->right = $right; + } + else + $this->right = $left; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + +class hbar +{ + function hbar( $colour ) + { + $this->type = "hbar"; + $this->values = array(); + $this->set_colour( $colour ); + } + + function append_value( $v ) + { + $this->values[] = $v; + } + + function set_values( $v ) + { + foreach( $v as $val ) + $this->append_value( new hbar_value( $val ) ); + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_key( $text, $size ) + { + $this->text = $text; + $tmp = 'font-size'; + $this->$tmp = $size; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + diff --git a/pages/php-ofc-library/ofc_line.php b/pages/php-ofc-library/ofc_line.php new file mode 100644 index 0000000000..945af208eb --- /dev/null +++ b/pages/php-ofc-library/ofc_line.php @@ -0,0 +1,9 @@ +type = "line"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_line_base.php b/pages/php-ofc-library/ofc_line_base.php new file mode 100644 index 0000000000..2e969f9e3f --- /dev/null +++ b/pages/php-ofc-library/ofc_line_base.php @@ -0,0 +1,70 @@ +type = "line_dot"; + $this->text = "Page views"; + $tmp = 'font-size'; + $this->$tmp = 10; + + $this->values = array(9,6,7,9,5,7,6,9,7); + } + + function set_values( $v ) + { + $this->values = $v; + } + + function set_width( $width ) + { + $this->width = $width; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_dot_size( $size ) + { + $tmp = 'dot-size'; + $this->$tmp = $size; + } + + function set_halo_size( $size ) + { + $tmp = 'halo-size'; + $this->$tmp = $size; + } + + function set_key( $text, $font_size ) + { + $this->text = $text; + $tmp = 'font-size'; + $this->$tmp = $font_size; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } + + function set_on_click( $text ) + { + $tmp = 'on-click'; + $this->$tmp = $text; + } + + function loop() + { + $this->loop = true; + } + + function line_style( $s ) + { + $tmp = "line-style"; + $this->$tmp = $s; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_line_dot.php b/pages/php-ofc-library/ofc_line_dot.php new file mode 100644 index 0000000000..52f2ad0eb9 --- /dev/null +++ b/pages/php-ofc-library/ofc_line_dot.php @@ -0,0 +1,33 @@ +value = $value; + $this->colour = $colour; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_size( $size ) + { + $this->size = $size; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } +} + +class line_dot extends line_base +{ + function line_dot() + { + $this->type = "line_dot"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_line_hollow.php b/pages/php-ofc-library/ofc_line_hollow.php new file mode 100644 index 0000000000..783fdf7785 --- /dev/null +++ b/pages/php-ofc-library/ofc_line_hollow.php @@ -0,0 +1,9 @@ +type = "line_hollow"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_line_style.php b/pages/php-ofc-library/ofc_line_style.php new file mode 100644 index 0000000000..4ba5ff8b65 --- /dev/null +++ b/pages/php-ofc-library/ofc_line_style.php @@ -0,0 +1,11 @@ +style = "dash"; + $this->on = $on; + $this->off = $off; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_pie.php b/pages/php-ofc-library/ofc_pie.php new file mode 100644 index 0000000000..be50008e56 --- /dev/null +++ b/pages/php-ofc-library/ofc_pie.php @@ -0,0 +1,109 @@ +value = $value; + $this->label = $label; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_label( $label, $label_colour, $font_size ) + { + $this->label = $label; + + $tmp = 'label-colour'; + $this->$tmp = $label_colour; + + $tmp = 'font-size'; + $this->$tmp = $font_size; + + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } + + function on_click( $event ) + { + $tmp = 'on-click'; + $this->$tmp = $event; + } + +} + +class pie +{ + function pie() + { + $this->type = 'pie'; + $this->colours = array("#d01f3c","#356aa0","#C79810"); + $this->border = 2; + } + + function set_colours( $colours ) + { + $this->colours = $colours; + } + + function set_alpha( $alpha ) + { + $this->alpha = $alpha; + } + + function set_values( $v ) + { + $this->values = $v; + } + + // boolean + function set_animate( $animate ) + { + $this->animate = $animate; + } + + // real + function set_start_angle( $angle ) + { + $tmp = 'start-angle'; + $this->$tmp = $angle; + } + + function set_tooltip( $tip ) + { + $this->tip = $tip; + } + + function set_gradient_fill() + { + $tmp = 'gradient-fill'; + $this->$tmp = true; + } + + function set_label_colour( $label_colour ) + { + $tmp = 'label-colour'; + $this->$tmp = $label_colour; + } + + /** + * Turn off the labels + */ + function set_no_labels() + { + $tmp = 'no-labels'; + $this->$tmp = true; + } + + function on_click( $event ) + { + $tmp = 'on-click'; + $this->$tmp = $event; + } +} diff --git a/pages/php-ofc-library/ofc_radar_axis.php b/pages/php-ofc-library/ofc_radar_axis.php new file mode 100644 index 0000000000..9c8d7885f9 --- /dev/null +++ b/pages/php-ofc-library/ofc_radar_axis.php @@ -0,0 +1,47 @@ +set_max( $max ); + } + + function set_max( $max ) + { + $this->max = $max; + } + + function set_steps( $steps ) + { + $this->steps = $steps; + } + + function set_stroke( $s ) + { + $this->stroke = $s; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_grid_colour( $colour ) + { + $tmp = 'grid-colour'; + $this->$tmp = $colour; + } + + function set_labels( $labels ) + { + $this->labels = $labels; + } + + function set_spoke_labels( $labels ) + { + $tmp = 'spoke-labels'; + $this->$tmp = $labels; + } +} + diff --git a/pages/php-ofc-library/ofc_radar_axis_labels.php b/pages/php-ofc-library/ofc_radar_axis_labels.php new file mode 100644 index 0000000000..1c4321cce0 --- /dev/null +++ b/pages/php-ofc-library/ofc_radar_axis_labels.php @@ -0,0 +1,15 @@ +labels = $labels; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_radar_spoke_labels.php b/pages/php-ofc-library/ofc_radar_spoke_labels.php new file mode 100644 index 0000000000..11b6682f15 --- /dev/null +++ b/pages/php-ofc-library/ofc_radar_spoke_labels.php @@ -0,0 +1,15 @@ +labels = $labels; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_scatter.php b/pages/php-ofc-library/ofc_scatter.php new file mode 100644 index 0000000000..978dc04873 --- /dev/null +++ b/pages/php-ofc-library/ofc_scatter.php @@ -0,0 +1,42 @@ +x = $x; + $this->y = $y; + + if( $dot_size > 0 ) + { + $tmp = 'dot-size'; + $this->$tmp = $dot_size; + } + } +} + +class scatter +{ + function scatter( $colour, $dot_size ) + { + $this->type = "scatter"; + $this->set_colour( $colour ); + $this->set_dot_size( $dot_size ); + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_dot_size( $dot_size ) + { + $tmp = 'dot-size'; + $this->$tmp = $dot_size; + } + + function set_values( $values ) + { + $this->values = $values; + } +} diff --git a/pages/php-ofc-library/ofc_scatter_line.php b/pages/php-ofc-library/ofc_scatter_line.php new file mode 100644 index 0000000000..3c4e21c0fe --- /dev/null +++ b/pages/php-ofc-library/ofc_scatter_line.php @@ -0,0 +1,27 @@ +type = "scatter_line"; + $this->set_colour( $colour ); + $this->set_dot_size( $dot_size ); + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_dot_size( $dot_size ) + { + $tmp = 'dot-size'; + $this->$tmp = $dot_size; + } + + function set_values( $values ) + { + $this->values = $values; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_shape.php b/pages/php-ofc-library/ofc_shape.php new file mode 100644 index 0000000000..3bab90790a --- /dev/null +++ b/pages/php-ofc-library/ofc_shape.php @@ -0,0 +1,25 @@ +x = $x; + $this->y = $y; + } +} + +class shape +{ + function shape( $colour ) + { + $this->type = "shape"; + $this->colour = $colour; + $this->values = array(); + } + + function append_value( $p ) + { + $this->values[] = $p; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_title.php b/pages/php-ofc-library/ofc_title.php new file mode 100644 index 0000000000..6dd19bbb86 --- /dev/null +++ b/pages/php-ofc-library/ofc_title.php @@ -0,0 +1,15 @@ +text = $text; + } + + function set_style( $css ) + { + $this->style = $css; + //"{font-size: 20px; color:#0000ff; font-family: Verdana; text-align: center;}"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_tooltip.php b/pages/php-ofc-library/ofc_tooltip.php new file mode 100644 index 0000000000..161a77ba03 --- /dev/null +++ b/pages/php-ofc-library/ofc_tooltip.php @@ -0,0 +1,51 @@ +shadow = $shadow; + } + + // stroke in pixels (e.g. 5 ) + function set_stroke( $stroke ) + { + $this->stroke = $stroke; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_background_colour( $bg ) + { + $this->background = $bg; + } + + // a css style + function set_title_style( $style ) + { + $this->title = $style; + } + + function set_body_style( $style ) + { + $this->body = $style; + } + + function set_proximity() + { + $this->mouse = 1; + } + + function set_hover() + { + $this->mouse = 2; + } +} + diff --git a/pages/php-ofc-library/ofc_upload_image.php b/pages/php-ofc-library/ofc_upload_image.php new file mode 100644 index 0000000000..b726af946d --- /dev/null +++ b/pages/php-ofc-library/ofc_upload_image.php @@ -0,0 +1,61 @@ + save_image debug mode, you +// will see the 'echo' text in a new window. +// + +/* + +print_r( $_GET ); +print_r( $_POST ); +print_r( $_FILES ); + +print_r( $GLOBALS ); +print_r( $GLOBALS["HTTP_RAW_POST_DATA"] ); + +*/ + + +// default path for the image to be stored // +$default_path = '../tmp-upload-images/'; + +if (!file_exists($default_path)) mkdir($default_path, 0777, true); + +// full path to the saved image including filename // +$destination = $default_path . basename( $_GET[ 'name' ] ); + +echo 'Saving your image to: '. $destination; + +$jfh = fopen($destination, 'w') or die("can't open file"); +fwrite($jfh, $GLOBALS['HTTP_RAW_POST_DATA']); +fclose($jfh); + +// +// LOOK: +// +exit(); + + +// +// PHP5: +// + + +// default path for the image to be stored // +$default_path = 'tmp-upload-images/'; + +if (!file_exists($default_path)) mkdir($default_path, 0777, true); + +// full path to the saved image including filename // +$destination = $default_path . basename( $_FILES[ 'Filedata' ][ 'name' ] ); + +// move the image into the specified directory // +if (move_uploaded_file($_FILES[ 'Filedata' ][ 'tmp_name' ], $destination)) { + echo "The file " . basename( $_FILES[ 'Filedata' ][ 'name' ] ) . " has been uploaded;"; +} else { + echo "FILE UPLOAD FAILED"; +} + + +?> diff --git a/pages/php-ofc-library/ofc_x_axis.php b/pages/php-ofc-library/ofc_x_axis.php new file mode 100644 index 0000000000..b268bdeb6e --- /dev/null +++ b/pages/php-ofc-library/ofc_x_axis.php @@ -0,0 +1,77 @@ +stroke = $stroke; + } + + function set_colours( $colour, $grid_colour ) + { + $this->set_colour( $colour ); + $this->set_grid_colour( $grid_colour ); + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_tick_height( $height ) + { + $tmp = 'tick-height'; + $this->$tmp = $height; + } + + function set_grid_colour( $colour ) + { + $tmp = 'grid-colour'; + $this->$tmp = $colour; + } + + // $o is a boolean + function set_offset( $o ) + { + $this->offset = $o?true:false; + } + + function set_steps( $steps ) + { + $this->steps = $steps; + } + + function set_3d( $val ) + { + $tmp = '3d'; + $this->$tmp = $val; + } + + function set_labels( $x_axis_labels ) + { + //$this->labels = $v; + $this->labels = $x_axis_labels; + } + + // + // helper function to make the examples + // simpler. + // + function set_labels_from_array( $a ) + { + $x_axis_labels = new x_axis_labels(); + $x_axis_labels->set_labels( $a ); + $this->labels = $x_axis_labels; + + if( isset( $this->steps ) ) + $x_axis_labels->set_steps( $this->steps ); + } + + function set_range( $min, $max ) + { + $this->min = $min; + $this->max = $max; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_x_axis_label.php b/pages/php-ofc-library/ofc_x_axis_label.php new file mode 100644 index 0000000000..11a79d7ede --- /dev/null +++ b/pages/php-ofc-library/ofc_x_axis_label.php @@ -0,0 +1,42 @@ +set_text( $text ); + $this->set_colour( $colour ); + $this->set_size( $size ); + $this->set_rotate( $rotate ); + } + + function set_text( $text ) + { + $this->text = $text; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_size( $size ) + { + $this->size = $size; + } + + function set_rotate( $rotate ) + { + $this->rotate = $rotate; + } + + function set_vertical() + { + $this->rotate = "vertical"; + } + + function set_visible() + { + $this->visible = true; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_x_axis_labels.php b/pages/php-ofc-library/ofc_x_axis_labels.php new file mode 100644 index 0000000000..9781025ad4 --- /dev/null +++ b/pages/php-ofc-library/ofc_x_axis_labels.php @@ -0,0 +1,34 @@ +steps = $steps; + } + + // + // An array of [x_axis_label or string] + // + function set_labels( $labels ) + { + $this->labels = $labels; + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_size( $size ) + { + $this->size = $size; + } + + function set_vertical() + { + $this->rotate = "vertical"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_x_legend.php b/pages/php-ofc-library/ofc_x_legend.php new file mode 100644 index 0000000000..f36c017f4c --- /dev/null +++ b/pages/php-ofc-library/ofc_x_legend.php @@ -0,0 +1,15 @@ +text = $text; + } + + function set_style( $css ) + { + $this->style = $css; + //"{font-size: 20px; color:#0000ff; font-family: Verdana; text-align: center;}"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_y_axis.php b/pages/php-ofc-library/ofc_y_axis.php new file mode 100644 index 0000000000..c8720a192d --- /dev/null +++ b/pages/php-ofc-library/ofc_y_axis.php @@ -0,0 +1,17 @@ +$tmp = $colour; + } + +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_y_axis_base.php b/pages/php-ofc-library/ofc_y_axis_base.php new file mode 100644 index 0000000000..5cfc61f15e --- /dev/null +++ b/pages/php-ofc-library/ofc_y_axis_base.php @@ -0,0 +1,56 @@ +stroke = $s; + } + + function set_tick_length( $val ) + { + $tmp = 'tick-length'; + $this->$tmp = $val; + } + + function set_colours( $colour, $grid_colour ) + { + $this->set_colour( $colour ); + $this->set_grid_colour( $grid_colour ); + } + + function set_colour( $colour ) + { + $this->colour = $colour; + } + + function set_grid_colour( $colour ) + { + $tmp = 'grid-colour'; + $this->$tmp = $colour; + } + + function set_range( $min, $max, $steps=1 ) + { + $this->min = $min; + $this->max = $max; + $this->set_steps( $steps ); + } + + function set_offset( $off ) + { + $this->offset = $off?1:0; + } + + function set_labels( $labels ) + { + $this->labels = $labels; + } + + function set_steps( $steps ) + { + $this->steps = $steps; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/ofc_y_axis_right.php b/pages/php-ofc-library/ofc_y_axis_right.php new file mode 100644 index 0000000000..1e0911194d --- /dev/null +++ b/pages/php-ofc-library/ofc_y_axis_right.php @@ -0,0 +1,6 @@ +text = $text; + } + + function set_style( $css ) + { + $this->style = $css; + //"{font-size: 20px; color:#0000ff; font-family: Verdana; text-align: center;}"; + } +} \ No newline at end of file diff --git a/pages/php-ofc-library/open-flash-chart-object.php b/pages/php-ofc-library/open-flash-chart-object.php new file mode 100644 index 0000000000..d3b64aa488 --- /dev/null +++ b/pages/php-ofc-library/open-flash-chart-object.php @@ -0,0 +1,109 @@ +'; + + if( !isset( $open_flash_chart_seqno ) ) + { + $open_flash_chart_seqno = 1; + $out[] = ''; + } + else + { + $open_flash_chart_seqno++; + $obj_id .= '_'. $open_flash_chart_seqno; + $div_name .= '_'. $open_flash_chart_seqno; + } + + if( $use_swfobject ) + { + // Using library for auto-enabling Flash object on IE, disabled-Javascript proof + $out[] = '
    '; + $out[] = ''; + $out[] = ''; + } + + return implode("\n",$out); +} +?> \ No newline at end of file diff --git a/pages/php-ofc-library/open-flash-chart.php b/pages/php-ofc-library/open-flash-chart.php new file mode 100644 index 0000000000..6147da5310 --- /dev/null +++ b/pages/php-ofc-library/open-flash-chart.php @@ -0,0 +1,138 @@ +title = new title( "Many data lines" ); + $this->elements = array(); + } + + function set_title( $t ) + { + $this->title = $t; + } + + function set_x_axis( $x ) + { + $this->x_axis = $x; + } + + function set_y_axis( $y ) + { + $this->y_axis = $y; + } + + function add_y_axis( $y ) + { + $this->y_axis = $y; + } + + function set_y_axis_right( $y ) + { + $this->y_axis_right = $y; + } + + function add_element( $e ) + { + $this->elements[] = $e; + } + + function set_x_legend( $x ) + { + $this->x_legend = $x; + } + + function set_y_legend( $y ) + { + $this->y_legend = $y; + } + + function set_bg_colour( $colour ) + { + $this->bg_colour = $colour; + } + + function set_radar_axis( $radar ) + { + $this->radar_axis = $radar; + } + + function set_tooltip( $tooltip ) + { + $this->tooltip = $tooltip; + } + + function toString() + { + if (function_exists('json_encode')) + { + return json_encode($this); + } + else + { + $json = new Services_JSON(); + return $json->encode( $this ); + } + } + + function toPrettyString() + { + return json_format( $this->toString() ); + } +} + + + +// +// there is no PHP end tag so we don't mess the headers up! +// \ No newline at end of file diff --git a/pages/schema.php b/pages/schema.php new file mode 100644 index 0000000000..ef06f230ff --- /dev/null +++ b/pages/schema.php @@ -0,0 +1,475 @@ + link to a class + */ +function MakeClassHLink($sClass) +{ + return "".MetaModel::GetName($sClass).""; +} + +/** + * Helper for this page -> link to a class + */ +function MakeRelationHLink($sRelCode) +{ + $sDec = MetaModel::GetRelationProperty($sRelCode, 'description'); + //$sVerbDown = MetaModel::GetRelationProperty($sRelCode, 'verb_down'); + //$sVerbUp = MetaModel::GetRelationProperty($sRelCode, 'verb_up'); + return "".$sRelCode.""; +} + +/** + * Helper for the global list and the details of a given class + */ +function DisplaySubclasses($oPage, $sClass) +{ + $aChildClasses = MetaModel::EnumChildClasses($sClass); + if (count($aChildClasses) != 0) + { + $oPage->add("
      \n"); + foreach($aChildClasses as $sClassName) + { + // Skip indirect childs, they will be handled somewhere else + if (MetaModel::GetParentPersistentClass($sClassName) == $sClass) + { + $oPage->add("
    • ".MakeClassHLink($sClassName)."\n"); + DisplaySubclasses($oPage, $sClassName); + $oPage->add("
    • \n"); + } + } + $oPage->add("
    \n"); + } +} + +/** + * Helper for the global list and the details of a given class + */ +function DisplayReferencingClasses($oPage, $sClass) +{ + $bSkipLinkingClasses = false; + $aRefs = MetaModel::EnumReferencingClasses($sClass, $bSkipLinkingClasses); + if (count($aRefs) != 0) + { + $oPage->add("
      \n"); + foreach ($aRefs as $sRemoteClass => $aRemoteKeys) + { + foreach ($aRemoteKeys as $sExtKeyAttCode) + { + $oPage->add("
    • ".MakeClassHLink($sRemoteClass)." by $sExtKeyAttCode
    • \n"); + } + } + $oPage->add("
    \n"); + } +} + +/** + * Helper for the global list and the details of a given class + */ +function DisplayLinkingClasses($oPage, $sClass) +{ + $bSkipLinkingClasses = false; + $aRefs = MetaModel::EnumLinkingClasses($sClass); + if (count($aRefs) != 0) + { + $oPage->add("
      \n"); + foreach ($aRefs as $sLinkClass => $aRemoteClasses) + { + foreach($aRemoteClasses as $sExtKeyAttCode => $sRemoteClass) + { + $oPage->add("
    • ".MakeClassHLink($sRemoteClass)." by ".MakeClassHLink($sLinkClass)."::$sExtKeyAttCode
    • \n"); + } + } + $oPage->add("
    \n"); + } +} + +/** + * Helper for the global list and the details of a given class + */ +function DisplayRelatedClassesBestInClass($oPage, $sClass, $iLevels = 20, &$aVisitedClasses = array(), $bSubtree = true) +{ + if ($iLevels <= 0) return; + $iLevels--; + + if (array_key_exists($sClass, $aVisitedClasses)) return; + $aVisitedClasses[$sClass] = true; + + if ($bSubtree) $oPage->add("
      \n"); + foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) + { + DisplayRelatedClassesBestInClass($oPage, $sParentClass, $iLevels, $aVisitedClasses, false); + } + ////$oPage->add("
      "); + foreach (MetaModel::EnumReferencedClasses($sClass) as $sExtKeyAttCode => $sRemoteClass) + { + $sVisited = (array_key_exists($sRemoteClass, $aVisitedClasses)) ? " ..." : ""; + if (MetaModel::GetAttributeOrigin($sClass, $sExtKeyAttCode) == $sClass) + { + $oPage->add("
    • $sClass| $sExtKeyAttCode =>".MakeClassHLink($sRemoteClass)."$sVisited
    • \n"); + DisplayRelatedClassesBestInClass($oPage, $sRemoteClass, $iLevels, $aVisitedClasses); + } + } + foreach (MetaModel::EnumReferencingClasses($sClass) as $sRemoteClass => $aRemoteKeys) + { + foreach ($aRemoteKeys as $sExtKeyAttCode) + { + $sVisited = (array_key_exists($sRemoteClass, $aVisitedClasses)) ? " ..." : ""; + $oPage->add("
    • $sClass| <=".MakeClassHLink($sRemoteClass)."::$sExtKeyAttCode$sVisited
    • \n"); + DisplayRelatedClassesBestInClass($oPage, $sRemoteClass, $iLevels, $aVisitedClasses); + } + } + ////$oPage->add("
      "); + if ($bSubtree) $oPage->add("
    \n"); +} + +/** + * Helper for the list of classes related to the given class + */ +function DisplayRelatedClasses($oPage, $sClass) +{ + $oPage->add("

    Childs

    \n"); + DisplaySubclasses($oPage, $sClass); + + $oPage->add("

    Pointed to by...

    \n"); + DisplayReferencingClasses($oPage, $sClass); + + $oPage->add("

    Linked to ...

    \n"); + DisplayLinkingClasses($oPage, $sClass); + + $oPage->add("

    ZE Graph ...

    \n"); + DisplayRelatedClassesBestInClass($oPage, $sClass, 4); +} + +/** + * Helper for the lifecycle details of a given class + */ +function DisplayLifecycle($oPage, $sClass) +{ + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + if (empty($sStateAttCode)) + { + $oPage->p("no lifecycle for this class"); + } + else + { + $aStates = MetaModel::EnumStates($sClass); + $aStimuli = MetaModel::EnumStimuli($sClass); + $oPage->add("\n"); + $oPage->add("

    Transitions

    \n"); + $oPage->add("
      \n"); + foreach ($aStates as $sStateCode => $aStateDef) + { + $sStateLabel = $aStates[$sStateCode]['label']; + $sStateDescription = $aStates[$sStateCode]['description']; + $oPage->add("
    • $sStateLabel ($sStateDescription)
    • \n"); + $oPage->add("
        \n"); + foreach(MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) + { + $sStimulusLabel = $aStimuli[$sStimulusCode]->Get('label'); + $sTargetStateLabel = $aStates[$aTransitionDef['target_state']]['label']; + if (count($aTransitionDef['actions']) > 0) + { + $sActions = " (".implode(', ', $aTransitionDef['actions']).")"; + } + else + { + $sActions = ""; + } + $oPage->add("
      • $sStimulusLabel => $sTargetStateLabel $sActions
      • \n"); + } + $oPage->add("
      \n"); + } + $oPage->add("
    \n"); + + $oPage->add("

    Attribute options

    \n"); + $oPage->add("
      \n"); + foreach ($aStates as $sStateCode => $aStateDef) + { + $sStateLabel = $aStates[$sStateCode]['label']; + $sStateDescription = $aStates[$sStateCode]['description']; + $oPage->add("
    • $sStateLabel ($sStateDescription)
    • \n"); + if (count($aStates[$sStateCode]['attribute_list']) > 0) + { + $oPage->add("
        \n"); + foreach($aStates[$sStateCode]['attribute_list'] as $sAttCode => $iOptions) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sAttLabel = $oAttDef->GetLabel(); + + $aOptions = array(); + if ($iOptions & OPT_ATT_HIDDEN) $aOptions[] = 'Hidden'; + if ($iOptions & OPT_ATT_READONLY) $aOptions[] = 'Read-only'; + if ($iOptions & OPT_ATT_MANDATORY) $aOptions[] = 'Mandatory'; + if ($iOptions & OPT_ATT_MUSTCHANGE) $aOptions[] = 'Must change'; + if ($iOptions & OPT_ATT_MUSTPROMPT) $aOptions[] = 'Must be proposed for changing'; + if (count($aOptions)) + { + $sOptions = implode(', ', $aOptions); + } + else + { + $sOptions = ""; + } + + $oPage->add("
      • $sAttLabel $sOptions
      • \n"); + } + $oPage->add("
      \n"); + } + else + { + $oPage->p("empty list"); + } + } + $oPage->add("
    \n"); + } +} + + +/** + * Display the list of classes from the business model + */ +function DisplayClassesList($oPage) +{ + $oPage->add("

    iTop objects schema

    \n"); + + $oPage->add("
      \n"); + foreach(MetaModel::EnumCategories() as $sCategory) + { + if (empty($sCategory)) continue; // means 'all' -> skip + + $sClosed = ($sCategory == 'bizmodel') ? '' : ' class="closed"'; + $oPage->add("Category $sCategory\n"); + + $oPage->add("
        \n"); + foreach(MetaModel::GetClasses($sCategory) as $sClassName) + { + if (MetaModel::IsStandaloneClass($sClassName)) + { + $oPage->add("
      • ".MakeClassHLink($sClassName)."
      • \n"); + } + else if (MetaModel::IsRootClass($sClassName)) + { + $oPage->add("
      • ".MakeClassHLink($sClassName)."\n"); + DisplaySubclasses($oPage, $sClassName); + $oPage->add("
      • \n"); + } + } + $oPage->add("
      \n"); + + $oPage->add("\n"); + } + $oPage->add("
    \n"); + + + $oPage->add("

    Relationships

    \n"); + + $oPage->add("
      \n"); + foreach (MetaModel::EnumRelations() as $sRelCode) + { + $oPage->add("
    • ".MakeRelationHLink($sRelCode)."\n"); + $oPage->add("
        \n"); + foreach (MetaModel::EnumRelationProperties($sRelCode) as $sProp => $sValue) + { + $oPage->add("
      • $sProp: ".htmlentities($sValue)."
      • \n"); + } + $oPage->add("
      \n"); + $oPage->add("
    • \n"); + } + $oPage->add("
    \n"); + $oPage->add_ready_script('$("#ClassesList").treeview();'); + $oPage->add_ready_script('$("#ClassesRelationships").treeview();'); +} + +/** + * Display the details of a given class of objects + */ +function DisplayClassDetails($oPage, $sClass) +{ + $oPage->p("

    $sClass


    \n".MetaModel::GetClassDescription($sClass)."
    \n"); + $oPage->p("

    Class Hierarchy

    "); + $oPage->p("[All classes]"); + // List the parent classes + $sParent = MetaModel::GetParentPersistentClass($sClass); + $aParents = array(); + $aParents[] = $sClass; + while($sParent != "" && $sParent != 'cmdbAbstractObject') + { + $aParents[] = $sParent; + $sParent = MetaModel::GetParentPersistentClass($sParent); + } + $iIndex = count($aParents); + $sSpace =""; + $oPage->add("
      "); + while ($iIndex > 0) + { + $iIndex--; + $oPage->add("
    • ".MakeClassHLink($aParents[$iIndex])."\n"); + $oPage->add("
        \n"); + } + for($iIndex = 0; $iIndex < count($aParents); $iIndex++) + { + $oPage->add("
      \n
    • \n"); + } + $oPage->add("
    \n"); + $oPage->add_ready_script('$("#ClassHierarchy").treeview();'); + $oPage->p(''); + $oPage->AddTabContainer('details'); + $oPage->SetCurrentTabContainer('details'); + // List the attributes of the object + $aDetails = array(); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $sValue = "External key to ".MakeClassHLink($oAttDef->GetTargetClass()); + } + else + { + $sValue = $oAttDef->GetDescription(); + } + $sType = $oAttDef->GetType().' ('.$oAttDef->GetTypeDesc().')'; + $sOrigin = MetaModel::GetAttributeOrigin($sClass, $sAttCode); + $sAllowedValues = ""; + $oAllowedValuesDef = $oAttDef->GetValuesDef(); + $sMoreInfo = ""; + if (is_subclass_of($oAttDef, 'AttributeDBFieldVoid')) + { + $aMoreInfo = array(); + $aMoreInfo[] = "Column: ".$oAttDef->GetSQLExpr().""; + $aMoreInfo[] = "Default: '".$oAttDef->GetDefaultValue()."'"; + $aMoreInfo[] = $oAttDef->IsNullAllowed() ? "Null allowed" : "Null NOT allowed"; + //$aMoreInfo[] = $oAttDef->DBGetUsedFields(); + $sMoreInfo .= implode(', ', $aMoreInfo); + } + + if (is_object($oAllowedValuesDef)) $sAllowedValues = $oAllowedValuesDef->GetValuesDescription(); + else $sAllowedValues = ''; + + $aDetails[] = array('code' => $oAttDef->GetCode(), 'type' => $sType, 'origin' => $sOrigin, 'label' => $oAttDef->GetLabel(), 'description' => $sValue, 'values' => $sAllowedValues, 'moreinfo' => $sMoreInfo); + } + $oPage->SetCurrentTab('Attributes'); + $aConfig = array( 'code' => array('label' => 'Attribute code', 'description' => 'Code of this attribute'), + 'label' => array('label' => 'Label', 'description' => 'Label of this attribute'), + 'type' => array('label' => 'Type', 'description' => 'Data type of this attribute'), + 'origin' => array('label' => 'Origin', 'description' => 'The base class for this attribute'), + 'description' => array('label' => 'Description', 'description' => 'Description of this attribute'), + 'values' => array('label' => 'Allowed Values', 'description' => 'Restrictions on the possible values for this attribute'), + 'moreinfo' => array('label' => 'More info', 'description' => 'More info for the fields related to a Database field'), + ); + $oPage->table($aConfig, $aDetails); + + // List the search criteria for this object + $aDetails = array(); + foreach (MetaModel::GetClassFilterDefs($sClass) as $sFilterCode => $oFilterDef) + { + $aOpDescs = array(); + foreach ($oFilterDef->GetOperators() as $sOpCode => $sOpDescription) + { + $sIsTheLooser = ($sOpCode == $oFilterDef->GetLooseOperator()) ? " (loose search)" : ""; + $aOpDescs[] = "$sOpCode ($sOpDescription)$sIsTheLooser"; + } + $aDetails[] = array( 'code' => $sFilterCode, 'description' => $oFilterDef->GetLabel(),'operators' => implode(" / ", $aOpDescs)); + } + $oPage->SetCurrentTab('Search criteria'); + $aConfig = array( 'code' => array('label' => 'Filter code', 'description' => 'Code of this search criteria'), + 'description' => array('label' => 'Description', 'description' => 'Description of this search criteria'), + 'operators' => array('label' => 'Available operators', 'description' => 'Possible operators for this search criteria') + ); + $oPage->table($aConfig, $aDetails); + + $oPage->SetCurrentTab('Child classes'); + DisplaySubclasses($oPage, $sClass); + + $oPage->SetCurrentTab('Referencing classes'); + DisplayReferencingClasses($oPage, $sClass); + + $oPage->SetCurrentTab('Related classes'); + DisplayRelatedClasses($oPage, $sClass); + + $oPage->SetCurrentTab('Lifecycle'); + DisplayLifecycle($oPage, $sClass); + + $oPage->SetCurrentTab(); + $oPage->SetCurrentTabContainer(); +} + + +/** + * Display the details of a given relation (e.g. "impacts") + */ +function DisplayRelationDetails($oPage, $sRelCode) +{ + $sDesc = MetaModel::GetRelationProperty($sRelCode, 'description'); + $sVerbDown = MetaModel::GetRelationProperty($sRelCode, 'verb_down'); + $sVerbUp = MetaModel::GetRelationProperty($sRelCode, 'verb_up'); + $oPage->add("

    Relation $sRelCode ($sDesc)

    "); + $oPage->p("Down: $sVerbDown"); + $oPage->p("Up: $sVerbUp"); + + $oPage->add("
      \n"); + foreach(MetaModel::GetClasses() as $sClass) + { + $aRelQueries = MetaModel::EnumRelationQueries($sClass, $sRelCode); + if (count($aRelQueries) > 0) + { + $oPage->add("
    • class ".MakeClassHLink($sClass)."\n"); + $oPage->add("
        \n"); + foreach ($aRelQueries as $sRelKey => $aQuery) + { + $sQuery = $aQuery['sQuery']; + $bPropagate = $aQuery['bPropagate'] ? "Propagate" : "Do not propagate"; + $iDistance = $aQuery['iDistance']; + + $oPage->add("
      • $sRelKey: $bPropagate ($iDistance) ".DBObjectSearch::SibuSQLAsHtml($sQuery)."
      • \n"); + } + $oPage->add("
      \n"); + $oPage->add("
    • \n"); + } + } + $oPage->add_ready_script('$("#RelationshipDetails").treeview();'); +} + + +require_once('../application/loginwebpage.class.inc.php'); +login_web_page::DoLogin(); // Check user rights and prompt if needed + +// Display the menu on the left +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +$iActiveNodeId = utils::ReadParam('menu', -1); +$currentOrganization = utils::ReadParam('org_id', 1); +$operation = utils::ReadParam('operation', ''); + +$oPage = new iTopWebPage("iTop objects schema", $currentOrganization); +$oPage->no_cache(); + +$operation = utils::ReadParam('operation', ''); + +switch($operation) +{ + case 'details_class': + $sClass = utils::ReadParam('class', 'logRealObject'); + DisplayClassDetails($oPage, $sClass); + break; + + case 'details_relation': + $sRelCode = utils::ReadParam('relcode', ''); + DisplayRelationDetails($oPage, $sRelCode); + break; + + case 'details': + $oPage->p('operation=details has been deprecated, please use details_class'); + break; + case 'list': + default: + DisplayClassesList($oPage); +} + +$oPage->output(); +?> diff --git a/pages/sibusql.php b/pages/sibusql.php new file mode 100644 index 0000000000..956d28d4de --- /dev/null +++ b/pages/sibusql.php @@ -0,0 +1,57 @@ +FYI: '$sClearText'
      \n"; + $oFilter = DBObjectSearch::unserialize($sExpression); + $sExpression = $oFilter->ToOQL(); + exit; +} +else +{ + // leave $sExpression as is +} + +$oP->add('
      '."\n"); +$oP->add('Expression to evaluate:
      '."\n"); +$oP->add(''."

      Example:
      SELECT bizPerson AS B WHERE B.name LIKE '%A%'

      \n"); +$oP->add(''."\n"); +$oP->add('
      '."\n"); + +if (!empty($sExpression)) +{ + $oFilter = DBObjectSearch::FromOQL($sExpression); + if ($oFilter) + { + $oP->p('Query expression: '.$oFilter->ToOQL()); + $oP->p('Serialized filter: '.$oFilter->serialize()); + + $oSet = new CMDBObjectSet($oFilter); + $oP->p('The query returned '.$oSet->count().' results(s)'); + cmdbAbstractObject::DisplaySet($oP, $oSet); + } +} + +$oP->output(); +?> diff --git a/pages/test.php b/pages/test.php new file mode 100644 index 0000000000..e9e9bbea40 --- /dev/null +++ b/pages/test.php @@ -0,0 +1,126 @@ +Missing mandatory argument $sName

      "; + exit; + } + return $value; +} + +function IsAValidTestClass($sClassName) +{ + // Must be a child of TestHandler + // + if (!is_subclass_of($sClassName, 'TestHandler')) return false; + + // Must not be abstract + // + $oReflectionClass = new ReflectionClass($sClassName); + if (!$oReflectionClass->isInstantiable()) return false; + + return true; +} + +function DisplayEvents($aEvents, $sTitle) +{ + echo "

      $sTitle

      \n"; + if (count($aEvents) > 0) + { + echo "
        \n"; + foreach ($aEvents as $sEvent) + { + echo "
      • $sEvent
      • \n"; + } + echo "
      \n"; + } + else + { + echo "

      none

      \n"; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Main +/////////////////////////////////////////////////////////////////////////////// + + +require_once('../application/utils.inc.php'); +require_once('../core/test.class.inc.php'); +require_once('testlist.inc.php'); + +require_once('../core/cmdbobject.class.inc.php'); + +$sTodo = utils::ReadParam("todo", ""); +if ($sTodo == '') +{ + // Show the list of tests + // + echo "

      Existing tests

      \n"; + echo "
        \n"; + foreach (get_declared_classes() as $sClassName) + { + if (!IsAValidTestClass($sClassName)) continue; + + $sName = call_user_func(array($sClassName, 'GetName')); + $sDescription = call_user_func(array($sClassName, 'GetDescription')); + echo "
      • $sName ($sDescription)\n"; +} +else if ($sTodo == 'exec') +{ + // Execute a test + // + $sTestClass = ReadMandatoryParam("testid"); + + if (!IsAValidTestClass($sTestClass)) + { + echo "

        Wrong value for testid, expecting a valid class name

        \n"; + } + else + { + $oTest = new $sTestClass(); + echo "

        Testing: ".$oTest->GetName()."

        \n"; + $bRes = $oTest->Execute(); + } + +/* +MyHelpers::var_dump_html($oTest->GetResults()); +MyHelpers::var_dump_html($oTest->GetWarnings()); +MyHelpers::var_dump_html($oTest->GetErrors()); +*/ + + if ($bRes) + { + echo "

        Success :-)

        \n"; + DisplayEvents($oTest->GetResults(), 'Results'); + } + else + { + echo "

        Failure :-(

        \n"; + } + DisplayEvents($oTest->GetErrors(), 'Errors'); + DisplayEvents($oTest->GetWarnings(), 'Warnings'); + + // Render the output + // + echo "

        Actual output

        \n"; + echo "
        \n"; + echo $oTest->GetOutput(); + echo "
        \n"; +} +else +{ +} + + +?> diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php new file mode 100644 index 0000000000..947d484891 --- /dev/null +++ b/pages/testlist.inc.php @@ -0,0 +1,1132 @@ +new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new BinaryExpression(new FieldExpression('column1', 'myTableAlias'), 'LIKE', new ScalarExpression('trash')), + $aFullTextNeedles = array('column1'), + $bToDelete = false, + $aValues = array() + ); + $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); + + $oSubQuery1 = new SQLQuery( + $sTable = 'myTable1', + $sTableAlias = 'myTable1Alias', + $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oSubQuery2 = new SQLQuery( + $sTable = 'myTable2', + $sTableAlias = 'myTable2Alias', + $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oQuery->AddInnerJoin($oSubQuery1, 'column1', 'column1_1'); + $oQuery->AddLeftJoin($oSubQuery2, 'column2', 'column2_2'); + + $oQuery->DisplayHtml(); + $oQuery->RenderDelete(); + $oQuery->RenderUpdate(); + echo '

        '.$oQuery->RenderSelect().'

        '; + $oQuery->RenderSelect(array('column1')); + $oQuery->RenderSelect(array('column1', 'column2')); + } +} + +class TestOQLParser extends TestFunction +{ + static public function GetName() {return 'Check OQL parsing';} + static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + $oOql = new OqlInterpreter($sQuery); + try + { + $oTrash = $oOql->ParseQuery(); + MyHelpers::var_dump_html($oTrash, true); + } + catch (OQLException $OqlException) + { + if ($bIsCorrectQuery) + { + echo "

        More info on this unexpected failure:
        ".$OqlException->getHtmlDesc()."

        \n"; + throw $OqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

        More info on this expected failure:
        ".$OqlException->getHtmlDesc()."

        \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if ($bIsCorrectQuery) + { + return true; + } + else + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + } + + protected function TestQuery($sQuery, $bIsCorrectQuery) + { + if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) + { + return false; + } + return true; + } + + public function DoExecute() + { + $aQueries = array( + 'SELECT toto' => true, + 'SELECT toto WHERE toto.a = 1' => true, + 'SELECT toto WHERE toto.a=1' => true, + 'SELECT toto WHERE toto.a = "1"' => true, + 'SELECT toto WHHHERE toto.a = "1"' => false, + 'SELECT toto WHERE toto.a == "1"' => false, + 'SELECT toto WHERE toto.a % 1' => false, + //'SELECT toto WHERE toto.a LIKE 1' => false, + 'SELECT toto WHERE toto.a like \'arg\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, + + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE ""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, + "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, + + 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, + + //'SELECT toto WHERE toto.a > \'asd\'' => false, + 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, + + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, + + 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, + ); + + $iErrors = 0; + + foreach($aQueries as $sQuery => $bIsCorrectQuery) + { + $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; + echo "

        Testing query: $sQuery ($sIsOk)

        \n"; + $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); + if (!$bRet) $iErrors++; + } + + return ($iErrors == 0); + } +} + + +class TestGenericItoMyModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} +} + +class TestGenericItopBigModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-itopv06.php';} +} + +class TestUserRightsMatrixItop extends TestUserRights +{ + static public function GetName() + { + return 'User rights test on user rights matrix'; + } + + static public function GetDescription() + { + return 'blah blah blah'; + } + + public function DoPrepare() + { + parent::DoPrepare(); + MetaModel::Startup('../config-test-itopv06.php'); + } + + protected function DoExecute() + { + $sUser = 'Romain'; + echo "

        Totor: ".(UserRights::Login('Totor', 'toto') ? 'ok' : 'NO')."

        \n"; + echo "

        Romain: ".(UserRights::Login('Romain', 'toto') ? 'ok' : 'NO')."

        \n"; + echo "

        User: ".UserRights::GetUser()."

        \n"; + echo "

        On behalf of...".UserRights::GetRealUser()."

        \n"; + + echo "

        Denis (impersonate) : ".(UserRights::Impersonate('Denis', 'tutu') ? 'ok' : 'NO')."

        \n"; + echo "

        User: ".UserRights::GetUser()."

        \n"; + echo "

        On behalf of...".UserRights::GetRealUser()."

        \n"; + + UserRights::GetFilter('bizOrganization'); // returns a filter object + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); + echo "

        IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

        \n"; + echo "

        IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

        \n"; + echo "

        IsActionAllowedOnAttribute...".(UserRights::IsActionAllowedOnAttribute('bizOrganization', 'myattribute', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

        \n"; + return true; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +class TestMyBizModel extends TestBizModel +{ + static public function GetName() + { + return 'A series of tests on a weird business model'; + } + + static public function GetDescription() + { + return 'Attempts various operations and build complex queries'; + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} + + function test_linksinfo() + { + echo "

        Enum links

        "; + MyHelpers::var_dump_html(MetaModel::EnumReferencedClasses("cmdbTeam")); + MyHelpers::var_dump_html(MetaModel::EnumReferencingClasses("Organization")); + + MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses()); + MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses("cmdbContact")); + MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses("cmdWorkshop")); + MyHelpers::var_dump_html(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); + } + + function test_list_attributes() + { + echo "

        List attributes

        "; + foreach(MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode=>$oAttDef) + { + echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
        \n"; + } + } + + function test_search() + { + echo "

        Two searches

        "; + $oFilterAllDevs = new DBObjectSearch("cmdbTeam"); + $oAllDevs = new DBObjectSet($oFilterAllDevs); + + echo "Found ".$oAllDevs->Count()." items.
        \n"; + while ($oDev = $oAllDevs->Fetch()) + { + $aValues = array(); + foreach(MetaModel::GetAttributesList($oAllDevs->GetClass()) as $sAttCode) + { + $aValues[] = MetaModel::GetLabel(get_class($oDev), $sAttCode)." (".MetaModel::GetDescription(get_class($oDev), $sAttCode).") = ".$oDev->GetAsHTML($sAttCode); + } + echo $oDev->GetKey()." => ".implode(", ", $aValues)."
        \n"; + } + + // a second one + $oMyFilter = new DBObjectSearch("cmdbContact"); + //$oMyFilter->AddCondition("name", "aii", "Finishes with"); + $oMyFilter->AddCondition("name", "aii"); + $this->search_and_show_list($oMyFilter); + + } + + function test_reload() + { + echo "

        Reload

        "; + $team = MetaModel::GetObject("cmdbContact", "2"); + echo "Chargement de l'attribut headcount: {$team->Get("headcount")}
        \n"; + MyHelpers::var_dump_html($team); + } + + function test_setattribute() + { + echo "

        Set attribute and update

        "; + $team = MetaModel::GetObject("cmdbTeam", "2"); + $team->Set("headcount", rand(1,1000)); + $team->Set("email", "Luis ".rand(9,250)); + MyHelpers::var_dump_html($team->ListChanges()); + echo "New headcount = {$team->Get("headcount")}
        \n"; + echo "Computed name = {$team->Get("name")}
        \n"; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + //MetaModel::StartDebugQuery(); + $team->DBUpdateTracked($oMyChange); + //MetaModel::StopDebugQuery(); + + echo "

        Check the modified team

        "; + $oTeam = MetaModel::GetObject("cmdbTeam", "2"); + MyHelpers::var_dump_html($oTeam); + } + function test_newobject() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_newobject / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + echo "

        Create a new object (team)

        "; + $oNewTeam = MetaModel::NewObject("cmdbTeam"); + $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); + $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); + $oNewTeam->Set("email", null); + $oNewTeam->Set("owner", "ITOP"); + $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
        "; + echo "

        Delete team #$iId

        "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
        "; + MyHelpers::var_dump_html($oTeam); + } + + + function test_updatecolumn() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_updatecolumn / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + $sNewEmail = "updatecol".rand(9,250)."@quedlaballe.com"; + echo "

        Update a the email: set to '$sNewEmail'

        "; + $oMyFilter = new DBObjectSearch("cmdbContact"); + $oMyFilter->AddCondition("name", "o", "Contains"); + + echo "Candidates before:
        "; + $this->search_and_show_list($oMyFilter); + + MetaModel::BulkUpdateTracked($oMyChange, $oMyFilter, array("email" => $sNewEmail)); + + echo "Candidates after:
        "; + $this->search_and_show_list($oMyFilter); + } + + function test_error() + { + trigger_error("Stop requested", E_USER_ERROR); + } + + function test_changetracking() + { + echo "

        Create a change

        "; + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + echo "Created new change: $iChangeId
        "; + MyHelpers::var_dump_html($oMyChange); + + echo "

        Create a new object (team)

        "; + $oNewTeam = MetaModel::NewObject("cmdbTeam"); + $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); + $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); + $oNewTeam->Set("email", null); + $oNewTeam->Set("owner", "ITOP"); + $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
        "; + echo "

        Delete team #$iId

        "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
        "; + MyHelpers::var_dump_html($oTeam); + } + + function test_zlist() + { + echo "

        Test ZLists

        "; + $aZLists = MetaModel::EnumZLists(); + foreach ($aZLists as $sListCode) + { + $aListInfos = MetaModel::GetZListInfo($sListCode); + echo "

        List '".$sListCode."' (".$aListInfos["description"].") of type '".$aListInfos["type"]."'

        \n"; + + foreach (MetaModel::GetSubclasses("cmdbObjectHomeMade") as $sKlass) + { + $aItems = MetaModel::GetZListItems($sKlass, $sListCode); + if (count($aItems) == 0) continue; + + echo "$sKlass - $sListCode : {".implode(", ", $aItems)."}
        \n"; + } + } + + echo "

        IsAttributeInZList()...

        "; + echo "Liens_entre_contacts_et_workshop::ws_info in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "ws_info") ? "yes" : "no")."
        \n"; + echo "Liens_entre_contacts_et_workshop::toworkshop in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "toworkshop") ? "yes" : "no")."
        \n"; + + } + + function test_SibuSQL() + { + echo "

        Simple But Structured Query Language

        "; + + $oMyFilter = new DBObjectSearch("cmdbContact"); + echo "Tous les contacts: ".$oMyFilter->ToSibuSQL()."
        \n"; + $oNewFilter = DBObjectSearch::FromSibuSQL($oMyFilter->ToSibuSQL()); + echo "En passant par un filtre, ca revient en : ".$oNewFilter->ToSibuSQL()."
        \n"; + $this->search_and_show_list($oNewFilter); + + $sFilterDesc = "cmdbContact: name Begins with '$[debutnom:as:debut du nom]' AND ownername NotLike $[ddd::]"; + echo "Construction d'un filtre a partir de sa description en SibuSQL: $sFilterDesc
        \n"; + + MyHelpers::var_dump_html(DBObjectSearch::ListSibusQLParams($sFilterDesc)); + $oNewFilter = DBObjectSearch::FromSibuSQL($sFilterDesc, array('ddd'=>123)); + echo "Ca revient en: ".$oNewFilter->ToSibuSQL(); + } + + function test_pkey() + { + echo "

        Test search on pkey

        "; + $sExpr1 = "cmdbContact: pkey IN {40, 42}"; + $sExpr2 = "cmdbContact: pkey NOTIN {40, 42}"; + $this->search_and_show_list_from_sibusql($sExpr1); + $this->search_and_show_list_from_sibusql($sExpr2); + + echo "Et maintenant, on fusionne....
        \n"; + $oSet1 = new CMDBObjectSet(DBObjectSearch::FromSibuSQL($sExpr1)); + $oSet2 = new CMDBObjectSet(DBObjectSearch::FromSibuSQL($sExpr2)); + $oIntersect = $oSet1->CreateIntersect($oSet2); + $oDelta = $oSet1->CreateDelta($oSet2); + + $oMerge = clone $oSet1; + $oMerge->Merge($oSet2); + $oMerge->Merge($oSet2); + + echo "Set1 - Found ".$oSet1->Count()." items.
        \n"; + echo "Set2 - Found ".$oSet2->Count()." items.
        \n"; + echo "Intersect - Found ".$oIntersect->Count()." items.
        \n"; + echo "Delta - Found ".$oDelta->Count()." items.
        \n"; + echo "Merge - Found ".$oMerge->Count()." items.
        \n"; + //$this->show_list($oObjSet); + } + + function test_relations() + { + echo "

        Test relations

        "; + + //MyHelpers::var_dump_html(MetaModel::EnumRelationQueries("cmdbObjectHomeMade", "Potes")); + MyHelpers::var_dump_html(MetaModel::EnumRelationQueries("cmdbContact", "Potes")); + + $iMaxDepth = 9; + echo "Max depth = $iMaxDepth
        \n"; + + $oObj = MetaModel::GetObject("cmdbContact", 18); + $aRels = $oObj->GetRelatedObjects("Potes", $iMaxDepth); + echo $oObj->Get('name')." has some 'Potes'...
        \n"; + foreach ($aRels as $sClass => $aObjs) + { + echo "$sClass, count = ".count($aObjs)." => ".implode(', ', array_keys($aObjs))."
        \n"; + $oObjectSet = CMDBObjectSet::FromArray($sClass, $aObjs); + $this->show_list($oObjectSet); + } + + echo "

        Test relations - same results, by the mean of a SibuSQL

        "; + $this->search_and_show_list_from_sibusql("cmdbContact: RELATED (Potes, $iMaxDepth) TO (cmdbContact: pkey = 18)"); + + } + + function test_linkedset() + { + echo "

        Linked set attributes

        \n"; + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
        Current workshops
        \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + echo "
        Setting workshops
        \n"; + $oNewLink = new cmdbLiens(); + $oNewLink->Set('toworkshop', 2); + $oNewLink->Set('function', 'mafonctioooon'); + $oNewLink->Set('a1', 'tralala1'); + $oNewLink->Set('a2', 'F7M'); + $oSetWorkshops = CMDBObjectSet::FromArray("cmdbLiens", array($oNewLink)); + $oObj->Set("myworkshops", $oSetWorkshops); + $this->show_list($oSetWorkshops); + + echo "
        New workshops
        \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_linkedset / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
        After the write
        \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + } + + function test_object_lifecycle() + { + echo "

        Test object lifecycle

        "; + + + MyHelpers::var_dump_html(MetaModel::GetStateAttributeCode("cmdbContact")); + MyHelpers::var_dump_html(MetaModel::EnumStates("cmdbContact")); + MyHelpers::var_dump_html(MetaModel::EnumStimuli("cmdbContact")); + foreach(MetaModel::EnumStates("cmdbContact") as $sStateCode => $aStateDef) + { + echo "

        Transition from $sStateCode

        \n"; + MyHelpers::var_dump_html(MetaModel::EnumTransitions("cmdbContact", $sStateCode)); + } + + $oObj = MetaModel::GetObject("cmdbContact", 18); + echo "Current state: ".$oObj->GetState()."... let's go to school..."; + MyHelpers::var_dump_html($oObj->EnumTransitions()); + $oObj->ApplyStimulus("toschool"); + echo "New state: ".$oObj->GetState()."... let's get older..."; + MyHelpers::var_dump_html($oObj->EnumTransitions()); + $oObj->ApplyStimulus("raise"); + echo "New state: ".$oObj->GetState()."... let's try to go further... (should give an error)"; + MyHelpers::var_dump_html($oObj->EnumTransitions()); + $oObj->ApplyStimulus("raise"); // should give an error + } + + + protected function DoExecute() + { +// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + //$this->test_linksinfo(); + //$this->test_list_attributes(); + //$this->test_search(); + //$this->test_reload(); + //$this->test_newobject(); + $this->test_setattribute(); + //$this->test_updatecolumn(); + //$this->test_error(); + //$this->test_changetracking(); + $this->test_zlist(); + $this->test_SibuSQL(); + //$this->test_pkey(); + $this->test_relations(); + $this->test_linkedset(); + $this->test_object_lifecycle(); + return true; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +class TestQueriesOnFarm extends TestBizModel +{ + static public function GetName() + { + return 'Farm test'; + } + + static public function GetDescription() + { + return 'A series of tests on the farm business model (SQL generation)'; + } + + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected $m_oChange; + protected function ObjectToDB(CMDBObject $oNew) + { + if (!isset($this->m_oChange)) + { + new CMDBChange(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Administrator"); + $iChangeId = $oMyChange->DBInsertNoReload(); + $this->m_oChange = $oMyChange; + } + $iId = $oNew->DBInsertTrackedNoReload($this->m_oChange); + return $iId; +} + + protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) + { + $oNew = MetaModel::NewObject('Mammal'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('name', $sName); + $oNew->Set('height', $iHeight); + $oNew->Set('birth', $sBirth); + return $this->ObjectToDB($oNew); + } + + protected function InsertBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId) + { + $oNew = MetaModel::NewObject('Bird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + return $this->ObjectToDB($oNew); + } + + protected function InsertFlyingBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $iFlyingSpeed) + { + $oNew = MetaModel::NewObject('FlyingBird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('flyingspeed', $iFlyingSpeed); + return $this->ObjectToDB($oNew); + } + + private function InsertGroup($sName, $iLeaderId) + { + $oNew = MetaModel::NewObject('Group'); + $oNew->Set('name', $sName); + $oNew->Set('leader', $iLeaderId); + $iId = $oNew->DBInsertNoReload(); + return $iId; + } + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + if ($bIsCorrectQuery) + { + echo "

        $sQuery

        \n"; + } + else + { + echo "

        $sQuery

        \n"; + } + try + { + //$oOql = new OqlInterpreter($sQuery); + //$oTrash = $oOql->ParseQuery(); + //MyHelpers::var_dump_html($oTrash, true); + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + catch (OQLException $oOqlException) + { + if ($bIsCorrectQuery) + { + echo "

        More info on this unexpected failure:
        ".$oOqlException->getHtmlDesc()."

        \n"; + throw $oOqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

        More info on this expected failure:\n"; + echo "

          \n"; + echo "
        • ".get_class($oOqlException)."
        • \n"; + echo "
        • ".$oOqlException->getMessage()."
        • \n"; + echo "
        • ".$oOqlException->getHtmlDesc()."
        • \n"; + echo "
        \n"; + echo "

        \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if (!$bIsCorrectQuery) + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + echo "

        To OQL: ".$oMyFilter->ToOQL()."

        "; + + $this->search_and_show_list($oMyFilter); + + //echo "

        first pass

        \n"; + //MyHelpers::var_dump_html($oMyFilter, true); + $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + //echo "

        second pass

        \n"; + //MyHelpers::var_dump_html($oMyFilter, true); + //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + + $sSerialize = $oMyFilter->serialize(); + echo "

        Serialized:$sSerialize

        \n"; + $oFilter2 = DBObjectSearch::unserialize($sSerialize); + try + { + $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); + } + catch (Exception $e) + { + echo "

        Could not compute the query after unserialize

        \n"; + echo "

        Query 1: $sQuery1

        \n"; + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + throw $e; + } + //if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same! + if ($sQuery1 != $sQuery2) + { + echo "

        serialize/unserialize mismatch :-(

        \n"; + MyHelpers::var_cmp_html($sQuery1, $sQuery2); + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + return false; + } + return true; + } + + protected function DoExecute() + { +// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + echo "

        Create protagonists...

        "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + // Benchmarking + // + if (false) + { + define ('COUNT_BENCHMARK', 10); + echo "

        Parsing a long query, ".COUNT_BENCHMARK." times

        "; + $sQuery = "SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR) AND Dad.height * 2 <= ROUND(TO_DAYS(Dad.birth) / (3 + 1) * 5 - 3)"; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + echo "

        Mean time by op: $fParsingDuration

        "; + } + + echo "

        Test queries...

        "; + + $aQueries = array( + 'SELECT Animal' => true, + 'SELECT Animal WHERE Animal.pkey = 1' => false, + 'SELECT Animal WHERE Animal.id = 1' => true, + 'SELECT Aniiimal' => false, + 'SELECTe Animal' => false, + 'SELECT * FROM Animal' => false, + 'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE espece = \'human\'' => false, + 'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false, + 'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true, + 'SELECT Animal AS zoo WHERE zoo.species NOT IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false, + 'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true, + 'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true, + 'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true, + 'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true, + 'SELECT Mammal AS m WHERE m.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR)' => true, + 'SELECT Mammal AS m WHERE m.birth > DATE_SUB(NOW(), INTERVAL 2000 DAY)' => true, + 'SELECT Mammal AS m WHERE (TO_DAYS(NOW()) - TO_DAYS(m.birth)) > 2000' => true, + 'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true, + 'SELECT Mammal AS m WHERE (1 + 2' => false, + 'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true, + 'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true, + 'SELECT Mammal AS m WHERE 1/0' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, + 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, + 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.masterchief = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.pkey' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true, + 'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false, + 'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false, + 'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true, + 'SELECT Group AS G WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, + ); + //$aQueries = array( + // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + //); + foreach($aQueries as $sQuery => $bIsCorrect) + { + $this->CheckQuery($sQuery, $bIsCorrect); + } + return true; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestBulkChangeOnFarm extends TestBizModel +{ + static public function GetName() + { + return 'Farm test - data load'; + } + + static public function GetDescription() + { + return 'Bulk load'; + } + + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected function DoExecute() + { +// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + + $oParser = new CSVParser("#denomination,hauteur,age + suzy,123,2009-01-01 + chita,456, + "); + $oParser->SetSeparator(','); + $aData = $oParser->ToArray(array('_name', '_height', '_birth')); + MyHelpers::var_dump_html($aData); + + $oBulk = new BulkChange( + 'Mammal', + $aData, + array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), + array('name'), + array() + ); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Testor"); + $iChangeId = $oMyChange->DBInsert(); +// echo "Created new change: $iChangeId
        "; + + echo "

        Planned for loading...

        "; + $aRes = $oBulk->Process(); + print_r($aRes); + echo "

        Go for loading...

        "; + $aRes = $oBulk->Process($oMyChange); + print_r($aRes); + + return true; + + $oRawData = array( + 'Mammal', + array('species', 'sex', 'speed', 'mother', 'father', 'name', 'height', 'birth'), + "human,male,23,0,0,romulus,192,1971 + human,male,23,0,0,remus,154,-50 + human,male,23,0,0,julius,160,-49 + human,female,23,0,0,cleopatra,142,-50 + pig,female,23,0,0,confucius,50,2003" + ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Benchmark queries +/////////////////////////////////////////////////////////////////////////// + +class TestItopEfficiency extends TestBizModel +{ + static public function GetName() + { + return 'Itop - benchmark'; + } + + static public function GetDescription() + { + return 'Measure time to perform the queries'; + } + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoBenchmark($sOqlQuery) + { + echo "

        Testing query: $sOqlQuery

        "; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oFilter = DBObjectSearch::FromOQL($sOqlQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $sSQL = MetaModel::MakeSelectQuery($oFilter); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fBuildDuration = $fDuration / COUNT_BENCHMARK; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $res = CMDBSource::Query($sSQL); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fQueryDuration = $fDuration / COUNT_BENCHMARK; + + // The fetch could not be repeated with the same results + // But we've seen so far that is was very very quick to exec + // So it makes sense to benchmark it a single time + $fStart = MyHelpers::getmicrotime(); + $aRow = CMDBSource::FetchArray($res); + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fFetchDuration = $fDuration; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $sOql = $oFilter->ToOQL(); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fToOqlDuration = $fDuration / COUNT_BENCHMARK; + + echo "
          \n"; + echo "
        • Parsing: $fParsingDuration
        • \n"; + echo "
        • Build: $fBuildDuration
        • \n"; + echo "
        • Query: $fQueryDuration
        • \n"; + echo "
        • Fetch: $fFetchDuration
        • \n"; + echo "
        • ToOql: $fToOqlDuration
        • \n"; + echo "
        \n"; + + // Everything but the ToOQL (wich is interesting, anyhow) + $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; + + return array( + 'rows' => CMDBSource::NbRows($res), + 'duration (s)' => round($fTotal, 4), + 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), + 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), + 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), + 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), + 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), + 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), + ); + } + + protected function DoExecute() + { + define ('COUNT_BENCHMARK', 3); + echo "

        The test will be repeated ".COUNT_BENCHMARK." times

        "; + + $aQueries = array( + 'SELECT CMDBChangeOpSetAttribute', + 'SELECT CMDBChangeOpSetAttribute WHERE id=10', + 'SELECT CMDBChangeOpSetAttribute WHERE id=123456789', + 'SELECT CMDBChangeOpSetAttribute WHERE CMDBChangeOpSetAttribute.id=10', + 'SELECT bizIncidentTicket', + 'SELECT bizIncidentTicket WHERE id=1', + 'SELECT bizPerson', + 'SELECT bizPerson WHERE id=1', + 'SELECT bizIncidentTicket JOIN bizPerson ON bizIncidentTicket.agent_id = bizPerson.id WHERE bizPerson.id = 5', + ); + $aStats = array(); + foreach ($aQueries as $sOQL) + { + $aStats[$sOQL] = $this->DoBenchmark($sOQL); + } + + $aData = array(); + foreach ($aStats as $sOQL => $aResults) + { + $aValues = array(); + $aValues['OQL'] = htmlentities($sOQL); + + foreach($aResults as $sDesc => $sInfo) + { + $aValues[$sDesc] = htmlentities($sInfo); + } + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + return true; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestItopWebServices extends TestWebServices +{ + static public function GetName() + { + return 'Itop - web services'; + } + + static public function GetDescription() + { + return 'Bulk load and ???'; + } + + protected function DoExecSingleLoad($aLoadSpec) + { + $sTitle = 'Load: '.$aLoadSpec['class']; + $sClass = $aLoadSpec['class']; + $sCsvData = $aLoadSpec['csvdata']; + + $aPostData = array('class' => $sClass, 'csvdata' => $sCsvData); + $sRes = self::DoPostRequestAuth('webservices/import.php', $aPostData); + + echo "

        $sTitle

        $sCsvData
        $sRes
        "; + } + + protected function DoExecute() + { + + $aLoads = array( + array( + 'class' => 'bizOrganization', + 'csvdata' => "name;code\nWorldCompany;WCY" + ), + array( + 'class' => 'bizLocation', + 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca" + ), + array( + 'class' => 'bizPerson', + 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789" + ), + array( + 'class' => 'bizTeam', + 'csvdata' => "name;org_id;location_id\nSquadra Azzura;1;1" + ), + array( + 'class' => 'bizWorkgroup', + 'csvdata' => "name;org_id;team_id\ntravailleurs alpins;1;6" + ), + array( + 'class' => 'bizIncidentTicket', + 'csvdata' => "name;title;type;customer_id;initial_situation;start_date;next_update;caller_id;workgroup_id;agent_id\nOVSD-12345;server down;Network;1;server was found down;2009-04-10 12:00;2009-04-10 15:00;3;317;5" + ), + ); + + foreach ($aLoads as $aLoadSpec) + { + $this->DoExecSingleLoad($aLoadSpec); + } + + return true; + } +} +?> diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php new file mode 100644 index 0000000000..bc8f75f615 --- /dev/null +++ b/setup/ajax.dataloader.php @@ -0,0 +1,64 @@ +Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + $oDataLoader->StartSession($oChange); + } + + $oDataLoader->LoadFile($sFileName); + + if ($sSessionStatus == 'end') + { + $oDataLoader->EndSession(); + } + $sResult = sprintf("Info - loading of %s done. (Overall %d %% completed).", basename($sFileName), $iPercent); + echo $sResult; + setup_web_page::log($sResult); +} +catch(Exception $e) +{ + echo "

        An error happened while loading the data

        \n"; + echo '

        '.$e."

        \n"; + setup_web_page::log("Error - An error happened while loading the data. ".$e); + +} + +?> diff --git a/setup/data/01.organizations.xml b/setup/data/01.organizations.xml new file mode 100644 index 0000000000..2e0eb1c5b4 --- /dev/null +++ b/setup/data/01.organizations.xml @@ -0,0 +1,15 @@ + + + +Demo +MCO +implementation +3 + + +Oracle +ORA +production +6 + + \ No newline at end of file diff --git a/setup/data/02.locations.xml b/setup/data/02.locations.xml new file mode 100644 index 0000000000..ddfdbf4cf9 --- /dev/null +++ b/setup/data/02.locations.xml @@ -0,0 +1,22 @@ + + + +Grenoble +production +3 +high +
        5 Avenue de la Poste
        +France +1 +
        + +Paris +implementation +3 +high +
        5 rue de la Paroisse +15eme arrondissement
        +France +1 +
        +
        \ No newline at end of file diff --git a/setup/data/03.persons.xml b/setup/data/03.persons.xml new file mode 100644 index 0000000000..fa6317ff5e --- /dev/null +++ b/setup/data/03.persons.xml @@ -0,0 +1,33 @@ + + + +Denis +production +3 +denis.flaven@gmail.com + +29 +Flaven + + + +Quetiez +production +3 +romain.quetiez@gmail.com + +1 +Romain + + + +Taloc +production +3 +erwan.taloc@gmail.com +33172382223 +1 +Erwan +e12345 + + \ No newline at end of file diff --git a/setup/data/04.teams.xml b/setup/data/04.teams.xml new file mode 100644 index 0000000000..7773a127e4 --- /dev/null +++ b/setup/data/04.teams.xml @@ -0,0 +1,19 @@ + + + +Application support +implementation +3 +application@mecanorama.com +33456456788 +29 + + +ITOP admin team +production +3 +combodo@gmail.com +33123453612 +1 + + \ No newline at end of file diff --git a/setup/data/05.pcs.xml b/setup/data/05.pcs.xml new file mode 100644 index 0000000000..4af8e9b8aa --- /dev/null +++ b/setup/data/05.pcs.xml @@ -0,0 +1,1865 @@ + + + +PC01 +production +3 +high +1 +Compaq +nc6005 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.14 +10.22.31.1 + + +PC02 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.15 +10.22.31.1 + + +PC03 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.16 +10.22.31.1 + + +PC04 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.17 +10.22.31.1 + + +PC05 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.18 +10.22.31.1 + + +PC06 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.19 +10.22.31.1 + + +PC07 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.20 +10.22.31.1 + + +PC08 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.21 +10.22.31.1 + + +PC09 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.22 +10.22.31.1 + + +PC10 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.23 +10.22.31.1 + + +PC11 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.24 +10.22.31.1 + + +PC12 +production +3 +high +1 +Compaq +nc6008 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.25 +10.22.31.1 + + +PC13 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.26 +10.22.31.1 + + +PC14 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.27 +10.22.31.1 + + +PC15 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.28 +10.22.31.1 + + +PC16 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.29 +10.22.31.1 + + +PC17 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.30 +10.22.31.1 + + +PC18 +production +3 +high +1 +Compaq +nc6000 + +desktop PC +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.32.31 +10.22.31.1 + + +PC19 +production +3 +high +1 +Compaq +nc6000 + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.32 +10.22.31.1 + + +PC20 +production +3 +high +1 +Compaq +nc6000 + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.33 +10.22.31.1 + + +PC21 +production +3 +high +1 +Compaq +nc6000 + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.34 +10.22.31.1 + + +PC22 +production +3 +high +1 +Compaq +nc6000 + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.35 +10.22.31.1 + + +PC23 +production +3 +medium +1 +Compaq +nc6000 + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.36 +10.22.31.1 + + +PC24 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.37 +10.22.31.1 + + +PC25 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.38 +10.22.31.1 + + +PC26 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.39 +10.22.31.1 + + +PC27 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.40 +10.22.31.1 + + +PC28 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.41 +10.22.31.1 + + +PC29 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.42 +10.22.31.1 + + +PC30 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.43 +10.22.31.1 + + +PC31 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.44 +10.22.31.1 + + +PC32 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.45 +10.22.31.1 + + +PC33 +production +3 +medium +1 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.32.46 +10.22.31.1 + + +PC34 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.5 +10.22.30.1 + + +PC35 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.6 +10.22.30.1 + + +PC36 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.7 +10.22.30.1 + + +PC37 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.8 +10.22.30.1 + + +PC38 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.9 +10.22.30.1 + + +PC39 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.10 +10.22.30.1 + + +PC40 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.11 +10.22.30.1 + + +PC41 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.12 +10.22.30.1 + + +PC42 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.13 +10.22.30.1 + + +PC43 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.14 +10.22.30.1 + + +PC44 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.15 +10.22.30.1 + + +PC45 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.16 +10.22.30.1 + + +PC46 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.17 +10.22.30.1 + + +PC47 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.18 +10.22.30.1 + + +PC48 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.19 +10.22.30.1 + + +PC49 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.20 +10.22.30.1 + + +PC50 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.21 +10.22.30.1 + + +PC5000 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.33 +10.22.30.1 + + +PC51 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.22 +10.22.30.1 + + +PC52 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.23 +10.22.30.1 + + +PC53 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.24 +10.22.30.1 + + +PC54 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.25 +10.22.30.1 + + +PC55 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.26 +10.22.30.1 + + +PC56 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.27 +10.22.30.1 + + +PC57 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.28 +10.22.30.1 + + +PC58 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.29 +10.22.30.1 + + +PC59 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.30 +10.22.30.1 + + +PC60 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.31 +10.22.30.1 + + +PC61 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.32 +10.22.30.1 + + +PC62 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.33 +10.22.30.1 + + +PC63 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.34 +10.22.30.1 + + +PC64 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.35 +10.22.30.1 + + +PC65 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Celeron 2x 1.6Ghz +60Gb +Windows +XP + +10.22.30.36 +10.22.30.1 + + +PC66 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.37 +10.22.30.1 + + +PC67 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.38 +10.22.30.1 + + +PC68 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.39 +10.22.30.1 + + +PC69 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.40 +10.22.30.1 + + +PC70 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.41 +10.22.30.1 + + +PC71 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.42 +10.22.30.1 + + +PC72 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.43 +10.22.30.1 + + +PC73 +production +3 +medium +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.44 +10.22.30.1 + + +PC74 +production +3 +high +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.45 +10.22.30.1 + + +PC75 +production +3 +high +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.46 +10.22.30.1 + + +PC76 +production +3 +high +29 +Dell +Studio 15 laptop + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.47 +10.22.30.1 + + +PC77 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.48 +10.22.30.1 + + +PC78 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +XP + +10.22.30.49 +10.22.30.1 + + +PC79 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.50 +10.22.30.1 + + +PC80 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.51 +10.22.30.1 + + +PC81 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.52 +10.22.30.1 + + +PC82 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.53 +10.22.30.1 + + +PC83 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.54 +10.22.30.1 + + +PC84 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.55 +10.22.30.1 + + +PC85 +production +3 +high +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.56 +10.22.30.1 + + +PC86 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.57 +10.22.30.1 + + +PC87 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.58 +10.22.30.1 + + +PC88 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.59 +10.22.30.1 + + +PC89 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.60 +10.22.30.1 + + +PC90 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.61 +10.22.30.1 + + +PC91 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.62 +10.22.30.1 + + +PC92 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.63 +10.22.30.1 + + +PC93 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.64 +10.22.30.1 + + +PC94 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.65 +10.22.30.1 + + +PC95 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.66 +10.22.30.1 + + +PC96 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.67 +10.22.30.1 + + +PC97 +production +3 +low +29 +Toshiba +Satelite L300D + +laptop +2048 +Pentium Dual Core 1.8Ghz +60Gb +Windows +Vista + +10.22.30.68 +10.22.30.1 + + \ No newline at end of file diff --git a/setup/data/06.servers.xml b/setup/data/06.servers.xml new file mode 100644 index 0000000000..d83b266779 --- /dev/null +++ b/setup/data/06.servers.xml @@ -0,0 +1,4063 @@ + + + +domino.combodo.com +In Production +3 +high +1 +IBM +Power 520 Express +S4523 +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.10 +10.22.28.1 + + +server02 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.11 +10.22.28.1 + + +server03 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.12 +10.22.28.1 + + +server04 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.13 +10.22.28.1 + + +server05 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.14 +10.22.28.1 + + +server06 +Production Candidate +3 +medium +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.15 +10.22.28.1 + + +server07 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.16 +10.22.28.1 + + +server08 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.17 +10.22.28.1 + + +server09 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.18 +10.22.28.1 + + +server10 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.19 +10.22.28.1 + + +server100 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.109 +10.22.28.1 + + +server101 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.110 +10.22.28.1 + + +server102 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.111 +10.22.28.1 + + +server103 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.112 +10.22.28.1 + + +server104 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.113 +10.22.28.1 + + +server105 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.114 +10.22.28.1 + + +server106 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.115 +10.22.28.1 + + +server107 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.116 +10.22.28.1 + + +server108 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.117 +10.22.28.1 + + +server109 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.118 +10.22.28.1 + + +server11 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.20 +10.22.28.1 + + +server110 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.119 +10.22.28.1 + + +server111 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.120 +10.22.28.1 + + +server112 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.121 +10.22.28.1 + + +server113 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.122 +10.22.28.1 + + +server114 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.123 +10.22.28.1 + + +server115 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.124 +10.22.28.1 + + +server116 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.125 +10.22.28.1 + + +server117 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.126 +10.22.28.1 + + +server118 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.127 +10.22.28.1 + + +server119 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.128 +10.22.28.1 + + +server12 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.21 +10.22.28.1 + + +server120 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.129 +10.22.28.1 + + +server121 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.130 +10.22.28.1 + + +server122 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.131 +10.22.28.1 + + +server123 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.132 +10.22.28.1 + + +server124 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.133 +10.22.28.1 + + +server125 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.134 +10.22.28.1 + + +server126 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.135 +10.22.28.1 + + +server127 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.136 +10.22.28.1 + + +server128 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.137 +10.22.28.1 + + +server129 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.138 +10.22.28.1 + + +server13 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.22 +10.22.28.1 + + +server130 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.139 +10.22.28.1 + + +server131 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.140 +10.22.28.1 + + +server132 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.141 +10.22.28.1 + + +server133 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.142 +10.22.28.1 + + +server134 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.143 +10.22.28.1 + + +server135 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.144 +10.22.28.1 + + +server136 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.145 +10.22.28.1 + + +server137 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.146 +10.22.28.1 + + +server138 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.147 +10.22.28.1 + + +server139 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.148 +10.22.28.1 + + +server14 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.23 +10.22.28.1 + + +server140 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.149 +10.22.28.1 + + +server141 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.150 +10.22.28.1 + + +server142 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.151 +10.22.28.1 + + +server143 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.152 +10.22.28.1 + + +server144 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.153 +10.22.28.1 + + +server145 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.154 +10.22.28.1 + + +server146 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.155 +10.22.28.1 + + +server147 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.156 +10.22.28.1 + + +server148 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.157 +10.22.28.1 + + +server149 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.158 +10.22.28.1 + + +server15 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.24 +10.22.28.1 + + +server150 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.159 +10.22.28.1 + + +server151 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.160 +10.22.28.1 + + +server152 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.161 +10.22.28.1 + + +server153 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.162 +10.22.28.1 + + +server154 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.163 +10.22.28.1 + + +server155 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.164 +10.22.28.1 + + +server156 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.165 +10.22.28.1 + + +server157 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.166 +10.22.28.1 + + +server158 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.167 +10.22.28.1 + + +server159 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.168 +10.22.28.1 + + +server16 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.25 +10.22.28.1 + + +server160 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.169 +10.22.28.1 + + +server161 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.170 +10.22.28.1 + + +server162 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.171 +10.22.28.1 + + +server163 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.172 +10.22.28.1 + + +server164 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.173 +10.22.28.1 + + +server165 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.174 +10.22.28.1 + + +server166 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.175 +10.22.28.1 + + +server167 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.176 +10.22.28.1 + + +server168 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.177 +10.22.28.1 + + +server169 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.178 +10.22.28.1 + + +server17 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.26 +10.22.28.1 + + +server170 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.179 +10.22.28.1 + + +server171 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.180 +10.22.28.1 + + +server172 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.181 +10.22.28.1 + + +server173 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.182 +10.22.28.1 + + +server174 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.183 +10.22.28.1 + + +server175 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.184 +10.22.28.1 + + +server176 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.185 +10.22.28.1 + + +server177 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.186 +10.22.28.1 + + +server178 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.187 +10.22.28.1 + + +server179 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.188 +10.22.28.1 + + +server18 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.27 +10.22.28.1 + + +server180 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.189 +10.22.28.1 + + +server181 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.190 +10.22.28.1 + + +server182 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.191 +10.22.28.1 + + +server183 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.192 +10.22.28.1 + + +server184 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.193 +10.22.28.1 + + +server185 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.194 +10.22.28.1 + + +server186 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.195 +10.22.28.1 + + +server187 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.196 +10.22.28.1 + + +server188 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.197 +10.22.28.1 + + +server189 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.198 +10.22.28.1 + + +server19 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.28 +10.22.28.1 + + +server190 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.199 +10.22.28.1 + + +server191 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.200 +10.22.28.1 + + +server192 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.201 +10.22.28.1 + + +server193 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.202 +10.22.28.1 + + +server194 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.203 +10.22.28.1 + + +server195 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.204 +10.22.28.1 + + +server196 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.205 +10.22.28.1 + + +server197 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.206 +10.22.28.1 + + +server198 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.207 +10.22.28.1 + + +server199 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.208 +10.22.28.1 + + +server20 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.29 +10.22.28.1 + + +server21 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.30 +10.22.28.1 + + +server22 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.31 +10.22.28.1 + + +server23 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.32 +10.22.28.1 + + +server24 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.22.28.33 +10.22.28.1 + + +server25 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.34 +10.22.28.1 + + +server26 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.35 +10.22.28.1 + + +server27 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.36 +10.22.28.1 + + +server28 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.37 +10.22.28.1 + + +server29 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.38 +10.22.28.1 + + +server30 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.39 +10.22.28.1 + + +server31 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.40 +10.22.28.1 + + +server32 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.41 +10.22.28.1 + + +server33 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.42 +10.22.28.1 + + +server34 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.43 +10.22.28.1 + + +server35 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.44 +10.22.28.1 + + +server36 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.45 +10.22.28.1 + + +server37 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.46 +10.22.28.1 + + +server38 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.47 +10.22.28.1 + + +server39 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.48 +10.22.28.1 + + +server40 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.49 +10.22.28.1 + + +server41 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.50 +10.22.28.1 + + +server42 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.51 +10.22.28.1 + + +server43 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.52 +10.22.28.1 + + +server44 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.53 +10.22.28.1 + + +server45 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.54 +10.22.28.1 + + +server46 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.55 +10.22.28.1 + + +server47 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.56 +10.22.28.1 + + +server48 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.57 +10.22.28.1 + + +server49 +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.3 + +10.22.28.58 +10.22.28.1 + + +server50 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.59 +10.22.28.1 + + +server51 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.60 +10.22.28.1 + + +server52 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.61 +10.22.28.1 + + +server53 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.62 +10.22.28.1 + + +server54 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.63 +10.22.28.1 + + +server55 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.64 +10.22.28.1 + + +server56 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.65 +10.22.28.1 + + +server57 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.66 +10.22.28.1 + + +server58 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.67 +10.22.28.1 + + +server59 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.68 +10.22.28.1 + + +server60 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.69 +10.22.28.1 + + +server61 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.70 +10.22.28.1 + + +server62 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.71 +10.22.28.1 + + +server63 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.72 +10.22.28.1 + + +server64 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.73 +10.22.28.1 + + +server65 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.74 +10.22.28.1 + + +server66 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.75 +10.22.28.1 + + +server67 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.76 +10.22.28.1 + + +server68 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.77 +10.22.28.1 + + +server69 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.78 +10.22.28.1 + + +server70 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.79 +10.22.28.1 + + +server71 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.80 +10.22.28.1 + + +server72 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.81 +10.22.28.1 + + +server73 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.82 +10.22.28.1 + + +server74 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.83 +10.22.28.1 + + +server75 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.84 +10.22.28.1 + + +server76 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.85 +10.22.28.1 + + +server77 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.86 +10.22.28.1 + + +server78 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.87 +10.22.28.1 + + +server79 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.88 +10.22.28.1 + + +server80 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.89 +10.22.28.1 + + +server81 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.90 +10.22.28.1 + + +server82 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.91 +10.22.28.1 + + +server83 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.92 +10.22.28.1 + + +server84 +Production Candidate +3 +high +1 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.93 +10.22.28.1 + + +server85 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.94 +10.22.28.1 + + +server86 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.95 +10.22.28.1 + + +server87 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.96 +10.22.28.1 + + +server88 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.97 +10.22.28.1 + + +server89 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.98 +10.22.28.1 + + +server90 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.99 +10.22.28.1 + + +server91 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.100 +10.22.28.1 + + +server92 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.101 +10.22.28.1 + + +server93 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.102 +10.22.28.1 + + +server94 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.103 +10.22.28.1 + + +server95 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.104 +10.22.28.1 + + +server96 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.105 +10.22.28.1 + + +server97 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.11 + +10.22.28.106 +10.22.28.1 + + +server98 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.107 +10.22.28.1 + + +server99 +Production Candidate +3 +high +29 +HP + + +8Gb +rp3440 +4 +4x120Gb +200Gb +HP-UX +11.23 + +10.22.28.108 +10.22.28.1 + + +srv01.combodo.com +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.32.28.11 +10.32.28.1 + + +srv02.combodo.com +Production Candidate +3 +high +1 +HP + + +8Gb +rp3410 +4 +4x120Gb +200Gb +HP UX +11.23 + +10.32.28.12 +10.32.28.1 + + +srv03.combodo.com +Production Candidate +3 +high +1 +IBM + + +8Gb +Power 520 Express +4 +4x120Gb +200Gb +AIX +4.1 + +10.32.28.13 +10.32.28.1 + + +srv04.combodo.com +Production Candidate +3 +high +1 +HP + + +8Gb +rp3410 +4 +4x120Gb +200Gb +HP UX +11.23 + +10.32.28.14 +10.32.28.1 + + \ No newline at end of file diff --git a/setup/data/07.applications.xml b/setup/data/07.applications.xml new file mode 100644 index 0000000000..c6adc36094 --- /dev/null +++ b/setup/data/07.applications.xml @@ -0,0 +1,33 @@ + + + +Oracle 10g +production +3 +high +5 +2008-11-05 00:00:00 +10g +Database + + +outlook Office +production +3 +high +2 +2008-12-30 00:00:00 +8.4 +Mail client + + +Outlook server +implementation +3 +high +5 +2008-12-20 00:00:00 +8.1 +Mail Server + + \ No newline at end of file diff --git a/setup/data/08.nw-devices.xml b/setup/data/08.nw-devices.xml new file mode 100644 index 0000000000..97e6a22000 --- /dev/null +++ b/setup/data/08.nw-devices.xml @@ -0,0 +1,71 @@ + + + +router01 +production +3 +high +1 +Cisco +6500 +S2345985 +switch +10.25.3.45 +10.25.3.1 +8.2 +flash :1=16.48MB +public +private + + +router02 +production +3 +high +1 +Cisco +6500 +S2345985 +switch +10.25.3.46 +10.25.3.1 +8.2 +flash :1=16.48MB +public +private + + +router03 +production +3 +high +1 +Cisco +6500 +S2345985 +switch +10.25.3.47 +10.25.3.1 +8.2 +flash :1=16.48MB +public +private + + +switch01 +production +3 +high +1 + + + +switch + + + + + + + + \ No newline at end of file diff --git a/setup/data/09.links_contacts.xml b/setup/data/09.links_contacts.xml new file mode 100644 index 0000000000..ebd943f138 --- /dev/null +++ b/setup/data/09.links_contacts.xml @@ -0,0 +1,23 @@ + + + +8 +7 +business model expert + + +21 +2 +owner + + +7 +5 +Server Owner + + +8 +21 +Team Leader + + \ No newline at end of file diff --git a/setup/data/10.workgroups.xml b/setup/data/10.workgroups.xml new file mode 100644 index 0000000000..7147715128 --- /dev/null +++ b/setup/data/10.workgroups.xml @@ -0,0 +1,17 @@ + + + +FLS Desktop +production +3 +8 +1st level support + + +FLS Network +production +3 +8 +2nd level support + + \ No newline at end of file diff --git a/setup/data/11.incidents.xml b/setup/data/11.incidents.xml new file mode 100644 index 0000000000..a448488d7e --- /dev/null +++ b/setup/data/11.incidents.xml @@ -0,0 +1,132 @@ + + + +I-000001 +PC issue +Desktop +3 +WorkInProgress +there is an issue with my PC + +2009-02-10 21:30:55 + + + +21 +not able to start IE +19 +7 +We are about to connect to your PC via Netmeeting and troubleshoot what's up. + +Then we will try to contact Microsoft to understand if it is a known issue +critical +1 + + + +I-000002 +Network issue +Network +3 +Assigned +No more access to the network +Network connectivity lost +2009-02-19 19:07:25 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +7 +high +24 +21 +need to investigate the network +low +2 + + + +I-000003 +Disk Failure on Server01 +Server +3 +WorkInProgress +Partition /var is no more accessible. +It seems that hard disk is broken +Filesystem is full +2009-03-21 08:27:46 +2009-02-10 22:25:00 +2009-02-10 00:25:00 +0000-00-00 00:00:00 +20 +Really important for the business +19 +7 +There are lot of logs file to be trimed +medium +2 + + + +I-000004 +Oracle Database down on domino +Server +3 +New +Database Oracle is no more accessible on Server01 +Customer cannot access Order web site. +2009-03-17 09:31:09 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +7 +Very critical +19 +0 + +low +0 + + + +I-000005 +Issue with my pc +Desktop +3 +Assigned +My PC is locked +We have to analyze +2009-03-01 23:22:38 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +7 + +19 +20 + +medium +1 + + + +I-000006 +Demo +Server +3 +New +gdfgl + +2009-03-18 22:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +20 +personne ne peut acceder au site de paris +19 +0 + +low +0 + + + \ No newline at end of file diff --git a/setup/data/12.relatedtickets.xml b/setup/data/12.relatedtickets.xml new file mode 100644 index 0000000000..dd5eeaa71f --- /dev/null +++ b/setup/data/12.relatedtickets.xml @@ -0,0 +1,8 @@ + + + +2 +1 +Real cause is a network incident + + \ No newline at end of file diff --git a/setup/data/13.infratickets.xml b/setup/data/13.infratickets.xml new file mode 100644 index 0000000000..4d99748434 --- /dev/null +++ b/setup/data/13.infratickets.xml @@ -0,0 +1,33 @@ + + + +125 +6 +c'est down + + +5 +3 +main application is broken + + +5 +2 +no more access to network + + +2 +1 +not able to start IE + + +28 +5 +not able to work + + +5 +4 +Order web site is no more working + + \ No newline at end of file diff --git a/setup/data/14.contacttickets.xml b/setup/data/14.contacttickets.xml new file mode 100644 index 0000000000..2b24a62153 --- /dev/null +++ b/setup/data/14.contacttickets.xml @@ -0,0 +1,13 @@ + + + +8 +1 +Team that need to be informed + + +21 +3 +to be informed + + \ No newline at end of file diff --git a/setup/data/15.changetickets.xml b/setup/data/15.changetickets.xml new file mode 100644 index 0000000000..bdbb71970d --- /dev/null +++ b/setup/data/15.changetickets.xml @@ -0,0 +1,85 @@ + + + +CM-0000003 +1st change +Routine +Desktop +reason +20 +3 +PlannedScheduled +2009-02-19 21:35:47 +2009-02-18 18:33:14 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +There is no impact +19 +21 +19 +20 +19 +7 +No +I would like to install SQL plus on my laptop. +Could you make it please +We will push via netmeeting the package and install it at 10:00 tomorrow +If there is any issue we will uninstall it +0 + + +CM-0000004 +Oracle upgrade on domino +Routine +Application +Patch Installation for Oracle +21 +3 +Approved +2009-03-17 14:50:34 +2009-03-17 14:50:34 +2009-02-24 00:00:00 +2009-02-24 02:00:00 +0000-00-00 00:00:00 +application impacted +19 +20 +19 +21 +19 +7 +Yes +We wan to install last Oracle Patch in order to fix rollback management issues +We will install patch and reboot the server +If not working we will remove this patch +0 + + +CM-0000005 +IOS upgrade +Routine +Desktop +security issue +21 +3 +New +2009-04-04 23:23:37 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 +0000-00-00 00:00:00 + +19 +21 +19 +20 +19 +7 +No +Upgrade to new IOS due to security reason + + +0 + + \ No newline at end of file diff --git a/setup/data/16.infrachangetickets.xml b/setup/data/16.infrachangetickets.xml new file mode 100644 index 0000000000..af099731b8 --- /dev/null +++ b/setup/data/16.infrachangetickets.xml @@ -0,0 +1,23 @@ + + + +22 +2 +impacted application + + +5 +2 +impacted server + + +2 +1 +PC where to install new appli + + +6 +3 +router not available + + \ No newline at end of file diff --git a/setup/data/17.contactchangetickets.xml b/setup/data/17.contactchangetickets.xml new file mode 100644 index 0000000000..d22e889924 --- /dev/null +++ b/setup/data/17.contactchangetickets.xml @@ -0,0 +1,23 @@ + + + +7 +1 +DB admin + + +20 +2 +Db administrator + + +21 +1 +notify me + + +7 +2 +Server Owner for reboot + + \ No newline at end of file diff --git a/setup/data/18.contracts.xml b/setup/data/18.contracts.xml new file mode 100644 index 0000000000..4de2eff8ea --- /dev/null +++ b/setup/data/18.contracts.xml @@ -0,0 +1,23 @@ + + + +Oracle Support +3 +6 +Software support +8 +Gold +Global +Yearly +30000 +Euros +This contract define Oracle Support contract between Mecanorama and Oracle company. + +Yearly cost is 30.000 euros +2009-02-22 18:54:14 +2009-03-04 00:01:38 +Signed +Software +3 + + \ No newline at end of file diff --git a/setup/data/19.infracontracts.xml b/setup/data/19.infracontracts.xml new file mode 100644 index 0000000000..509a3d947e --- /dev/null +++ b/setup/data/19.infracontracts.xml @@ -0,0 +1,9 @@ + + + +22 +1 +Office Hour +Gold + + \ No newline at end of file diff --git a/setup/data/20.contactcontracts.xml b/setup/data/20.contactcontracts.xml new file mode 100644 index 0000000000..f33a083329 --- /dev/null +++ b/setup/data/20.contactcontracts.xml @@ -0,0 +1,13 @@ + + + +21 +1 +Contract Manager + + +8 +1 +Team in charge to manage Oracle + + \ No newline at end of file diff --git a/setup/data/21.auditcategories.xml b/setup/data/21.auditcategories.xml new file mode 100644 index 0000000000..ae806c8557 --- /dev/null +++ b/setup/data/21.auditcategories.xml @@ -0,0 +1,8 @@ + + + +Devices in production +Checking all devices in production +bizDevice: status = 'production' + + \ No newline at end of file diff --git a/setup/data/22.auditrules.xml b/setup/data/22.auditrules.xml new file mode 100644 index 0000000000..8298ae182f --- /dev/null +++ b/setup/data/22.auditrules.xml @@ -0,0 +1,17 @@ + + + +Devices in production on a Location not in production + +bizDevice: location_id IN (bizLocation: status != 'production') +false +1 + + +Devices not attached to a monitoring group + +bizDevice: PKEY IS infra_id IN (lnkInfraGrouping: infra_group_id IN (bizInfraGroup: type = 'Monitoring')) +true +1 + + \ No newline at end of file diff --git a/setup/data/export.cmd b/setup/data/export.cmd new file mode 100644 index 0000000000..5c95ce8605 --- /dev/null +++ b/setup/data/export.cmd @@ -0,0 +1,27 @@ +SET WEBROOT=http://localhost:81 +SET USER=Erwan +SET PWD=Taloc +REM The order (numbering) of the files is important since +REM it dictates the order to import them back +wget --output-document=01.organizations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizOrganization&format=xml" +wget --output-document=02.locations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizLocation&format=xml" +wget --output-document=03.persons.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizPerson&format=xml" +wget --output-document=04.teams.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizTeam&format=xml" +wget --output-document=05.pcs.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizPC&format=xml" +wget --output-document=06.servers.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizServer&format=xml" +wget --output-document=07.applications.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizApplication&format=xml" +wget --output-document=08.nw-devices.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizNetworkDevice&format=xml" +wget --output-document=09.links_contacts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactRealObject&format=xml" +wget --output-document=10.workgroups.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizWorkgroup&format=xml" +wget --output-document=11.incidents.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizIncidentTicket&format=xml" +wget --output-document=12.relatedtickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkRelatedTicket&format=xml" +wget --output-document=13.infratickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkInfraTicket&format=xml" +wget --output-document=14.contacttickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactTicket&format=xml" +wget --output-document=15.changetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizChangeTicket&format=xml" +wget --output-document=16.infrachangetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkInfraChangeTicket&format=xml" +wget --output-document=17.contactchangetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactChange&format=xml" +wget --output-document=18.contracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizContract&format=xml" +wget --output-document=19.infracontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkInfraContract&format=xml" +wget --output-document=20.contactcontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactContract&format=xml" +wget --output-document=21.auditcategories.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT AuditCategory&format=xml" +wget --output-document=22.auditrules.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT AuditRule&format=xml" diff --git a/setup/export_menus.cmd b/setup/export_menus.cmd new file mode 100644 index 0000000000..e7cc514aff --- /dev/null +++ b/setup/export_menus.cmd @@ -0,0 +1,6 @@ +SET WEBROOT=http://localhost:81 +SET USER=Erwan +SET PWD=Taloc +REM The order (numbering) of the files is important since +REM it dictates the order to import them back +wget --output-document=1.menus.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT menuNode WHERE type%%3D%%27application%%27&format=xml" diff --git a/setup/index.php b/setup/index.php new file mode 100644 index 0000000000..20a5472fbd --- /dev/null +++ b/setup/index.php @@ -0,0 +1,536 @@ +log('Info - CheckPHPVersion'); + if (version_compare(phpversion(), PHP_MIN_VERSION, '>=')) + { + $oP->ok("The current PHP Version (".phpversion().") is greater than the minimum required version (".PHP_MIN_VERSION.")"); + } + else + { + $oP->error("Error: The current PHP Version (".phpversion().") is lower than the minimum required version (".PHP_MIN_VERSION.")"); + return false; + } + $aMandatoryExtensions = array('mysql', 'iconv', 'simplexml'); + asort($aMandatoryExtensions); // Sort the list to look clean ! + $aExtensionsOk = array(); + $aMissingExtensions = array(); + $aMissingExtensionsLinks = array(); + foreach($aMandatoryExtensions as $sExtension) + { + if (extension_loaded($sExtension)) + { + $aExtensionsOk[] = $sExtension; + } + else + { + $aMissingExtensions[] = $sExtension; + $aMissingExtensionsLinks[] = "$sExtension"; + } + } + if (count($aExtensionsOk) > 0) + { + $oP->ok("Required PHP extension(s): ".implode(', ', $aExtensionsOk)."."); + } + if (count($aMissingExtensions) > 0) + { + $oP->error("Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks)."."); + return false; + } + return true; +} + +/** + * Helper function check the connection to the database and (if connected) to enumerate + * the existing databases + * @return Array The list of databases found in the server + */ +function CheckServerConnection(setup_web_page $oP, $sDBServer, $sDBUser, $sDBPwd) +{ + $aResult = array(); + $oP->log('Info - CheckServerConnection'); + try + { + $oDBSource = new CMDBSource; + $oDBSource->Init($sDBServer, $sDBUser, $sDBPwd); + $oP->ok("Connection to '$sDBServer' as '$sDBUser' successful."); + $sDBVersion = $oDBSource->GetDBVersion(); + if (version_compare($sDBVersion, MYSQL_MIN_VERSION, '>=')) + { + $oP->ok("Current MySQL version ($sDBVersion), greater than minimum required version (".MYSQL_MIN_VERSION.")"); + } + else + { + $oP->error("Error: Current MySQL version is ($sDBVersion), minimum required version (".MYSQL_MIN_VERSION.")"); + return false; + } + try + { + $aResult = $oDBSource->ListDB(); + } + catch(Exception $e) + { + $oP->warning("Warning: unable to enumerate the current databases."); + $aResult = true; // Not an array to differentiate with an empty array + } + } + catch(Exception $e) + { + $oP->error("Error: Connection to '$sDBServer' as '$sDBUser' failed."); + $oP->p($e->GetHtmlDesc()); + $aResult = false; + } + return $aResult; +} + +/** + * Helper function to initialize the ORM and load the data model + * from the given file + * @param $sConfigFileName string The name of the configuration file to load + * @param $bAllowMissingDatabase boolean Whether or not to allow loading a data model with no corresponding DB + * @return none + */ +function InitDataModel(setup_web_page $oP, $sConfigFileName, $bAllowMissingDatabase = true) +{ + require_once('../core/coreexception.class.inc.php'); + require_once('../core/attributedef.class.inc.php'); + require_once('../core/filterdef.class.inc.php'); + require_once('../core/stimulus.class.inc.php'); + require_once('../core/MyHelpers.class.inc.php'); + require_once('../core/expression.class.inc.php'); + require_once('../core/cmdbsource.class.inc.php'); + require_once('../core/sqlquery.class.inc.php'); + require_once('../core/dbobject.class.php'); + require_once('../core/dbobjectsearch.class.php'); + require_once('../core/dbobjectset.class.php'); + require_once('../core/userrights.class.inc.php'); + $oP->log("Info - MetaModel::Startup from file '$sConfigFileName' (AllowMissingDB = $bAllowMissingDatabase)"); + MetaModel::Startup($sConfigFileName, $bAllowMissingDatabase); +} +/** + * Helper function to create the database structure + * @return boolean true on success, false otherwise + */ +function CreateDatabaseStructure(setup_web_page $oP, Config $oConfig, $sDBName, $sDBPrefix) +{ + InitDataModel($oP, TMP_CONFIG_FILE, true); // Allow the DB to NOT exist since we're about to create it ! + $oP->log('Info - CreateDatabaseStructure'); + $oP->info("Creating the structure in '$sDBName' (prefix = '$sDBPrefix')."); + //MetaModel::CheckDefinitions(); + if (!MetaModel::DBExists()) + { + MetaModel::DBCreate(); + $oP->ok("Database structure created in '$sDBName' (prefix = '$sDBPrefix')."); + } + else + { + $oP->error("Error: database '$sDBName' (prefix = '$sDBPrefix') already exists."); + $oP->p("Tables with conflicting names already exist in the database. + Try selecting another database instance or specifiy a prefix to prevent conflicting table names."); + return false; + } + return true; +} + +/** + * Helper function to create and administrator account for iTop + * @return boolean true on success, false otherwise + */ +function CreateAdminAccount(setup_web_page $oP, Config $oConfig, $sAdminUser, $sAdminPwd) +{ + $oP->log('Info - CreateAdminAccount'); + InitDataModel($oP, TMP_CONFIG_FILE, true); // allow missing DB + if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd)) + { + $oP->ok("Administrator account '$sAdminUser' created."); + return true; + } + else + { + $oP->error("Failed to create the administrator account '$sAdminUser'."); + return false; + } +} + +/** + * Scans the ./data directory for XML files and output them as a Javascript array + */ +function PopulateDataFilesList(setup_web_page $oP) +{ + if ($hDir = @opendir(SETUP_DATA_DIR)) + { + $aFilesToLoad = array(); + // This is the correct way to loop over the directory. (according the documentation) + while (($sFile = readdir($hDir)) !== false) + { + $sExtension = pathinfo($sFile, PATHINFO_EXTENSION ); + if (strcasecmp($sExtension, 'xml') == 0) + { + $aFilesToLoad[] = SETUP_DATA_DIR.'/'.$sFile; + } + } + closedir($hDir); + // Load order is important we expect the files to be ordered + // like numbered 1.Organizations.xml 2.Locations.xml , etc. + asort($aFilesToLoad); + // Menus can be loaded any time... like here at the end + + $oP->add("\n"); + } + else + { + $oP->error("Data directory (".SETUP_DATA_DIR.") no found or not readable."); + } +} + +/** + * Display the form for the first step of the configuration wizard + * which consists in the database server selection + */ +function DisplayStep1(setup_web_page $oP) +{ + $sNextOperation = 'step2'; + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Checking prerequisites

        \n"); + if (CheckPHPVersion($oP)) + { + $sRedStar = '*'; + $oP->add("

        Step 1: Configuration of the database connection

        \n"); + $oP->add("
        \n"); + // Form goes here + $oP->add("
        Database connection\n"); + $aForm = array(); + $aForm[] = array('label' => "Server name$sRedStar:", 'input' => "", + 'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23".'); + $aForm[] = array('label' => "User name$sRedStar:", 'input' => ""); + $aForm[] = array('label' => 'Password:', 'input' => ""); + $oP->form($aForm); + $oP->add("
        \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
        \n"); + } +} + +/** + * Display the form for the second step of the configuration wizard + * which consists in + * 1) Validating the parameters by connecting to the database server + * 2) Prompting to select an existing database or to create a new one + */ +function DisplayStep2(setup_web_page $oP, Config $oConfig, $sDBServer, $sDBUser, $sDBPwd) +{ + $sNextOperation = 'step3'; + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Step 2: Database selection

        \n"); + $oP->add("
        \n"); + $aDatabases = CheckServerConnection($oP, $sDBServer, $sDBUser, $sDBPwd); + if ($aDatabases === false) + { + // Connection failed, invalid credentials ? Go back + $oP->add("\n"); + } + else + { + // Connection is Ok, save it and continue the setup wizard + $oConfig->SetDBHost($sDBServer); + $oConfig->SetDBUser($sDBUser); + $oConfig->SetDBPwd($sDBPwd); + $oConfig->WriteToFile(); + + $oP->add("
        Specify a database*\n"); + $aForm = array(); + if (is_array($aDatabases)) + { + foreach($aDatabases as $sDBName) + { + $aForm[] = array('label' => ""); + } + } + else + { + $aForm[] = array('label' => " "); + $oP->add_ready_script("$('#current_db_name').click( function() { $('#current_db').attr('checked', true); });"); + } + $aForm[] = array('label' => " "); + $oP->form($aForm); + + $oP->add_ready_script("$('#new_db_name').click( function() { $('#new_db').attr('checked', true); })"); + $oP->add("
        \n"); + $aForm = array(); + $aForm[] = array('label' => "Add a prefix to all the tables: "); + $oP->form($aForm); + + $oP->add("\n"); + $oP->add("\n"); + $oP->add("    \n"); + $oP->add("\n"); + } + $oP->add("
        \n"); +} + +/** + * Display the form for the third step of the configuration wizard + * which consists in + * 1) Validating the parameters by connecting to the database server & selecting the database + * 2) Creating the database structure + * 3) Prompting for the admin account to be created + */ +function DisplayStep3(setup_web_page $oP, Config $oConfig, $sDBName, $sDBPrefix) +{ + $sNextOperation = 'step4'; + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Creation of the database structure

        \n"); + $oP->add("
        \n"); + $oConfig->SetDBName($sDBName); + $oConfig->SetDBSubname($sDBPrefix); + $oConfig->WriteToFile(TMP_CONFIG_FILE); + if (CreateDatabaseStructure($oP, $oConfig, $sDBName, $sDBPrefix)) + { + $sRedStar = "*"; + $oP->add("

        Step 3: Definition of the administrator account

        \n"); + // Database created, continue with admin creation + $oP->add("
        Administrator account\n"); + $aForm = array(); + $aForm[] = array('label' => "Login$sRedStar:", 'input' => ""); + $aForm[] = array('label' => "Password$sRedStar:", 'input' => ""); + $aForm[] = array('label' => "Retype password$sRedStar:", 'input' => ""); + $oP->form($aForm); + $oP->add("
        \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("    \n"); + $oP->add("\n"); + } + else + { + $oP->add("\n"); + } + // Form goes here + $oP->add("
        \n"); +} + +/** + * Display the form for the fourth step of the configuration wizard + * which consists in + * 1) Creating the admin user account + * 2) Prompting to load some sample data + */ +function DisplayStep4(setup_web_page $oP, Config $oConfig, $sAdminUser, $sAdminPwd) +{ + $sNextOperation = 'step5'; + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Creation of the administrator account

        \n"); + + $oP->add("
        \n"); + if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd)) + { + $oP->add("

        Step 4: Loading of sample data

        \n"); + $oP->p("
        Do you want to load sample data into the database ? \n"); + $oP->p(" Yes, for testing purposes, populate the database with sample data.\n"); + $oP->p(" No, this is a production system, I will load real data myself.\n"); + $oP->p("
        \n"); + $oP->add("\n"); + $oP->add("    \n"); + $oP->add("\n"); + } + else + { + // Creation failed + $oP->add("\n"); + } + // End of visible form + $oP->add("
        \n"); + // Hidden form + $oP->add("
        \n"); + $oP->add("\n"); // To be compatible with login page + $oP->add("\n"); // To be compatible with login page + $oP->add("\n"); + $oP->add("
        \n"); + $oP->add_linked_script('./jquery.progression.js'); + + PopulateDataFilesList($oP); +} +/** + * Display the form for the fifth (and final) step of the configuration wizard + * which consists in + * 1) Creating the final configuration file + * 2) Prompting the user to make the file read-only + */ +function DisplayStep5(setup_web_page $oP, Config $oConfig, $sAuthUser, $sAuthPwd) +{ + try + { + session_start(); + + // Write the final configuration file + $oConfig->WriteToFile(FINAL_CONFIG_FILE); + + // Start the application + InitDataModel($oP, FINAL_CONFIG_FILE, false); // DO NOT allow missing DB + if (UserRights::Login($sAuthUser, $sAuthPwd)) + { + $_SESSION['auth_user'] = $sAuthUser; + $_SESSION['auth_pwd'] = $sAuthPwd; + // remove the tmp config file + @unlink(TMP_CONFIG_FILE); + // try to make the final config file read-only + @chmod(FINAL_CONFIG_FILE, 0440); // Read-only for owner and group, nothing for others + + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Configuration completed

        \n"); + $oP->add("
        \n"); + $oP->ok("The initialization completed successfully."); + // Form goes here + $oP->add("\n"); + $oP->add("    \n"); + $oP->add("\n"); + $oP->add("
        \n"); + } + else + { + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Step 5: Configuration completed

        \n"); + + @unlink(FINAL_CONFIG_FILE); // remove the aborted config + $oP->error("Error: Failed to login for user: '$sAuthUser'\n"); + + $oP->add("
        \n"); + $oP->add("\n"); + $oP->add("    \n"); + $oP->add("
        \n"); + } + } + catch(Exception $e) + { + $oP->error("Error: unable to create the configuration file."); + $oP->p($e->getHtmlDesc()); + $oP->p("Did you forget to remove the previous (read-only) configuration file ?"); + } +} +/** + * Main program + */ + clearstatcache(); // Make sure we know what we are doing ! +if (file_exists(FINAL_CONFIG_FILE)) +{ + // The configuration file already exists + if (is_writable(FINAL_CONFIG_FILE)) + { + $oP->warning("Warning: a configuration file '".FINAL_CONFIG_FILE."' already exists, and will be overwritten."); + } + else + { + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Fatal error

        \n"); + $oP->error("Error: the configuration file '".FINAL_CONFIG_FILE."' already exists and cannot be overwritten."); + $oP->p("The wizard cannot create the configuration file for you. Please remove the file '".realpath(FINAL_CONFIG_FILE)."' or change its access-rights/read-only flag before continuing."); + $oP->output(); + exit; + } +} +else +{ + // No configuration file yet + // Check that the wizard can write into the root dir to create the configuration file + if (!is_writable(dirname(FINAL_CONFIG_FILE))) + { + $oP->add("

        iTop configuration wizard

        \n"); + $oP->add("

        Fatal error

        \n"); + $oP->error("Error: the directory where to store the configuration file is not writable."); + $oP->p("The wizard cannot create the configuration file for you. Please make sure that the directory '".realpath(dirname(FINAL_CONFIG_FILE))."' is writable for the web server."); + $oP->output(); + exit; + } + +} +try +{ + $oConfig = new Config(TMP_CONFIG_FILE); +} +catch(Exception $e) +{ + // We'll end here when the tmp config file does not exist. It's normal + $oConfig = new Config(TMP_CONFIG_FILE, false /* Don't try to load it */); +} +switch($sOperation) +{ + case 'step1': + $oP->log("Info - ========= Wizard step 1 ========"); + DisplayStep1($oP); + break; + + case 'step2': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 2 ========"); + $sDBServer = Utils::ReadParam('db_server'); + $sDBUser = Utils::ReadParam('db_user'); + $sDBPwd = Utils::ReadParam('db_pwd'); + DisplayStep2($oP, $oConfig, $sDBServer, $sDBUser, $sDBPwd); + break; + + case 'step3': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 3 ========"); + $sDBName = Utils::ReadParam('db_name'); + if (empty($sDBName)) + { + $sDBName = Utils::ReadParam('new_db_name'); + } + $sDBPrefix = Utils::ReadParam('db_prefix'); + DisplayStep3($oP, $oConfig, $sDBName, $sDBPrefix); + break; + + case 'step4': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 4 ========"); + $sAdminUser = Utils::ReadParam('auth_user'); + $sAdminPwd = Utils::ReadParam('auth_pwd'); + DisplayStep4($oP, $oConfig, $sAdminUser, $sAdminPwd); + break; + + case 'step5': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 5 ========"); + $sAdminUser = Utils::ReadParam('auth_user'); + $sAdminPwd = Utils::ReadParam('auth_pwd'); + DisplayStep5($oP, $oConfig, $sAdminUser, $sAdminPwd); + break; + + default: + $oP->error("Error: unsupported operation '$sOperation'"); + +} +$oP->output(); +?> diff --git a/setup/jquery.progression.js b/setup/jquery.progression.js new file mode 100644 index 0000000000..9f9d4ea529 --- /dev/null +++ b/setup/jquery.progression.js @@ -0,0 +1,193 @@ +/* + * Progression - jQuery plugin for Progress Bar 1.3 + * + * http://www.anthor.net/fr/jquery-progression.html + * + * Copyright (c) 2008 FOURNET Loïc + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +(function($) { + $.fn.progression = function(options) { + // Récupération des options par défaut + var opts = $.extend({ + Current: 50, + Maximum: 100, + Background: '#FFFFFF', + TextColor: '#000000', + aBackground: '#FF0000', + aTextColor: '#FFFFFF', + BorderColor: '#000000', + Animate: true, + AnimateTimeOut: 3000, + Easing: 'linear', + startFct : null, + endFct : null + }, $.fn.progression.defaults, options); + + if(options) + var newCurrent = options.Current; + + // Boucle sur les éléments appelés + return this.each(function() { + $this = $(this); // Elément en cours + $innerdiv=$this.find(".progress"); // On recherche si l'élément a déjà été traité + + // Options Spécifiques + Métadata ? + var o = $.metadata ? $.extend({}, opts, $this.metadata()) : opts; + + // Premier traitement de l'élément, pour la mise en place + if($innerdiv.length!=1) + BuildBarre($this, o); + else + { + // Si c'est une nouvelle valeur, la fonction doit avoir la priorité sur les métadata + if(newCurrent) + o.Current = newCurrent; + o.Maximum = parseInt($this.attr('pmax')); + } + + // Valeur supérieur au maximum ? + if( o.Current > o.Maximum ) + { + debug('La valeur demandee doit etre inférieur ou egale a la valeur maximale.') + return false; + } + + // Calcul du pourcentage actuel + var aWidth = Math.round(parseInt($this.attr('pcur'))/o.Maximum*100); + // Calcul du nouveaux pourcentage + var Width = Math.round(parseInt(o.Current)/o.Maximum*100); + + //Start Callback + if (typeof o.startFct == 'function') + o.startFct(o); + + if(o.Animate) + { + var oldCurrent = parseInt($this.attr('pcur')); + var Steps = Math.abs(oldCurrent - o.Current); + var StepsTimeOut = Math.floor(o.AnimateTimeOut/o.Maximum); + + $innerdiv.queue("fx", []); + $innerdiv.stop(); + $innerdiv.animate({ width: Width+"%" }, { + duration: Math.round(StepsTimeOut*(Steps+1)), + queue: false, + easing: o.Easing, + complete: function(){ + if (typeof o.endFct == 'function') + o.endFct(o); + } + }); + + for (i=0; i<=Steps; i++) { + $innerdiv.animate({opacity: 1},{ + duration: Math.round(StepsTimeOut*i), + queue: false, + complete: function(){ + if(oldCurrent<=o.Current) + $(this).progressionSetTextTo(oldCurrent++); + else + $(this).progressionSetTextTo(oldCurrent--); + } + }); + } + } + else + { + $innerdiv.css({ width: Width+'%' }); + $innerdiv.progressionSetTextTo(o.Current); + + if (typeof o.endFct == 'function') + o.endFct(o); + } + }); + }; + + // Fonction de création de la barre + function BuildBarre($this, o) { + // On vide la barre + $this.html(''); + + $this.css({ + textAlign: 'left', + position: 'relative', + overflow: 'hidden', + backgroundColor: o.Background, + borderColor: o.BorderColor, + color: o.TextColor + }); + // Si largeur Spécifique + if(o.Width) + $this.css('width', o.Width); + // Si hauteur Spécifique + if(o.Height) + $this.css({ height: o.Height, lineHeight: o.Height }); + // Si image de fond + if(o.BackgroundImg) + $this.css({ backgroundImage: 'url(' + o.BackgroundImg + ')' }); + + $innerdiv=$("
        "); + + $("
         
        ").css({ + position: 'absolute', + width: '100%', + height: '100%', + textAlign: 'center' + }).appendTo($this); + + $(" ") + .css({ + position: 'absolute', + width: $this.width(), + textAlign: 'center' + }) + .appendTo($innerdiv); + + $this.append($innerdiv); + + // On applique le CSS de $innerdiv + $innerdiv.css({ + position: 'absolute', + width: 0, + height: '100%', + overflow: 'hidden', + backgroundColor: o.aBackground, + color: o.aTextColor + }); + // Si image de fond active + if(o.aBackgroundImg) + $innerdiv.css({ backgroundImage: 'url(' + o.aBackgroundImg + ')' }); + + $this.attr('pmax', o.Maximum); + $this.attr('pcur', 0); + }; + + // Fonction pour aller à une valeur précise + $.fn.progressionSetTextTo = function(i) { + return this.each(function() { + $this = $(this).parent(); + if($this.attr('pmax')!=100) + $this.find(".text").html(i+"/"+$this.attr('pmax')); + else + $this.find(".text").html(i+" %"); + + $this.attr('pcur', i); + }); + }; + + // Fonction d'impression dans la console javascript + function debug($txt) { + if (window.console && window.console.log) + window.console.log('jQuery Progression: ' + $txt); + }; + + + $.fn.progression.defaults = {}; + +})(jQuery); \ No newline at end of file diff --git a/setup/menus.xml b/setup/menus.xml new file mode 100644 index 0000000000..ca6bdeb5d3 --- /dev/null +++ b/setup/menus.xml @@ -0,0 +1,610 @@ + + + +0 +Admin Tools + +UI.php + +7 +application + + +5 +All Applications + +UI.php + +999 +application + + +5 +All Circuits + +UI.php + +999 +application + + +64 +All Contracts + +./UI.php + +2 +application + + +5 +All Interfaces + +UI.php + +999 +application + + +5 +All Network devices + +UI.php + +999 +application + + +5 +All Patches + +UI.php + +999 +application + + +5 +All PCs + +UI.php + +999 +application + + +5 +All Servers + +UI.php + +999 +application + + +1 +Audit + +/pages/audit.php + +4 +application + + +17 +Backup & Restore + +./db_importer.php + +998 +application + + +0 +Change Management + +./UI.php + +4 +application + + +66 +Closed Changes + +UI.php + +2 +application + + +61 +Closed Incident + +./UI.php + +2 +application + + +1 +Configuration Items + +UI.php + +2 +application + + +0 +Configuration Management + +UI.php + +2 +application + + +1 +Contacts + +UI.php + +1 +application + + +17 +CSV import + +csvimport.php + +998 +application + + +17 +Data Model + +schema.php + +999 +application + + +1 +Document + +UI.php + +6 +application + + +17 +Export + +./export.php + +1000 +application + + +1 +Grouping + +UI.php + +3 +application + + +0 +Incident Management + +./UI.php + +3 +application + + +61 +Known Errors + +./UI.php + +999 +application + + +1 +Locations + +UI.php + +5 +application + + +64 +Negociating contracts + +UI.php + +1 +application + + +66 +Open Changes + +./UI.php + +1 +application + + +61 +Open Incidents + +UI.php + +1 +application + + +2 +Persons + +UI.php + +7 +application + + +17 +Run queries + +./sibusql.php + +1001 +application + + +66 +Scheduled Outages + +./UI.php + +999 +application + + +0 +Service Management + +./UI.php + +5 +application + + +2 +Teams + +UI.php + +8 +application + + +17 +Universal Search + +UniversalSearch.php + +999 +application + + +0 +Welcome + +./UI.php + +1 +application + + diff --git a/setup/orange-progress.gif b/setup/orange-progress.gif new file mode 100644 index 0000000000000000000000000000000000000000..eec03c09e7b0208580b12b39b42f6e93844489ae GIT binary patch literal 40302 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 '+sMsg}); + } + return bResult; +} + +var aFilesToLoad = new Array(); + +function DoLoadDataAsynchronous() +{ + // Check if sample data must be loaded, or just the menus + aFilesToLoad[aFilesToLoad.length] = './menus.xml'; // First load the menus + if (($("#sample_data:checked").length == 1)) + { + PopulateDataFilesList(); // Function created in PHP to get the list of XML files on the server + } + $('#setup').block({message: '

        Loading data...

        0%

        '}); + $('#progress').progression( {Current:0, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); + LoadNextDataFile('', ''); + return false; // Stop here for now +} + +var iCounter = 0; + +function LoadNextDataFile(sData, sTextStatus) +{ + //$("#progress").html(sData); + if (iCounter < aFilesToLoad.length) + { + if (iCounter == (aFilesToLoad.length - 1)) + { + // Last file in the list (or only 1 file), this completes the session + sSessionStatus = 'end'; + } + else if (iCounter == 0) + { + // First file in the list, start the session + sSessionStatus = 'start'; + } + else + { + sSessionStatus = 'continue'; + } + iPercent = Math.round((100.0 * (1+iCounter)) / aFilesToLoad.length); + sFileName = aFilesToLoad[iCounter]; + //alert('Loading file '+sFileName+' ('+iPercent+' %) - '+sSessionStatus); + $("#progress").progression({ Current: iPercent }); + iCounter++; + $.get( 'ajax.dataloader.php', { 'file': sFileName, 'percent': iPercent, 'session_status': sSessionStatus }, LoadNextDataFile, 'html'); + } + else + { + // We're done + $('#setup').unblock(); + $('#GoToNextStep').submit(); // Use the hidden form to navigate to the next step + } +} diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php new file mode 100644 index 0000000000..4ed7aade4b --- /dev/null +++ b/setup/setuppage.class.inc.php @@ -0,0 +1,176 @@ +add_linked_script("../js/jquery.blockUI.js"); + $this->add_linked_script("./setup.js"); + $this->add_style(" +body { + background-color: #eee; + margin: 0; + padding: 0; + font-size: 10pt; +} +#setup { + width: 600px; + margin-left: auto; + margin-right: auto; + margin-top: 50px; + padding: 20px; + background-color: #fff; + border: 1px solid #000; +} +.center { + text-align: center; +} + +h1 { + color: #83b217; + font-size: 16pt; +} +h2 { + color: #000; + font-size: 14pt; +} +.v-spacer { + padding-top: 1em; +} +button { + margin-top: 1em; + padding-left: 1em; + padding-right: 1em; +} +p.info { + padding-left: 50px; + background: url(../images/info-mid.png) no-repeat top left; + height: 48px; + line-height: 48px; +} +p.ok { + padding-left: 50px; + background: url(../images/clean-mid.png) no-repeat top left; + height: 48px; + line-height: 48px; +} +p.warning { + padding-left: 50px; + background: url(../images/messagebox_warning-mid.png) no-repeat top left; + height: 48px; + line-height: 48px; +} +p.error { + padding-left: 50px; + background: url(../images/stop-mid.png) no-repeat top left; + height: 48px; + line-height: 48px; +} +td.label { + text-align: left; +} +td.input { + text-align: left; +} +table.formTable { + border: 0; + cellpadding: 2px; + cellspacing: 0; +} +.wizlabel, .wizinput { + color: #000; + font-size: 10pt; +} +.wizhelp { + color: #333; + font-size: 8pt; +} +#progress { + border:1px solid #000000; + width: 180px; + height: 20px; + line-height: 20px; + text-align: center; + margin: 5px; +} + "); + } + public function info($sText) + { + $this->add("

        $sText

        \n"); + $this->log("Info - ".$sText); + } + + public function ok($sText) + { + $this->add("

        $sText

        \n"); + $this->log("Ok - ".$sText); + } + + public function warning($sText) + { + $this->add("

        $sText

        \n"); + $this->log("Warning - ".$sText); + } + + public function error($sText) + { + $this->add("

        $sText

        \n"); + $this->log("Error - ".$sText); + } + + public function form($aData) + { + $this->add("\n"); + foreach($aData as $aRow) + { + $this->add("\n"); + if (isset($aRow['label']) && isset($aRow['label']) && isset($aRow['help'])) + { + $this->add("\n"); + $this->add("\n"); + $this->add("\n"); + } + else if (isset($aRow['label']) && isset($aRow['help'])) + { + $this->add("\n"); + $this->add("\n"); + } + else if (isset($aRow['label']) && isset($aRow['input'])) + { + $this->add("\n"); + $this->add("\n"); + } + else if (isset($aRow['label'])) + { + $this->add("\n"); + } + $this->add("\n"); + } + $this->add("
        {$aRow['label']}{$aRow['input']}{$aRow['help']}{$aRow['label']}{$aRow['help']}{$aRow['label']}{$aRow['input']}{$aRow['label']}
        \n"); + } + + public function output() + { + $this->s_content = "
        {$this->s_content}\n
        \n"; + return parent::output(); + } + + public static function log($sText) + { + $hLogFile = @fopen(INSTALL_LOG_FILE, 'a'); + if ($hLogFile !== false) + { + $sDate = date('Y-m-d H:i:s'); + fwrite($hLogFile, "$sDate - $sText\n"); + fclose($hLogFile); + } + } +} // End of class +?> diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php new file mode 100644 index 0000000000..f4149aa41d --- /dev/null +++ b/setup/xmldataloader.class.inc.php @@ -0,0 +1,282 @@ +StartSession(); + * $oLoader->LoadFile('./organizations.xml'); + * $oLoader->LoadFile('./locations.xml'); + * $oLoader->EndSession(); + */ +class XMLDataLoader +{ + protected $m_aKeys; + protected $m_aObjectsCache; + protected $m_bSessionActive; + protected $m_oChange; + protected $m_sCacheFileName; + + public function __construct($sConfigFileName) + { + $this->m_aKeys = array(); + $this->m_aObjectsCache = array(); + $this->m_oChange = null; + $this->m_sCacheFileName = dirname(__FILE__).'/'.KEYS_CACHE_FILE; + $this->InitDataModel($sConfigFileName); + $this->LoadKeysCache(); + $this->m_bSessionActive = true; + + } + + public function StartSession($oChange) + { + $this->m_oChange = $oChange; + $this->m_bSessionActive = true; + } + + public function EndSession() + { + $this->ResolveExternalKeys(); + $this->m_bSessionActive = false; + } + + public function __destruct() + { + // Stopping in the middle of a session, let's save the context information + if ($this->m_bSessionActive) + { + $this->SaveKeysCache(); + } + else + { + $this->ClearKeysCache(); + } + } + + /** + * Initializes the ORM (MetaModel) + */ + protected function InitDataModel($sConfigFileName, $bAllowMissingDatabase = true) + { + require_once('../core/coreexception.class.inc.php'); + require_once('../core/attributedef.class.inc.php'); + require_once('../core/filterdef.class.inc.php'); + require_once('../core/stimulus.class.inc.php'); + require_once('../core/MyHelpers.class.inc.php'); + require_once('../core/expression.class.inc.php'); + require_once('../core/cmdbsource.class.inc.php'); + require_once('../core/sqlquery.class.inc.php'); + require_once('../core/dbobject.class.php'); + require_once('../core/dbobjectsearch.class.php'); + require_once('../core/dbobjectset.class.php'); + require_once('../core/userrights.class.inc.php'); + MetaModel::Startup($sConfigFileName, $bAllowMissingDatabase); + } + + /** + * Stores the keys & object cache in a file + */ + protected function SaveKeysCache() + { + $hFile = @fopen($this->m_sCacheFileName, 'w'); + if ($hFile !== false) + { + $sData = serialize( array('keys' => $this->m_aKeys, + 'objects' => $this->m_aObjectsCache, + 'change' => $this->m_oChange)); + fwrite($hFile, $sData); + fclose($hFile); + } + else + { + echo "

        Error: Cannot write to file: '{$this->m_sCacheFileName}'!

        "; + } + } + + /** + * Loads the keys & object cache from the tmp file + */ + protected function LoadKeysCache() + { + $sFileContent = @file_get_contents($this->m_sCacheFileName); + if (!empty($sFileContent)) + { + $aCache = unserialize($sFileContent); + $this->m_aKeys = $aCache['keys']; + $this->m_aObjectsCache = $aCache['objects']; + $this->m_oChange = $aCache['change']; + } + } + + /** + * Remove the tmp file used to store the keys cache + */ + protected function ClearKeysCache() + { + if(is_file($this->m_sCacheFileName)) + { + unlink($this->m_sCacheFileName); + } + else + { + //echo "

        Hm, it looks like the file does not exist!!!

        "; + } + $this->m_aKeys = array(); + $this->m_aObjectsCache = array(); + } + + /** + * Helper function to load the objects from a standard XML file into the database + */ + function LoadFile($sFilePath) + { + global $aKeys; + + $oXml = simplexml_load_file($sFilePath); + + $aReplicas = array(); + foreach($oXml as $sClass => $oXmlObj) + { + $iSrcId = (integer)$oXmlObj['id']; // Mandatory to cast + + // Import algorithm + // Here enumerate all the attributes of the object + // for all attribute that is neither an external field + // not an external key, assign it + // Store all external keys for further reference + // Create the object an store the correspondence between its newly created Id + // and its original Id + // Once all the objects have been created re-assign all the external keys to + // their actual Ids + $oTargetObj = MetaModel::NewObject($sClass); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()) && ($sAttCode != 'finalclass') ) + { + if ($oAttDef->IsExternalKey()) + { + $iDstObj = (integer)($oXmlObj->$sAttCode); + $iExtKey = $this->GetObjectKey($oAttDef->GetTargetClass(), $iDstObj); + if ($iExtKey == 0) + { + $iExtKey = -$iDstObj; // Convention: Unresolved keys are stored as negative ! + } + // tested by Romain, little impact on perf (not significant on the intial setup) + //$oTargetObj->CheckValue($sAttCode, $iExtKey); + $oTargetObj->Set($sAttCode, $iExtKey); + } + else + { + // tested by Romain, little impact on perf (not significant on the intial setup) + //$oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode); + $oTargetObj->Set($sAttCode, (string)$oXmlObj->$sAttCode); + } + } + } + $this->StoreObject($sClass, $oTargetObj, $iSrcId); + } + return true; + } + + /** + * Get the new ID of an object in the database given its original ID + * This may fail (return 0) if the object has not yet been created in the database + * This is why the order of the import may be important + */ + protected function GetObjectKey($sClass, $iSrcId) + { + if (isset($this->m_aKeys[$sClass]) && isset($this->m_aKeys[$sClass][$iSrcId])) + { + return $this->m_aKeys[$sClass][$iSrcId]; + } + return 0; + } + + /** + * Store an object in the database and remember the mapping + * between its original ID and the newly created ID in the database + */ + protected function StoreObject($sClass, $oTargetObj, $iSrcId) + { + try + { + if (is_subclass_of($oTargetObj, 'CMDBObject')) + { + $iObjId = $oTargetObj->DBInsertTrackedNoReload($this->m_oChange); + } + else + { + $iObjId = $oTargetObj->DBInsertNoReload(); + } + + } + catch(Exception $e) + { + echo $e->GetHtmlDesc(); + } + $aParentClasses = MetaModel::EnumParentClasses($sClass); + $aParentClasses[] = $sClass; + foreach($aParentClasses as $sObjClass) + { + $this->m_aKeys[$sObjClass][$iSrcId] = $iObjId; + } + $this->m_aObjectsCache[$sClass][$iObjId] = $oTargetObj; + } + + /** + * Maps an external key to its (newly created) value + */ + protected function ResolveExternalKeys() + { + foreach($this->m_aObjectsCache as $sClass => $oObjList) + { + foreach($oObjList as $oTargetObj) + { + $bChanged = false; + $sClass = get_class($oTargetObj); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ( ($oAttDef->IsExternalKey()) && ($oTargetObj->Get($sAttCode) < 0) ) // Convention unresolved key = negative + { + $iExtKey = $this->GetObjectKey($oAttDef->GetTargetClass(), -$oTargetObj->Get($sAttCode)); + if ($iExtKey == 0) + { + echo "Warning: unresolved extkey in $sClass::".$oTargetObj->GetName()."(".$oTargetObj->GetKey().")::$sAttCode=".$oAttDef->GetTargetClass()."[".$oTargetObj->Get($sAttCode)."]
        \n"; + echo "
        aKeys[".$oAttDef->GetTargetClass()."]:\n";
        +							print_r($this->m_aKeys[$oAttDef->GetTargetClass()]);
        +							echo "
        \n"; + } + else + { + $bChanged = true; + $oTargetObj->Set($sAttCode, $iExtKey); + } + } + } + if ($bChanged) + { + try + { + if (is_subclass_of($oTargetObj, 'CMDBObject')) + { + $oTargetObj->DBUpdateTracked($this->m_oChange); + } + else + { + $oTargetObj->DBUpdate(); + } + } + catch(Exception $e) + { + echo $e->GetHtmlDesc(); + } + } + } + } + + return true; + } +} +?> diff --git a/toolkit.php b/toolkit.php new file mode 100644 index 0000000000..b71abae3ca --- /dev/null +++ b/toolkit.php @@ -0,0 +1,53 @@ +iTop"; + +$sStyle = " + border: 1px dashed #CCC; + background: #CCF; + padding:1.5em; +"; +echo "
        \n"; +echo "Main page
        \n"; +echo "CSV import (shortcut)
        \n"; +echo "Universal search (shortcut)
        \n"; +echo "

        Itop consultant

        \n"; +echo "Check model, Create DB, Update DB (new class, new attribute)
        \n"; +echo "Backup and restore (shortcut)
        \n"; +echo "Objects schema (shortcut)
        \n"; +echo "

        Not working or deprecated

        \n"; +echo "Data generator
        \n"; +echo "ITop finder
        \n"; +echo "navITop
        \n"; +echo "@ITop
        \n"; +echo "
        \n"; + +echo "

        phpMyORM

        "; + + +$sStyle = " + border: 1px dashed #CCC; + background: #CFC; + padding:1.5em; +"; +echo "
        \n"; + + +echo "Manage configurations
        \n"; +echo "Core unit tests
        \n"; + + +echo "
        \n"; + +echo "

        phpinfo()

        "; + + +$sStyle = " + border: 1px dashed #CCC; + background: #FCC; + padding:1.5em; +"; +echo "
        \n"; +phpinfo(); +echo "
        \n"; +?> diff --git a/webservices/export.php b/webservices/export.php new file mode 100644 index 0000000000..530f952790 --- /dev/null +++ b/webservices/export.php @@ -0,0 +1,73 @@ +add("Unsupported format '$sFormat'. Possible values are: html, csv or xml."); + } + } + } + catch(Exception $e) + { + $oP = new web_page("iTop - Export"); + $oP->p("Error the query can not be executed."); + $oP->p($e->GetHtmlDesc()); + } +} +if (!$oP) +{ + // Display a short message about how to use this page + $oP = new web_page("iTop - Export"); + $oP->p("General purpose export page."); + $oP->p("Parameters:"); + $oP->p("
        • expression: an OQL expression (URL encoded if needed)
        • +
        • format: (optional, default is html) the desired output format. Can be one of 'html', 'csv' or 'xml'
        • +
        "); +} + +$oP->output(); +?> diff --git a/webservices/import.php b/webservices/import.php new file mode 100644 index 0000000000..59b9a2cc68 --- /dev/null +++ b/webservices/import.php @@ -0,0 +1,168 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +// +// Known limitations +// - output still in html, because the errors are not displayed in xml +// - could not set the external keys by the mean of a reconciliation (the exact key must be given) +// - reconciliation is made on the first column +// - no option to force 'always create' or 'never create' +// +// Known issues +// - ALMOST impossible to troubleshoot when an externl key has a wrong value +// - no character escaping in the xml output (yes !?!?!) +// - not outputing xml when a wrong input is given (class, attribute names) +// - for a bizIncidentTicket you may use the name as the reconciliation key, +// but that attribute is in fact recomputed by the application! An error should be raised somewhere +// + +require_once('../application/application.inc.php'); +require_once('../application/webpage.class.inc.php'); +require_once('../application/csvpage.class.inc.php'); +require_once('../application/xmlpage.class.inc.php'); + +require_once('../application/startup.inc.php'); + +require_once('../application/loginwebpage.class.inc.php'); +login_web_page::DoLogin(); // Check user rights and prompt if needed + +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +//$iActiveNodeId = utils::ReadParam('menu', -1); +//$currentOrganization = utils::ReadParam('org_id', ''); + +// Main program + +//$oP = new XMLPage("iTop - Bulk import"); +$oP = new web_page("iTop - Bulk import"); +try +{ + $sClass = utils::ReadParam('class', ''); + $sSep = utils::ReadParam('separator', ';'); + $sCSVData = utils::ReadPostedParam('csvdata'); + + $oCSVParser = new CSVParser($sCSVData); + $oCSVParser->SetSeparator($sSep); + $oCSVParser->SetSkipLines(1); + + // Limitation: as the attribute list is in the first line, we can not match external key by an third-party attribute + $sRawFieldList = $oCSVParser->ListFields(); + $aAttList = array(); + foreach($sRawFieldList as $iField => $sFieldName) + { + $aAttList[$sFieldName] = $iField; + } + $aExtKeys = array(); + + // Limitation: the reconciliation key is the first attribute + $aReconcilKeys = array($sRawFieldList[0]); + +// print_r($oCSVParser->ListFields()); +// print_r($oCSVParser->ToArray($oCSVParser->ListFields())); + + $aData = $oCSVParser->ToArray(); + $oBulk = new BulkChange( + $sClass, + $aData, + $aAttList, + $aReconcilKeys, + $aExtKeys + ); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString.' (bulk load by web service)'); + $iChangeId = $oMyChange->DBInsert(); + + $aRes = $oBulk->Process($oMyChange); + + // Setup result presentation + // + $aDisplayConfig = array(); + $aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>""); + $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); + if (isset($iPKeyId)) + { + $aDisplayConfig["col$iPKeyId"] = array("label"=>"pkey", "description"=>""); + } + foreach($aReconcilKeys as $iCol => $sAttCode) + { + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + foreach ($aAttList as $sAttCode => $iCol) + { + $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); + $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); + } + + + $aResultDisp = array(); // to be displayed + foreach($aRes as $iRow => $aRowData) + { + $aRowDisp = array(); + $aRowDisp["__RECONCILIATION__"] = $aRowData["__RECONCILIATION__"]; + $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); + foreach($aRowData as $sKey => $value) + { + if ($sKey == '__RECONCILIATION__') continue; + if ($sKey == '__STATUS__') continue; + + switch (get_class($value)) + { + case 'CellChangeSpec_Void': + $sClass = ''; + break; + case 'CellChangeSpec_Unchanged': + $sClass = ''; + break; + case 'CellChangeSpec_Modify': + $sClass = 'csvimport_ok'; + break; + case 'CellChangeSpec_Init': + $sClass = 'csvimport_init'; + break; + case 'CellChangeSpec_Issue': + $sClass = 'csvimport_error'; + break; + } + if (empty($sClass)) + { + $aRowDisp[$sKey] = $value->GetDescription(); + } + else + { + $aRowDisp[$sKey] = "
        ".$value->GetDescription()."
        "; + } + } + $aResultDisp[$iRow] = $aRowDisp; + } + $oP->table($aDisplayConfig, $aResultDisp); + +} +catch(Exception $e) +{ + $oP->add(''.((string)$e).''); +} + +$oP->output(); +?> From 0957c555c33dccb82128525412c79af90dbd9db8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 28 Apr 2009 13:24:12 +0000 Subject: [PATCH 003/970] Fixed bugs linked to the bookmark creation: - There must be a user specific menu entry in the list of menuNodes - The asynchronous ajax page must use the authentication stored in the session - The query must be stored in OQL instead of sibusQL SVN:trunk[58] --- .../userrights/userrightsmatrix.class.inc.php | 23 +++++++++++++++ application/menunode.class.inc.php | 29 ++++++++++++------- pages/UniversalSearch.php | 6 ++-- pages/ajax.render.php | 12 ++++++-- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index de90baa473..75becda135 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -268,6 +268,29 @@ class UserRightsMatrix extends UserRightsAddOnAPI } } } + // Create the "My Bookmarks" menu item (parent_id = 0, rank = 6) + if ($bNewUser) + { + $bAddMenu = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT menuNode WHERE type = 'user' AND parent_id = 0 AND user_id = $iUserId")); + $bAddMenu = ($oSet->Count() < 1); + } + if ($bAddMenu) + { + $oMenu = MetaModel::NewObject('menuNode'); + $oMenu->Set('type', 'user'); + $oMenu->Set('parent_id', 0); // It's a toplevel entry + $oMenu->Set('rank', 6); // Located just above the Admin Tools section (=7) + $oMenu->Set('name', 'My Bookmarks'); + $oMenu->Set('label', 'My Favorite Items'); + $oMenu->Set('hyperlink', 'UI.php'); + $oMenu->Set('template', '

        My bookmarks

        This section contains my most favorite search results

        '); + $oMenu->Set('user_id', $iUserId); + $oMenu->DBInsert(); + } } diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index f571f40f30..e0b72ec622 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -89,14 +89,17 @@ class menuNode extends DBObject return $this->Get('hyperlink')."?".implode("&", $aParams); } - public function GetChildNodesSet($sType) + public function GetChildNodesSet($sType = null) { $oSearchFilter = new DBObjectSearch("menuNode"); $oSearchFilter->AddCondition('parent_id', $this->GetKey(), '='); - $oSearchFilter->AddCondition('type', $sType, '='); - if ($sType == 'user') + if ($sType != null) { - $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + $oSearchFilter->AddCondition('type', $sType, '='); + if ($sType == 'user') + { + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + } } $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); return $oSet; @@ -184,10 +187,13 @@ class menuNode extends DBObject { $oSearchFilter = new DbObjectSearch("menuNode"); $oSearchFilter->AddCondition('parent_id', 0, '='); - $oSearchFilter->AddCondition('type', $sType, '='); - if ($sType == 'user') + if ($sType != null) { - $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + $oSearchFilter->AddCondition('type', $sType, '='); + if ($sType == 'user') + { + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + } } $oRootSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); while($oNode = $oRootSet->Fetch()) @@ -207,10 +213,13 @@ class menuNode extends DBObject $oSearchFilter = new DBObjectSearch("menuNode"); $oSearchFilter->AddCondition('parent_id', $this->Get('parent_id')); $oSearchFilter->AddCondition('rank', $this->Get('rank'), '>'); - $oSearchFilter->AddCondition('type', $sType, '='); - if ($sType == 'user') + if ($sType != null) { - $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + $oSearchFilter->AddCondition('type', $sType, '='); + if ($sType == 'user') + { + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + } } $oSet = new DBObjectSet($oSearchFilter, array('rank'=> true)); // Order by rank (true means ascending) return $oSet; diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index ebe762fb36..e649f8d2b8 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -58,14 +58,14 @@ if ($oFilter != null) $oResultBlock->RenderContent($oP); // Menu node - $sFilter = $oFilter->ToSibusQL(); + $sFilter = $oFilter->ToOQL(); $sMenuNodeContent = << -$sFilter +$sFilter

        -$sFilter +$sFilter
        EOF; diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 176a32128f..35af6a8589 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -6,10 +6,18 @@ require_once('../application/wizardhelper.class.inc.php'); require_once('../application/ui.linkswidget.class.inc.php'); require_once('../application/startup.inc.php'); -if (isset($_SERVER['PHP_AUTH_USER'])) +session_start(); +if (isset($_SESSION['auth_user'])) { + $sAuthUser = $_SESSION['auth_user']; + $sAuthPwd = $_SESSION['auth_pwd']; // Attempt to login, fails silently - UserRights::Login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); + UserRights::Login($sAuthUser, $sAuthPwd); +} +else +{ + // No session information + echo "

        No session information

        \n"; } $oPage = new ajax_page(""); From da72a5c778cbcd76f118b0b5563b4788bf318831 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Jun 2009 14:33:48 +0000 Subject: [PATCH 004/970] Fix for Ticket #22 (impossible to create change or incident tickets) SVN:trunk[68] --- js/{LinksWidget.js => linkswidget.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename js/{LinksWidget.js => linkswidget.js} (100%) diff --git a/js/LinksWidget.js b/js/linkswidget.js similarity index 100% rename from js/LinksWidget.js rename to js/linkswidget.js From 08573d2491d8343468da03a9eb671fe68775438c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 8 Jul 2009 14:53:53 +0000 Subject: [PATCH 005/970] Fixed bug #24: choice of organizations was limited to organization in "implementation". SVN:trunk[75] --- business/itop.business.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 31e561e923..f4ca40d13b 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -146,7 +146,7 @@ class logRealObject extends cmdbAbstractObject MetaModel::Init_Params($aParams); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization Id", "description"=>"ID of the object owner organization", "allowed_values"=>new ValueSetObjects("bizOrganization: status Contains 'implementation'", 'name', array('name'=>true)), "sql"=>"org_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization Id", "description"=>"ID of the object owner organization", "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddFilterFromAttribute("name"); From a3345bc02107720e4d1ea951a6dad07ac2194419 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 8 Jul 2009 16:10:17 +0000 Subject: [PATCH 006/970] - The page was broken because there was no user login ! SVN:trunk[79] --- pages/audit.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pages/audit.php b/pages/audit.php index 6f69cfdbb8..88ce7c90ab 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -7,6 +7,9 @@ $currentOrganization = utils::ReadParam('org_id', ''); $operation = utils::ReadParam('operation', ''); $oAppContext = new ApplicationContext(); +require_once('../application/loginwebpage.class.inc.php'); +login_web_page::DoLogin(); // Check user rights and prompt if needed + $oP = new iTopWebPage("iTop - CMDB Audit", $currentOrganization); function GetRuleResultSet($iRuleId, $oDefinitionFilter) From 10442e65faa77ed7147778ac8903f5ec8e610b48 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 8 Jul 2009 16:13:10 +0000 Subject: [PATCH 007/970] - Fixed audit menu - Removed backup/restore menu that is not working... - Moved 'export' to the 'webservices' directory SVN:trunk[80] --- setup/menus.xml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/setup/menus.xml b/setup/menus.xml index ca6bdeb5d3..087d64ea3f 100644 --- a/setup/menus.xml +++ b/setup/menus.xml @@ -151,20 +151,11 @@ 1 Audit -/pages/audit.php +./audit.php 4 application - -17 -Backup & Restore - -./db_importer.php - -998 -application - 0 Change Management @@ -367,7 +358,7 @@ td.dashboard { 17 Export -./export.php +../webservices/export.php 1000 application From 665d90175fd5f9ecd6ab074aea63881247d25a69 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 18 Jul 2009 17:24:25 +0000 Subject: [PATCH 008/970] - reintegrated the changes (mostly bug fixes) from the 0.7.2 branch SVN:trunk[83] --- .../userrights/userrightsmatrix.class.inc.php | 14 ++-- application/cmdbabstract.class.inc.php | 18 ++--- application/displayblock.class.inc.php | 30 +++++++-- application/itopwebpage.class.inc.php | 3 +- application/ui.linkswidget.class.inc.php | 2 +- application/uiwizard.class.inc.php | 67 ++++++++++--------- business/templates/Circuits.html | 2 +- business/templates/application.html | 2 +- business/templates/change.html | 2 +- business/templates/contract.html | 2 +- business/templates/default.html | 2 +- business/templates/document.html | 2 +- business/templates/group.html | 2 +- business/templates/interface.html | 2 +- business/templates/knownError.html | 2 +- business/templates/location.html | 2 +- business/templates/network.device.html | 2 +- business/templates/pc.html | 2 +- business/templates/person.html | 2 +- business/templates/server.html | 2 +- business/templates/service.html | 2 +- business/templates/software.html | 2 +- business/templates/team.html | 2 +- business/templates/ticket.html | 2 +- core/userrights.class.inc.php | 8 +-- pages/UI.php | 9 +-- pages/opensearch.xml | 8 --- pages/schema.php | 2 +- setup/data/03.persons.xml | 20 +++--- setup/menus.xml | 2 +- 30 files changed, 114 insertions(+), 105 deletions(-) delete mode 100644 pages/opensearch.xml diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 75becda135..8bbe15cc81 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -312,7 +312,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI $oLogin = $oSet->Fetch(); if ($oLogin->Get('password') == $sPassword) { - return true; + return $oLogin->Get('userid'); } // todo: throw an exception? return false; @@ -324,7 +324,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $oNullFilter; } - public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances) { if (!array_key_exists($iActionCode, self::$m_aActionCodes)) { @@ -332,7 +332,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI } $sAction = self::$m_aActionCodes[$iActionCode]; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassGrant WHERE class = '$sClass' AND action = '$sAction' AND userid = '$iUserId'")); if ($oSet->Count() < 1) { return UR_ALLOWED_NO; @@ -352,7 +352,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $iRetCode; } - public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) { if (!array_key_exists($iActionCode, self::$m_aActionCodes)) { @@ -360,7 +360,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI } $sAction = self::$m_aActionCodes[$iActionCode]; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixAttributeGrant WHERE UserRightsMatrixAttributeGrant.class = '$sClass' AND UserRightsMatrixAttributeGrant.attcode = '$sAttCode' AND UserRightsMatrixAttributeGrant.action = '$sAction' AND UserRightsMatrixAttributeGrant.login = '$sUserName'")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixAttributeGrant WHERE class = '$sClass' AND attcode = '$sAttCode' AND action = '$sAction' AND userid = '$iUserId'")); if ($oSet->Count() < 1) { return UR_ALLOWED_NO; @@ -380,9 +380,9 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $iRetCode; } - public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances) { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND userid = '$iUserId'")); if ($oSet->Count() < 1) { return UR_ALLOWED_NO; diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index d29008dd2c..3bd6401a5f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -142,7 +142,7 @@ abstract class cmdbAbstractObject extends CMDBObject if (!empty($sTemplate)) { $oTemplate = new DisplayTemplate($sTemplate); - $oTemplate->Render($oPage, array('class'=> get_class($this),'pkey'=> $this->GetKey(), 'name' => $this->GetName())); + $oTemplate->Render($oPage, array('class_name'=> MetaModel::GetName(get_class($this)),'class'=> get_class($this),'pkey'=> $this->GetKey(), 'name' => $this->GetName())); } else { @@ -152,7 +152,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oSingletonFilter->AddCondition('pkey', array($this->GetKey())); $oBlock = new MenuBlock($oSingletonFilter, 'popup', false); $oBlock->Display($oPage, -1); - $oPage->add("

        ".Metamodel::GetName(get_class($this)).": ".$this->GetDisplayName()."

        \n"); + $oPage->add("

        ".Metamodel::GetName(MetaModel::GetName(get_class($this))).": ".$this->GetDisplayName()."

        \n"); $oHistoryFilter = new DBObjectSearch('CMDBChangeOpSetAttribute'); $oHistoryFilter->AddCondition('objkey', $this->GetKey()); $oBlock = new HistoryBlock($oHistoryFilter, 'toggle', false); @@ -276,13 +276,15 @@ abstract class cmdbAbstractObject extends CMDBObject } $oMenuBlock = new MenuBlock($oSet->GetFilter()); $sHtml .= ''; + $sColspan = ''; if ($bDisplayMenu) { - $sHtml .= ''; } - $sHtml .= ''; $sHtml .= '
        '; + $sColspan = 'colspan="2"'; + $sHtml .= '
         '.$oSet->Count().' object(s)'; $sHtml .= $oMenuBlock->GetRenderContent($oPage, $sLinkageAttribute); $sHtml .= '
        '; + $sHtml .= "
        "; $sHtml .= $oPage->GetTable($aAttribs, $aValues, array('class'=>$sClassName, 'filter'=>$oSet->GetFilter()->serialize(), 'preview' => true)); $sHtml .= '
        '; @@ -519,14 +521,14 @@ abstract class cmdbAbstractObject extends CMDBObject { $sHTMLValue = ""; } - else if (count($aAllowedValues) > 20) + else if (count($aAllowedValues) > 50) { // too many choices, use an autocomplete // The input for the auto complete - $sHTMLValue = ""; + $sHTMLValue = ""; // another hidden input to store & pass the object's Id - $sHTMLValue .= "\n"; - $oPage->add_ready_script("\$('#$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); + $sHTMLValue .= "\n"; + $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); } else { diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index d93cb7cd4e..f41ab6a288 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -248,10 +248,6 @@ class DisplayBlock $bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false; if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) { - if (!$bDashboardMode) - { - $sHtml .= $oPage->GetP($this->m_oSet->Count()." object(s)."); - } $sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : ''; $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $sLinkage, !$bDashboardMode /* bDisplayMenu */); } @@ -616,7 +612,8 @@ class MenuBlock extends DisplayBlock } else { - $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("http://localhost:81/pages/UI.php?operation=search&filter=$sFilter&$sContext")); + $sUrl = self::GetAbsoluteUrl(); + $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); $aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } @@ -663,7 +660,8 @@ class MenuBlock extends DisplayBlock else { // many objects in the set, possible actions are: new / modify all / delete all - $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("http://localhost:81/pages/UI.php?operation=search&filter=$sFilter&$sContext")); + $sUrl = self::GetAbsoluteUrl(); + $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); $aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } @@ -680,7 +678,25 @@ class MenuBlock extends DisplayBlock $sHtml .= "
      \n\n
    \n"; $oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n"); return $sHtml; - } + } + static public function GetAbsoluteUrl() + { + // Build an absolute URL to this page on this server/port + $sServerName = $_SERVER['SERVER_NAME']; + $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + if ($sProtocol == 'http') + { + $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + else + { + $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + $sPath = $_SERVER['REQUEST_URI']; + $sUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}"; + + return $sUrl; + } } ?> diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index de1cd14ca5..0b69f6669f 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -255,7 +255,8 @@ EOF $sText = "Your search"; $sOnClick = " onclick=\"this.value='';this.onclick=null;\""; } - echo "
    "; + $sUserName = UserRights::GetUser(); + echo "
    Logged as '$sUserName'   "; echo "
    \n"; echo "
    \n"; diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index f980adec10..31be4bef4a 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -61,7 +61,7 @@ class UILinksWidget } // Many values (or even a unknown list) display an autocomplete - if ( (count($aAllowedValues) == 0) || (count($aAllowedValues) > 20) ) + if ( (count($aAllowedValues) == 0) || (count($aAllowedValues) > 50) ) { // too many choices, use an autocomplete // The input for the auto complete diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 3c8a61192d..53ef12a88c 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -34,42 +34,45 @@ class UIWizard $sJSHandlerCode = ''; // Javascript code to be executed each time this step of the wizard is entered foreach($aStep as $sAttCode) { - $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); - $sAttLabel = $oAttDef->GetLabel(); - $iOptions = isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0; - - $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); - if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) + if ($sAttCode != 'finalclass') // Do not displa the attribute that stores the actual class name { - $aFields[$sAttCode] = array(); - foreach($aPrerequisites as $sCode) + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + $sAttLabel = $oAttDef->GetLabel(); + $iOptions = isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0; + + $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); + if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) { - $aFields[$sAttCode][$sCode] = ''; + $aFields[$sAttCode] = array(); + foreach($aPrerequisites as $sCode) + { + $aFields[$sAttCode][$sCode] = ''; + } } + if (count($aPrerequisites) > 0) + { + $aOptions[] = 'Prerequisites: '.implode(', ', $aPrerequisites); + } + + $sFieldFlag = ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) ? ' *' : ''; + $oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId"); + $aFieldsMap[$iMaxInputId] = $sAttCode; + $aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "
    $sHTMLValue
    "); + if ($oAttDef->GetValuesDef() != null) + { + $sJSHandlerCode .= "\toWizardHelper.RequestAllowedValues('$sAttCode');\n"; + } + if ($oAttDef->GetDefaultValue() != null) + { + $sJSHandlerCode .= "\toWizardHelper.RequestDefaultValue('$sAttCode');\n"; + } + if ($oAttDef->IsLinkSet()) + { + $sJSHandlerCode .= "\toLinkWidgetatt_$iMaxInputId.Init();"; + } + $iMaxInputId++; } - if (count($aPrerequisites) > 0) - { - $aOptions[] = 'Prerequisites: '.implode(', ', $aPrerequisites); - } - - $sFieldFlag = ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) ? ' *' : ''; - $oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId"); - $aFieldsMap[$iMaxInputId] = $sAttCode; - $aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "
    $sHTMLValue
    "); - if ($oAttDef->GetValuesDef() != null) - { - $sJSHandlerCode .= "\toWizardHelper.RequestAllowedValues('$sAttCode');\n"; - } - if ($oAttDef->GetDefaultValue() != null) - { - $sJSHandlerCode .= "\toWizardHelper.RequestDefaultValue('$sAttCode');\n"; - } - if ($oAttDef->IsLinkSet()) - { - $sJSHandlerCode .= "\toLinkWidgetatt_$iMaxInputId.Init();"; - } - $iMaxInputId++; } //$aDetails[] = array('label' => '', 'value' => ''); $this->m_oPage->details($aDetails); diff --git a/business/templates/Circuits.html b/business/templates/Circuits.html index c3de83c890..f04fe3a879 100644 --- a/business/templates/Circuits.html +++ b/business/templates/Circuits.html @@ -1,6 +1,6 @@ diff --git a/business/templates/application.html b/business/templates/application.html index 4e3c7575e7..f934b7b489 100644 --- a/business/templates/application.html +++ b/business/templates/application.html @@ -1,6 +1,6 @@ diff --git a/business/templates/change.html b/business/templates/change.html index 694b26fd63..f2bcd67d7a 100644 --- a/business/templates/change.html +++ b/business/templates/change.html @@ -1,6 +1,6 @@ diff --git a/business/templates/contract.html b/business/templates/contract.html index 574c6e37e8..110fcdb0e5 100644 --- a/business/templates/contract.html +++ b/business/templates/contract.html @@ -1,6 +1,6 @@ diff --git a/business/templates/default.html b/business/templates/default.html index 238a714d85..564b009b51 100644 --- a/business/templates/default.html +++ b/business/templates/default.html @@ -1,6 +1,6 @@ diff --git a/business/templates/document.html b/business/templates/document.html index 470559e8fa..cd40cfe785 100644 --- a/business/templates/document.html +++ b/business/templates/document.html @@ -1,6 +1,6 @@ diff --git a/business/templates/group.html b/business/templates/group.html index 11bc838458..fca5fb1543 100644 --- a/business/templates/group.html +++ b/business/templates/group.html @@ -1,6 +1,6 @@ diff --git a/business/templates/interface.html b/business/templates/interface.html index 3f1fe7f95a..ffa8c7495e 100644 --- a/business/templates/interface.html +++ b/business/templates/interface.html @@ -1,6 +1,6 @@ diff --git a/business/templates/knownError.html b/business/templates/knownError.html index 3c321384da..75bcc9eaf6 100644 --- a/business/templates/knownError.html +++ b/business/templates/knownError.html @@ -1,6 +1,6 @@ bizKnownError: pkey = $pkey$ diff --git a/business/templates/location.html b/business/templates/location.html index af2df49e01..748ccd7393 100644 --- a/business/templates/location.html +++ b/business/templates/location.html @@ -1,6 +1,6 @@ diff --git a/business/templates/network.device.html b/business/templates/network.device.html index cb3a323851..01085e2ecb 100644 --- a/business/templates/network.device.html +++ b/business/templates/network.device.html @@ -1,6 +1,6 @@ diff --git a/business/templates/pc.html b/business/templates/pc.html index 55597a4acb..9323a913de 100644 --- a/business/templates/pc.html +++ b/business/templates/pc.html @@ -1,6 +1,6 @@ diff --git a/business/templates/person.html b/business/templates/person.html index 9bc09ad331..567f7b5431 100644 --- a/business/templates/person.html +++ b/business/templates/person.html @@ -1,6 +1,6 @@ diff --git a/business/templates/server.html b/business/templates/server.html index f6f5793ed6..98185836b2 100644 --- a/business/templates/server.html +++ b/business/templates/server.html @@ -1,6 +1,6 @@ diff --git a/business/templates/service.html b/business/templates/service.html index 747ab187ab..7e6443eba4 100644 --- a/business/templates/service.html +++ b/business/templates/service.html @@ -1,6 +1,6 @@ diff --git a/business/templates/software.html b/business/templates/software.html index ee51b9651b..3f690daad2 100644 --- a/business/templates/software.html +++ b/business/templates/software.html @@ -1,6 +1,6 @@ diff --git a/business/templates/team.html b/business/templates/team.html index 05e0ec2d2b..7e1e5cd6ec 100644 --- a/business/templates/team.html +++ b/business/templates/team.html @@ -1,6 +1,6 @@ diff --git a/business/templates/ticket.html b/business/templates/ticket.html index 1c034cc7b2..425c928401 100644 --- a/business/templates/ticket.html +++ b/business/templates/ticket.html @@ -1,6 +1,6 @@ diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index b62c8a0424..01428ae73c 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -176,7 +176,7 @@ class UserRights public static function GetFilter($sClass) { - if (!MetaModel::HasCategory($sClass, 'bizModel')) return new DBObjectSearch($sClass); + if (!MetaModel::HasCategory($sClass, 'bizmodel')) return new DBObjectSearch($sClass); if (!self::CheckLogin()) return false; return self::$m_oAddOn->GetFilter(self::$m_iUserId, $sClass); @@ -184,7 +184,7 @@ class UserRights public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $aInstances) { - if (!MetaModel::HasCategory($sClass, 'bizModel')) return true; + if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $aInstances); @@ -192,7 +192,7 @@ class UserRights public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $aInstances) { - if (!MetaModel::HasCategory($sClass, 'bizModel')) return true; + if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $aInstances); @@ -200,7 +200,7 @@ class UserRights public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) { - if (!MetaModel::HasCategory($sClass, 'bizModel')) return true; + if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $aInstances); diff --git a/pages/UI.php b/pages/UI.php index 3b77f4ce71..48bd6081b3 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -243,22 +243,17 @@ switch($operation) $oP->add_linked_script("../js/jquery.blockUI.js"); $oWizard = new UIWizard($oP, $sClass, $sStateCode); $sStateCode = $oWizard->GetTargetState(); // Will computes the default state if none was supplied + $sClassLabel = MetaModel::GetName($sClass); + $oP->p("

    Creation of a new $sClassLabel

    "); if (!empty($sStateCode)) { $aStates = MetaModel::EnumStates($sClass); $sStateLabel = $aStates[$sStateCode]['label']; - $oP->p("Wizard for creating an object of class '$sClass' in state '$sStateCode'."); - } - else - { - // Stateless object - $oP->p("Wizard for creating an object of class '$sClass'."); } $aWizardSteps = $oWizard->GetWizardStructure(); // Display the structure of the wizard $iStepIndex = 1; - $oP->p("

    Wizard Steps for creating an object of class '$sClass' in state '$sStateCode'

    \n"); $iMaxInputId = 0; $aFieldsMap = array(); foreach($aWizardSteps['mandatory'] as $aSteps) diff --git a/pages/opensearch.xml b/pages/opensearch.xml deleted file mode 100644 index 2620b2d1f5..0000000000 --- a/pages/opensearch.xml +++ /dev/null @@ -1,8 +0,0 @@ - -iTop -webmaster@itop.com -Recherche dans iTop -ISO-8859-1 - -http://localhost:81/pages/UI.php - \ No newline at end of file diff --git a/pages/schema.php b/pages/schema.php index ef06f230ff..f3a4ba52ed 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -160,7 +160,7 @@ function DisplayLifecycle($oPage, $sClass) { $aStates = MetaModel::EnumStates($sClass); $aStimuli = MetaModel::EnumStimuli($sClass); - $oPage->add("\n"); + $oPage->add("\n"); $oPage->add("

    Transitions

    \n"); $oPage->add("
      \n"); foreach ($aStates as $sStateCode => $aStateDef) diff --git a/setup/data/03.persons.xml b/setup/data/03.persons.xml index fa6317ff5e..9a102ad6e9 100644 --- a/setup/data/03.persons.xml +++ b/setup/data/03.persons.xml @@ -1,33 +1,33 @@ -Denis +Verne production 3 -denis.flaven@gmail.com +jules.verne@gmail.com 29 -Flaven +Jules -Quetiez +Dumas production 3 -romain.quetiez@gmail.com +alexandre.dumas@gmail.com 1 -Romain +Dumas -Taloc +Hugo production 3 -erwan.taloc@gmail.com +victor.hugo@gmail.com 33172382223 1 -Erwan +Victor e12345 - \ No newline at end of file + diff --git a/setup/menus.xml b/setup/menus.xml index 087d64ea3f..756cfd4ba9 100644 --- a/setup/menus.xml +++ b/setup/menus.xml @@ -592,7 +592,7 @@ text-align:center; <p></p> <p style="text-align:center; font-family:Georgia, 'Times New Roman', Times, serif; font-size:32px;">Welcome to iTop</p> <p></p> -<p style="text-align:center; font-family:Georgia, 'Times New Roman', Times, serif; font-size:14px;"><i>Version 0.7</i></p> +<p style="text-align:center; font-family:Georgia, 'Times New Roman', Times, serif; font-size:14px;"><i>Version 0.8</i></p> 1 From bce801021a786207eba162efa79b80e5ee96a9fe Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 19 Jul 2009 09:32:44 +0000 Subject: [PATCH 009/970] - enhancement: trap exceptions to keep a nice display in case of error SVN:trunk[84] --- pages/sibusql.php | 71 ++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/pages/sibusql.php b/pages/sibusql.php index 956d28d4de..3780896f29 100644 --- a/pages/sibusql.php +++ b/pages/sibusql.php @@ -19,38 +19,51 @@ $oP = new iTopWebPage("iTop - Expression Evaluation", $currentOrganization); $sExpression = utils::ReadParam('expression', ''); $sEncoding = utils::ReadParam('encoding', 'oql'); -if ($sEncoding == 'crypted') +try { - // Translate $sExpression into a oql expression - $sClearText = base64_decode($sExpression); - echo "FYI: '$sClearText'
      \n"; - $oFilter = DBObjectSearch::unserialize($sExpression); - $sExpression = $oFilter->ToOQL(); - exit; -} -else -{ - // leave $sExpression as is -} - -$oP->add('
      '."\n"); -$oP->add('Expression to evaluate:
      '."\n"); -$oP->add(''."

      Example:
      SELECT bizPerson AS B WHERE B.name LIKE '%A%'

      \n"); -$oP->add(''."\n"); -$oP->add('
      '."\n"); - -if (!empty($sExpression)) -{ - $oFilter = DBObjectSearch::FromOQL($sExpression); - if ($oFilter) + if ($sEncoding == 'crypted') { - $oP->p('Query expression: '.$oFilter->ToOQL()); - $oP->p('Serialized filter: '.$oFilter->serialize()); - - $oSet = new CMDBObjectSet($oFilter); - $oP->p('The query returned '.$oSet->count().' results(s)'); - cmdbAbstractObject::DisplaySet($oP, $oSet); + // Translate $sExpression into a oql expression + $sClearText = base64_decode($sExpression); + echo "FYI: '$sClearText'
      \n"; + $oFilter = DBObjectSearch::unserialize($sExpression); + $sExpression = $oFilter->ToOQL(); + exit; } + else + { + // leave $sExpression as is + } + + $oP->add('
      '."\n"); + $oP->add('Expression to evaluate:
      '."\n"); + $oP->add(''."

      Example:
      SELECT bizPerson AS B WHERE B.name LIKE '%A%'

      \n"); + $oP->add(''."\n"); + $oP->add('
      '."\n"); + + if (!empty($sExpression)) + { + $oFilter = DBObjectSearch::FromOQL($sExpression); + if ($oFilter) + { + $oP->p('Query expression: '.$oFilter->ToOQL()); + $oP->p('Serialized filter: '.$oFilter->serialize()); + + $oSet = new CMDBObjectSet($oFilter); + $oP->p('The query returned '.$oSet->count().' results(s)'); + cmdbAbstractObject::DisplaySet($oP, $oSet); + } + } +} +catch(CoreException $e) +{ + $oP->p('An error occured while running the query:'); + $oP->p($e->getHtmlDesc()); +} +catch(Exception $e) +{ + $oP->p('An error occured while running the query:'); + $oP->p($e->getMessage()); } $oP->output(); From 05a236f06a61c22cfdad13a82c5521de3b02ee10 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 19 Jul 2009 09:34:32 +0000 Subject: [PATCH 010/970] - Fixed bug #31: HTML display in the "export" page uses the appropriate stylesheet. SVN:trunk[85] --- application/cmdbabstract.class.inc.php | 4 ++-- webservices/export.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3bd6401a5f..aaf8bad4f3 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -213,9 +213,9 @@ abstract class cmdbAbstractObject extends CMDBObject // Comment by Rom: this helper may be used to display objects of class DBObject // -> I am using this to display the changes history - public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '') + public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true) { - $oPage->add(self::GetDisplaySet($oPage, $oSet, $sLinkageAttribute)); + $oPage->add(self::GetDisplaySet($oPage, $oSet, $sLinkageAttribute, $bDisplayMenu)); } public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true) diff --git a/webservices/export.php b/webservices/export.php index 530f952790..f6645e81a9 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -1,6 +1,6 @@ Date: Mon, 20 Jul 2009 13:41:21 +0000 Subject: [PATCH 011/970] - new test case for JOINs SVN:trunk[86] --- pages/testlist.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 947d484891..db9b448d80 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -820,6 +820,7 @@ class TestQueriesOnFarm extends TestBizModel 'SELECT Mammal AS m WHERE 1/0' => true, 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, + 'SELECT Group JOIN Animal ON Group.leader = Animal.id' => true, 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, From 2a7c89af215b200050f3435e078b1326358b50c3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 21 Jul 2009 08:48:30 +0000 Subject: [PATCH 012/970] Initialized dev of the user rights managed by profiles, it is still not usable by the application, but its data model is likely to be stable. SVN:trunk[87] --- .../userrightsprofile.class.inc.php | 659 ++++++++++++++++++ application/menunode.class.inc.php | 2 +- config-dist.php | 2 +- 3 files changed, 661 insertions(+), 2 deletions(-) create mode 100644 addons/userrights/userrightsprofile.class.inc.php diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php new file mode 100644 index 0000000000..3fd9c9f24b --- /dev/null +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -0,0 +1,659 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + + +class URP_Users extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "user", + "description" => "users and credentials", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "login", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_users", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeInteger("userid", array("label"=>"User id", "description"=>"User identifier (depends on the business model)", "allowed_values"=>null, "sql"=>"userid", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("password", array("label"=>"password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"email", "description"=>"email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("firstname", array("label"=>"firstname", "description"=>"first name", "allowed_values"=>null, "sql"=>"firstname", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("lastname", array("label"=>"lastname", "description"=>"last name", "allowed_values"=>null, "sql"=>"lastname", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("userid"); + MetaModel::Init_AddFilterFromAttribute("login"); + } +} + +class URP_Profiles extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "profile", + "description" => "usage profiles", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_profiles", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("description"); + } +} + +class URP_Dimensions extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "dimension", + "description" => "application dimension (defining silos)", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_dimensions", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("description"); + } +} + +class URP_UserProfile extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "user_profile", + "description" => "user profiles", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_userprofile", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"URP_Users", "jointype"=> "", "label"=>"User", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("label"=>"Login", "description"=>"User's login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("userid"); + MetaModel::Init_AddFilterFromAttribute("profileid"); + } +} + +class URP_ProfileProjection extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "profile_projection", + "description" => "profile projections", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_profileprojection", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("dimensionid"); + MetaModel::Init_AddFilterFromAttribute("profileid"); + } + + public function ProjectUser(URP_Users $oUser) + { + // #@# to be implemented + } +} + +class URP_ClassProjection extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "class_projection", + "description" => "class projections", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_classprojection", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"Target class", "allowed_values"=>null, "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("dimensionid"); + MetaModel::Init_AddFilterFromAttribute("class"); + } +} + +class URP_ClassGrant extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "class_permission", + "description" => "permissions on classes", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_classes", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddFilterFromAttribute("profileid"); + MetaModel::Init_AddFilterFromAttribute("profile"); + MetaModel::Init_AddFilterFromAttribute("class"); + + MetaModel::Init_AddFilterFromAttribute("action"); + } +} + +class URP_StimulusGrant extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "stimulus_permission", + "description" => "permissions on stimilus in the life cycle of the object", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_stimulus", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddFilterFromAttribute("profileid"); + MetaModel::Init_AddFilterFromAttribute("profile"); + MetaModel::Init_AddFilterFromAttribute("class"); + + MetaModel::Init_AddFilterFromAttribute("stimulus"); + } +} + +class URP_AttributeGrant extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "name" => "attribute_permission", + "description" => "permissions at the attributes level", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_attributes", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddFilterFromAttribute("profileid"); + MetaModel::Init_AddFilterFromAttribute("profile"); + MetaModel::Init_AddFilterFromAttribute("class"); + + MetaModel::Init_AddFilterFromAttribute("attcode"); + MetaModel::Init_AddFilterFromAttribute("action"); + } +} + + + + +class UserRightsProfile extends UserRightsAddOnAPI +{ + static public $m_aActionCodes = array( + UR_ACTION_READ => 'read', + UR_ACTION_MODIFY => 'modify', + UR_ACTION_DELETE => 'delete', + UR_ACTION_BULK_READ => 'bulk read', + UR_ACTION_BULK_MODIFY => 'bulk modify', + UR_ACTION_BULK_DELETE => 'bulk delete', + ); + + // Installation: create the very first user + public function CreateAdministrator($sAdminUser, $sAdminPwd, $sAdminEmail, $sFirstName, $sLastName, $sPhoneNumber) + { + // Maybe we should check that no other user with userid == 0 exists + $oUser = new URP_Users(); + $oUser->Set('login', $sAdminUser); + $oUser->Set('password', $sAdminPwd); + $oUser->Set('email', $sAdminEmail); + $oUser->Set('firstname', $sFirstName); + $oUser->Set('lastname', $sLastName); + $oUser->Set('phonenumber', $sPhoneNumber); + $oUser->Set('userid', 1); // one is for root ! + $iUserId = $oUser->DBInsertNoReload(); + $this->SetupUser($iUserId, true); + return true; + } + + public function Setup() + { + // Dimensions/Profiles/Classes/Attributes/Stimuli could be added anytime + // This procedure will then update the matrix with expected default values + // + + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); + while ($oProfile = $oProfileSet->Fetch()) + { + $this->SetupProfile($oProfile); + } + + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + while ($oDimension = $oDimensionSet->Fetch()) + { + $this->SetupDimension($oDimension); + } + return true; + } + + protected function SetupDimension($oDimension, $bNewDimension = false) + { + $iDimensionId = $oDimension->GetKey(); + + // Create projections, for any class where it applies + // + foreach(array('bizmodel', 'application', 'gui', 'core/cmdb') as $sCategory) + { + foreach (MetaModel::GetClasses($sCategory) as $sClass) + { + if ($bNewDimension) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection WHERE class = '$sClass' AND dimensionid = $iDimensionId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + // Create a new entry + $oCProj = MetaModel::NewObject("URP_ClassProjection"); + $oCProj->Set("dimensionid", $iDimensionId); + $oCProj->Set("class", $sClass); + $oCProj->Set("value", "true"); + $iId = $oCProj->DBInsertNoReload(); + } + } + } + // Create projections, for any existing profile + // + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); + while ($oProfile = $oProfileSet->Fetch()) + { + $iProfileId = $oProfile->GetKey(); + if ($bNewDimension) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection WHERE dimensionid = $iDimensionId AND profileid = $iProfileId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + // Create a new entry + $oDProj = MetaModel::NewObject("URP_ProfileProjection"); + $oDProj->Set("dimensionid", $iDimensionId); + $oDProj->Set("profileid", $iProfileId); + $oDProj->Set("value", "true"); + $iId = $oDProj->DBInsertNoReload(); + } + } + } + + protected function SetupProfile($oProfile, $bNewProfile = false) + { + $iProfileId = $oProfile->GetKey(); + + // Create grant records, for any class where it applies + // + foreach(array('bizmodel', 'application', 'gui', 'core/cmdb') as $sCategory) + { + foreach (MetaModel::GetClasses($sCategory) as $sClass) + { + foreach (self::$m_aActionCodes as $iActionCode => $sAction) + { + if ($bNewProfile) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassGrant WHERE class = '$sClass' AND action = '$sAction' AND profileid = $iProfileId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + // Create a new entry + $oMyClassGrant = MetaModel::NewObject("URP_ClassGrant"); + $oMyClassGrant->Set("profileid", $iProfileId); + $oMyClassGrant->Set("class", $sClass); + $oMyClassGrant->Set("action", $sAction); + $oMyClassGrant->Set("permission", "yes"); + $iId = $oMyClassGrant->DBInsertNoReload(); + } + } + foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + if ($bNewProfile) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND profileid = $iProfileId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + // Create a new entry + $oMyClassGrant = MetaModel::NewObject("URP_StimulusGrant"); + $oMyClassGrant->Set("profileid", $iProfileId); + $oMyClassGrant->Set("class", $sClass); + $oMyClassGrant->Set("stimulus", $sStimulusCode); + $oMyClassGrant->Set("permission", "yes"); + $iId = $oMyClassGrant->DBInsertNoReload(); + } + } + foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) + { + if ($bNewProfile) + { + $bAddCell = true; + } + else + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE class = '$sClass' AND attcode = '$sAttCode' AND profileid = $iProfileId")); + $bAddCell = ($oSet->Count() < 1); + } + if ($bAddCell) + { + foreach (array('read', 'modify') as $sAction) + { + // Create a new entry + $oMyAttGrant = MetaModel::NewObject("URP_AttributeGrant"); + $oMyAttGrant->Set("profileid", $iProfileId); + $oMyAttGrant->Set("class", $sClass); + $oMyAttGrant->Set("attcode", $sAttCode); + $oMyAttGrant->Set("action", $sAction); + $oMyAttGrant->Set("permission", "yes"); + $iId = $oMyAttGrant->DBInsertNoReload(); + } + } + } + } + } + // Create the "My Bookmarks" menu item (parent_id = 0, rank = 6) + if ($bNewProfile) + { + $bAddMenu = true; + } + else + { + //$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT menuNode WHERE type = 'user' AND parent_id = 0 AND user_id = $iUserId")); + //$bAddMenu = ($oSet->Count() < 1); + } + $bAddMenu = false; + if ($bAddMenu) + { + $oMenu = MetaModel::NewObject('menuNode'); + $oMenu->Set('type', 'user'); + $oMenu->Set('parent_id', 0); // It's a toplevel entry + $oMenu->Set('rank', 6); // Located just above the Admin Tools section (=7) + $oMenu->Set('name', 'My Bookmarks'); + $oMenu->Set('label', 'My Favorite Items'); + $oMenu->Set('hyperlink', 'UI.php'); + $oMenu->Set('template', '

      My bookmarks

      This section contains my most favorite search results

      '); + $oMenu->Set('user_id', $iUserId); + $oMenu->DBInsert(); + } + } + + + public function Init() + { + // Could be loaded in a shared memory (?) + return true; + } + + public function CheckCredentials($sUserName, $sPassword) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = '$sUserName'")); + if ($oSet->Count() < 1) + { + // todo: throw an exception? + return false; + } + + $oLogin = $oSet->Fetch(); + if ($oLogin->Get('password') == $sPassword) + { + return true; + } + // todo: throw an exception? + return false; + } + + public function GetFilter($sUserName, $sClass) + { + $oNullFilter = new DBObjectSearch($sClass); + return $oNullFilter; + } + + public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) + { + if (!array_key_exists($iActionCode, self::$m_aActionCodes)) + { + return UR_ALLOWED_NO; + } + $sAction = self::$m_aActionCodes[$iActionCode]; + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'")); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + switch ($oGrantRecord->Get('permission')) + { + case 'yes': + $iRetCode = UR_ALLOWED_YES; + break; + case 'no': + default: + $iRetCode = UR_ALLOWED_NO; + break; + } + return $iRetCode; + } + + public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + { + if (!array_key_exists($iActionCode, self::$m_aActionCodes)) + { + return UR_ALLOWED_NO; + } + $sAction = self::$m_aActionCodes[$iActionCode]; + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE URP_AttributeGrant.class = '$sClass' AND URP_AttributeGrant.attcode = '$sAttCode' AND URP_AttributeGrant.action = '$sAction' AND URP_AttributeGrant.login = '$sUserName'")); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + switch ($oGrantRecord->Get('permission')) + { + case 'yes': + $iRetCode = UR_ALLOWED_YES; + break; + case 'no': + default: + $iRetCode = UR_ALLOWED_NO; + break; + } + return $iRetCode; + } + + public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'")); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + switch ($oGrantRecord->Get('permission')) + { + case 'yes': + $iRetCode = UR_ALLOWED_YES; + break; + case 'no': + default: + $iRetCode = UR_ALLOWED_NO; + break; + } + return $iRetCode; + } +} + +UserRights::SelectModule('UserRightsProfile'); + +?> diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index e0b72ec622..72ddfeb230 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -46,7 +46,7 @@ class menuNode extends DBObject MetaModel::Init_AddAttribute(new AttributeInteger("rank", array("label"=>"Display rank", "description"=>"Sort order for displaying the menu", "allowed_values"=>null, "sql"=>"rank", "default_value" => 999, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "sql"=>"parent_id", "targetclass"=>"menuNode", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("label"=>"Owner of the menu", "description"=>"User who owns this menu (for user defined menus)", "allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"UserRightsMatrixUsers", "is_null_allowed"=>true, "depends_on"=>array('type')))); + MetaModel::Init_AddAttribute(new AttributeInteger("user_id", array("label"=>"Owner of the menu", "description"=>"User who owns this menu (for user defined menus)", "allowed_values"=>null, "sql"=>"user_id", "default_value" => 0, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("label"); MetaModel::Init_AddFilterFromAttribute("parent_id"); diff --git a/config-dist.php b/config-dist.php index db5babff2c..58fea17670 100644 --- a/config-dist.php +++ b/config-dist.php @@ -29,7 +29,7 @@ $MyModules = array( // to be continued... ), 'addons' => array ( - 'user rights' => '../addons/userrights/userrightsmatrix.class.inc.php', + 'user rights' => '../addons/userrights/userrightsprofile.class.inc.php', // other modules to come later ) ); From 6b6f89f1c1e1f42051bd0d40f82d70997ff4dcf7 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 24 Jul 2009 13:14:51 +0000 Subject: [PATCH 013/970] User management by profile moving forward: pages to check the projection of objects/users in user defined dimensions Introduced parameters in OQL (:myvar) Added the verb MetaModel::IsValidObject($oMyObj) SVN:trunk[88] --- .../userrightsprofile.class.inc.php | 201 ++-- business/itop.business.class.inc.php | 2 + core/config.class.inc.php | 2 +- core/coreexception.class.inc.php | 4 +- core/dbobjectsearch.class.php | 17 +- core/dbobjectset.class.php | 7 +- core/expression.class.inc.php | 65 +- core/metamodel.class.php | 32 +- core/oql/oql-lexer.php | 106 +- core/oql/oql-lexer.plex | 10 +- core/oql/oql-parser.php | 956 +++++++++--------- core/oql/oql-parser.y | 17 +- core/oql/oqlinterpreter.class.inc.php | 18 +- core/oql/oqlquery.class.inc.php | 58 +- core/sqlquery.class.inc.php | 11 +- core/valuesetdef.class.inc.php | 44 +- pages/testlist.inc.php | 4 +- pages/usermanagement_classproj.php | 90 ++ pages/usermanagement_profileproj.php | 100 ++ 19 files changed, 1107 insertions(+), 637 deletions(-) create mode 100644 pages/usermanagement_classproj.php create mode 100644 pages/usermanagement_profileproj.php diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 3fd9c9f24b..7b72df453f 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -98,10 +98,81 @@ class URP_Dimensions extends DBObject //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"type", "description"=>"class name or data type (projection unit)", "allowed_values"=>new ValueSetEnumClasses('bizmodel', 'String,Integer'), "sql"=>"type", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("description"); + MetaModel::Init_AddFilterFromAttribute("type"); + } + + public function CheckProjectionSpec($oProjectionSpec) + { + $sExpression = $oProjectionSpec->Get('value'); + $sAttribute = $oProjectionSpec->Get('attribute'); + + // Shortcut: "any value" or "no value" means no projection + if (empty($sExpression)) return; + if ($sExpression == '') return; + + // 1st - compute the data type for the dimension + // + $sType = $this->Get('type'); + if (MetaModel::IsValidClass($sType)) + { + $sExpectedType = $sType; + } + else + { + $sExpectedType = '_scalar_'; + } + + // 2nd - compute the data type for the projection + // + $bIsOql = true; + $sExpressionClass = ''; + try + { + $oObjectSearch = DBObjectSearch::FromOQL($sExpression); + $sExpressionClass = $oObjectSearch->GetClass(); + } + catch (OqlException $e) + { + $bIsOql = false; + } + if ($bIsOql) + { + if (empty($sAttribute)) + { + $sFoundType = $sExpressionClass; + } + else + { + if (!MetaModel::IsValidAttCode($sExpressionClass, $sAttribute)) + { + throw new CoreException('Unkown attribute code in projection specification', array('found' => $sAttribute, 'expecting' => MetaModel::GetAttributesList($sExpressionClass), 'class' => $sExpressionClass, 'projection' => $oProjectionSpec)); + } + $oAttDef = MetaModel::GetAttributeDef($sExpressionClass, $sAttribute); + if ($oAttDef->IsExternalKey()) + { + $sFoundType = $oAttDef->GetTargetClass(); + } + else + { + $sFoundType = '_scalar_'; + } + } + } + else + { + $sFoundType = '_scalar_'; + } + + // Compare the dimension type and projection type + if ($sFoundType != $sExpectedType) + { + throw new CoreException('Wrong type in projection specification', array('found' => $sFoundType, 'expecting' => $sExpectedType, 'expression' => $sExpression, 'attribute' => $sAttribute, 'projection' => $oProjectionSpec)); + } } } @@ -163,7 +234,8 @@ class URP_ProfileProjection extends DBObject MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant | ", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("dimensionid"); @@ -172,7 +244,25 @@ class URP_ProfileProjection extends DBObject public function ProjectUser(URP_Users $oUser) { - // #@# to be implemented + $sExpr = $this->Get('value'); + if (preg_match('/^\s*([a-zA-Z0-9;]+)\s*$/', $sExpr, $aMatches)) + { + // Constant value(s) + $aRes = explode(';', $aMatches[1]); + } + elseif ($sExpr == '') + { + $aRes = array(''); + } + else + { + $sColumn = $this->Get('attribute'); + // SELECT... + $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); + $aValues = $oValueSetDef->GetValues(array('user' => $oUser), ''); + $aRes = array_values($aValues); + } + return $aRes; } } @@ -201,29 +291,53 @@ class URP_ClassProjection extends DBObject MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"Target class", "allowed_values"=>null, "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant | ", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("dimensionid"); MetaModel::Init_AddFilterFromAttribute("class"); } + + public function ProjectObject($oObject) + { + $sExpr = $this->Get('value'); + if (preg_match('/^\s*([a-zA-Z0-9;]+)\s*$/', $sExpr, $aMatches)) + { + // Constant value(s) + $aRes = explode(';', $aMatches[1]); + } + elseif ($sExpr == '') + { + $aRes = array(''); + } + else + { + $sColumn = $this->Get('attribute'); + // SELECT... + $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); + $aValues = $oValueSetDef->GetValues(array('this' => $oObject), ''); + $aRes = array_values($aValues); + } + return $aRes; + } } -class URP_ClassGrant extends DBObject +class URP_ActionGrant extends DBObject { public static function Init() { $aParams = array ( "category" => "addon/userrights", - "name" => "class_permission", + "name" => "action_permission", "description" => "permissions on classes", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), - "db_table" => "priv_urp_grant_classes", + "db_table" => "priv_urp_grant_actions", "db_key_field" => "id", "db_finalclass_field" => "", ); @@ -308,23 +422,12 @@ class URP_AttributeGrant extends DBObject MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); - + MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "label"=>"Action grant", "description"=>"action grant", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); - // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) - MetaModel::Init_AddFilterFromAttribute("profileid"); - MetaModel::Init_AddFilterFromAttribute("profile"); - MetaModel::Init_AddFilterFromAttribute("class"); - + MetaModel::Init_AddFilterFromAttribute("actiongrantid"); MetaModel::Init_AddFilterFromAttribute("attcode"); - MetaModel::Init_AddFilterFromAttribute("action"); } } @@ -454,18 +557,18 @@ class UserRightsProfile extends UserRightsAddOnAPI } else { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassGrant WHERE class = '$sClass' AND action = '$sAction' AND profileid = $iProfileId")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = '$sClass' AND action = '$sAction' AND profileid = $iProfileId")); $bAddCell = ($oSet->Count() < 1); } if ($bAddCell) { // Create a new entry - $oMyClassGrant = MetaModel::NewObject("URP_ClassGrant"); - $oMyClassGrant->Set("profileid", $iProfileId); - $oMyClassGrant->Set("class", $sClass); - $oMyClassGrant->Set("action", $sAction); - $oMyClassGrant->Set("permission", "yes"); - $iId = $oMyClassGrant->DBInsertNoReload(); + $oMyActionGrant = MetaModel::NewObject("URP_ActionGrant"); + $oMyActionGrant->Set("profileid", $iProfileId); + $oMyActionGrant->Set("class", $sClass); + $oMyActionGrant->Set("action", $sAction); + $oMyActionGrant->Set("permission", "yes"); + $iId = $oMyActionGrant->DBInsertNoReload(); } } foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) @@ -482,38 +585,12 @@ class UserRightsProfile extends UserRightsAddOnAPI if ($bAddCell) { // Create a new entry - $oMyClassGrant = MetaModel::NewObject("URP_StimulusGrant"); - $oMyClassGrant->Set("profileid", $iProfileId); - $oMyClassGrant->Set("class", $sClass); - $oMyClassGrant->Set("stimulus", $sStimulusCode); - $oMyClassGrant->Set("permission", "yes"); - $iId = $oMyClassGrant->DBInsertNoReload(); - } - } - foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) - { - if ($bNewProfile) - { - $bAddCell = true; - } - else - { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE class = '$sClass' AND attcode = '$sAttCode' AND profileid = $iProfileId")); - $bAddCell = ($oSet->Count() < 1); - } - if ($bAddCell) - { - foreach (array('read', 'modify') as $sAction) - { - // Create a new entry - $oMyAttGrant = MetaModel::NewObject("URP_AttributeGrant"); - $oMyAttGrant->Set("profileid", $iProfileId); - $oMyAttGrant->Set("class", $sClass); - $oMyAttGrant->Set("attcode", $sAttCode); - $oMyAttGrant->Set("action", $sAction); - $oMyAttGrant->Set("permission", "yes"); - $iId = $oMyAttGrant->DBInsertNoReload(); - } + $oMyStGrant = MetaModel::NewObject("URP_StimulusGrant"); + $oMyStGrant->Set("profileid", $iProfileId); + $oMyStGrant->Set("class", $sClass); + $oMyStGrant->Set("stimulus", $sStimulusCode); + $oMyStGrant->Set("permission", "yes"); + $iId = $oMyStGrant->DBInsertNoReload(); } } } @@ -577,13 +654,15 @@ class UserRightsProfile extends UserRightsAddOnAPI public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) { + // #@# temporary + return true; if (!array_key_exists($iActionCode, self::$m_aActionCodes)) { return UR_ALLOWED_NO; } $sAction = self::$m_aActionCodes[$iActionCode]; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'")); if ($oSet->Count() < 1) { return UR_ALLOWED_NO; @@ -605,6 +684,8 @@ class UserRightsProfile extends UserRightsAddOnAPI public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) { + // #@# temporary + return true; if (!array_key_exists($iActionCode, self::$m_aActionCodes)) { return UR_ALLOWED_NO; @@ -633,6 +714,8 @@ class UserRightsProfile extends UserRightsAddOnAPI public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) { + // #@# temporary + return true; $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'")); if ($oSet->Count() < 1) { diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index f4ca40d13b..dcad88f706 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -260,6 +260,8 @@ class bizPerson extends bizContact MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"first Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("employe_number", array("label"=>"Employe Number", "description"=>"employe number", "allowed_values"=>null, "sql"=>"employe_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("login_id", array("targetclass"=>"URP_Users", "label"=>"Login", "description"=>"Login information", "allowed_values"=>null, "sql"=>"login_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("first_name"); MetaModel::Init_AddFilterFromAttribute("employe_number"); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 94bfe87175..c18c716ffb 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -256,7 +256,7 @@ class Config fwrite($hFile, "\t\t// to be continued...\n"); fwrite($hFile, "\t),\n"); fwrite($hFile, "\t'addons' => array (\n"); - fwrite($hFile, "\t\t'user rights' => '../addons/userrights/userrightsmatrix.class.inc.php',\n"); + fwrite($hFile, "\t\t'user rights' => '../addons/userrights/userrightsprofile.class.inc.php',\n"); fwrite($hFile, "\t\t// other modules to come later\n"); fwrite($hFile, "\t)\n"); fwrite($hFile, ");\n"); diff --git a/core/coreexception.class.inc.php b/core/coreexception.class.inc.php index eb5ae8b9da..a31f87d66b 100644 --- a/core/coreexception.class.inc.php +++ b/core/coreexception.class.inc.php @@ -23,11 +23,11 @@ class CoreException extends Exception { if (is_array($val)) { - $aPairs[$key] = '('.implode(', ', $val).')'; + $aPairs[] = $key.'=>('.implode(', ', $val).')'; } else { - $aPairs[$key] = $val; + $aPairs[] = $key.'=>'.$val; } } $sValue = '{'.implode('; ', $aPairs).'}'; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 86d6e057ae..236a0b7213 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -662,7 +662,7 @@ class DBObjectSearch } if (count($aParams) > 0) { - throw new CoreException("Unused parameter(s) for this SibusQL expression: (".implode(', ', array_keys($aParams)).")"); +// throw new CoreException("Unused parameter(s) for this SibusQL expression: (".implode(', ', array_keys($aParams)).")"); } return $sQuery; } @@ -736,6 +736,10 @@ class DBObjectSearch return new FieldExpression($sFltCode, $sClassAlias); } + elseif ($oExpression instanceof VariableOqlExpression) + { + return new VariableExpression($oExpression->GetName()); + } elseif ($oExpression instanceof TrueOqlExpression) { return new TrueExpression; @@ -763,7 +767,7 @@ class DBObjectSearch if (empty($sQuery)) return null; $oOql = new OqlInterpreter($sQuery); - $oOqlQuery = $oOql->ParseQuery(); + $oOqlQuery = $oOql->ParseObjectQuery(); $sClass = $oOqlQuery->GetClass(); $sClassAlias = $oOqlQuery->GetClassAlias(); @@ -860,13 +864,14 @@ class DBObjectSearch if (empty($sQuery)) return null; $sQuery = self::privProcessParams($sQuery, $aParams, $oObject); + if (preg_match('@^\\s*SELECT@', $sQuery)) + { + return self::FromOQL($sQuery, $aParams, $oObject); + } + $iSepPos = strpos($sQuery, ":"); if ($iSepPos === false) { - if (preg_match('@^\\s*SELECT@', $sQuery)) - { - return self::FromOQL($sQuery, $aParams, $oObject); - } // Only the class was specified -> all rows are required $sClass = trim($sQuery); $oFilter = new DBObjectSearch($sClass); diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index d2719c5cd7..9e6cf5459d 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -21,10 +21,11 @@ class DBObjectSet private $m_aId2Row; private $m_iCurrRow; - public function __construct($oFilter, $aOrderBy = array()) + public function __construct($oFilter, $aOrderBy = array(), $aArgs = array()) { $this->m_oFilter = $oFilter; $this->m_aOrderBy = $aOrderBy; + $this->m_aArgs = $aArgs; $this->m_bLoaded = false; $this->m_aData = array(); @@ -109,8 +110,8 @@ class DBObjectSet public function Load() { if ($this->m_bLoaded) return; -// #@# debug - echo "Loading (".$this->m_oFilter->ToSibuSQL().")....
      \n"; - $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy); + + $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs); $resQuery = CMDBSource::Query($sSQL); if (!$resQuery) return; diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index 76409cdc15..f2646802e4 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -17,7 +17,7 @@ abstract class Expression abstract public function Translate($aTranslationData, $bMatchAll = true); // recursive rendering - abstract public function Render(); + abstract public function Render($aArgs = array()); // recursively builds an array of class => fieldname abstract public function ListRequiredFields(); @@ -119,11 +119,11 @@ class BinaryExpression extends Expression } // recursive rendering - public function Render() + public function Render($aArgs = array()) { $sOperator = $this->GetOperator(); - $sLeft = $this->GetLeftExpr()->Render(); - $sRight = $this->GetRightExpr()->Render(); + $sLeft = $this->GetLeftExpr()->Render($aArgs); + $sRight = $this->GetRightExpr()->Render($aArgs); return "($sLeft $sOperator $sRight)"; } @@ -164,7 +164,7 @@ class UnaryExpression extends Expression } // recursive rendering - public function Render() + public function Render($aArgs = array()) { return CMDBSource::Quote($this->m_value); } @@ -228,7 +228,7 @@ class FieldExpression extends UnaryExpression public function GetName() {return $this->m_sName;} // recursive rendering - public function Render() + public function Render($aArgs = array()) { if (empty($this->m_sParent)) { @@ -270,6 +270,43 @@ class FieldExpression extends UnaryExpression } +class VariableExpression extends UnaryExpression +{ + protected $m_sName; + + public function __construct($sName) + { + parent::__construct($sName); + + $this->m_sName = $sName; + } + + public function IsTrue() + { + // return true if we are certain that it will be true + return false; + } + + public function GetName() {return $this->m_sName;} + + // recursive rendering + public function Render($aArgs = array()) + { + if (array_key_exists($this->m_sName, $aArgs)) + { + return $aArgs[$this->m_sName]; + } + elseif (is_null($aArgs)) + { + return ':'.$this->m_sName; + } + else + { + throw new CoreException('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs)); + } + } +} + // Temporary, until we implement functions and expression casting! // ... or until we implement a real full text search based in the MATCH() expression class ListExpression extends Expression @@ -293,12 +330,12 @@ class ListExpression extends Expression } // recursive rendering - public function Render() + public function Render($aArgs = array()) { $aRes = array(); foreach ($this->m_aExpressions as $oExpr) { - $aRes[] = $oExpr->Render(); + $aRes[] = $oExpr->Render($aArgs); } return '('.implode(', ', $aRes).')'; } @@ -353,12 +390,12 @@ class FunctionExpression extends Expression } // recursive rendering - public function Render() + public function Render($aArgs = array()) { $aRes = array(); foreach ($this->m_aArgs as $oExpr) { - $aRes[] = $oExpr->Render(); + $aRes[] = $oExpr->Render($aArgs); } return $this->m_sVerb.'('.implode(', ', $aRes).')'; } @@ -412,9 +449,9 @@ class IntervalExpression extends Expression } // recursive rendering - public function Render() + public function Render($aArgs = array()) { - return 'INTERVAL '.$this->m_oValue->Render().' '.$this->m_sUnit; + return 'INTERVAL '.$this->m_oValue->Render($aArgs).' '.$this->m_sUnit; } public function Translate($aTranslationData, $bMatchAll = true) @@ -449,12 +486,12 @@ class CharConcatExpression extends Expression } // recursive rendering - public function Render() + public function Render($aArgs = array()) { $aRes = array(); foreach ($this->m_aExpressions as $oExpr) { - $sCol = $oExpr->Render(); + $sCol = $oExpr->Render($aArgs); // Concat will be globally NULL if one single argument is null ! $aRes[] = "COALESCE($sCol, '')"; } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 275ad454ad..bb7e92a05d 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -394,6 +394,12 @@ abstract class MetaModel return (array_key_exists($sClass, self::$m_aAttribDefs)); } + public static function IsValidObject($oObject) + { + if (!is_object($oObject)) return false; + return (self::IsValidClass(get_class($oObject))); + } + public static function IsReconcKey($sClass, $sAttCode) { return (in_array($sAttCode, self::GetReconcKeys($sClass))); @@ -1161,7 +1167,7 @@ abstract class MetaModel return false; } - public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array()) + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) { $aTranslation = array(); $aClassAliases = array(); @@ -1188,8 +1194,30 @@ abstract class MetaModel } } + // Prepare arguments (translate any object into scalars) + // + $aScalarArgs = array(); + foreach($aArgs as $sArgName => $value) + { + if (self::IsValidObject($value)) + { + $aScalarArgs[$sArgName] = $value->GetKey(); + $aScalarArgs[$sArgName.'->id'] = $value->GetKey(); + + $sClass = get_class($value); + foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + $aScalarArgs[$sArgName.'->'.$sAttCode] = $value->Get($sAttCode); + } + } + else + { + $aScalarArgs[$sArgName] = (string) $value; + } + } + //MyHelpers::var_dump_html($oSelect->RenderSelect($aOrderBy)); - return $oSelect->RenderSelect($aOrderBy); + return $oSelect->RenderSelect($aOrderBy, $aScalarArgs); } public static function MakeDeleteQuery(DBObjectSearch $oFilter) diff --git a/core/oql/oql-lexer.php b/core/oql/oql-lexer.php index dede6ca1f4..8853786fff 100644 --- a/core/oql/oql-lexer.php +++ b/core/oql/oql-lexer.php @@ -84,6 +84,7 @@ class OQLLexerRaw $rules = array( '/^[ \t\n]+/', '/^SELECT/', + '/^FROM/', '/^AS/', '/^WHERE/', '/^JOIN/', @@ -131,6 +132,7 @@ class OQLLexerRaw '/^[0-9]+|0x[0-9a-fA-F]+/', '/^\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/', '/^([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/', + '/^:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/', '/^\\./', ); $match = false; @@ -237,239 +239,249 @@ class OQLLexerRaw function yy_r1_2($yy_subpatterns) { - $this->token = OQLParser::AS_ALIAS; + $this->token = OQLParser::FROM; } function yy_r1_3($yy_subpatterns) { - $this->token = OQLParser::WHERE; + $this->token = OQLParser::AS_ALIAS; } function yy_r1_4($yy_subpatterns) { - $this->token = OQLParser::JOIN; + $this->token = OQLParser::WHERE; } function yy_r1_5($yy_subpatterns) { - $this->token = OQLParser::ON; + $this->token = OQLParser::JOIN; } function yy_r1_6($yy_subpatterns) { - $this->token = OQLParser::MATH_DIV; + $this->token = OQLParser::ON; } function yy_r1_7($yy_subpatterns) { - $this->token = OQLParser::MATH_MULT; + $this->token = OQLParser::MATH_DIV; } function yy_r1_8($yy_subpatterns) { - $this->token = OQLParser::MATH_PLUS; + $this->token = OQLParser::MATH_MULT; } function yy_r1_9($yy_subpatterns) { - $this->token = OQLParser::MATH_MINUS; + $this->token = OQLParser::MATH_PLUS; } function yy_r1_10($yy_subpatterns) { - $this->token = OQLParser::LOG_AND; + $this->token = OQLParser::MATH_MINUS; } function yy_r1_11($yy_subpatterns) { - $this->token = OQLParser::LOG_OR; + $this->token = OQLParser::LOG_AND; } function yy_r1_12($yy_subpatterns) { - $this->token = OQLParser::COMA; + $this->token = OQLParser::LOG_OR; } function yy_r1_13($yy_subpatterns) { - $this->token = OQLParser::PAR_OPEN; + $this->token = OQLParser::COMA; } function yy_r1_14($yy_subpatterns) { - $this->token = OQLParser::PAR_CLOSE; + $this->token = OQLParser::PAR_OPEN; } function yy_r1_15($yy_subpatterns) { - $this->token = OQLParser::EQ; + $this->token = OQLParser::PAR_CLOSE; } function yy_r1_16($yy_subpatterns) { - $this->token = OQLParser::NOT_EQ; + $this->token = OQLParser::EQ; } function yy_r1_17($yy_subpatterns) { - $this->token = OQLParser::GT; + $this->token = OQLParser::NOT_EQ; } function yy_r1_18($yy_subpatterns) { - $this->token = OQLParser::LT; + $this->token = OQLParser::GT; } function yy_r1_19($yy_subpatterns) { - $this->token = OQLParser::GE; + $this->token = OQLParser::LT; } function yy_r1_20($yy_subpatterns) { - $this->token = OQLParser::LE; + $this->token = OQLParser::GE; } function yy_r1_21($yy_subpatterns) { - $this->token = OQLParser::LIKE; + $this->token = OQLParser::LE; } function yy_r1_22($yy_subpatterns) { - $this->token = OQLParser::NOT_LIKE; + $this->token = OQLParser::LIKE; } function yy_r1_23($yy_subpatterns) { - $this->token = OQLParser::IN; + $this->token = OQLParser::NOT_LIKE; } function yy_r1_24($yy_subpatterns) { - $this->token = OQLParser::NOT_IN; + $this->token = OQLParser::IN; } function yy_r1_25($yy_subpatterns) { - $this->token = OQLParser::INTERVAL; + $this->token = OQLParser::NOT_IN; } function yy_r1_26($yy_subpatterns) { - $this->token = OQLParser::F_IF; + $this->token = OQLParser::INTERVAL; } function yy_r1_27($yy_subpatterns) { - $this->token = OQLParser::F_ELT; + $this->token = OQLParser::F_IF; } function yy_r1_28($yy_subpatterns) { - $this->token = OQLParser::F_COALESCE; + $this->token = OQLParser::F_ELT; } function yy_r1_29($yy_subpatterns) { - $this->token = OQLParser::F_CONCAT; + $this->token = OQLParser::F_COALESCE; } function yy_r1_30($yy_subpatterns) { - $this->token = OQLParser::F_SUBSTR; + $this->token = OQLParser::F_CONCAT; } function yy_r1_31($yy_subpatterns) { - $this->token = OQLParser::F_TRIM; + $this->token = OQLParser::F_SUBSTR; } function yy_r1_32($yy_subpatterns) { - $this->token = OQLParser::F_DATE; + $this->token = OQLParser::F_TRIM; } function yy_r1_33($yy_subpatterns) { - $this->token = OQLParser::F_DATE_FORMAT; + $this->token = OQLParser::F_DATE; } function yy_r1_34($yy_subpatterns) { - $this->token = OQLParser::F_CURRENT_DATE; + $this->token = OQLParser::F_DATE_FORMAT; } function yy_r1_35($yy_subpatterns) { - $this->token = OQLParser::F_NOW; + $this->token = OQLParser::F_CURRENT_DATE; } function yy_r1_36($yy_subpatterns) { - $this->token = OQLParser::F_TIME; + $this->token = OQLParser::F_NOW; } function yy_r1_37($yy_subpatterns) { - $this->token = OQLParser::F_TO_DAYS; + $this->token = OQLParser::F_TIME; } function yy_r1_38($yy_subpatterns) { - $this->token = OQLParser::F_FROM_DAYS; + $this->token = OQLParser::F_TO_DAYS; } function yy_r1_39($yy_subpatterns) { - $this->token = OQLParser::F_YEAR; + $this->token = OQLParser::F_FROM_DAYS; } function yy_r1_40($yy_subpatterns) { - $this->token = OQLParser::F_MONTH; + $this->token = OQLParser::F_YEAR; } function yy_r1_41($yy_subpatterns) { - $this->token = OQLParser::F_DAY; + $this->token = OQLParser::F_MONTH; } function yy_r1_42($yy_subpatterns) { - $this->token = OQLParser::F_DATE_ADD; + $this->token = OQLParser::F_DAY; } function yy_r1_43($yy_subpatterns) { - $this->token = OQLParser::F_DATE_SUB; + $this->token = OQLParser::F_DATE_ADD; } function yy_r1_44($yy_subpatterns) { - $this->token = OQLParser::F_ROUND; + $this->token = OQLParser::F_DATE_SUB; } function yy_r1_45($yy_subpatterns) { - $this->token = OQLParser::F_FLOOR; + $this->token = OQLParser::F_ROUND; } function yy_r1_46($yy_subpatterns) { - $this->token = OQLParser::NUMVAL; + $this->token = OQLParser::F_FLOOR; } function yy_r1_47($yy_subpatterns) { - $this->token = OQLParser::STRVAL; + $this->token = OQLParser::NUMVAL; } function yy_r1_48($yy_subpatterns) { - $this->token = OQLParser::NAME; + $this->token = OQLParser::STRVAL; } function yy_r1_49($yy_subpatterns) + { + + $this->token = OQLParser::NAME; + } + function yy_r1_50($yy_subpatterns) + { + + $this->token = OQLParser::VARNAME; + } + function yy_r1_51($yy_subpatterns) { $this->token = OQLParser::DOT; diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index 8196f540a5..c8e90dda17 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -55,7 +55,8 @@ class OQLLexerRaw %line $this->line %matchlongest 1 whitespace = /[ \t\n]+/ -select = "SELECT" +select = "SELECT" +from = "FROM" as_alias = "AS" where = "WHERE" join = "JOIN" @@ -103,6 +104,7 @@ f_floor = "FLOOR" numval = /[0-9]+|0x[0-9a-fA-F]+/ strval = /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/ name = /([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/ +varname = /:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/ dot = "." */ @@ -113,6 +115,9 @@ whitespace { select { $this->token = OQLParser::SELECT; } +from { + $this->token = OQLParser::FROM; +} as_alias { $this->token = OQLParser::AS_ALIAS; } @@ -254,6 +259,9 @@ strval { name { $this->token = OQLParser::NAME; } +varname { + $this->token = OQLParser::VARNAME; +} dot { $this->token = OQLParser::DOT; } diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index c3995650c9..e66f482743 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -125,44 +125,45 @@ class OQLParserRaw#line 102 "oql-parser.php" const F_MONTH = 12; const F_YEAR = 13; const DOT = 14; - const NAME = 15; - const NUMVAL = 16; - const STRVAL = 17; - const NOT_EQ = 18; - const LOG_AND = 19; - const LOG_OR = 20; - const MATH_DIV = 21; - const MATH_MULT = 22; - const MATH_PLUS = 23; - const MATH_MINUS = 24; - const GT = 25; - const LT = 26; - const GE = 27; - const LE = 28; - const LIKE = 29; - const NOT_LIKE = 30; - const IN = 31; - const NOT_IN = 32; - const F_IF = 33; - const F_ELT = 34; - const F_COALESCE = 35; - const F_CONCAT = 36; - const F_SUBSTR = 37; - const F_TRIM = 38; - const F_DATE = 39; - const F_DATE_FORMAT = 40; - const F_CURRENT_DATE = 41; - const F_NOW = 42; - const F_TIME = 43; - const F_TO_DAYS = 44; - const F_FROM_DAYS = 45; - const F_DATE_ADD = 46; - const F_DATE_SUB = 47; - const F_ROUND = 48; - const F_FLOOR = 49; - const YY_NO_ACTION = 205; - const YY_ACCEPT_ACTION = 204; - const YY_ERROR_ACTION = 203; + const VARNAME = 15; + const NAME = 16; + const NUMVAL = 17; + const STRVAL = 18; + const NOT_EQ = 19; + const LOG_AND = 20; + const LOG_OR = 21; + const MATH_DIV = 22; + const MATH_MULT = 23; + const MATH_PLUS = 24; + const MATH_MINUS = 25; + const GT = 26; + const LT = 27; + const GE = 28; + const LE = 29; + const LIKE = 30; + const NOT_LIKE = 31; + const IN = 32; + const NOT_IN = 33; + const F_IF = 34; + const F_ELT = 35; + const F_COALESCE = 36; + const F_CONCAT = 37; + const F_SUBSTR = 38; + const F_TRIM = 39; + const F_DATE = 40; + const F_DATE_FORMAT = 41; + const F_CURRENT_DATE = 42; + const F_NOW = 43; + const F_TIME = 44; + const F_TO_DAYS = 45; + const F_FROM_DAYS = 46; + const F_DATE_ADD = 47; + const F_DATE_SUB = 48; + const F_ROUND = 49; + const F_FLOOR = 50; + const YY_NO_ACTION = 209; + const YY_ACCEPT_ACTION = 208; + const YY_ERROR_ACTION = 207; /* Next are that tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement @@ -214,159 +215,161 @@ class OQLParserRaw#line 102 "oql-parser.php" ** shifting non-terminals after a reduce. ** self::$yy_default Default action for each state. */ - const YY_SZ_ACTTAB = 419; + const YY_SZ_ACTTAB = 429; static public $yy_action = array( - /* 0 */ 5, 57, 8, 4, 95, 96, 97, 6, 93, 76, - /* 10 */ 77, 89, 2, 53, 86, 50, 54, 25, 52, 55, - /* 20 */ 51, 46, 47, 49, 56, 70, 94, 110, 109, 108, - /* 30 */ 107, 111, 112, 115, 114, 113, 106, 71, 98, 99, - /* 40 */ 100, 104, 103, 26, 66, 38, 42, 9, 81, 5, - /* 50 */ 62, 44, 82, 95, 96, 97, 3, 93, 76, 77, - /* 60 */ 39, 102, 92, 75, 74, 73, 72, 75, 74, 73, - /* 70 */ 72, 10, 66, 41, 91, 94, 110, 109, 108, 107, - /* 80 */ 111, 112, 115, 114, 113, 106, 71, 98, 99, 100, - /* 90 */ 104, 103, 5, 63, 90, 22, 95, 96, 97, 61, - /* 100 */ 93, 76, 77, 65, 64, 60, 83, 11, 80, 79, - /* 110 */ 91, 33, 91, 22, 21, 18, 16, 12, 94, 110, - /* 120 */ 109, 108, 107, 111, 112, 115, 114, 113, 106, 71, - /* 130 */ 98, 99, 100, 104, 103, 204, 105, 87, 42, 23, - /* 140 */ 43, 24, 66, 88, 30, 28, 84, 45, 36, 6, - /* 150 */ 22, 20, 58, 15, 32, 37, 1, 76, 77, 101, - /* 160 */ 116, 75, 74, 73, 72, 41, 13, 66, 7, 160, - /* 170 */ 67, 93, 24, 42, 35, 78, 173, 173, 88, 34, - /* 180 */ 28, 84, 45, 40, 42, 173, 20, 173, 15, 69, - /* 190 */ 37, 173, 173, 173, 59, 173, 75, 74, 73, 72, - /* 200 */ 41, 42, 173, 173, 173, 173, 88, 34, 28, 84, - /* 210 */ 45, 41, 173, 173, 20, 173, 15, 173, 37, 173, - /* 220 */ 173, 173, 68, 173, 75, 74, 73, 72, 41, 173, - /* 230 */ 173, 173, 85, 42, 173, 173, 173, 173, 88, 30, - /* 240 */ 28, 84, 45, 173, 173, 173, 20, 173, 15, 173, - /* 250 */ 37, 173, 173, 173, 173, 173, 75, 74, 73, 72, - /* 260 */ 41, 42, 173, 173, 173, 173, 88, 17, 28, 84, - /* 270 */ 45, 173, 173, 173, 20, 173, 15, 42, 37, 173, - /* 280 */ 173, 48, 44, 173, 75, 74, 73, 72, 41, 173, - /* 290 */ 173, 173, 173, 42, 173, 173, 173, 173, 88, 27, - /* 300 */ 28, 84, 45, 173, 41, 173, 20, 173, 15, 173, - /* 310 */ 37, 173, 173, 173, 173, 173, 75, 74, 73, 72, - /* 320 */ 41, 42, 173, 173, 173, 173, 88, 173, 28, 84, - /* 330 */ 45, 173, 173, 173, 20, 173, 15, 173, 31, 173, - /* 340 */ 173, 173, 173, 173, 75, 74, 73, 72, 41, 173, - /* 350 */ 173, 173, 173, 42, 173, 173, 173, 173, 88, 173, - /* 360 */ 28, 84, 45, 173, 173, 173, 20, 173, 14, 173, - /* 370 */ 173, 173, 173, 173, 173, 173, 75, 74, 73, 72, - /* 380 */ 41, 42, 173, 173, 173, 173, 88, 173, 28, 84, - /* 390 */ 45, 42, 173, 173, 19, 173, 88, 173, 29, 84, - /* 400 */ 45, 173, 173, 173, 75, 74, 73, 72, 41, 173, - /* 410 */ 173, 173, 173, 173, 75, 74, 73, 72, 41, + /* 0 */ 4, 54, 8, 5, 96, 97, 98, 86, 95, 94, + /* 10 */ 76, 77, 80, 79, 53, 49, 51, 25, 45, 52, + /* 20 */ 55, 57, 46, 47, 50, 56, 70, 111, 110, 109, + /* 30 */ 108, 112, 113, 117, 116, 115, 114, 71, 106, 99, + /* 40 */ 100, 101, 105, 104, 26, 66, 66, 41, 67, 81, + /* 50 */ 4, 48, 43, 82, 96, 97, 98, 3, 95, 94, + /* 60 */ 76, 77, 38, 83, 11, 75, 74, 73, 72, 75, + /* 70 */ 74, 73, 72, 91, 63, 42, 22, 111, 110, 109, + /* 80 */ 108, 112, 113, 117, 116, 115, 114, 71, 106, 99, + /* 90 */ 100, 101, 105, 104, 4, 35, 6, 22, 96, 97, + /* 100 */ 98, 7, 95, 94, 76, 77, 65, 64, 60, 23, + /* 110 */ 6, 12, 13, 58, 41, 41, 91, 18, 61, 43, + /* 120 */ 69, 111, 110, 109, 108, 112, 113, 117, 116, 115, + /* 130 */ 114, 71, 106, 99, 100, 101, 105, 104, 208, 107, + /* 140 */ 87, 41, 42, 42, 39, 66, 88, 34, 27, 84, + /* 150 */ 89, 44, 93, 2, 36, 20, 22, 15, 92, 37, + /* 160 */ 76, 77, 102, 118, 9, 75, 74, 73, 72, 42, + /* 170 */ 94, 91, 66, 16, 24, 163, 21, 41, 103, 32, + /* 180 */ 1, 62, 88, 33, 27, 84, 89, 44, 40, 21, + /* 190 */ 31, 20, 78, 15, 90, 37, 10, 177, 177, 59, + /* 200 */ 177, 75, 74, 73, 72, 42, 41, 177, 177, 177, + /* 210 */ 177, 88, 33, 27, 84, 89, 44, 177, 177, 177, + /* 220 */ 20, 177, 15, 177, 37, 177, 177, 177, 68, 177, + /* 230 */ 75, 74, 73, 72, 42, 177, 177, 85, 41, 177, + /* 240 */ 177, 177, 177, 88, 34, 27, 84, 89, 44, 177, + /* 250 */ 177, 177, 20, 177, 15, 177, 37, 177, 177, 177, + /* 260 */ 177, 177, 75, 74, 73, 72, 42, 41, 177, 177, + /* 270 */ 177, 177, 88, 17, 27, 84, 89, 44, 177, 177, + /* 280 */ 177, 20, 177, 15, 177, 37, 177, 177, 177, 177, + /* 290 */ 177, 75, 74, 73, 72, 42, 177, 177, 177, 41, + /* 300 */ 177, 177, 177, 177, 88, 29, 27, 84, 89, 44, + /* 310 */ 177, 177, 177, 20, 177, 15, 177, 37, 177, 177, + /* 320 */ 177, 177, 177, 75, 74, 73, 72, 42, 41, 177, + /* 330 */ 177, 177, 177, 88, 177, 27, 84, 89, 44, 177, + /* 340 */ 177, 177, 20, 177, 15, 177, 30, 177, 177, 177, + /* 350 */ 177, 177, 75, 74, 73, 72, 42, 177, 177, 177, + /* 360 */ 41, 177, 177, 177, 177, 88, 177, 27, 84, 89, + /* 370 */ 44, 177, 177, 177, 20, 177, 14, 177, 177, 177, + /* 380 */ 177, 177, 177, 177, 75, 74, 73, 72, 42, 41, + /* 390 */ 177, 177, 177, 177, 88, 177, 27, 84, 89, 44, + /* 400 */ 41, 177, 177, 19, 177, 88, 177, 28, 84, 89, + /* 410 */ 44, 177, 177, 75, 74, 73, 72, 42, 177, 177, + /* 420 */ 177, 177, 177, 177, 75, 74, 73, 72, 42, ); static public $yy_lookahead = array( - /* 0 */ 7, 6, 70, 10, 11, 12, 13, 73, 15, 16, - /* 10 */ 17, 8, 9, 18, 56, 83, 84, 54, 23, 24, - /* 20 */ 25, 26, 27, 28, 29, 30, 33, 34, 35, 36, + /* 0 */ 7, 6, 72, 10, 11, 12, 13, 57, 15, 16, + /* 10 */ 17, 18, 32, 33, 19, 85, 86, 55, 55, 24, + /* 20 */ 25, 26, 27, 28, 29, 30, 31, 34, 35, 36, /* 30 */ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, - /* 40 */ 47, 48, 49, 1, 81, 54, 54, 68, 62, 7, - /* 50 */ 58, 59, 62, 11, 12, 13, 3, 15, 16, 17, - /* 60 */ 74, 82, 8, 77, 78, 79, 80, 77, 78, 79, - /* 70 */ 80, 7, 81, 81, 20, 33, 34, 35, 36, 37, - /* 80 */ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - /* 90 */ 48, 49, 7, 55, 66, 57, 11, 12, 13, 56, - /* 100 */ 15, 16, 17, 11, 12, 13, 8, 9, 31, 32, - /* 110 */ 20, 55, 20, 57, 2, 54, 6, 5, 33, 34, - /* 120 */ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - /* 130 */ 45, 46, 47, 48, 49, 51, 52, 53, 54, 2, - /* 140 */ 54, 4, 81, 59, 60, 61, 62, 63, 55, 73, - /* 150 */ 57, 67, 76, 69, 14, 71, 7, 16, 17, 21, - /* 160 */ 22, 77, 78, 79, 80, 81, 5, 81, 72, 14, - /* 170 */ 19, 15, 4, 54, 65, 81, 85, 85, 59, 60, - /* 180 */ 61, 62, 63, 64, 54, 85, 67, 85, 69, 59, - /* 190 */ 71, 85, 85, 85, 75, 85, 77, 78, 79, 80, - /* 200 */ 81, 54, 85, 85, 85, 85, 59, 60, 61, 62, - /* 210 */ 63, 81, 85, 85, 67, 85, 69, 85, 71, 85, - /* 220 */ 85, 85, 75, 85, 77, 78, 79, 80, 81, 85, - /* 230 */ 85, 85, 53, 54, 85, 85, 85, 85, 59, 60, - /* 240 */ 61, 62, 63, 85, 85, 85, 67, 85, 69, 85, - /* 250 */ 71, 85, 85, 85, 85, 85, 77, 78, 79, 80, - /* 260 */ 81, 54, 85, 85, 85, 85, 59, 60, 61, 62, - /* 270 */ 63, 85, 85, 85, 67, 85, 69, 54, 71, 85, - /* 280 */ 85, 58, 59, 85, 77, 78, 79, 80, 81, 85, - /* 290 */ 85, 85, 85, 54, 85, 85, 85, 85, 59, 60, - /* 300 */ 61, 62, 63, 85, 81, 85, 67, 85, 69, 85, - /* 310 */ 71, 85, 85, 85, 85, 85, 77, 78, 79, 80, - /* 320 */ 81, 54, 85, 85, 85, 85, 59, 85, 61, 62, - /* 330 */ 63, 85, 85, 85, 67, 85, 69, 85, 71, 85, - /* 340 */ 85, 85, 85, 85, 77, 78, 79, 80, 81, 85, - /* 350 */ 85, 85, 85, 54, 85, 85, 85, 85, 59, 85, - /* 360 */ 61, 62, 63, 85, 85, 85, 67, 85, 69, 85, - /* 370 */ 85, 85, 85, 85, 85, 85, 77, 78, 79, 80, - /* 380 */ 81, 54, 85, 85, 85, 85, 59, 85, 61, 62, - /* 390 */ 63, 54, 85, 85, 67, 85, 59, 85, 61, 62, - /* 400 */ 63, 85, 85, 85, 77, 78, 79, 80, 81, 85, - /* 410 */ 85, 85, 85, 85, 77, 78, 79, 80, 81, + /* 40 */ 47, 48, 49, 50, 1, 83, 83, 55, 20, 63, + /* 50 */ 7, 59, 60, 63, 11, 12, 13, 3, 15, 16, + /* 60 */ 17, 18, 76, 8, 9, 79, 80, 81, 82, 79, + /* 70 */ 80, 81, 82, 21, 56, 83, 58, 34, 35, 36, + /* 80 */ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + /* 90 */ 47, 48, 49, 50, 7, 56, 75, 58, 11, 12, + /* 100 */ 13, 74, 15, 16, 17, 18, 11, 12, 13, 2, + /* 110 */ 75, 5, 5, 78, 55, 55, 21, 55, 59, 60, + /* 120 */ 60, 34, 35, 36, 37, 38, 39, 40, 41, 42, + /* 130 */ 43, 44, 45, 46, 47, 48, 49, 50, 52, 53, + /* 140 */ 54, 55, 83, 83, 55, 83, 60, 61, 62, 63, + /* 150 */ 64, 65, 8, 9, 56, 69, 58, 71, 8, 73, + /* 160 */ 17, 18, 22, 23, 70, 79, 80, 81, 82, 83, + /* 170 */ 16, 21, 83, 6, 2, 14, 4, 55, 84, 14, + /* 180 */ 7, 57, 60, 61, 62, 63, 64, 65, 66, 4, + /* 190 */ 67, 69, 83, 71, 68, 73, 7, 87, 87, 77, + /* 200 */ 87, 79, 80, 81, 82, 83, 55, 87, 87, 87, + /* 210 */ 87, 60, 61, 62, 63, 64, 65, 87, 87, 87, + /* 220 */ 69, 87, 71, 87, 73, 87, 87, 87, 77, 87, + /* 230 */ 79, 80, 81, 82, 83, 87, 87, 54, 55, 87, + /* 240 */ 87, 87, 87, 60, 61, 62, 63, 64, 65, 87, + /* 250 */ 87, 87, 69, 87, 71, 87, 73, 87, 87, 87, + /* 260 */ 87, 87, 79, 80, 81, 82, 83, 55, 87, 87, + /* 270 */ 87, 87, 60, 61, 62, 63, 64, 65, 87, 87, + /* 280 */ 87, 69, 87, 71, 87, 73, 87, 87, 87, 87, + /* 290 */ 87, 79, 80, 81, 82, 83, 87, 87, 87, 55, + /* 300 */ 87, 87, 87, 87, 60, 61, 62, 63, 64, 65, + /* 310 */ 87, 87, 87, 69, 87, 71, 87, 73, 87, 87, + /* 320 */ 87, 87, 87, 79, 80, 81, 82, 83, 55, 87, + /* 330 */ 87, 87, 87, 60, 87, 62, 63, 64, 65, 87, + /* 340 */ 87, 87, 69, 87, 71, 87, 73, 87, 87, 87, + /* 350 */ 87, 87, 79, 80, 81, 82, 83, 87, 87, 87, + /* 360 */ 55, 87, 87, 87, 87, 60, 87, 62, 63, 64, + /* 370 */ 65, 87, 87, 87, 69, 87, 71, 87, 87, 87, + /* 380 */ 87, 87, 87, 87, 79, 80, 81, 82, 83, 55, + /* 390 */ 87, 87, 87, 87, 60, 87, 62, 63, 64, 65, + /* 400 */ 55, 87, 87, 69, 87, 60, 87, 62, 63, 64, + /* 410 */ 65, 87, 87, 79, 80, 81, 82, 83, 87, 87, + /* 420 */ 87, 87, 87, 87, 79, 80, 81, 82, 83, ); - const YY_SHIFT_USE_DFLT = -8; + const YY_SHIFT_USE_DFLT = -21; const YY_SHIFT_MAX = 45; static public $yy_shift_ofst = array( - /* 0 */ 42, -7, -7, 85, 85, 85, 85, 85, 85, 85, - /* 10 */ 141, 141, 156, 156, -5, -5, 156, 92, 137, 138, - /* 20 */ 138, 156, 168, 156, 156, 168, 156, 54, 77, 77, - /* 30 */ 90, 151, 156, 53, 90, 64, 53, 151, 112, 98, - /* 40 */ 3, 155, 140, 161, 110, 149, + /* 0 */ 43, -7, -7, 87, 87, 87, 87, 87, 87, 87, + /* 10 */ 143, 143, 154, 154, -5, -5, 154, 95, 172, 140, + /* 20 */ 140, 154, 185, 154, 154, 185, 154, -20, -20, 150, + /* 30 */ 28, 189, 154, 52, 52, 54, 54, 28, 55, 107, + /* 40 */ 144, 165, 161, 167, 173, 106, ); - const YY_REDUCE_USE_DFLT = -69; + const YY_REDUCE_USE_DFLT = -71; const YY_REDUCE_MAX = 37; static public $yy_reduce_ofst = array( - /* 0 */ 84, 119, 147, 179, 207, 239, 267, 299, 327, 337, - /* 10 */ -14, -10, 223, -8, -68, -68, 130, 76, 93, -21, - /* 20 */ -21, 86, 38, -37, -9, 56, 61, -66, 109, 109, - /* 30 */ -66, 96, 94, 43, -66, 28, -42, 96, + /* 0 */ 86, 122, 151, 183, 244, 212, 273, 305, 334, 345, + /* 10 */ -14, -10, 59, -8, -70, -70, 60, 35, 98, 94, + /* 20 */ 94, 89, 18, -37, -38, 39, 62, 123, 123, 21, + /* 30 */ 27, 126, 109, 21, 21, 124, -50, 27, ); static public $yyExpectedTokens = array( - /* 0 */ array(1, 7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 1 */ array(7, 10, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 2 */ array(7, 10, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 3 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 4 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 5 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 6 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 7 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 8 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 9 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ), - /* 10 */ array(16, 17, ), - /* 11 */ array(16, 17, ), - /* 12 */ array(15, ), - /* 13 */ array(15, ), - /* 14 */ array(6, 18, 23, 24, 25, 26, 27, 28, 29, 30, ), - /* 15 */ array(6, 18, 23, 24, 25, 26, 27, 28, 29, 30, ), - /* 16 */ array(15, ), - /* 17 */ array(11, 12, 13, 20, ), + /* 0 */ array(1, 7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 1 */ array(7, 10, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 2 */ array(7, 10, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 3 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 4 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 5 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 6 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 7 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 8 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 9 */ array(7, 11, 12, 13, 15, 16, 17, 18, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, ), + /* 10 */ array(17, 18, ), + /* 11 */ array(17, 18, ), + /* 12 */ array(16, ), + /* 13 */ array(16, ), + /* 14 */ array(6, 19, 24, 25, 26, 27, 28, 29, 30, 31, ), + /* 15 */ array(6, 19, 24, 25, 26, 27, 28, 29, 30, 31, ), + /* 16 */ array(16, ), + /* 17 */ array(11, 12, 13, 21, ), /* 18 */ array(2, 4, ), - /* 19 */ array(21, 22, ), - /* 20 */ array(21, 22, ), - /* 21 */ array(15, ), + /* 19 */ array(22, 23, ), + /* 20 */ array(22, 23, ), + /* 21 */ array(16, ), /* 22 */ array(4, ), - /* 23 */ array(15, ), - /* 24 */ array(15, ), + /* 23 */ array(16, ), + /* 24 */ array(16, ), /* 25 */ array(4, ), - /* 26 */ array(15, ), - /* 27 */ array(8, 20, ), - /* 28 */ array(31, 32, ), - /* 29 */ array(31, 32, ), + /* 26 */ array(16, ), + /* 27 */ array(32, 33, ), + /* 28 */ array(32, 33, ), + /* 29 */ array(8, 21, ), /* 30 */ array(20, ), - /* 31 */ array(19, ), - /* 32 */ array(15, ), - /* 33 */ array(3, ), - /* 34 */ array(20, ), - /* 35 */ array(7, ), + /* 31 */ array(7, ), + /* 32 */ array(16, ), + /* 33 */ array(21, ), + /* 34 */ array(21, ), + /* 35 */ array(3, ), /* 36 */ array(3, ), - /* 37 */ array(19, ), - /* 38 */ array(2, 5, ), - /* 39 */ array(8, 9, ), + /* 37 */ array(20, ), + /* 38 */ array(8, 9, ), + /* 39 */ array(2, 5, ), /* 40 */ array(8, 9, ), /* 41 */ array(14, ), /* 42 */ array(14, ), - /* 43 */ array(5, ), - /* 44 */ array(6, ), - /* 45 */ array(7, ), + /* 43 */ array(6, ), + /* 44 */ array(7, ), + /* 45 */ array(5, ), /* 46 */ array(), /* 47 */ array(), /* 48 */ array(), @@ -438,20 +441,22 @@ static public $yy_action = array( /* 114 */ array(), /* 115 */ array(), /* 116 */ array(), + /* 117 */ array(), + /* 118 */ array(), ); static public $yy_default = array( - /* 0 */ 203, 146, 203, 203, 203, 203, 203, 203, 203, 203, - /* 10 */ 203, 203, 203, 203, 140, 139, 203, 203, 125, 138, - /* 20 */ 137, 203, 124, 203, 203, 125, 203, 203, 135, 136, - /* 30 */ 129, 142, 203, 122, 149, 203, 122, 141, 203, 203, - /* 40 */ 203, 158, 203, 203, 203, 203, 176, 177, 127, 178, - /* 50 */ 165, 175, 173, 168, 166, 174, 179, 167, 150, 147, - /* 60 */ 153, 120, 126, 123, 152, 151, 160, 169, 148, 128, - /* 70 */ 180, 194, 157, 156, 155, 154, 162, 163, 159, 182, - /* 80 */ 181, 144, 145, 143, 130, 121, 119, 118, 131, 132, - /* 90 */ 134, 170, 133, 161, 183, 198, 197, 196, 195, 199, - /* 100 */ 200, 171, 164, 202, 201, 117, 193, 187, 186, 185, - /* 110 */ 184, 188, 189, 192, 191, 190, 172, + /* 0 */ 207, 149, 207, 207, 207, 207, 207, 207, 207, 207, + /* 10 */ 207, 207, 207, 207, 143, 142, 207, 207, 127, 141, + /* 20 */ 140, 207, 126, 207, 207, 127, 207, 138, 139, 207, + /* 30 */ 145, 207, 207, 152, 131, 124, 124, 144, 207, 207, + /* 40 */ 207, 207, 161, 207, 207, 207, 180, 181, 129, 169, + /* 50 */ 182, 170, 177, 172, 171, 178, 183, 179, 153, 150, + /* 60 */ 156, 128, 122, 125, 155, 154, 163, 173, 151, 130, + /* 70 */ 184, 197, 160, 159, 158, 157, 166, 167, 162, 186, + /* 80 */ 185, 147, 148, 146, 132, 123, 121, 120, 133, 134, + /* 90 */ 137, 174, 136, 135, 165, 164, 202, 201, 200, 199, + /* 100 */ 203, 204, 175, 168, 206, 205, 198, 119, 190, 189, + /* 110 */ 188, 187, 191, 192, 196, 195, 194, 193, 176, ); /* The next thing included is series of defines which control ** various aspects of the generated parser. @@ -468,11 +473,11 @@ static public $yy_action = array( ** self::YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. */ - const YYNOCODE = 86; + const YYNOCODE = 88; const YYSTACKDEPTH = 100; - const YYNSTATE = 117; - const YYNRULE = 86; - const YYERRORSYMBOL = 50; + const YYNSTATE = 119; + const YYNRULE = 88; + const YYERRORSYMBOL = 51; const YYERRSYMDT = 'yy0'; const YYFALLBACK = 0; /** The next table maps tokens into fallback tokens. If a construct @@ -557,25 +562,25 @@ static public $yy_action = array( '$', 'SELECT', 'AS_ALIAS', 'WHERE', 'JOIN', 'ON', 'EQ', 'PAR_OPEN', 'PAR_CLOSE', 'COMA', 'INTERVAL', 'F_DAY', - 'F_MONTH', 'F_YEAR', 'DOT', 'NAME', - 'NUMVAL', 'STRVAL', 'NOT_EQ', 'LOG_AND', - 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', - 'MATH_MINUS', 'GT', 'LT', 'GE', - 'LE', 'LIKE', 'NOT_LIKE', 'IN', - 'NOT_IN', 'F_IF', 'F_ELT', 'F_COALESCE', - 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', 'F_DATE', - 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', 'F_TIME', - 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', 'F_DATE_SUB', - 'F_ROUND', 'F_FLOOR', 'error', 'result', - 'query', 'condition', 'class_name', 'join_statement', - 'where_statement', 'join_item', 'join_condition', 'field_id', - 'expression_prio4', 'expression_basic', 'scalar', 'func_name', - 'arg_list', 'list_operator', 'list', 'expression_prio1', - 'operator1', 'expression_prio2', 'operator2', 'expression_prio3', - 'operator3', 'operator4', 'scalar_list', 'argument', - 'interval_unit', 'num_scalar', 'str_scalar', 'num_value', - 'str_value', 'name', 'num_operator1', 'num_operator2', - 'str_operator', + 'F_MONTH', 'F_YEAR', 'DOT', 'VARNAME', + 'NAME', 'NUMVAL', 'STRVAL', 'NOT_EQ', + 'LOG_AND', 'LOG_OR', 'MATH_DIV', 'MATH_MULT', + 'MATH_PLUS', 'MATH_MINUS', 'GT', 'LT', + 'GE', 'LE', 'LIKE', 'NOT_LIKE', + 'IN', 'NOT_IN', 'F_IF', 'F_ELT', + 'F_COALESCE', 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', + 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', + 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', + 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', 'error', + 'result', 'query', 'condition', 'class_name', + 'join_statement', 'where_statement', 'join_item', 'join_condition', + 'field_id', 'expression_prio4', 'expression_basic', 'scalar', + 'var_name', 'func_name', 'arg_list', 'list_operator', + 'list', 'expression_prio1', 'operator1', 'expression_prio2', + 'operator2', 'expression_prio3', 'operator3', 'operator4', + 'scalar_list', 'argument', 'interval_unit', 'num_scalar', + 'str_scalar', 'num_value', 'str_value', 'name', + 'num_operator1', 'num_operator2', 'str_operator', ); /** @@ -598,77 +603,79 @@ static public $yy_action = array( /* 12 */ "condition ::= expression_prio4", /* 13 */ "expression_basic ::= scalar", /* 14 */ "expression_basic ::= field_id", - /* 15 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", - /* 16 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", - /* 17 */ "expression_basic ::= expression_basic list_operator list", - /* 18 */ "expression_prio1 ::= expression_basic", - /* 19 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", - /* 20 */ "expression_prio2 ::= expression_prio1", - /* 21 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", - /* 22 */ "expression_prio3 ::= expression_prio2", - /* 23 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", - /* 24 */ "expression_prio4 ::= expression_prio3", - /* 25 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", - /* 26 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", - /* 27 */ "scalar_list ::= scalar", - /* 28 */ "scalar_list ::= scalar_list COMA scalar", - /* 29 */ "arg_list ::=", - /* 30 */ "arg_list ::= argument", - /* 31 */ "arg_list ::= arg_list COMA argument", - /* 32 */ "argument ::= expression_prio4", - /* 33 */ "argument ::= INTERVAL expression_prio4 interval_unit", - /* 34 */ "interval_unit ::= F_DAY", - /* 35 */ "interval_unit ::= F_MONTH", - /* 36 */ "interval_unit ::= F_YEAR", - /* 37 */ "scalar ::= num_scalar", - /* 38 */ "scalar ::= str_scalar", - /* 39 */ "num_scalar ::= num_value", - /* 40 */ "str_scalar ::= str_value", - /* 41 */ "field_id ::= name", - /* 42 */ "field_id ::= class_name DOT name", - /* 43 */ "class_name ::= name", - /* 44 */ "name ::= NAME", - /* 45 */ "num_value ::= NUMVAL", - /* 46 */ "str_value ::= STRVAL", - /* 47 */ "operator1 ::= num_operator1", - /* 48 */ "operator2 ::= num_operator2", - /* 49 */ "operator2 ::= str_operator", - /* 50 */ "operator2 ::= EQ", - /* 51 */ "operator2 ::= NOT_EQ", - /* 52 */ "operator3 ::= LOG_AND", - /* 53 */ "operator4 ::= LOG_OR", - /* 54 */ "num_operator1 ::= MATH_DIV", - /* 55 */ "num_operator1 ::= MATH_MULT", - /* 56 */ "num_operator2 ::= MATH_PLUS", - /* 57 */ "num_operator2 ::= MATH_MINUS", - /* 58 */ "num_operator2 ::= GT", - /* 59 */ "num_operator2 ::= LT", - /* 60 */ "num_operator2 ::= GE", - /* 61 */ "num_operator2 ::= LE", - /* 62 */ "str_operator ::= LIKE", - /* 63 */ "str_operator ::= NOT_LIKE", - /* 64 */ "list_operator ::= IN", - /* 65 */ "list_operator ::= NOT_IN", - /* 66 */ "func_name ::= F_IF", - /* 67 */ "func_name ::= F_ELT", - /* 68 */ "func_name ::= F_COALESCE", - /* 69 */ "func_name ::= F_CONCAT", - /* 70 */ "func_name ::= F_SUBSTR", - /* 71 */ "func_name ::= F_TRIM", - /* 72 */ "func_name ::= F_DATE", - /* 73 */ "func_name ::= F_DATE_FORMAT", - /* 74 */ "func_name ::= F_CURRENT_DATE", - /* 75 */ "func_name ::= F_NOW", - /* 76 */ "func_name ::= F_TIME", - /* 77 */ "func_name ::= F_TO_DAYS", - /* 78 */ "func_name ::= F_FROM_DAYS", - /* 79 */ "func_name ::= F_YEAR", - /* 80 */ "func_name ::= F_MONTH", - /* 81 */ "func_name ::= F_DAY", - /* 82 */ "func_name ::= F_DATE_ADD", - /* 83 */ "func_name ::= F_DATE_SUB", - /* 84 */ "func_name ::= F_ROUND", - /* 85 */ "func_name ::= F_FLOOR", + /* 15 */ "expression_basic ::= var_name", + /* 16 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", + /* 17 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", + /* 18 */ "expression_basic ::= expression_basic list_operator list", + /* 19 */ "expression_prio1 ::= expression_basic", + /* 20 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", + /* 21 */ "expression_prio2 ::= expression_prio1", + /* 22 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", + /* 23 */ "expression_prio3 ::= expression_prio2", + /* 24 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", + /* 25 */ "expression_prio4 ::= expression_prio3", + /* 26 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", + /* 27 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", + /* 28 */ "scalar_list ::= scalar", + /* 29 */ "scalar_list ::= scalar_list COMA scalar", + /* 30 */ "arg_list ::=", + /* 31 */ "arg_list ::= argument", + /* 32 */ "arg_list ::= arg_list COMA argument", + /* 33 */ "argument ::= expression_prio4", + /* 34 */ "argument ::= INTERVAL expression_prio4 interval_unit", + /* 35 */ "interval_unit ::= F_DAY", + /* 36 */ "interval_unit ::= F_MONTH", + /* 37 */ "interval_unit ::= F_YEAR", + /* 38 */ "scalar ::= num_scalar", + /* 39 */ "scalar ::= str_scalar", + /* 40 */ "num_scalar ::= num_value", + /* 41 */ "str_scalar ::= str_value", + /* 42 */ "field_id ::= name", + /* 43 */ "field_id ::= class_name DOT name", + /* 44 */ "class_name ::= name", + /* 45 */ "var_name ::= VARNAME", + /* 46 */ "name ::= NAME", + /* 47 */ "num_value ::= NUMVAL", + /* 48 */ "str_value ::= STRVAL", + /* 49 */ "operator1 ::= num_operator1", + /* 50 */ "operator2 ::= num_operator2", + /* 51 */ "operator2 ::= str_operator", + /* 52 */ "operator2 ::= EQ", + /* 53 */ "operator2 ::= NOT_EQ", + /* 54 */ "operator3 ::= LOG_AND", + /* 55 */ "operator4 ::= LOG_OR", + /* 56 */ "num_operator1 ::= MATH_DIV", + /* 57 */ "num_operator1 ::= MATH_MULT", + /* 58 */ "num_operator2 ::= MATH_PLUS", + /* 59 */ "num_operator2 ::= MATH_MINUS", + /* 60 */ "num_operator2 ::= GT", + /* 61 */ "num_operator2 ::= LT", + /* 62 */ "num_operator2 ::= GE", + /* 63 */ "num_operator2 ::= LE", + /* 64 */ "str_operator ::= LIKE", + /* 65 */ "str_operator ::= NOT_LIKE", + /* 66 */ "list_operator ::= IN", + /* 67 */ "list_operator ::= NOT_IN", + /* 68 */ "func_name ::= F_IF", + /* 69 */ "func_name ::= F_ELT", + /* 70 */ "func_name ::= F_COALESCE", + /* 71 */ "func_name ::= F_CONCAT", + /* 72 */ "func_name ::= F_SUBSTR", + /* 73 */ "func_name ::= F_TRIM", + /* 74 */ "func_name ::= F_DATE", + /* 75 */ "func_name ::= F_DATE_FORMAT", + /* 76 */ "func_name ::= F_CURRENT_DATE", + /* 77 */ "func_name ::= F_NOW", + /* 78 */ "func_name ::= F_TIME", + /* 79 */ "func_name ::= F_TO_DAYS", + /* 80 */ "func_name ::= F_FROM_DAYS", + /* 81 */ "func_name ::= F_YEAR", + /* 82 */ "func_name ::= F_MONTH", + /* 83 */ "func_name ::= F_DAY", + /* 84 */ "func_name ::= F_DATE_ADD", + /* 85 */ "func_name ::= F_DATE_SUB", + /* 86 */ "func_name ::= F_ROUND", + /* 87 */ "func_name ::= F_FLOOR", ); /** @@ -1033,92 +1040,94 @@ static public $yy_action = array( * */ static public $yyRuleInfo = array( - array( 'lhs' => 51, 'rhs' => 1 ), - array( 'lhs' => 51, 'rhs' => 1 ), - array( 'lhs' => 52, 'rhs' => 4 ), - array( 'lhs' => 52, 'rhs' => 6 ), + array( 'lhs' => 52, 'rhs' => 1 ), + array( 'lhs' => 52, 'rhs' => 1 ), + array( 'lhs' => 53, 'rhs' => 4 ), + array( 'lhs' => 53, 'rhs' => 6 ), + array( 'lhs' => 57, 'rhs' => 2 ), + array( 'lhs' => 57, 'rhs' => 0 ), array( 'lhs' => 56, 'rhs' => 2 ), + array( 'lhs' => 56, 'rhs' => 1 ), array( 'lhs' => 56, 'rhs' => 0 ), - array( 'lhs' => 55, 'rhs' => 2 ), - array( 'lhs' => 55, 'rhs' => 1 ), - array( 'lhs' => 55, 'rhs' => 0 ), - array( 'lhs' => 57, 'rhs' => 6 ), - array( 'lhs' => 57, 'rhs' => 4 ), - array( 'lhs' => 58, 'rhs' => 3 ), - array( 'lhs' => 53, 'rhs' => 1 ), - array( 'lhs' => 61, 'rhs' => 1 ), - array( 'lhs' => 61, 'rhs' => 1 ), - array( 'lhs' => 61, 'rhs' => 4 ), - array( 'lhs' => 61, 'rhs' => 3 ), - array( 'lhs' => 61, 'rhs' => 3 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 3 ), + array( 'lhs' => 58, 'rhs' => 6 ), + array( 'lhs' => 58, 'rhs' => 4 ), + array( 'lhs' => 59, 'rhs' => 3 ), + array( 'lhs' => 54, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 4 ), + array( 'lhs' => 62, 'rhs' => 3 ), + array( 'lhs' => 62, 'rhs' => 3 ), array( 'lhs' => 69, 'rhs' => 1 ), array( 'lhs' => 69, 'rhs' => 3 ), array( 'lhs' => 71, 'rhs' => 1 ), array( 'lhs' => 71, 'rhs' => 3 ), - array( 'lhs' => 60, 'rhs' => 1 ), - array( 'lhs' => 60, 'rhs' => 3 ), + array( 'lhs' => 73, 'rhs' => 1 ), + array( 'lhs' => 73, 'rhs' => 3 ), + array( 'lhs' => 61, 'rhs' => 1 ), + array( 'lhs' => 61, 'rhs' => 3 ), + array( 'lhs' => 68, 'rhs' => 3 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 3 ), + array( 'lhs' => 66, 'rhs' => 0 ), + array( 'lhs' => 66, 'rhs' => 1 ), array( 'lhs' => 66, 'rhs' => 3 ), - array( 'lhs' => 74, 'rhs' => 1 ), - array( 'lhs' => 74, 'rhs' => 3 ), - array( 'lhs' => 64, 'rhs' => 0 ), - array( 'lhs' => 64, 'rhs' => 1 ), - array( 'lhs' => 64, 'rhs' => 3 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 3 ), - array( 'lhs' => 76, 'rhs' => 1 ), - array( 'lhs' => 76, 'rhs' => 1 ), - array( 'lhs' => 76, 'rhs' => 1 ), - array( 'lhs' => 62, 'rhs' => 1 ), - array( 'lhs' => 62, 'rhs' => 1 ), array( 'lhs' => 77, 'rhs' => 1 ), + array( 'lhs' => 77, 'rhs' => 3 ), array( 'lhs' => 78, 'rhs' => 1 ), - array( 'lhs' => 59, 'rhs' => 1 ), - array( 'lhs' => 59, 'rhs' => 3 ), - array( 'lhs' => 54, 'rhs' => 1 ), - array( 'lhs' => 81, 'rhs' => 1 ), + array( 'lhs' => 78, 'rhs' => 1 ), + array( 'lhs' => 78, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), + array( 'lhs' => 63, 'rhs' => 1 ), array( 'lhs' => 79, 'rhs' => 1 ), array( 'lhs' => 80, 'rhs' => 1 ), - array( 'lhs' => 68, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 60, 'rhs' => 1 ), + array( 'lhs' => 60, 'rhs' => 3 ), + array( 'lhs' => 55, 'rhs' => 1 ), + array( 'lhs' => 64, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 81, 'rhs' => 1 ), + array( 'lhs' => 82, 'rhs' => 1 ), array( 'lhs' => 70, 'rhs' => 1 ), array( 'lhs' => 72, 'rhs' => 1 ), - array( 'lhs' => 73, 'rhs' => 1 ), - array( 'lhs' => 82, 'rhs' => 1 ), - array( 'lhs' => 82, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 74, 'rhs' => 1 ), + array( 'lhs' => 75, 'rhs' => 1 ), array( 'lhs' => 84, 'rhs' => 1 ), array( 'lhs' => 84, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), + array( 'lhs' => 65, 'rhs' => 1 ), array( 'lhs' => 65, 'rhs' => 1 ), array( 'lhs' => 65, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), - array( 'lhs' => 63, 'rhs' => 1 ), ); /** @@ -1143,77 +1152,79 @@ static public $yy_action = array( 12 => 12, 13 => 12, 14 => 12, - 18 => 12, - 20 => 12, - 22 => 12, - 24 => 12, - 32 => 12, - 34 => 12, + 15 => 12, + 19 => 12, + 21 => 12, + 23 => 12, + 25 => 12, + 33 => 12, 35 => 12, 36 => 12, 37 => 12, 38 => 12, - 15 => 15, + 39 => 12, 16 => 16, 17 => 17, - 19 => 17, - 21 => 17, - 23 => 17, - 25 => 17, - 26 => 26, + 18 => 18, + 20 => 18, + 22 => 18, + 24 => 18, + 26 => 18, 27 => 27, - 30 => 27, 28 => 28, 31 => 28, 29 => 29, - 33 => 33, - 39 => 39, - 40 => 39, - 41 => 41, + 32 => 29, + 30 => 30, + 34 => 34, + 40 => 40, + 41 => 40, 42 => 42, 43 => 43, - 66 => 43, - 67 => 43, - 68 => 43, - 69 => 43, - 70 => 43, - 71 => 43, - 72 => 43, - 73 => 43, - 74 => 43, - 75 => 43, - 76 => 43, - 77 => 43, - 78 => 43, - 79 => 43, - 80 => 43, - 81 => 43, - 82 => 43, - 83 => 43, - 84 => 43, - 85 => 43, 44 => 44, + 68 => 44, + 69 => 44, + 70 => 44, + 71 => 44, + 72 => 44, + 73 => 44, + 74 => 44, + 75 => 44, + 76 => 44, + 77 => 44, + 78 => 44, + 79 => 44, + 80 => 44, + 81 => 44, + 82 => 44, + 83 => 44, + 84 => 44, + 85 => 44, + 86 => 44, + 87 => 44, 45 => 45, - 47 => 45, - 48 => 45, - 49 => 45, - 50 => 45, - 51 => 45, - 52 => 45, - 53 => 45, - 54 => 45, - 55 => 45, - 56 => 45, - 57 => 45, - 58 => 45, - 59 => 45, - 60 => 45, - 61 => 45, - 62 => 45, - 63 => 45, - 64 => 45, - 65 => 45, 46 => 46, + 47 => 47, + 49 => 47, + 50 => 47, + 51 => 47, + 52 => 47, + 53 => 47, + 54 => 47, + 55 => 47, + 56 => 47, + 57 => 47, + 58 => 47, + 59 => 47, + 60 => 47, + 61 => 47, + 62 => 47, + 63 => 47, + 64 => 47, + 65 => 47, + 66 => 47, + 67 => 47, + 48 => 48, ); /* Beginning here are the reduction cases. A typical example ** follows: @@ -1223,101 +1234,104 @@ static public $yy_action = array( */ #line 29 "oql-parser.y" function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } -#line 1230 "oql-parser.php" +#line 1241 "oql-parser.php" #line 32 "oql-parser.y" function yy_r2(){ - $this->_retvalue = new OqlQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1235 "oql-parser.php" +#line 1246 "oql-parser.php" #line 35 "oql-parser.y" function yy_r3(){ - $this->_retvalue = new OqlQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1240 "oql-parser.php" -#line 39 "oql-parser.y" +#line 1251 "oql-parser.php" +#line 48 "oql-parser.y" function yy_r4(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1243 "oql-parser.php" -#line 40 "oql-parser.y" +#line 1254 "oql-parser.php" +#line 49 "oql-parser.y" function yy_r5(){ $this->_retvalue = null; } -#line 1246 "oql-parser.php" -#line 42 "oql-parser.y" +#line 1257 "oql-parser.php" +#line 51 "oql-parser.y" function yy_r6(){ // insert the join statement on top of the existing list array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); // and return the updated array $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1254 "oql-parser.php" -#line 48 "oql-parser.y" +#line 1265 "oql-parser.php" +#line 57 "oql-parser.y" function yy_r7(){ $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); } -#line 1259 "oql-parser.php" -#line 54 "oql-parser.y" +#line 1270 "oql-parser.php" +#line 63 "oql-parser.y" function yy_r9(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1265 "oql-parser.php" -#line 59 "oql-parser.y" +#line 1276 "oql-parser.php" +#line 68 "oql-parser.y" function yy_r10(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1271 "oql-parser.php" -#line 64 "oql-parser.y" +#line 1282 "oql-parser.php" +#line 73 "oql-parser.y" function yy_r11(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } -#line 1274 "oql-parser.php" -#line 66 "oql-parser.y" +#line 1285 "oql-parser.php" +#line 75 "oql-parser.y" function yy_r12(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1277 "oql-parser.php" -#line 70 "oql-parser.y" - function yy_r15(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1280 "oql-parser.php" -#line 71 "oql-parser.y" - function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } -#line 1283 "oql-parser.php" -#line 72 "oql-parser.y" - function yy_r17(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1286 "oql-parser.php" -#line 87 "oql-parser.y" - function yy_r26(){ +#line 1288 "oql-parser.php" +#line 80 "oql-parser.y" + function yy_r16(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } +#line 1291 "oql-parser.php" +#line 81 "oql-parser.y" + function yy_r17(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } +#line 1294 "oql-parser.php" +#line 82 "oql-parser.y" + function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1297 "oql-parser.php" +#line 97 "oql-parser.y" + function yy_r27(){ $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); } -#line 1291 "oql-parser.php" -#line 90 "oql-parser.y" - function yy_r27(){ +#line 1302 "oql-parser.php" +#line 100 "oql-parser.y" + function yy_r28(){ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); } -#line 1296 "oql-parser.php" -#line 93 "oql-parser.y" - function yy_r28(){ +#line 1307 "oql-parser.php" +#line 103 "oql-parser.y" + function yy_r29(){ array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; } -#line 1302 "oql-parser.php" -#line 98 "oql-parser.y" - function yy_r29(){ +#line 1313 "oql-parser.php" +#line 108 "oql-parser.y" + function yy_r30(){ $this->_retvalue = array(); } -#line 1307 "oql-parser.php" -#line 109 "oql-parser.y" - function yy_r33(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1310 "oql-parser.php" -#line 118 "oql-parser.y" - function yy_r39(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1313 "oql-parser.php" -#line 121 "oql-parser.y" - function yy_r41(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1316 "oql-parser.php" -#line 122 "oql-parser.y" - function yy_r42(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } -#line 1319 "oql-parser.php" -#line 123 "oql-parser.y" - function yy_r43(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1322 "oql-parser.php" -#line 125 "oql-parser.y" - function yy_r44(){ +#line 1318 "oql-parser.php" +#line 119 "oql-parser.y" + function yy_r34(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1321 "oql-parser.php" +#line 128 "oql-parser.y" + function yy_r40(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1324 "oql-parser.php" +#line 131 "oql-parser.y" + function yy_r42(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1327 "oql-parser.php" +#line 132 "oql-parser.y" + function yy_r43(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } +#line 1330 "oql-parser.php" +#line 133 "oql-parser.y" + function yy_r44(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1333 "oql-parser.php" +#line 136 "oql-parser.y" + function yy_r45(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } +#line 1336 "oql-parser.php" +#line 138 "oql-parser.y" + function yy_r46(){ if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') { $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); @@ -1328,13 +1342,13 @@ static public $yy_action = array( } $this->_retvalue = new OqlName($name, $this->m_iColPrev); } -#line 1335 "oql-parser.php" -#line 137 "oql-parser.y" - function yy_r45(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1338 "oql-parser.php" -#line 138 "oql-parser.y" - function yy_r46(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } -#line 1341 "oql-parser.php" +#line 1349 "oql-parser.php" +#line 150 "oql-parser.y" + function yy_r47(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1352 "oql-parser.php" +#line 151 "oql-parser.y" + function yy_r48(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } +#line 1355 "oql-parser.php" /** * placeholder for the left hand side in a reduce operation. @@ -1449,7 +1463,7 @@ static public $yy_action = array( #line 25 "oql-parser.y" throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); -#line 1457 "oql-parser.php" +#line 1471 "oql-parser.php" } /** @@ -1601,7 +1615,7 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo } } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); } -}#line 186 "oql-parser.y" +}#line 199 "oql-parser.y" class OQLParserException extends OQLException @@ -1666,4 +1680,4 @@ class OQLParser extends OQLParserRaw } } -#line 1676 "oql-parser.php" +#line 1690 "oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index 551a82ada6..6d0a5c555d 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -30,12 +30,21 @@ result ::= query(X). { $this->my_result = X; } result ::= condition(X). { $this->my_result = X; } query(A) ::= SELECT class_name(X) join_statement(J) where_statement(W). { - A = new OqlQuery(X, X, W, J); + A = new OqlObjectQuery(X, X, W, J); } query(A) ::= SELECT class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). { - A = new OqlQuery(X, Y, W, J); + A = new OqlObjectQuery(X, Y, W, J); } +/* +query(A) ::= SELECT field_id(E) FROM class_name(X) join_statement(J) where_statement(W). { + A = new OqlValueSetQuery(E, X, X, W, J); +} +query(A) ::= SELECT field_id(E) FROM class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). { + A = new OqlValueSetQuery(E, X, Y, W, J); +} +*/ + where_statement(A) ::= WHERE condition(C). { A = C;} where_statement(A) ::= . { A = null;} @@ -67,6 +76,7 @@ condition(A) ::= expression_prio4(X). { A = X; } expression_basic(A) ::= scalar(X). { A = X; } expression_basic(A) ::= field_id(X). { A = X; } +expression_basic(A) ::= var_name(X). { A = X; } expression_basic(A) ::= func_name(X) PAR_OPEN arg_list(Y) PAR_CLOSE. { A = new FunctionOqlExpression(X, Y); } expression_basic(A) ::= PAR_OPEN expression_prio4(X) PAR_CLOSE. { A = X; } expression_basic(A) ::= expression_basic(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); } @@ -122,6 +132,9 @@ field_id(A) ::= name(X). { A = new FieldOqlExpression(X); } field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); } class_name(A) ::= name(X). { A=X; } + +var_name(A) ::= VARNAME(X). { A = new VariableOqlExpression(substr(X, 1)); } + name(A) ::= NAME(X). { if (X[0] == '`') { diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index a464413da2..ac72a12d9a 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -35,16 +35,28 @@ class OqlInterpreter return $res; } - public function ParseQuery() + public function ParseObjectQuery() { $oRes = $this->Parse(); - if (!$oRes instanceof OqlQuery) + if (!$oRes instanceof OqlObjectQuery) { - throw new OqlException('Expecting an OQL query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlQuery')); + throw new OqlException('Expecting an OQL query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlObjectQuery')); } return $oRes; } +/* + public function ParseValueSetQuery() + { + $oRes = $this->Parse(); + if (!$oRes instanceof OqlValueSetQuery) + { + throw new OqlException('Expecting a value set query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlValueSetQuery')); + } + return $oRes; + } +*/ + public function ParseExpression() { $oRes = $this->Parse(); diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index ad71950b3f..f38cb2e027 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -111,6 +111,10 @@ class FieldOqlExpression extends FieldExpression } } +class VariableOqlExpression extends VariableExpression +{ +} + class ListOqlExpression extends ListExpression { } @@ -122,19 +126,38 @@ class FunctionOqlExpression extends FunctionExpression class IntervalOqlExpression extends IntervalExpression { } -class OqlQuery + +abstract class OqlQuery +{ + protected $m_aJoins; // array of OqlJoinSpec + protected $m_oCondition; // condition tree (expressions) + + public function __construct($oCondition = null, $aJoins = null) + { + $this->m_aJoins = $aJoins; + $this->m_oCondition = $oCondition; + } + + public function GetJoins() + { + return $this->m_aJoins; + } + public function GetCondition() + { + return $this->m_oCondition; + } +} + +class OqlObjectQuery extends OqlQuery { protected $m_oClass; protected $m_oClassAlias; - protected $m_aJoins; // array of OqlJoinSpec - protected $m_oCondition; // condition tree (expressions) public function __construct($oClass, $oClassAlias = '', $oCondition = null, $aJoins = null) { $this->m_oClass = $oClass; $this->m_oClassAlias = $oClassAlias; - $this->m_aJoins = $aJoins; - $this->m_oCondition = $oCondition; + parent::__construct($oCondition, $aJoins); } public function GetClass() @@ -154,15 +177,22 @@ class OqlQuery { return $this->m_oClassAlias; } - - public function GetJoins() - { - return $this->m_aJoins; - } - public function GetCondition() - { - return $this->m_oCondition; - } } + +class OqlValueSetQuery extends OqlObjectQuery +{ + protected $m_oSelectExpr; + + public function __construct($oSelectExpr, $oClass, $oClassAlias = '', $oCondition = null, $aJoins = null) + { + $this->m_oSelectExpr = $oSelectExpr; + parent::__construct($oClass, $oClassAlias, $oCondition, $aJoins); + } + + public function GetSelectExpression() + { + return $this->m_oSelectExpr; + } +} ?> diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php index 8a55d33c8e..1661ed4a24 100644 --- a/core/sqlquery.class.inc.php +++ b/core/sqlquery.class.inc.php @@ -28,6 +28,9 @@ class TrueSQLExpression extends TrueExpression class FieldSQLExpression extends FieldExpression { } +class VariableSQLExpression extends VariableExpression +{ +} @@ -212,7 +215,7 @@ class SQLQuery } // Interface, build the SQL query - public function RenderSelect($aOrderBy = array()) + public function RenderSelect($aOrderBy = array(), $aArgs = array()) { // The goal will be to complete the lists as we build the Joins $aFrom = array(); @@ -224,7 +227,7 @@ class SQLQuery $sSelect = self::ClauseSelect($aFields); $sFrom = self::ClauseFrom($aFrom); - $sWhere = self::ClauseWhere($oCondition); + $sWhere = self::ClauseWhere($oCondition, $aArgs); $sOrderBy = self::ClauseOrderBy($aOrderBy); if (!empty($sOrderBy)) { @@ -294,9 +297,9 @@ class SQLQuery return $sSetValues; } - private static function ClauseWhere($oConditionExpr) + private static function ClauseWhere($oConditionExpr, $aArgs = array()) { - return $oConditionExpr->Render(); + return $oConditionExpr->Render($aArgs); } private static function ClauseOrderBy($aOrderBy) diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index c541683cce..2adae3aff0 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -104,14 +104,15 @@ class ValueSetObjects extends ValueSetDefinition { $this->m_aValues = array(); - $oFilter = DBObjectSearch::FromSibuSQL($this->m_sFilterExpr, $aArgs); + $oFilter = DBObjectSearch::FromSibusQL($this->m_sFilterExpr, $aArgs); if (!$oFilter) return false; - if (empty($this->m_sValueAttCode)) - { - $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); - } - $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy); + if (empty($this->m_sValueAttCode)) + { + $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); + } + + $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs); while ($oObject = $oObjects->Fetch()) { $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($this->m_sValueAttCode); @@ -235,4 +236,35 @@ class ValueSetEnum extends ValueSetDefinition } } + +/** + * Data model classes + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class ValueSetEnumClasses extends ValueSetEnum +{ + public function __construct($sCategory = '', $sAdditionalValues = '') + { + // First, build it from the series of additional values + parent::__construct($sAdditionalValues); + + // Second: add the list of classes + foreach (MetaModel::GetClasses($sCategory) as $sClass) + { + $this->m_aValues[$sClass] = MetaModel::GetName($sClass); + } + } + + protected function LoadValues($aArgs) + { + return true; + } +} + ?> diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index db9b448d80..fcdb246cd9 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -75,7 +75,7 @@ class TestOQLParser extends TestFunction $oOql = new OqlInterpreter($sQuery); try { - $oTrash = $oOql->ParseQuery(); + $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery MyHelpers::var_dump_html($oTrash, true); } catch (OQLException $OqlException) @@ -687,7 +687,7 @@ class TestQueriesOnFarm extends TestBizModel try { //$oOql = new OqlInterpreter($sQuery); - //$oTrash = $oOql->ParseQuery(); + //$oTrash = $oOql->ParseObjectQuery(); //MyHelpers::var_dump_html($oTrash, true); $oMyFilter = DBObjectSearch::FromOQL($sQuery); } diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php new file mode 100644 index 0000000000..a2e8f71952 --- /dev/null +++ b/pages/usermanagement_classproj.php @@ -0,0 +1,90 @@ +Fetch()) + { + $aDimensions[$oDimension->GetKey()] = $oDimension; + } + + // Load the class projections for a further usage + // + $aClassProj = array(); + $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); + while ($oClassProj = $oClassProjSet->Fetch()) + { + $aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; + } + + // Setup display structure + // + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => 'Class', 'description' => 'Class'); + $aDisplayConfig['object'] = array('label' => 'Object', 'description' => 'Projected object'); + foreach ($aDimensions as $iDimension => $oDimension) + { + $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); + } + + // Load objects + // + $aDisplayData = array(); + $oObjectSet = new DBObjectSet(DBObjectSearch::FromOQL($sScope)); + $sClass = $oObjectSet->GetClass(); + while ($oObject = $oObjectSet->Fetch()) + { + $aObjectProj = array(); + $oObjectProj['class'] = $sClass; + $oObjectProj['object'] = $oObject->GetName(); + foreach ($aDimensions as $iDimension => $oDimension) + { + // #@# to be moved, may be time consuming + $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]); + + $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); + $sValues = implode(', ', $aValues); + $oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + } + + $aDisplayData[] = $oObjectProj; + } + + $oPage->table($aDisplayConfig, $aDisplayData); + +//$oPage->SetCurrentTab('Attributes'); +//$oPage->p("[All classes]"); +//$oPage->add("
    \n"); + +} + + +require_once('../application/loginwebpage.class.inc.php'); +login_web_page::DoLogin(); // Check user rights and prompt if needed + +// Display the menu on the left +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +$iActiveNodeId = utils::ReadParam('menu', -1); +$currentOrganization = utils::ReadParam('org_id', 1); +$sScope = utils::ReadParam('scope', 'SELECT bizDevice'); + +$oPage = new iTopWebPage("iTop user management - class projections", $currentOrganization); +$oPage->no_cache(); + +ComputeProjections($oPage, $sScope); +$oPage->output(); + +?> diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php new file mode 100644 index 0000000000..a7497d0e01 --- /dev/null +++ b/pages/usermanagement_profileproj.php @@ -0,0 +1,100 @@ +Fetch()) + { + $aProfiles[$oProfile->GetKey()] = $oProfile; + } + + // Load the dimensions for a further usage + // + $aDimensions = array(); + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + while ($oDimension = $oDimensionSet->Fetch()) + { + $aDimensions[$oDimension->GetKey()] = $oDimension; + } + + // Load the profile projections for a further usage + // + $aProPro = array(); + $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); + while ($oProPro = $oProProSet->Fetch()) + { + $aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; + } + + // Setup display structure + // + $aDisplayConfig = array(); + $aDisplayConfig['user'] = array('label' => 'User', 'description' => 'User concerned by the projection'); + $aDisplayConfig['profile'] = array('label' => 'Profile', 'description' => 'Profile in which the projection is specified'); + foreach ($aDimensions as $iDimension => $oDimension) + { + $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); + } + + // Load users, and create a record per couple user/profile + // + $aDisplayData = array(); + $oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users")); + while ($oUser = $oUserSet->Fetch()) + { + $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile WHERE userid = :user->id"), array(), array('user' => $oUser)); + while ($oUserProfile = $oUserProfileSet->Fetch()) + { + $iProfile = $oUserProfile->Get('profileid'); + $oProfile = $aProfiles[$iProfile]; + + $aUserProfileProj = array(); + $aUserProfileProj['user'] = $oUser->GetName(); + $aUserProfileProj['profile'] = $oProfile->GetName(); + foreach ($aDimensions as $iDimension => $oDimension) + { + // #@# to be moved, may be time consuming + $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]); + + $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + $sValues = implode(', ', $aValues); + $aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + } + + $aDisplayData[] = $aUserProfileProj; + } + } + + $oPage->table($aDisplayConfig, $aDisplayData); + +//$oPage->SetCurrentTab('Attributes'); +//$oPage->p("[All classes]"); +//$oPage->add("\n"); + +} + + +require_once('../application/loginwebpage.class.inc.php'); +login_web_page::DoLogin(); // Check user rights and prompt if needed + +// Display the menu on the left +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +$iActiveNodeId = utils::ReadParam('menu', -1); +$currentOrganization = utils::ReadParam('org_id', 1); + +$oPage = new iTopWebPage("iTop user management - profile projections", $currentOrganization); +$oPage->no_cache(); + +ComputeProjections($oPage); +$oPage->output(); + +?> From 1fdd65939513a633aa505068996793122ed7c00e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 24 Jul 2009 15:44:51 +0000 Subject: [PATCH 014/970] - Better display of the archives/change log on each object (Trac ticket #9) SVN:trunk[89] --- application/displayblock.class.inc.php | 56 ++++++++++++++++++----- application/template.class.inc.php | 2 +- application/templates/audit_category.html | 2 +- business/itop.business.class.inc.php | 3 +- business/templates/Circuits.html | 2 +- business/templates/application.html | 2 +- business/templates/change.html | 2 +- business/templates/contract.html | 2 +- business/templates/default.html | 2 +- business/templates/document.html | 2 +- business/templates/group.html | 2 +- business/templates/interface.html | 2 +- business/templates/knownError.html | 2 +- business/templates/location.html | 2 +- business/templates/network.device.html | 7 ++- business/templates/pc.html | 4 +- business/templates/person.html | 2 +- business/templates/server.html | 4 +- business/templates/service.html | 2 +- business/templates/software.html | 2 +- business/templates/team.html | 2 +- business/templates/ticket.html | 2 +- core/cmdbchangeop.class.inc.php | 43 +++++++++++++++++ 23 files changed, 116 insertions(+), 35 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index f41ab6a288..b12486d5e3 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -553,7 +553,13 @@ class HistoryBlock extends DisplayBlock switch($this->m_sStyle) { case 'toggle': - $oLatestChangeOp = $oSet->Fetch(); + // First the latest change that the user is allowed to see + do + { + $oLatestChangeOp = $oSet->Fetch(); + } + while(is_object($oLatestChangeOp) && ($oLatestChangeOp->GetDescription() == '')); + if (is_object($oLatestChangeOp)) { global $oContext; // User Context.. should be statis instead of global... @@ -561,9 +567,36 @@ class HistoryBlock extends DisplayBlock $sDate = $oLatestChangeOp->GetAsHTML('date'); $oChange = $oContext->GetObject('CMDBChange', $oLatestChangeOp->Get('change')); $sUserInfo = $oChange->GetAsHTML('userinfo'); - $oSet->Load(); // Reset the pointer to the beginning of the set: there should be a better way to do this... + $oSet->Rewind(); // Reset the pointer to the beginning of the set $sHtml .= $oPage->GetStartCollapsibleSection("Last modified on $sDate by $sUserInfo."); - $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $oSet); + //$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $oSet); + $aChanges = array(); + while($oChangeOp = $oSet->Fetch()) + { + $sChangeDescription = $oChangeOp->GetDescription(); + if ($sChangeDescription != '') + { + // The change is visible for the current user + $changeId = $oChangeOp->Get('change'); + $aChanges[$changeId]['date'] = $oChangeOp->Get('date'); + $aChanges[$changeId]['userinfo'] = $oChangeOp->Get('userinfo'); + if (!isset($aChanges[$changeId]['log'])) + { + $aChanges[$changeId]['log'] = array(); + } + $aChanges[$changeId]['log'][] = $sChangeDescription; + } + } + $aAttribs = array('date' => array('label' => 'Date', 'description' => 'Date of the change'), + 'userinfo' => array('label' => 'User', 'description' => 'User who made the change'), + 'log' => array('label' => 'Changes', 'description' => 'Changes made to the object'), + ); + $aValues = array(); + foreach($aChanges as $aChange) + { + $aValues[] = array('date' => $aChange['date'], 'userinfo' => $aChange['userinfo'], 'log' => "
    • ".implode('
    • ', $aChange['log'])."
    "); + } + $sHtml .= $oPage->GetTable($aAttribs, $aValues); $sHtml .= $oPage->GetEndCollapsibleSection(); } break; @@ -605,10 +638,10 @@ class MenuBlock extends DisplayBlock // Just one object in the set, possible actions are "new / clone / modify and delete" if (isset($aExtraParams['linkage'])) { - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "#"); } - if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "#"); } - if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } - if ($bIsModifyAllowed | $bIsDeleteAllowed) { $aActions[] = array ('label' => 'Manage Links...', 'url' => "#"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add #...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone #...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify #...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } + if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove #', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } } else { @@ -652,10 +685,11 @@ class MenuBlock extends DisplayBlock if (isset($aExtraParams['linkage'])) { $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "#"); } - if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "#"); } - if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } - if ($bIsModifyAllowed | $bIsDeleteAllowed) { $aActions[] = array ('label' => 'Manage Links...', 'url' => "#"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add #...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } + if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove #', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify #...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } + if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All #...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } + if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All #...', 'url' => "#"); } } else { diff --git a/application/template.class.inc.php b/application/template.class.inc.php index b6bdd8a619..db768c3af1 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -195,7 +195,7 @@ class DisplayTemplate $sTemplate = ' bizNetworkDevice: pkey = $pkey$ diff --git a/application/templates/audit_category.html b/application/templates/audit_category.html index 7a25e87911..a5e1c89c5e 100644 --- a/application/templates/audit_category.html +++ b/application/templates/audit_category.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index dcad88f706..7641490076 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -260,7 +260,7 @@ class bizPerson extends bizContact MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"first Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("employe_number", array("label"=>"Employe Number", "description"=>"employe number", "allowed_values"=>null, "sql"=>"employe_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("login_id", array("targetclass"=>"URP_Users", "label"=>"Login", "description"=>"Login information", "allowed_values"=>null, "sql"=>"login_id", "is_null_allowed"=>true, "depends_on"=>array()))); +// MetaModel::Init_AddAttribute(new AttributeExternalKey("login_id", array("targetclass"=>"URP_Users", "label"=>"Login", "description"=>"Login information", "allowed_values"=>null, "sql"=>"login_id", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("first_name"); @@ -1425,6 +1425,7 @@ class bizApplication extends logInfra MetaModel::Init_AddFilterFromAttribute("function"); MetaModel::Init_AddFilterFromAttribute("version"); MetaModel::Init_AddFilterFromAttribute("device_id"); + MetaModel::Init_AddFilterFromAttribute("device_name"); diff --git a/business/templates/Circuits.html b/business/templates/Circuits.html index f04fe3a879..3af1748141 100644 --- a/business/templates/Circuits.html +++ b/business/templates/Circuits.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/templates/application.html b/business/templates/application.html index f934b7b489..a17bdd05be 100644 --- a/business/templates/application.html +++ b/business/templates/application.html @@ -1,7 +1,7 @@ bizApplication: pkey = $pkey$ diff --git a/business/templates/change.html b/business/templates/change.html index f2bcd67d7a..700a83983f 100644 --- a/business/templates/change.html +++ b/business/templates/change.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/templates/contract.html b/business/templates/contract.html index 110fcdb0e5..e6c5fdcc84 100644 --- a/business/templates/contract.html +++ b/business/templates/contract.html @@ -1,7 +1,7 @@ bizContract: pkey = $pkey$ diff --git a/business/templates/default.html b/business/templates/default.html index 564b009b51..2464cd2164 100644 --- a/business/templates/default.html +++ b/business/templates/default.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/templates/document.html b/business/templates/document.html index cd40cfe785..b957b24576 100644 --- a/business/templates/document.html +++ b/business/templates/document.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/templates/group.html b/business/templates/group.html index fca5fb1543..e8741bf001 100644 --- a/business/templates/group.html +++ b/business/templates/group.html @@ -1,7 +1,7 @@ bizInfraGroup: pkey = $pkey$ diff --git a/business/templates/interface.html b/business/templates/interface.html index ffa8c7495e..81bb5fbbae 100644 --- a/business/templates/interface.html +++ b/business/templates/interface.html @@ -1,7 +1,7 @@ bizInterface: pkey = $pkey$ diff --git a/business/templates/knownError.html b/business/templates/knownError.html index 75bcc9eaf6..87d4184f10 100644 --- a/business/templates/knownError.html +++ b/business/templates/knownError.html @@ -1,7 +1,7 @@ bizKnownError: pkey = $pkey$ diff --git a/business/templates/location.html b/business/templates/location.html index 748ccd7393..82f8c4377e 100644 --- a/business/templates/location.html +++ b/business/templates/location.html @@ -1,7 +1,7 @@ bizLocation: pkey = $pkey$ diff --git a/business/templates/network.device.html b/business/templates/network.device.html index 01085e2ecb..cbad128d79 100644 --- a/business/templates/network.device.html +++ b/business/templates/network.device.html @@ -1,13 +1,13 @@ bizNetworkDevice: pkey = $pkey$ - bizInterface: device_id = $pkey$ + SELECT bizInterface WHERE device_id = $pkey$ lnkContactRealObject: object_id = $pkey$ @@ -21,5 +21,8 @@ lnkDocumentRealObject: object_id = $pkey$ + + + diff --git a/business/templates/pc.html b/business/templates/pc.html index 9323a913de..41dc445c32 100644 --- a/business/templates/pc.html +++ b/business/templates/pc.html @@ -1,7 +1,7 @@ bizPC: pkey = $pkey$ @@ -16,7 +16,7 @@ lnkContactRealObject: object_id = $pkey$ - bizInterface: device_id = $pkey$ + SELECT bizInterface WHERE device_id = $pkey$ bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) diff --git a/business/templates/person.html b/business/templates/person.html index 567f7b5431..6bc94916d6 100644 --- a/business/templates/person.html +++ b/business/templates/person.html @@ -1,7 +1,7 @@ bizPerson: pkey = $pkey$ diff --git a/business/templates/server.html b/business/templates/server.html index 98185836b2..8f72fd8787 100644 --- a/business/templates/server.html +++ b/business/templates/server.html @@ -1,7 +1,7 @@ bizServer: pkey = $pkey$ @@ -13,7 +13,7 @@ bizPatch: device_id = $pkey$ - bizInterface: device_id = $pkey$ + SELECT bizInterface WHERE device_id = $pkey$ lnkContactRealObject: object_id = $pkey$ diff --git a/business/templates/service.html b/business/templates/service.html index 7e6443eba4..a99953b4a3 100644 --- a/business/templates/service.html +++ b/business/templates/service.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/templates/software.html b/business/templates/software.html index 3f690daad2..fb50db6a69 100644 --- a/business/templates/software.html +++ b/business/templates/software.html @@ -1,7 +1,7 @@ bizSoftware: pkey = $pkey$ diff --git a/business/templates/team.html b/business/templates/team.html index 7e1e5cd6ec..12537bdd5e 100644 --- a/business/templates/team.html +++ b/business/templates/team.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/business/templates/ticket.html b/business/templates/ticket.html index 425c928401..efce97e2de 100644 --- a/business/templates/ticket.html +++ b/business/templates/ticket.html @@ -1,7 +1,7 @@ $class$: pkey = $pkey$ diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 0ed8776ded..05da24db5b 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -42,6 +42,16 @@ class CMDBChangeOp extends DBObject MetaModel::Init_AddFilterFromAttribute("objkey"); MetaModel::Init_AddFilterFromAttribute("date"); MetaModel::Init_AddFilterFromAttribute("userinfo"); + + MetaModel::Init_SetZListItems('details', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details + } + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + return ''; } } @@ -80,6 +90,14 @@ class CMDBChangeOpCreate extends CMDBChangeOp MetaModel::Init_InheritFilters(); } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + return 'Object created'; + } } @@ -116,6 +134,13 @@ class CMDBChangeOpDelete extends CMDBChangeOp MetaModel::Init_InheritFilters(); } + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + return 'Object deleted'; + } } @@ -162,6 +187,24 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + $sResult = ''; + $oEmptySet = new DBObjectSet($this->Get('objclass')); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oEmptySet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sNewValue = $this->Get('newvalue'); + $sOldValue = $this->Get('oldvalue'); + $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + } + return $sResult; + } } ?> From b5c7cbf5092053fef37af1eee1f447412ad192aa Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 31 Jul 2009 13:18:44 +0000 Subject: [PATCH 015/970] User management by profile ready for integration with the UI and some caching has been implemented for the building of queries (both from an OQL or from a programmatic query) SVN:trunk[90] --- .../userrights/userrightsmatrix.class.inc.php | 13 + .../userrights/userrightsnull.class.inc.php | 13 +- .../userrightsprofile.class.inc.php | 433 ++++++++++++++---- core/dbobjectsearch.class.php | 42 +- core/dbobjectset.class.php | 18 + core/expression.class.inc.php | 53 ++- core/metamodel.class.php | 102 ++++- core/userrights.class.inc.php | 57 ++- pages/usermanagement_classproj.php | 9 +- pages/usermanagement_profileproj.php | 9 +- pages/usermanagement_userstatus.php | 285 ++++++++++++ 11 files changed, 884 insertions(+), 150 deletions(-) create mode 100644 pages/usermanagement_userstatus.php diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 8bbe15cc81..80a4b97ee0 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -318,6 +318,19 @@ class UserRightsMatrix extends UserRightsAddOnAPI return false; } + public function GetUserId($sUserName) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixUsers WHERE login = '$sUserName'")); + if ($oSet->Count() < 1) + { + // todo: throw an exception? + return false; + } + + $oLogin = $oSet->Fetch(); + return $oLogin->Get('userid'); + } + public function GetFilter($sUserName, $sClass) { $oNullFilter = new DBObjectSearch($sClass); diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index c5480f7eac..0d9656fef0 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -34,7 +34,12 @@ class UserRightsNull extends UserRightsAddOnAPI public function CheckCredentials($sUserName, $sPassword) { - return true; + return 1; + } + + public function GetUserId($sUserName) + { + return 1; } public function GetFilter($sUserName, $sClass) @@ -43,17 +48,17 @@ class UserRightsNull extends UserRightsAddOnAPI return $oNullFilter; } - public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances) { return UR_ALLOWED_YES; } - public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances) { return UR_ALLOWED_YES; } - public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) { return UR_ALLOWED_YES; } diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 7b72df453f..df80a8cafa 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -245,23 +245,23 @@ class URP_ProfileProjection extends DBObject public function ProjectUser(URP_Users $oUser) { $sExpr = $this->Get('value'); - if (preg_match('/^\s*([a-zA-Z0-9;]+)\s*$/', $sExpr, $aMatches)) - { - // Constant value(s) - $aRes = explode(';', $aMatches[1]); - } - elseif ($sExpr == '') - { - $aRes = array(''); - } - else - { + if (strtolower(substr($sExpr, 0, 6)) == 'select') + { $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); $aValues = $oValueSetDef->GetValues(array('user' => $oUser), ''); $aRes = array_values($aValues); } + elseif ($sExpr == '') + { + $aRes = null; + } + else + { + // Constant value(s) + $aRes = explode(';', trim($sExpr)); + } return $aRes; } } @@ -302,23 +302,23 @@ class URP_ClassProjection extends DBObject public function ProjectObject($oObject) { $sExpr = $this->Get('value'); - if (preg_match('/^\s*([a-zA-Z0-9;]+)\s*$/', $sExpr, $aMatches)) - { - // Constant value(s) - $aRes = explode(';', $aMatches[1]); - } - elseif ($sExpr == '') - { - $aRes = array(''); - } - else - { + if (strtolower(substr($sExpr, 0, 6)) == 'select') + { $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); - $aValues = $oValueSetDef->GetValues(array('this' => $oObject), ''); + $aValues = $oValueSetDef->GetValues(array('user' => $oObject), ''); $aRes = array_values($aValues); } + elseif ($sExpr == '') + { + $aRes = null; + } + else + { + // Constant value(s) + $aRes = explode(';', trim($sExpr)); + } return $aRes; } } @@ -389,7 +389,7 @@ class URP_StimulusGrant extends DBObject MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"stimulus", "description"=>"stimulus code", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) @@ -498,7 +498,7 @@ class UserRightsProfile extends UserRightsAddOnAPI } else { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection WHERE class = '$sClass' AND dimensionid = $iDimensionId")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection WHERE class = :class AND dimensionid = :dimension"), array(), array('class'=>$sClass, 'dimension'=>$iDimensionId)); $bAddCell = ($oSet->Count() < 1); } if ($bAddCell) @@ -524,7 +524,7 @@ class UserRightsProfile extends UserRightsAddOnAPI } else { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection WHERE dimensionid = $iDimensionId AND profileid = $iProfileId")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection WHERE dimensionid = :dimension AND profileid = :profile"), array(), array('dimension'=>$iDimensionId, 'profile'=>$iProfileId)); $bAddCell = ($oSet->Count() < 1); } if ($bAddCell) @@ -557,7 +557,7 @@ class UserRightsProfile extends UserRightsAddOnAPI } else { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = '$sClass' AND action = '$sAction' AND profileid = $iProfileId")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile"), array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfileId)); $bAddCell = ($oSet->Count() < 1); } if ($bAddCell) @@ -579,7 +579,7 @@ class UserRightsProfile extends UserRightsAddOnAPI } else { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND profileid = $iProfileId")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile"), array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfileId)); $bAddCell = ($oSet->Count() < 1); } if ($bAddCell) @@ -621,119 +621,368 @@ class UserRightsProfile extends UserRightsAddOnAPI } } - public function Init() + { + MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData')); + } + + protected $m_aUsers = array(); // id -> object + protected $m_aDimensions = array(); // id -> object + protected $m_aClassProj = array(); // class,dimensionid -> object + protected $m_aProfiles = array(); // id -> object + protected $m_aUserProfiles = array(); // userid,profileid -> object + protected $m_aProPro = array(); // profileid,dimensionid -> object + + protected $m_aClassActionGrants = array(); // profile, class, action -> permission + protected $m_aObjectActionGrants = array(); // userid, class, id, action -> permission, list of attributes + + public function CacheData() { // Could be loaded in a shared memory (?) + + $oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users")); + while ($oUser = $oUserSet->Fetch()) + { + $this->m_aUsers[$oUser->GetKey()] = $oUser; + } + + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + while ($oDimension = $oDimensionSet->Fetch()) + { + $this->m_aDimensions[$oDimension->GetKey()] = $oDimension; + } + + $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); + while ($oClassProj = $oClassProjSet->Fetch()) + { + $this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; + } + + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); + while ($oProfile = $oProfileSet->Fetch()) + { + $this->m_aProfiles[$oProfile->GetKey()] = $oProfile; + } + + $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile")); + while ($oUserProfile = $oUserProfileSet->Fetch()) + { + $this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; + } + + $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); + while ($oProPro = $oProProSet->Fetch()) + { + $this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; + } + +/* + echo "
    \n";
    +		print_r($this->m_aUsers);
    +		print_r($this->m_aDimensions);
    +		print_r($this->m_aClassProjs);
    +		print_r($this->m_aProfiles);
    +		print_r($this->m_aUserProfiles);
    +		print_r($this->m_aProPros);
    +		echo "
    \n"; +exit; +*/ + return true; } public function CheckCredentials($sUserName, $sPassword) { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = '$sUserName'")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = :login"), array(), array('login' => $sUserName)); if ($oSet->Count() < 1) { // todo: throw an exception? return false; } - $oLogin = $oSet->Fetch(); - if ($oLogin->Get('password') == $sPassword) + $oUser = $oSet->Fetch(); + if ($oUser->Get('password') == $sPassword) { - return true; + return $oUser->GetKey(); } // todo: throw an exception? return false; } + public function GetUserId($sUserName) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = :login"), array(), array('login' => $sUserName)); + if ($oSet->Count() < 1) + { + // todo: throw an exception? + return false; + } + + $oUser = $oSet->Fetch(); + return $oUser->GetKey(); + } + public function GetFilter($sUserName, $sClass) { $oNullFilter = new DBObjectSearch($sClass); return $oNullFilter; } - public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances) + protected function GetClassActionGrant($iProfile, $sClass, $sAction) { - // #@# temporary - return true; - if (!array_key_exists($iActionCode, self::$m_aActionCodes)) - { - return UR_ALLOWED_NO; - } - $sAction = self::$m_aActionCodes[$iActionCode]; + $aTest = @$this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; + if (isset($aTest)) return $aTest; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'")); + // Get the permission for this profile/class/action + $oSearch = DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile"); + $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile)); if ($oSet->Count() < 1) { - return UR_ALLOWED_NO; + return null; } - $oGrantRecord = $oSet->Fetch(); - switch ($oGrantRecord->Get('permission')) - { - case 'yes': - $iRetCode = UR_ALLOWED_YES; - break; - case 'no': - default: - $iRetCode = UR_ALLOWED_NO; - break; - } - return $iRetCode; + + $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $oGrantRecord; + return $oGrantRecord; } - public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + protected function GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject) { - // #@# temporary - return true; - if (!array_key_exists($iActionCode, self::$m_aActionCodes)) - { - return UR_ALLOWED_NO; - } + // load and cache permissions for the current user on the given object + // + $aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$oObject->GetKey][$iActionCode]; + if (is_array($aTest)) return $aTest; + $sAction = self::$m_aActionCodes[$iActionCode]; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE URP_AttributeGrant.class = '$sClass' AND URP_AttributeGrant.attcode = '$sAttCode' AND URP_AttributeGrant.action = '$sAction' AND URP_AttributeGrant.login = '$sUserName'")); - if ($oSet->Count() < 1) + $iInstancePermission = UR_ALLOWED_NO; + $aAttributes = array(); + foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile) + { + $oGrantRecord = $this->GetClassActionGrant($iProfile, $sClass, $sAction); + if (is_null($oGrantRecord)) + { + continue; // loop to the next profile + } + elseif ($oGrantRecord->Get('permission') == 'yes') + { + $iInstancePermission = UR_ALLOWED_YES; + + // merge the list of attributes allowed for this profile + $oSearch = DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); + $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); + $aAttributes = array_merge($aAttributes, $oSet->GetColumnAsArray('attcode', false)); + } + } + + $aRes = array( + 'permission' => $iInstancePermission, + 'attributes' => $aAttributes, + ); + $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$oObject->GetKey()][$iActionCode] = $aRes; + return $aRes; + } + + public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances) + { + $oUser = $this->m_aUsers[$iUserId]; + + $oInstances->Rewind(); + while($oObject = $oInstances->Fetch()) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); + + $iInstancePermission = $aObjectPermissions['permission']; + if (isset($iGlobalPermission)) + { + if ($iInstancePermission != $iGlobalPermission) + { + $iGlobalPermission = UR_ALLOWED_DEPENDS; + } + } + else + { + $iGlobalPermission = $iInstancePermission; + } + } + if (isset($iGlobalPermission)) + { + return $iGlobalPermission; + } + else { return UR_ALLOWED_NO; } - - $oGrantRecord = $oSet->Fetch(); - switch ($oGrantRecord->Get('permission')) - { - case 'yes': - $iRetCode = UR_ALLOWED_YES; - break; - case 'no': - default: - $iRetCode = UR_ALLOWED_NO; - break; - } - return $iRetCode; } - public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances) + public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances) { - // #@# temporary - return true; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'")); - if ($oSet->Count() < 1) + $oUser = $this->m_aUsers[$iUserId]; + + $oInstances->Rewind(); + while($oObject = $oInstances->Fetch()) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); + + $aAttributes = $aObjectPermissions['attributes']; + if (in_array($sAttCode, $aAttributes)) + { + $iInstancePermission = $aObjectPermissions['permission']; + } + else + { + $iInstancePermission = UR_ALLOWED_NO; + } + + if (isset($iGlobalPermission)) + { + if ($iInstancePermission != $iGlobalPermission) + { + $iGlobalPermission = UR_ALLOWED_DEPENDS; + } + } + else + { + $iGlobalPermission = $iInstancePermission; + } + } + if (isset($iGlobalPermission)) + { + return $iGlobalPermission; + } + else { return UR_ALLOWED_NO; } + } - $oGrantRecord = $oSet->Fetch(); - switch ($oGrantRecord->Get('permission')) + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances) + { + $oUser = $this->m_aUsers[$iUserId]; + + // Note: this code is VERY close to the code of IsActionAllowed() + + $oInstances->Rewind(); + while($oObject = $oInstances->Fetch()) { - case 'yes': - $iRetCode = UR_ALLOWED_YES; - break; - case 'no': - default: - $iRetCode = UR_ALLOWED_NO; - break; + $iInstancePermission = UR_ALLOWED_NO; + foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile) + { + // Get the permission for this profile/class/stimulus + $oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile"); + $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); + if ($oSet->Count() < 1) + { + return UR_ALLOWED_NO; + } + + $oGrantRecord = $oSet->Fetch(); + $sPermission = $oGrantRecord->Get('permission'); + if ($sPermission == 'yes') + { + $iInstancePermission = UR_ALLOWED_YES; + } + } + if (isset($iGlobalPermission)) + { + if ($iInstancePermission != $iGlobalPermission) + { + $iGlobalPermission = UR_ALLOWED_DEPENDS; + } + } + else + { + $iGlobalPermission = $iInstancePermission; + } } - return $iRetCode; + if (isset($iGlobalPermission)) + { + return $iGlobalPermission; + } + else + { + return UR_ALLOWED_NO; + } + } + + protected function GetMatchingProfilesByDim($oUser, $oObject, $oDimension) + { + // + // List profiles for which the user projection overlaps the object projection in the given dimension + // + $iUser = $oUser->GetKey(); + $sClass = get_class($oObject); + $iPKey = $oObject->GetKey(); + $iDimension = $oDimension->GetKey(); + + $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); + + $aRes = array(); + foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) + { + if (is_null($aObjectProjection)) + { + $aRes[] = $iProfile; + } + else + { + // user projection to be cached on a given page ! + $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + + if (is_null($aUserProjection)) + { + $aRes[] = $iProfile; + } + else + { + $aMatchingValues = array_intersect($aObjectProjection, $aUserProjection); + if (count($aMatchingValues) > 0) + { + $aRes[] = $iProfile; + } + } + } + } + return $aRes; + } + + protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object + + protected function GetMatchingProfiles($oUser, $oObject) + { + $iUser = $oUser->GetKey(); + $sClass = get_class($oObject); + $iObject = $oObject->GetKey(); + // + // List profiles for which the user projection overlaps the object projection in each and every dimension + // Caches the result + // + $aTest = @$this->m_aMatchingProfiles[$iUser][$sClass][$iObject]; + if (is_array($aTest)) + { + return $aTest; + } + + $aProfileRes = array(); + foreach ($this->m_aDimensions as $iDimension => $oDimension) + { + foreach ($this->GetMatchingProfilesByDim($oUser, $oObject, $oDimension) as $iProfile) + { + @$aProfileRes[$iProfile] += 1; + } + } + + $aRes = array(); + $iDimCount = count($this->m_aDimensions); + foreach ($aProfileRes as $iProfile => $iMatches) + { + if ($iMatches == $iDimCount) + { + $aRes[] = $iProfile; + } + } + $this->m_aMatchingProfiles[$iUser][$sClass][$iObject] = $aRes; + return $aRes; } } diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 236a0b7213..e30d3a5fa8 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -48,6 +48,7 @@ class DBObjectSearch private $m_sClassAlias; private $m_aClasses; // queried classes (alias => class name) private $m_oSearchCondition; + private $m_aParams; private $m_aFullText; private $m_aPointingTo; private $m_aReferencedBy; @@ -64,6 +65,7 @@ class DBObjectSearch $this->m_sClassAlias = $sClassAlias; $this->m_aClasses = array($sClassAlias => $sClass); $this->m_oSearchCondition = new TrueExpression; + $this->m_aParams = array(); $this->m_aFullText = array(); $this->m_aPointingTo = array(); $this->m_aReferencedBy = array(); @@ -174,6 +176,8 @@ class DBObjectSearch { $oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false); $this->AddConditionExpression($oTranslated); + // #@# what about collisions in parameter names ??? + $this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams); } public function ResetCondition() @@ -232,22 +236,22 @@ class DBObjectSearch break; case 'Contains': - $oRightExpr = new ScalarExpression("%$value%"); + $this->m_aParams[$sFilterCode] = "%$value%"; $sOperator = 'LIKE'; break; case 'Begins with': - $oRightExpr = new ScalarExpression("$value%"); + $this->m_aParams[$sFilterCode] = "$value%"; $sOperator = 'LIKE'; break; case 'Finishes with': - $oRightExpr = new ScalarExpression("%$value"); + $this->m_aParams[$sFilterCode] = "%$value"; $sOperator = 'LIKE'; break; default: - $oRightExpr = new ScalarExpression($value); + $this->m_aParams[$sFilterCode] = $value; $sOperator = $sOpCode; } @@ -262,6 +266,7 @@ class DBObjectSearch case 'Begins with': case 'Finishes with': default: + $oRightExpr = new VariableExpression($sFilterCode); $oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr); } @@ -453,10 +458,14 @@ class DBObjectSearch { return $this->m_aRelatedTo; } + public function GetInternalParams() + { + return $this->m_aParams; + } public function RenderCondition() { - return $this->m_oSearchCondition->Render(); + return $this->m_oSearchCondition->Render($this->m_aParams); } public function serialize() @@ -588,11 +597,13 @@ class DBObjectSearch return $retValue; } - public function ToOQL() + public function ToOQL(&$aParams = null) { + $bRetrofitParams = (!is_null($aParams)); + $sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias(); $sRes .= $this->ToOQL_Joins(); - $sRes .= " WHERE ".$this->m_oSearchCondition->Render(); + $sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams); return $sRes; } @@ -762,10 +773,20 @@ class DBObjectSearch } } - static public function FromOQL($sQuery, array $aParams = array(), $oObject = null) + static protected $m_aOQLQueries = array(); + + static public function FromOQL($sQuery) { if (empty($sQuery)) return null; + // Query caching + $bOQLCacheEnabled = true; + if ($bOQLCacheEnabled && array_key_exists($sQuery, self::$m_aOQLQueries)) + { + // hit! + return clone self::$m_aOQLQueries[$sQuery]; + } + $oOql = new OqlInterpreter($sQuery); $oOqlQuery = $oOql->ParseObjectQuery(); @@ -856,6 +877,11 @@ class DBObjectSearch $oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases); } + if ($bOQLCacheEnabled) + { + self::$m_aOQLQueries[$sQuery] = clone $oResultFilter; + } + return $oResultFilter; } diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 9e6cf5459d..6384864d82 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -92,6 +92,24 @@ class DBObjectSet return $aRet; } + public function GetColumnAsArray($sAttCode, $bWithId = true) + { + $aRet = array(); + $this->Rewind(); + while ($oObject = $this->Fetch()) + { + if ($bWithId) + { + $aRet[$oObject->GetKey()] = $oObject->Get($sAttCode); + } + else + { + $aRet[] = $oObject->Get($sAttCode); + } + } + return $aRet; + } + public function GetFilter() { return $this->m_oFilter; diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index f2646802e4..834ac47b7b 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -16,8 +16,8 @@ abstract class Expression // recursive translation of identifiers abstract public function Translate($aTranslationData, $bMatchAll = true); - // recursive rendering - abstract public function Render($aArgs = array()); + // recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True + abstract public function Render(&$aArgs = null, $bRetrofitParams = false); // recursively builds an array of class => fieldname abstract public function ListRequiredFields(); @@ -119,11 +119,11 @@ class BinaryExpression extends Expression } // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { $sOperator = $this->GetOperator(); - $sLeft = $this->GetLeftExpr()->Render($aArgs); - $sRight = $this->GetRightExpr()->Render($aArgs); + $sLeft = $this->GetLeftExpr()->Render($aArgs, $bRetrofitParams); + $sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams); return "($sLeft $sOperator $sRight)"; } @@ -164,9 +164,18 @@ class UnaryExpression extends Expression } // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { - return CMDBSource::Quote($this->m_value); + if ($bRetrofitParams) + { + $iParamIndex = count($aArgs) + 1; // 1-based indexation + $aArgs['param'.$iParamIndex] = $this->m_value; + return ':param'.$iParamIndex; + } + else + { + return CMDBSource::Quote($this->m_value); + } } public function Translate($aTranslationData, $bMatchAll = true) @@ -228,7 +237,7 @@ class FieldExpression extends UnaryExpression public function GetName() {return $this->m_sName;} // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { if (empty($this->m_sParent)) { @@ -290,16 +299,16 @@ class VariableExpression extends UnaryExpression public function GetName() {return $this->m_sName;} // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { - if (array_key_exists($this->m_sName, $aArgs)) - { - return $aArgs[$this->m_sName]; - } - elseif (is_null($aArgs)) + if (is_null($aArgs) || $bRetrofitParams) { return ':'.$this->m_sName; } + elseif (array_key_exists($this->m_sName, $aArgs)) + { + return CMDBSource::Quote($aArgs[$this->m_sName]); + } else { throw new CoreException('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs)); @@ -330,12 +339,12 @@ class ListExpression extends Expression } // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { $aRes = array(); foreach ($this->m_aExpressions as $oExpr) { - $aRes[] = $oExpr->Render($aArgs); + $aRes[] = $oExpr->Render($aArgs, $bRetrofitParams); } return '('.implode(', ', $aRes).')'; } @@ -390,12 +399,12 @@ class FunctionExpression extends Expression } // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { $aRes = array(); foreach ($this->m_aArgs as $oExpr) { - $aRes[] = $oExpr->Render($aArgs); + $aRes[] = $oExpr->Render($aArgs, $bRetrofitParams); } return $this->m_sVerb.'('.implode(', ', $aRes).')'; } @@ -449,9 +458,9 @@ class IntervalExpression extends Expression } // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { - return 'INTERVAL '.$this->m_oValue->Render($aArgs).' '.$this->m_sUnit; + return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit; } public function Translate($aTranslationData, $bMatchAll = true) @@ -486,12 +495,12 @@ class CharConcatExpression extends Expression } // recursive rendering - public function Render($aArgs = array()) + public function Render(&$aArgs = null, $bRetrofitParams = false) { $aRes = array(); foreach ($this->m_aExpressions as $oExpr) { - $sCol = $oExpr->Render($aArgs); + $sCol = $oExpr->Render($aArgs, $bRetrofitParams); // Concat will be globally NULL if one single argument is null ! $aRes[] = "COALESCE($sCol, '')"; } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index bb7e92a05d..cbb0b16373 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -164,6 +164,10 @@ abstract class MetaModel } } + private static $m_bTraceQueries = true; + private static $m_aQueriesLog = array(); + + private static $m_sDBName = ""; private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB) private static $m_Category2Class = array(); @@ -1167,15 +1171,36 @@ abstract class MetaModel return false; } + protected static $m_aQueryStructCache = array(); + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) { - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + // Query caching + // + $bQueryCacheEnabled = true; + $sOqlQuery = $oFilter->ToOql(); + if ($bQueryCacheEnabled) + { + if (array_key_exists($sOqlQuery, self::$m_aQueryStructCache)) + { + // hit! + $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; + } + } + + if (!isset($oSelect)) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + + self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; + } // Check the order by specification + // foreach ($aOrderBy as $sFieldAlias => $bAscending) { MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); @@ -1196,7 +1221,7 @@ abstract class MetaModel // Prepare arguments (translate any object into scalars) // - $aScalarArgs = array(); + $aScalarArgs = $oFilter->GetInternalParams(); foreach($aArgs as $sArgName => $value) { if (self::IsValidObject($value)) @@ -1215,9 +1240,41 @@ abstract class MetaModel $aScalarArgs[$sArgName] = (string) $value; } } - - //MyHelpers::var_dump_html($oSelect->RenderSelect($aOrderBy)); - return $oSelect->RenderSelect($aOrderBy, $aScalarArgs); + + // Go + // + $sRes = $oSelect->RenderSelect($aOrderBy, $aScalarArgs); + + if (self::$m_bTraceQueries) + { + $aParams = array(); + if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog)) + { + self::$m_aQueriesLog[$sOqlQuery] = array( + 'sql' => array(), + 'count' => 0, + ); + } + self::$m_aQueriesLog[$sOqlQuery]['count']++; + self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes; + } + + return $sRes; + } + + public static function ShowQueryTrace() + { + $iTotal = 0; + foreach (self::$m_aQueriesLog as $sOql => $aQueryData) + { + echo "

    $sOql

    \n"; + $iTotal += $aQueryData['count']; + echo '

    '.$aQueryData['count'].'

    '; + echo '

    Example: '.$aQueryData['sql'][0].'

    '; + } + echo "

    Total

    \n"; + echo "

    Count of executed queries: $iTotal

    "; + echo "

    Count of built queries: ".count(self::$m_aQueriesLog)."

    "; } public static function MakeDeleteQuery(DBObjectSearch $oFilter) @@ -2390,6 +2447,8 @@ abstract class MetaModel throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); } } + // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) + self::InitPlugins(); } public static function LoadConfig($sConfigFile) @@ -2425,6 +2484,15 @@ abstract class MetaModel CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) } + protected static $m_aPlugins = array(); + public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) + { + self::$m_aPlugins[$sName] = array( + 'type' => $sType, + 'init' => $aInitCallSpec, + ); + } + protected static function Plugin($sConfigFile, $sModuleType, $sToInclude) { if (!file_exists($sToInclude)) @@ -2434,6 +2502,22 @@ abstract class MetaModel require_once($sToInclude); } + protected static function InitPlugins() + { + foreach(self::$m_aPlugins as $sName => $aData) + { + $aCallSpec = @$aData['init']; + if (count($aCallSpec) == 2) + { + if (!is_callable($aCallSpec)) + { + throw new CoreException('Wrong declaration in plugin', array('plugin' => $aData['name'], 'type' => $aData['type'], 'class' => $aData['class'], 'init' => $aData['init'])); + } + call_user_func($aCallSpec); + } + } + } + // Building an object // // diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 01428ae73c..25b5469208 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -46,11 +46,12 @@ abstract class UserRightsAddOnAPI { abstract public function Setup(); // initial installation abstract public function Init(); // loads data (possible optimizations) - abstract public function CheckCredentials($iUserId, $sPassword); // returns the id of the user or false - abstract public function GetFilter($iUserId, $sClass); // returns a filter object - abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances); - abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances); - abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances); + abstract public function CheckCredentials($sLogin, $sPassword); // returns the id of the user or false + abstract public function GetUserId($sLogin); // returns the id of the user or false + abstract public function GetFilter($sLogin, $sClass); // returns a filter object + abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances); + abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances); + abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances); } @@ -148,9 +149,18 @@ class UserRights return self::$m_sUser; } - public static function GetUserId() + public static function GetUserId($sName = '') { - return self::$m_iUserId; + if (empty($sName)) + { + // return current user id + return self::$m_iUserId; + } + else + { + // find the id out of the login string + return self::$m_oAddOn->GetUserId($sName); + } } public static function GetRealUser() @@ -182,28 +192,49 @@ class UserRights return self::$m_oAddOn->GetFilter(self::$m_iUserId, $sClass); } - public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $aInstances) + public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $oInstances, $iUserId = null) { if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; - return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $aInstances); + if (is_null($iUserId)) + { + return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $oInstances); + } + else + { + return self::$m_oAddOn->IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstances); + } } - public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $aInstances) + public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $oInstances, $iUserId = null) { if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; - return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $aInstances); + if (is_null($iUserId)) + { + return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $oInstances); + } + else + { + return self::$m_oAddOn->IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstances); + } } - public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances, $iUserId = null) { if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; - return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $aInstances); + if (is_null($iUserId)) + { + return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $oInstances); + } + else + { + return self::$m_oAddOn->IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstances); + } } } diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php index a2e8f71952..826f041119 100644 --- a/pages/usermanagement_classproj.php +++ b/pages/usermanagement_classproj.php @@ -55,7 +55,14 @@ function ComputeProjections($oPage, $sScope) $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]); $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); - $sValues = implode(', ', $aValues); + if (is_null($aValues)) + { + $sValues = ''; + } + else + { + $sValues = implode(', ', $aValues); + } $oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); } diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index a7497d0e01..4ca6d6aa57 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -65,7 +65,14 @@ function ComputeProjections($oPage) $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]); $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); - $sValues = implode(', ', $aValues); + if (is_null($aValues)) + { + $sValues = ''; + } + else + { + $sValues = implode(', ', $aValues); + } $aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); } diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php new file mode 100644 index 0000000000..475e39ad83 --- /dev/null +++ b/pages/usermanagement_userstatus.php @@ -0,0 +1,285 @@ +Fetch()) + { + $aDimensions[$oDimension->GetKey()] = $oDimension; + } + + // Load the class projections for a further usage + // + $aClassProj = array(); + $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); + while ($oClassProj = $oClassProjSet->Fetch()) + { + $aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; + } + + // Setup display structure + // + $aDisplayConfig = array(); + foreach ($aDimensions as $iDimension => $oDimension) + { + $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); + } + + // Load objects + // + $aDisplayData = array(); + $sClass = get_class($oObject); + $aObjectProj = array(); + foreach ($aDimensions as $iDimension => $oDimension) + { + // #@# to be moved, may be time consuming + $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]); + + $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); + if (is_null($aValues)) + { + $sValues = ''; + } + else + { + $sValues = implode(', ', $aValues); + } + $oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + } + + $aDisplayData[] = $oObjectProj; + + $oPage->table($aDisplayConfig, $aDisplayData); +} + + +function ComputeUserProjections($oPage, $oUser) +{ + // Load the profiles for a further usage + // + $aProfiles = array(); + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); + while ($oProfile = $oProfileSet->Fetch()) + { + $aProfiles[$oProfile->GetKey()] = $oProfile; + } + + // Load the dimensions for a further usage + // + $aDimensions = array(); + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + while ($oDimension = $oDimensionSet->Fetch()) + { + $aDimensions[$oDimension->GetKey()] = $oDimension; + } + + // Load the profile projections for a further usage + // + $aProPro = array(); + $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); + while ($oProPro = $oProProSet->Fetch()) + { + $aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; + } + + // Setup display structure + // + $aDisplayConfig = array(); + $aDisplayConfig['profile'] = array('label' => 'Profile', 'description' => 'Profile in which the projection is specified'); + foreach ($aDimensions as $iDimension => $oDimension) + { + $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); + } + + // Create a record per profile + // + $aDisplayData = array(); + $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile WHERE userid = :user->id"), array(), array('user' => $oUser)); + while ($oUserProfile = $oUserProfileSet->Fetch()) + { + $iProfile = $oUserProfile->Get('profileid'); + $oProfile = $aProfiles[$iProfile]; + + $aUserProfileProj = array(); + $aUserProfileProj['profile'] = $oProfile->GetName(); + foreach ($aDimensions as $iDimension => $oDimension) + { + // #@# to be moved, may be time consuming + $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]); + + $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + if (is_null($aValues)) + { + $sValues = ''; + } + else + { + $sValues = implode(', ', $aValues); + } + $aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + } + + $aDisplayData[] = $aUserProfileProj; + } + + $oPage->table($aDisplayConfig, $aDisplayData); +} + + +function ComputeUserRights($oPage, $oUser, $oObject) +{ + // Set the stage + // + $iUser = $oUser->GetKey(); + $sClass = get_class($oObject); + $iPKey = $oObject->GetKey(); + $oInstances = DBObjectSet::FromArray($sClass, array($oObject)); + $aPermissions = array( + UR_ALLOWED_NO => 'UR_ALLOWED_NO', + UR_ALLOWED_YES => 'UR_ALLOWED_YES', + UR_ALLOWED_DEPENDS => 'UR_ALLOWED_DEPENDS', + ); + $aActions = array( + UR_ACTION_READ => 'Read', + UR_ACTION_MODIFY => 'Modify', + UR_ACTION_DELETE => 'Delete', + UR_ACTION_BULK_READ => 'Bulk Read', + UR_ACTION_BULK_MODIFY => 'Bulk Modify', + UR_ACTION_BULK_DELETE => 'Bulk Delete', + ); + $aAttributeActions = array( + UR_ACTION_READ => 'Read', + UR_ACTION_MODIFY => 'Modify', + UR_ACTION_BULK_READ => 'Bulk Read', + UR_ACTION_BULK_MODIFY => 'Bulk Modify', + ); + + // Determine allowed actions for the object + // + $aDisplayData = array(); + foreach($aActions as $iActionCode => $sActionDesc) + { + $iPermission = UserRights::IsActionAllowed($sClass, $iActionCode, $oInstances, $iUser); + $aDisplayData[] = array( + 'action' => $sActionDesc, + 'permission' => $aPermissions[$iPermission], + ); + } + $aDisplayConfig = array(); + $aDisplayConfig['action'] = array('label' => 'Action', 'description' => ''); + $aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => ''); + $oPage->p('

    Actions

    '); + $oPage->table($aDisplayConfig, $aDisplayData); + + + // Determine allowed actions for the object + // + $aDisplayData = array(); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if (!$oAttDef->IsDirectField()) continue; + + foreach($aAttributeActions as $iActionCode => $sActionDesc) + { + $iPermission = UserRights::IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, $oInstances, $iUser); + $aDisplayData[] = array( + 'attribute' => $sAttCode, + 'action' => $sActionDesc, + 'permission' => $aPermissions[$iPermission], + ); + } + } + $oPage->p('

    Attributes

    '); + if (count($aDisplayData) > 0) + { + $aDisplayConfig = array(); + $aDisplayConfig['attribute'] = array('label' => 'Attribute', 'description' => ''); + $aDisplayConfig['action'] = array('label' => 'Action', 'description' => ''); + $aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => ''); + $oPage->table($aDisplayConfig, $aDisplayData); + } + else + { + $oPage->p('none'); + } + + // Determine allowed stimuli + // + $aDisplayData = array(); + foreach(MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + $iPermission = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oInstances, $iUser); + $aDisplayData[] = array( + 'stimulus' => $sStimulusCode, + 'permission' => $aPermissions[$iPermission], + ); + } + $oPage->p('

    Stimuli

    '); + if (count($aDisplayData) > 0) + { + $aDisplayConfig = array(); + $aDisplayConfig['stimulus'] = array('label' => 'Stimulus', 'description' => ''); + $aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => ''); + $oPage->table($aDisplayConfig, $aDisplayData); + } + else + { + $oPage->p('none'); + } +} + + +require_once('../application/loginwebpage.class.inc.php'); +login_web_page::DoLogin(); // Check user rights and prompt if needed + +// Display the menu on the left +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +$iActiveNodeId = utils::ReadParam('menu', -1); +$currentOrganization = utils::ReadParam('org_id', 1); +$iUser = utils::ReadParam('user_id', -1); +$sObjectClass = utils::ReadParam('object_class', ''); +$iObjectId = utils::ReadParam('object_id', 0); + +$oPage = new iTopWebPage("iTop user management - user status", $currentOrganization); +$oPage->no_cache(); + + +if ($iUser == -1) +{ + $oPage->p('Missing parameter "user_id" - current user is '.UserRights::GetUserId()); +} +else +{ + $oUser = MetaModel::GetObject('URP_Users', $iUser); + + $oPage->p('

    Projections for user '.$oUser->GetName().'

    '); + ComputeUserProjections($oPage, $oUser); + + if (strlen($sObjectClass) != 0) + { + $oObject = MetaModel::GetObject($sObjectClass, $iObjectId); + + $oPage->p('

    Projections for object '.$oObject->GetName().'

    '); + ComputeObjectProjections($oPage, $oObject); + + $oPage->p('

    Resulting rights

    '); + ComputeUserRights($oPage, $oUser, $oObject); + } +} + +$oPage->output(); + +?> From 3544b38ab9edb30a9601e251bc89336da3f67ea6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 17 Aug 2009 18:24:26 +0000 Subject: [PATCH 016/970] - Improvements to the history log for large text fields SVN:trunk[91] --- core/cmdbchangeop.class.inc.php | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 05da24db5b..aadbc656da 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -201,7 +201,29 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp $sAttName = $oAttDef->GetLabel(); $sNewValue = $this->Get('newvalue'); $sOldValue = $this->Get('oldvalue'); - $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + if ( (($oAttDef->GetType() == 'String') || ($oAttDef->GetType() == 'Text')) && + (strlen($sNewValue) > strlen($sOldValue)) ) + { + // Check if some text was not appended to the field + if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end + { + $sDelta = substr($sNewValue, strlen($sOldValue)); + $sResult = "$sDelta appended to $sAttName"; + } + else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning + { + $sDelta = substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue)); + $sResult = "$sDelta appended to $sAttName"; + } + else + { + $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + } + } + else + { + $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + } } return $sResult; } From ea59fa945a13586a7b38750f36aefa5f8da8531c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 18 Aug 2009 18:51:17 +0000 Subject: [PATCH 017/970] - New search form allowing to perform OQL queries for any class of object SVN:trunk[92] --- application/cmdbabstract.class.inc.php | 46 ++++++++++++++++++---- application/displayblock.class.inc.php | 1 - css/light-grey.css | 24 ++++++++++++ pages/UI.php | 54 ++++++++++++++++++++++++++ pages/UniversalSearch.php | 40 +++++++++++++------ 5 files changed, 144 insertions(+), 21 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index aaf8bad4f3..f95a03eab3 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -386,9 +386,19 @@ abstract class cmdbAbstractObject extends CMDBObject public static function GetSearchForm(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { + static $iSearchFormId = 0; $sHtml = ''; $numCols=4; + $iSearchFormId++; $sClassName = $oSet->GetFilter()->GetClass(); + + $sHtml .= "\n"; + // Simple search form + $sHtml .= "
    \n"; + $sHtml .= "

    Search for ".MetaModel::GetName($sClassName)." Objects

    \n"; $oUnlimitedFilter = new DBObjectSearch($sClassName); $sHtml .= "
    \n"; $index = 0; @@ -472,14 +482,34 @@ abstract class cmdbAbstractObject extends CMDBObject $sHtml .= "\n"; } $sHtml .= "\n"; - $sHtml .= "
    \n"; - // Soem Debug dumps... - //$sHtml .= "".$oSet->GetFilter()->__DescribeHTML()."
    \n"; - //$sHtml .= "encoding=\"text/serialize\" : ".$oSet->GetFilter()->serialize()."
    \n"; - //$sHtml .= "encoding=\"text/sibusql\" : ".$oSet->GetFilter()->ToSibusQL()."
    \n"; - //$sHtml .= "(Unlimited) ".$oUnlimitedFilter->__DescribeHTML()."
    \n"; - //$sHtml .= "encoding=\"text/serialize\" : ".$oUnlimitedFilter->serialize()."
    \n"; - //$sHtml .= "encoding=\"text/sibusql\" : ".$oUnlimitedFilter->ToSibusQL()."\n"; + $sHtml .= "\n"; + $sHtml .= "
    \n"; + + // OQL query builder + $sHtml .= "
    \n"; + $sHtml .= "

    OQL Query Builder

    \n"; + $sHtml .= "
    \n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aExtraParams as $sName => $sValue) + { + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "
     \n"; + $sHtml .= "
    \n"; + $sHtml .= "
    \n"; return $sHtml; } diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index b12486d5e3..33e639952d 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -308,7 +308,6 @@ class DisplayBlock $iSearchSectionId = 1; $sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; $sHtml .= "
    \n"; - $sHtml .= "

    Search form for ".Metamodel::GetName($this->m_oSet->GetClass())."

    \n"; $oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); $sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams); $sHtml .= "
    \n"; diff --git a/css/light-grey.css b/css/light-grey.css index 18f72c2e67..85717cb889 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -631,3 +631,27 @@ div.HRDrawer { border: 0; display: block; } +.mini_tabs a { + text-decoration: none; + font-weight:bold; + color: #ccc; + background-color:#333; + padding-left: 1em; + padding-right: 1em; + padding-bottom: 0.25em; +} +.mini_tabs a.selected { + color: #fff; + background-color: #83b217; + padding-top: 0.25em; +} +.mini_tabs ul { + margin: -10px; +} +.mini_tabs ul li { + float: right; + list-style: none; + nopadding-left: 1em; + nopadding-right: 1em; + margin-top: 0; +} diff --git a/pages/UI.php b/pages/UI.php index 48bd6081b3..45631dd69f 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -73,6 +73,60 @@ switch($operation) } break; + case 'search_form': + $sOQLClass = utils::ReadParam('oql_class', ''); + $sOQLClause = utils::ReadParam('oql_clause', ''); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sOQLClass)) + { + $oP->set_title("iTop - Error"); + $oP->add("

    'oql_class' must be specifed for this operation.

    \n"); + } + else + { + $oP->set_title("iTop - Search results"); + $sOQL = "SELECT $sOQLClass $sOQLClause"; + try + { + $oFilter = DBObjectSearch::FromOQL($sOQL); // To Do: Make sure we don't bypass security + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); + } + if (strtolower($sFormat) == 'csv') + { + $oBlock = new DisplayBlock($oFilter, 'csv', false); + $oBlock->Display($oP, 0); + } + else + { + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 0); + } + } + catch(CoreException $e) + { + $oFilter = new DBObjectSearch($sOQLClass); // To Do: Make sure we don't bypass security + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); + } + $oP->P("Error incorrect OQL query:"); + $oP->P($e->getHtmlDesc()); + } + catch(Exception $e) + { + $oP->p('An error occured while running the query:'); + $oP->p($e->getMessage()); + } + } + break; + case 'search': $sFilter = utils::ReadParam('filter', ''); $sFormat = utils::ReadParam('format', ''); diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index e649f8d2b8..bb4e156473 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -18,12 +18,13 @@ $oP = new iTopWebPage("iTop - Universal search", $currentOrganization); // From now on the context is limited to the the selected organization ?? // Now render the content of the page -$sClassName = utils::ReadParam('class', 'bizOrganization'); +$sOQLClass = utils::ReadParam('oql_class', 'bizOrganization'); +$sOQLClause = utils::ReadParam('oql_clause', ''); +$sClassName = utils::ReadParam('class', $sOQLClass); $sFilter = utils::ReadParam('filter', ''); $sOperation = utils::ReadParam('operation', ''); // First part: select the class to search for -$oP->add("
    "); $oP->add("
    "); $oP->add(""); $oP->add("Select the class to search:
    "); -// Second part: advanced search form: -$oFilter = null; -if (!empty($sFilter)) +try { - $oFilter = CMDBSearchFilter::unserialize($sFilter); + if ($sOperation == 'search_form') + { + $sOQL = "SELECT $sOQLClass $sOQLClause"; + $oFilter = DBObjectSearch::FromOQL($sOQL); + } + else + { + // Second part: advanced search form: + if (!empty($sFilter)) + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + } + else if (!empty($sClassName)) + { + $oFilter = new CMDBSearchFilter($sClassName); + } + } } -else if (!empty($sClassName)) +catch (CoreException $e) { $oFilter = new CMDBSearchFilter($sClassName); + $oP->P("Error:"); + $oP->P($e->getHtmlDesc()); } if ($oFilter != null) { - $oSet =new CMDBObjectSet($oFilter); - cmdbAbstractObject::DisplaySearchForm($oP, $oSet, array('org_id' => $currentOrganization, 'class' => $sClassName)); - $oP->add("
    \n"); + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); // Search results - $oP->add("
    "); $oResultBlock = new DisplayBlock($oFilter, 'list', false); $oResultBlock->RenderContent($oP); From daa9657e08ac656de7bdb8223384ed8f37d0adb5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 20 Aug 2009 13:09:41 +0000 Subject: [PATCH 018/970] Finalized the module "user rights by profile". It is fully integrated with the application setup (inc. sample data). and it it now ready for developing the user management GUI SVN:trunk[93] --- .../userrightsprofile.class.inc.php | 211 ++- business/itop.business.class.inc.php | 15 +- core/cmdbchangeop.class.inc.php | 9 +- core/dbobjectset.class.php | 2 +- core/metamodel.class.php | 64 +- core/sqlquery.class.inc.php | 8 +- pages/usermanagement_classproj.php | 6 +- pages/usermanagement_profileproj.php | 6 +- pages/usermanagement_userstatus.php | 12 +- setup/data/03.persons.xml | 9 +- setup/data/23.dimensions.xml | 13 + setup/data/24.profiles.xml | 11 + setup/data/25.classprojection.xml | 555 ++++++ setup/data/26.profileprojection.xml | 15 + setup/data/27.actiongrant.xml | 1659 +++++++++++++++++ setup/data/28.attributegrant.xml | 3 + setup/data/29.stimulusgrant.xml | 189 ++ setup/data/export.cmd | 53 +- 18 files changed, 2697 insertions(+), 143 deletions(-) create mode 100644 setup/data/23.dimensions.xml create mode 100644 setup/data/24.profiles.xml create mode 100644 setup/data/25.classprojection.xml create mode 100644 setup/data/26.profileprojection.xml create mode 100644 setup/data/27.actiongrant.xml create mode 100644 setup/data/28.attributegrant.xml create mode 100644 setup/data/29.stimulusgrant.xml diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index df80a8cafa..10528bc90d 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -14,6 +14,11 @@ */ +// It is supposed that this profile does exist in the DB +// Possible improvement: add it when executing the setup procedure +// +define('ADMIN_PROFILE_ID', 1); + class URP_Users extends DBObject { public static function Init() @@ -106,7 +111,7 @@ class URP_Dimensions extends DBObject MetaModel::Init_AddFilterFromAttribute("type"); } - public function CheckProjectionSpec($oProjectionSpec) + public function CheckProjectionSpec($oProjectionSpec, $sProjectedClass) { $sExpression = $oProjectionSpec->Get('value'); $sAttribute = $oProjectionSpec->Get('attribute'); @@ -129,30 +134,46 @@ class URP_Dimensions extends DBObject // 2nd - compute the data type for the projection // - $bIsOql = true; - $sExpressionClass = ''; - try + $sTargetClass = ''; + if (($sExpression == '') || ($sExpression == '')) { - $oObjectSearch = DBObjectSearch::FromOQL($sExpression); - $sExpressionClass = $oObjectSearch->GetClass(); + $sTargetClass = $sProjectedClass; } - catch (OqlException $e) + elseif ($sExpression == '') { - $bIsOql = false; + $sTargetClass = ''; } - if ($bIsOql) + else + { + // Evaluate wether it is a constant or not + try + { + $oObjectSearch = DBObjectSearch::FromOQL($sExpression); + + $sTargetClass = $oObjectSearch->GetClass(); + } + catch (OqlException $e) + { + } + } + + if (empty($sTargetClass)) + { + $sFoundType = '_void_'; + } + else { if (empty($sAttribute)) { - $sFoundType = $sExpressionClass; + $sFoundType = $sTargetClass; } else { - if (!MetaModel::IsValidAttCode($sExpressionClass, $sAttribute)) + if (!MetaModel::IsValidAttCode($sTargetClass, $sAttribute)) { - throw new CoreException('Unkown attribute code in projection specification', array('found' => $sAttribute, 'expecting' => MetaModel::GetAttributesList($sExpressionClass), 'class' => $sExpressionClass, 'projection' => $oProjectionSpec)); + throw new CoreException('Unkown attribute code in projection specification', array('found' => $sAttribute, 'expecting' => MetaModel::GetAttributesList($sTargetClass), 'class' => $sTargetClass, 'projection' => $oProjectionSpec)); } - $oAttDef = MetaModel::GetAttributeDef($sExpressionClass, $sAttribute); + $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttribute); if ($oAttDef->IsExternalKey()) { $sFoundType = $oAttDef->GetTargetClass(); @@ -163,13 +184,9 @@ class URP_Dimensions extends DBObject } } } - else - { - $sFoundType = '_scalar_'; - } // Compare the dimension type and projection type - if ($sFoundType != $sExpectedType) + if (($sFoundType != '_void_') && ($sFoundType != $sExpectedType)) { throw new CoreException('Wrong type in projection specification', array('found' => $sFoundType, 'expecting' => $sExpectedType, 'expression' => $sExpression, 'attribute' => $sAttribute, 'projection' => $oProjectionSpec)); } @@ -234,7 +251,7 @@ class URP_ProfileProjection extends DBObject MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant | ", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant | | +attribute code", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); @@ -245,17 +262,30 @@ class URP_ProfileProjection extends DBObject public function ProjectUser(URP_Users $oUser) { $sExpr = $this->Get('value'); - if (strtolower(substr($sExpr, 0, 6)) == 'select') + if ($sExpr == '') + { + $sColumn = $this->Get('attribute'); + if (empty($sColumn)) + { + $aRes = array($oUser->GetKey()); + } + else + { + $aRes = array($oUser->Get($sColumn)); + } + + } + elseif ($sExpr == '') + { + $aRes = null; + } + elseif (strtolower(substr($sExpr, 0, 6)) == 'select') { $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); $aValues = $oValueSetDef->GetValues(array('user' => $oUser), ''); - $aRes = array_values($aValues); - } - elseif ($sExpr == '') - { - $aRes = null; + $aRes = array_keys($aValues); } else { @@ -291,24 +321,42 @@ class URP_ClassProjection extends DBObject MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"Target class", "allowed_values"=>null, "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant | ", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant | | +attribute code", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("dimensionid"); + // #@# verifier MetaModel::Init_AddFilterFromAttribute("class"); } public function ProjectObject($oObject) { $sExpr = $this->Get('value'); - if (strtolower(substr($sExpr, 0, 6)) == 'select') + if ($sExpr == '') + { + $sColumn = $this->Get('attribute'); + if (empty($sColumn)) + { + $aRes = array($oObject->GetKey()); + } + else + { + $aRes = array($oObject->Get($sColumn)); + } + + } + elseif ($sExpr == '') + { + $aRes = null; + } + elseif (strtolower(substr($sExpr, 0, 6)) == 'select') { $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); - $aValues = $oValueSetDef->GetValues(array('user' => $oObject), ''); - $aRes = array_values($aValues); + $aValues = $oValueSetDef->GetValues(array('this' => $oObject), ''); + $aRes = array_keys($aValues); } elseif ($sExpr == '') { @@ -357,6 +405,7 @@ class URP_ActionGrant extends DBObject MetaModel::Init_AddFilterFromAttribute("profileid"); MetaModel::Init_AddFilterFromAttribute("profile"); MetaModel::Init_AddFilterFromAttribute("class"); + MetaModel::Init_AddFilterFromAttribute("permission"); MetaModel::Init_AddFilterFromAttribute("action"); } @@ -396,6 +445,7 @@ class URP_StimulusGrant extends DBObject MetaModel::Init_AddFilterFromAttribute("profileid"); MetaModel::Init_AddFilterFromAttribute("profile"); MetaModel::Init_AddFilterFromAttribute("class"); + MetaModel::Init_AddFilterFromAttribute("permission"); MetaModel::Init_AddFilterFromAttribute("stimulus"); } @@ -446,19 +496,22 @@ class UserRightsProfile extends UserRightsAddOnAPI ); // Installation: create the very first user - public function CreateAdministrator($sAdminUser, $sAdminPwd, $sAdminEmail, $sFirstName, $sLastName, $sPhoneNumber) + public function CreateAdministrator($sAdminUser, $sAdminPwd) { - // Maybe we should check that no other user with userid == 0 exists $oUser = new URP_Users(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); - $oUser->Set('email', $sAdminEmail); - $oUser->Set('firstname', $sFirstName); - $oUser->Set('lastname', $sLastName); - $oUser->Set('phonenumber', $sPhoneNumber); - $oUser->Set('userid', 1); // one is for root ! + $oUser->Set('email', 'n/a'); + $oUser->Set('firstname', 'administrator'); + $oUser->Set('lastname', 'itop'); + $oUser->Set('userid', 1); // let's mark it as #1 (for what purpose?) $iUserId = $oUser->DBInsertNoReload(); - $this->SetupUser($iUserId, true); + + // Add this user to the very specific 'admin' profile + $oUserProfile = new URP_UserProfile(); + $oUserProfile->Set('userid', $iUserId); + $oUserProfile->Set('profileid', ADMIN_PROFILE_ID); + $oUserProfile->DBInsertNoReload(); return true; } @@ -543,9 +596,9 @@ class UserRightsProfile extends UserRightsAddOnAPI { $iProfileId = $oProfile->GetKey(); - // Create grant records, for any class where it applies + // Create grant records, for any class where it matters (the rest could be done later on) // - foreach(array('bizmodel', 'application', 'gui', 'core/cmdb') as $sCategory) + foreach(array('bizmodel') as $sCategory) { foreach (MetaModel::GetClasses($sCategory) as $sClass) { @@ -633,6 +686,8 @@ class UserRightsProfile extends UserRightsAddOnAPI protected $m_aUserProfiles = array(); // userid,profileid -> object protected $m_aProPro = array(); // profileid,dimensionid -> object + protected $m_aAdmins = array(); // id of users being linked to the profile #ADMIN_PROFILE_ID + protected $m_aClassActionGrants = array(); // profile, class, action -> permission protected $m_aObjectActionGrants = array(); // userid, class, id, action -> permission, list of attributes @@ -667,7 +722,11 @@ class UserRightsProfile extends UserRightsAddOnAPI $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile")); while ($oUserProfile = $oUserProfileSet->Fetch()) { - $this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; + $this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; + if ($oUserProfile->Get('profileid') == ADMIN_PROFILE_ID) + { + $this->m_aAdmins[] = $oUserProfile->Get('userid'); + } } $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); @@ -734,7 +793,7 @@ exit; if (isset($aTest)) return $aTest; // Get the permission for this profile/class/action - $oSearch = DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile"); + $oSearch = DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'"); $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile)); if ($oSet->Count() < 1) { @@ -764,14 +823,24 @@ exit; { continue; // loop to the next profile } - elseif ($oGrantRecord->Get('permission') == 'yes') + else { $iInstancePermission = UR_ALLOWED_YES; - // merge the list of attributes allowed for this profile + // update the list of attributes with those allowed for this profile + // $oSearch = DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); - $aAttributes = array_merge($aAttributes, $oSet->GetColumnAsArray('attcode', false)); + $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); + if (count($aProfileAttributes) == 0) + { + $aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass)); + $aAttributes = array_merge($aAttributes, $aAllAttributes); + } + else + { + $aAttributes = array_merge($aAttributes, $aProfileAttributes); + } } } @@ -785,6 +854,9 @@ exit; public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances) { + // super admin rights + if (in_array($iUserId, $this->m_aAdmins)) return true; + $oUser = $this->m_aUsers[$iUserId]; $oInstances->Rewind(); @@ -798,6 +870,7 @@ exit; if ($iInstancePermission != $iGlobalPermission) { $iGlobalPermission = UR_ALLOWED_DEPENDS; + break; } } else @@ -805,6 +878,8 @@ exit; $iGlobalPermission = $iInstancePermission; } } + $oInstances->Rewind(); + if (isset($iGlobalPermission)) { return $iGlobalPermission; @@ -817,6 +892,9 @@ exit; public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances) { + // super admin rights + if (in_array($iUserId, $this->m_aAdmins)) return true; + $oUser = $this->m_aUsers[$iUserId]; $oInstances->Rewind(); @@ -846,6 +924,8 @@ exit; $iGlobalPermission = $iInstancePermission; } } + $oInstances->Rewind(); + if (isset($iGlobalPermission)) { return $iGlobalPermission; @@ -858,6 +938,9 @@ exit; public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances) { + // super admin rights + if (in_array($iUserId, $this->m_aAdmins)) return true; + $oUser = $this->m_aUsers[$iUserId]; // Note: this code is VERY close to the code of IsActionAllowed() @@ -869,19 +952,14 @@ exit; foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile) { // Get the permission for this profile/class/stimulus - $oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile"); + $oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); if ($oSet->Count() < 1) { return UR_ALLOWED_NO; } - - $oGrantRecord = $oSet->Fetch(); - $sPermission = $oGrantRecord->Get('permission'); - if ($sPermission == 'yes') - { - $iInstancePermission = UR_ALLOWED_YES; - } + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; } if (isset($iGlobalPermission)) { @@ -895,6 +973,8 @@ exit; $iGlobalPermission = $iInstancePermission; } } + $oInstances->Rewind(); + if (isset($iGlobalPermission)) { return $iGlobalPermission; @@ -918,28 +998,31 @@ exit; $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); $aRes = array(); - foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) + if (array_key_exists($iUser, $this->m_aUserProfiles) > 0) { - if (is_null($aObjectProjection)) + foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) { - $aRes[] = $iProfile; - } - else - { - // user projection to be cached on a given page ! - $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); - - if (is_null($aUserProjection)) + if (is_null($aObjectProjection)) { $aRes[] = $iProfile; } else { - $aMatchingValues = array_intersect($aObjectProjection, $aUserProjection); - if (count($aMatchingValues) > 0) + // user projection to be cached on a given page ! + $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + + if (is_null($aUserProjection)) { $aRes[] = $iProfile; } + else + { + $aMatchingValues = array_intersect($aObjectProjection, $aUserProjection); + if (count($aMatchingValues) > 0) + { + $aRes[] = $iProfile; + } + } } } } diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 7641490076..2015210b4a 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -249,7 +249,7 @@ class bizPerson extends bizContact "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_name", "first_name", "name"), // comment en dfinir plusieurs - // "reconc_keys" => array("org_name", "employe_number"), + // "reconc_keys" => array("org_name", "employee_number"), "db_table" => "persons", // Can it use the same physical DB table as any contact ? "db_key_field" => "id", "db_finalclass_field" => "", @@ -258,20 +258,21 @@ class bizPerson extends bizContact MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"first Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("employe_number", array("label"=>"Employe Number", "description"=>"employe number", "allowed_values"=>null, "sql"=>"employe_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("employee_number", array("label"=>"Employee Number", "description"=>"employee number", "allowed_values"=>null, "sql"=>"employee_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); -// MetaModel::Init_AddAttribute(new AttributeExternalKey("login_id", array("targetclass"=>"URP_Users", "label"=>"Login", "description"=>"Login information", "allowed_values"=>null, "sql"=>"login_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("login_id", array("targetclass"=>"URP_Users", "label"=>"Login", "description"=>"Login information", "allowed_values"=>null, "sql"=>"login_id", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("first_name"); - MetaModel::Init_AddFilterFromAttribute("employe_number"); + MetaModel::Init_AddFilterFromAttribute("employee_number"); + MetaModel::Init_AddFilterFromAttribute("login_id"); // Display lists - MetaModel::Init_SetZListItems('details', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone', 'employe_number')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone', 'employee_number', 'login_id')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list // Search criteria - MetaModel::Init_SetZListItems('standard_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employe_number')); // Criteria of the std search form - MetaModel::Init_SetZListItems('advanced_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employe_number')); // Criteria of the advanced search form + MetaModel::Init_SetZListItems('standard_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employee_number', 'login_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employee_number', 'login_id')); // Criteria of the advanced search form } public function Generate(cmdbDataGenerator $oGenerator) diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index aadbc656da..2c09139103 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -194,8 +194,13 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp public function GetDescription() { $sResult = ''; - $oEmptySet = new DBObjectSet($this->Get('objclass')); - if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oEmptySet) == UR_ALLOWED_YES) + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) { $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); $sAttName = $oAttDef->GetLabel(); diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 6384864d82..d1b80429c3 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -21,7 +21,7 @@ class DBObjectSet private $m_aId2Row; private $m_iCurrRow; - public function __construct($oFilter, $aOrderBy = array(), $aArgs = array()) + public function __construct(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) { $this->m_oFilter = $oFilter; $this->m_aOrderBy = $aOrderBy; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index cbb0b16373..5fe13c140c 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1173,6 +1173,32 @@ abstract class MetaModel protected static $m_aQueryStructCache = array(); + protected static function PrepareQueryArguments($aArgs) + { + // Translate any object into scalars + // + $aScalarArgs = array(); + foreach($aArgs as $sArgName => $value) + { + if (self::IsValidObject($value)) + { + $aScalarArgs[$sArgName] = $value->GetKey(); + $aScalarArgs[$sArgName.'->id'] = $value->GetKey(); + + $sClass = get_class($value); + foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + $aScalarArgs[$sArgName.'->'.$sAttCode] = $value->Get($sAttCode); + } + } + else + { + $aScalarArgs[$sArgName] = (string) $value; + } + } + return $aScalarArgs; + } + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) { // Query caching @@ -1219,30 +1245,9 @@ abstract class MetaModel } } - // Prepare arguments (translate any object into scalars) - // - $aScalarArgs = $oFilter->GetInternalParams(); - foreach($aArgs as $sArgName => $value) - { - if (self::IsValidObject($value)) - { - $aScalarArgs[$sArgName] = $value->GetKey(); - $aScalarArgs[$sArgName.'->id'] = $value->GetKey(); - - $sClass = get_class($value); - foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - $aScalarArgs[$sArgName.'->'.$sAttCode] = $value->Get($sAttCode); - } - } - else - { - $aScalarArgs[$sArgName] = (string) $value; - } - } - // Go // + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); $sRes = $oSelect->RenderSelect($aOrderBy, $aScalarArgs); if (self::$m_bTraceQueries) @@ -1277,17 +1282,18 @@ abstract class MetaModel echo "

    Count of built queries: ".count(self::$m_aQueriesLog)."

    "; } - public static function MakeDeleteQuery(DBObjectSearch $oFilter) + public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) { $aTranslation = array(); $aClassAliases = array(); $aTableAliases = array(); $oConditionTree = $oFilter->GetCriteria(); $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); - return $oSelect->RenderDelete(); + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + return $oSelect->RenderDelete($aScalarArgs); } - public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues) + public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) { // $aValues is an array of $sAttCode => $value $aTranslation = array(); @@ -1295,7 +1301,8 @@ abstract class MetaModel $aTableAliases = array(); $oConditionTree = $oFilter->GetCriteria(); $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues); - return $oSelect->RenderUpdate(); + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + return $oSelect->RenderUpdate($aScalarArgs); } private static function MakeQuery($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array()) @@ -2439,6 +2446,9 @@ abstract class MetaModel if (self::DBExists()) { CMDBSource::SelectDB(self::$m_sDBName); + + // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) + self::InitPlugins(); } else { @@ -2447,8 +2457,6 @@ abstract class MetaModel throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); } } - // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) - self::InitPlugins(); } public static function LoadConfig($sConfigFile) diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php index 1661ed4a24..700d0b33b6 100644 --- a/core/sqlquery.class.inc.php +++ b/core/sqlquery.class.inc.php @@ -171,7 +171,7 @@ class SQLQuery } // Interface, build the SQL query - public function RenderDelete() + public function RenderDelete($aArgs = array()) { // The goal will be to complete the list as we build the Joins $aFrom = array(); @@ -193,12 +193,12 @@ class SQLQuery throw new CoreException("Building a request wich will delete every object of a given table -looks suspicious- please use truncate instead..."); } */ - $sWhere = self::ClauseWhere($oCondition); + $sWhere = self::ClauseWhere($oCondition, $aArgs); return "DELETE $sDelete FROM $sFrom WHERE $sWhere"; } // Interface, build the SQL query - public function RenderUpdate() + public function RenderUpdate($aArgs = array()) { // The goal will be to complete the list as we build the Joins $aFrom = array(); @@ -210,7 +210,7 @@ class SQLQuery $sFrom = self::ClauseFrom($aFrom); $sValues = self::ClauseValues($aSetValues); - $sWhere = self::ClauseWhere($oCondition); + $sWhere = self::ClauseWhere($oCondition, $aArgs); return "UPDATE $sFrom SET $sValues WHERE $sWhere"; } diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php index 826f041119..18ee898bb3 100644 --- a/pages/usermanagement_classproj.php +++ b/pages/usermanagement_classproj.php @@ -52,18 +52,18 @@ function ComputeProjections($oPage, $sScope) foreach ($aDimensions as $iDimension => $oDimension) { // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]); + $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension], $sClass); $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); if (is_null($aValues)) { - $sValues = ''; + $sValues = htmlentities(''); } else { $sValues = implode(', ', $aValues); } - $oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + $oObjectProj['dim'.$oDimension->GetKey()] = $sValues; } $aDisplayData[] = $oObjectProj; diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index 4ca6d6aa57..c29f80849b 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -62,18 +62,18 @@ function ComputeProjections($oPage) foreach ($aDimensions as $iDimension => $oDimension) { // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]); + $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension], get_class($oUser)); $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); if (is_null($aValues)) { - $sValues = ''; + $sValues = htmlentities(''); } else { $sValues = implode(', ', $aValues); } - $aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + $aUserProfileProj['dim'.$oDimension->GetKey()] = $sValues; } $aDisplayData[] = $aUserProfileProj; diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index 475e39ad83..8b243ff2ac 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -45,18 +45,18 @@ function ComputeObjectProjections($oPage, $oObject) foreach ($aDimensions as $iDimension => $oDimension) { // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]); + $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension], $sClass); $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); if (is_null($aValues)) { - $sValues = ''; + $sValues = htmlentities(''); } else { $sValues = implode(', ', $aValues); } - $oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + $oObjectProj['dim'.$oDimension->GetKey()] = $sValues; } $aDisplayData[] = $oObjectProj; @@ -117,18 +117,18 @@ function ComputeUserProjections($oPage, $oUser) foreach ($aDimensions as $iDimension => $oDimension) { // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]); + $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension], get_class($oUser)); $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); if (is_null($aValues)) { - $sValues = ''; + $sValues = htmlentities(''); } else { $sValues = implode(', ', $aValues); } - $aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues); + $aUserProfileProj['dim'.$oDimension->GetKey()] = $sValues; } $aDisplayData[] = $aUserProfileProj; diff --git a/setup/data/03.persons.xml b/setup/data/03.persons.xml index 9a102ad6e9..edee771b0c 100644 --- a/setup/data/03.persons.xml +++ b/setup/data/03.persons.xml @@ -8,7 +8,8 @@ 29 Jules - + + Dumas @@ -18,7 +19,8 @@ 1 Dumas - + + Hugo @@ -28,6 +30,7 @@ 33172382223 1 Victor -e12345 +e12345 + diff --git a/setup/data/23.dimensions.xml b/setup/data/23.dimensions.xml new file mode 100644 index 0000000000..3104cd3d53 --- /dev/null +++ b/setup/data/23.dimensions.xml @@ -0,0 +1,13 @@ + + + +organization + +bizOrganization + + +site + + + + \ No newline at end of file diff --git a/setup/data/24.profiles.xml b/setup/data/24.profiles.xml new file mode 100644 index 0000000000..77121719e3 --- /dev/null +++ b/setup/data/24.profiles.xml @@ -0,0 +1,11 @@ + + + +Administrator +Has the rights on everything (ignores the grant records) + + +Delivery Manager France +Persons in charge of the operations for French customers + + diff --git a/setup/data/25.classprojection.xml b/setup/data/25.classprojection.xml new file mode 100644 index 0000000000..2677140460 --- /dev/null +++ b/setup/data/25.classprojection.xml @@ -0,0 +1,555 @@ + + + +1 +bizOrganization +<this> + + + +1 +logRealObject +<this> +org_id + + +1 +bizContact +<this> +org_id + + +1 +bizPerson +<this> +org_id + + +1 +bizTeam +<this> +org_id + + +1 +bizDocument +<this> +org_id + + +1 +bizDocVersion +<any> + + + +1 +lnkDocumentRealObject +<any> + + + +1 +lnkContactRealObject +<any> + + + +1 +logInfra +<this> +org_id + + +1 +bizLocation +<this> +org_id + + +1 +bizCircuit +<this> +org_id + + +1 +bizInterface +<this> +org_id + + +1 +lnkInterfaces +<this> +org_id + + +1 +bizDevice +<this> +org_id + + +1 +bizPC +<this> +org_id + + +1 +bizServer +<this> +org_id + + +1 +bizNetworkDevice +<this> +org_id + + +1 +bizInfraGroup +<this> +org_id + + +1 +bizApplication +<this> +org_id + + +1 +lnkInfraGrouping +<any> + + + +1 +lnkClientServer +<any> + + + +1 +bizPatch +<this> +org_id + + +1 +bizIncidentTicket +<this> +customer_id + + +1 +lnkRelatedTicket +<any> + + + +1 +lnkInfraTicket +<any> + + + +1 +lnkContactTicket +<any> + + + +1 +bizWorkgroup +<this> +org_id + + +1 +bizContract +<this> +org_id + + +1 +lnkInfraContract +<any> + + + +1 +lnkContactContract +<any> + + + +1 +lnkDocumentContract +<any> + + + +1 +bizChangeTicket +<this> +customer_id + + +1 +lnkInfraChangeTicket +<any> + + + +1 +lnkContactChange +<any> + + + +1 +bizKnownError +<this> +cust_id + + +1 +lnkInfraError +<any> + + + +1 +lnkDocumentError +<any> + + + +1 +AuditCategory +<any> + + + +1 +AuditRule +<any> + + + +1 +menuNode +<any> + + + +1 +CMDBChange +<any> + + + +1 +CMDBChangeOp +<any> + + + +1 +CMDBChangeOpCreate +<any> + + + +1 +CMDBChangeOpDelete +<any> + + + +1 +CMDBChangeOpSetAttribute +<any> + + + +2 +bizOrganization +<any> + + + +2 +logRealObject +<any> + + + +2 +bizContact +<any> + + + +2 +bizPerson +<any> + + + +2 +bizTeam +<any> + + + +2 +bizDocument +<any> + + + +2 +bizDocVersion +<any> + + + +2 +lnkDocumentRealObject +<any> + + + +2 +lnkContactRealObject +<any> + + + +2 +logInfra +<any> + + + +2 +bizLocation +<any> + + + +2 +bizCircuit +<any> + + + +2 +bizInterface +<any> + + + +2 +lnkInterfaces +<any> + + + +2 +bizDevice +<any> + + + +2 +bizPC +<any> + + + +2 +bizServer +<any> + + + +2 +bizNetworkDevice +<any> + + + +2 +bizInfraGroup +<any> + + + +2 +bizApplication +<any> + + + +2 +lnkInfraGrouping +<any> + + + +2 +lnkClientServer +<any> + + + +2 +bizPatch +<any> + + + +2 +bizIncidentTicket +<any> + + + +2 +lnkRelatedTicket +<any> + + + +2 +lnkInfraTicket +<any> + + + +2 +lnkContactTicket +<any> + + + +2 +bizWorkgroup +<any> + + + +2 +bizContract +<any> + + + +2 +lnkInfraContract +<any> + + + +2 +lnkContactContract +<any> + + + +2 +lnkDocumentContract +<any> + + + +2 +bizChangeTicket +<any> + + + +2 +lnkInfraChangeTicket +<any> + + + +2 +lnkContactChange +<any> + + + +2 +bizKnownError +<any> + + + +2 +lnkInfraError +<any> + + + +2 +lnkDocumentError +<any> + + + +2 +AuditCategory +<any> + + + +2 +AuditRule +<any> + + + +2 +menuNode +<any> + + + +2 +CMDBChange +<any> + + + +2 +CMDBChangeOp +<any> + + + +2 +CMDBChangeOpCreate +<any> + + + +2 +CMDBChangeOpDelete +<any> + + + +2 +CMDBChangeOpSetAttribute +<any> + + + \ No newline at end of file diff --git a/setup/data/26.profileprojection.xml b/setup/data/26.profileprojection.xml new file mode 100644 index 0000000000..6d94462787 --- /dev/null +++ b/setup/data/26.profileprojection.xml @@ -0,0 +1,15 @@ + + + +1 +2 +1;2 + + + +2 +2 +<any> + + + diff --git a/setup/data/27.actiongrant.xml b/setup/data/27.actiongrant.xml new file mode 100644 index 0000000000..6375b1b8d4 --- /dev/null +++ b/setup/data/27.actiongrant.xml @@ -0,0 +1,1659 @@ + + + +2 +bizOrganization +yes +read + + +2 +bizOrganization +yes +modify + + +2 +bizOrganization +yes +delete + + +2 +bizOrganization +yes +bulk read + + +2 +bizOrganization +yes +bulk modify + + +2 +bizOrganization +yes +bulk delete + + +2 +logRealObject +yes +read + + +2 +logRealObject +yes +modify + + +2 +logRealObject +yes +delete + + +2 +logRealObject +yes +bulk read + + +2 +logRealObject +yes +bulk modify + + +2 +logRealObject +yes +bulk delete + + +2 +bizContact +yes +read + + +2 +bizContact +yes +modify + + +2 +bizContact +yes +delete + + +2 +bizContact +yes +bulk read + + +2 +bizContact +yes +bulk modify + + +2 +bizContact +yes +bulk delete + + +2 +bizPerson +yes +read + + +2 +bizPerson +yes +modify + + +2 +bizPerson +yes +delete + + +2 +bizPerson +yes +bulk read + + +2 +bizPerson +yes +bulk modify + + +2 +bizPerson +yes +bulk delete + + +2 +bizTeam +yes +read + + +2 +bizTeam +yes +modify + + +2 +bizTeam +yes +delete + + +2 +bizTeam +yes +bulk read + + +2 +bizTeam +yes +bulk modify + + +2 +bizTeam +yes +bulk delete + + +2 +bizDocument +yes +read + + +2 +bizDocument +yes +modify + + +2 +bizDocument +yes +delete + + +2 +bizDocument +yes +bulk read + + +2 +bizDocument +yes +bulk modify + + +2 +bizDocument +yes +bulk delete + + +2 +bizDocVersion +yes +read + + +2 +bizDocVersion +yes +modify + + +2 +bizDocVersion +yes +delete + + +2 +bizDocVersion +yes +bulk read + + +2 +bizDocVersion +yes +bulk modify + + +2 +bizDocVersion +yes +bulk delete + + +2 +lnkDocumentRealObject +yes +read + + +2 +lnkDocumentRealObject +yes +modify + + +2 +lnkDocumentRealObject +yes +delete + + +2 +lnkDocumentRealObject +yes +bulk read + + +2 +lnkDocumentRealObject +yes +bulk modify + + +2 +lnkDocumentRealObject +yes +bulk delete + + +2 +lnkContactRealObject +yes +read + + +2 +lnkContactRealObject +yes +modify + + +2 +lnkContactRealObject +yes +delete + + +2 +lnkContactRealObject +yes +bulk read + + +2 +lnkContactRealObject +yes +bulk modify + + +2 +lnkContactRealObject +yes +bulk delete + + +2 +logInfra +yes +read + + +2 +logInfra +yes +modify + + +2 +logInfra +yes +delete + + +2 +logInfra +yes +bulk read + + +2 +logInfra +yes +bulk modify + + +2 +logInfra +yes +bulk delete + + +2 +bizLocation +yes +read + + +2 +bizLocation +yes +modify + + +2 +bizLocation +yes +delete + + +2 +bizLocation +yes +bulk read + + +2 +bizLocation +yes +bulk modify + + +2 +bizLocation +yes +bulk delete + + +2 +bizCircuit +yes +read + + +2 +bizCircuit +yes +modify + + +2 +bizCircuit +yes +delete + + +2 +bizCircuit +yes +bulk read + + +2 +bizCircuit +yes +bulk modify + + +2 +bizCircuit +yes +bulk delete + + +2 +bizInterface +yes +read + + +2 +bizInterface +yes +modify + + +2 +bizInterface +yes +delete + + +2 +bizInterface +yes +bulk read + + +2 +bizInterface +yes +bulk modify + + +2 +bizInterface +yes +bulk delete + + +2 +lnkInterfaces +yes +read + + +2 +lnkInterfaces +yes +modify + + +2 +lnkInterfaces +yes +delete + + +2 +lnkInterfaces +yes +bulk read + + +2 +lnkInterfaces +yes +bulk modify + + +2 +lnkInterfaces +yes +bulk delete + + +2 +bizDevice +yes +read + + +2 +bizDevice +yes +modify + + +2 +bizDevice +yes +delete + + +2 +bizDevice +yes +bulk read + + +2 +bizDevice +yes +bulk modify + + +2 +bizDevice +yes +bulk delete + + +2 +bizPC +yes +read + + +2 +bizPC +yes +modify + + +2 +bizPC +yes +delete + + +2 +bizPC +yes +bulk read + + +2 +bizPC +yes +bulk modify + + +2 +bizPC +yes +bulk delete + + +2 +bizServer +yes +read + + +2 +bizServer +yes +modify + + +2 +bizServer +yes +delete + + +2 +bizServer +yes +bulk read + + +2 +bizServer +yes +bulk modify + + +2 +bizServer +yes +bulk delete + + +2 +bizNetworkDevice +yes +read + + +2 +bizNetworkDevice +yes +modify + + +2 +bizNetworkDevice +yes +delete + + +2 +bizNetworkDevice +yes +bulk read + + +2 +bizNetworkDevice +yes +bulk modify + + +2 +bizNetworkDevice +yes +bulk delete + + +2 +bizInfraGroup +yes +read + + +2 +bizInfraGroup +yes +modify + + +2 +bizInfraGroup +yes +delete + + +2 +bizInfraGroup +yes +bulk read + + +2 +bizInfraGroup +yes +bulk modify + + +2 +bizInfraGroup +yes +bulk delete + + +2 +bizApplication +yes +read + + +2 +bizApplication +yes +modify + + +2 +bizApplication +yes +delete + + +2 +bizApplication +yes +bulk read + + +2 +bizApplication +yes +bulk modify + + +2 +bizApplication +yes +bulk delete + + +2 +lnkInfraGrouping +yes +read + + +2 +lnkInfraGrouping +yes +modify + + +2 +lnkInfraGrouping +yes +delete + + +2 +lnkInfraGrouping +yes +bulk read + + +2 +lnkInfraGrouping +yes +bulk modify + + +2 +lnkInfraGrouping +yes +bulk delete + + +2 +lnkClientServer +yes +read + + +2 +lnkClientServer +yes +modify + + +2 +lnkClientServer +yes +delete + + +2 +lnkClientServer +yes +bulk read + + +2 +lnkClientServer +yes +bulk modify + + +2 +lnkClientServer +yes +bulk delete + + +2 +bizPatch +yes +read + + +2 +bizPatch +yes +modify + + +2 +bizPatch +yes +delete + + +2 +bizPatch +yes +bulk read + + +2 +bizPatch +yes +bulk modify + + +2 +bizPatch +yes +bulk delete + + +2 +bizIncidentTicket +yes +read + + +2 +bizIncidentTicket +no +modify + + +2 +bizIncidentTicket +no +delete + + +2 +bizIncidentTicket +yes +bulk read + + +2 +bizIncidentTicket +no +bulk modify + + +2 +bizIncidentTicket +no +bulk delete + + +2 +lnkRelatedTicket +yes +read + + +2 +lnkRelatedTicket +yes +modify + + +2 +lnkRelatedTicket +yes +delete + + +2 +lnkRelatedTicket +yes +bulk read + + +2 +lnkRelatedTicket +yes +bulk modify + + +2 +lnkRelatedTicket +yes +bulk delete + + +2 +lnkInfraTicket +yes +read + + +2 +lnkInfraTicket +yes +modify + + +2 +lnkInfraTicket +yes +delete + + +2 +lnkInfraTicket +yes +bulk read + + +2 +lnkInfraTicket +yes +bulk modify + + +2 +lnkInfraTicket +yes +bulk delete + + +2 +lnkContactTicket +yes +read + + +2 +lnkContactTicket +yes +modify + + +2 +lnkContactTicket +yes +delete + + +2 +lnkContactTicket +yes +bulk read + + +2 +lnkContactTicket +yes +bulk modify + + +2 +lnkContactTicket +yes +bulk delete + + +2 +bizWorkgroup +yes +read + + +2 +bizWorkgroup +yes +modify + + +2 +bizWorkgroup +yes +delete + + +2 +bizWorkgroup +yes +bulk read + + +2 +bizWorkgroup +yes +bulk modify + + +2 +bizWorkgroup +yes +bulk delete + + +2 +bizContract +yes +read + + +2 +bizContract +yes +modify + + +2 +bizContract +yes +delete + + +2 +bizContract +yes +bulk read + + +2 +bizContract +yes +bulk modify + + +2 +bizContract +yes +bulk delete + + +2 +lnkInfraContract +yes +read + + +2 +lnkInfraContract +yes +modify + + +2 +lnkInfraContract +yes +delete + + +2 +lnkInfraContract +yes +bulk read + + +2 +lnkInfraContract +yes +bulk modify + + +2 +lnkInfraContract +yes +bulk delete + + +2 +lnkContactContract +yes +read + + +2 +lnkContactContract +yes +modify + + +2 +lnkContactContract +yes +delete + + +2 +lnkContactContract +yes +bulk read + + +2 +lnkContactContract +yes +bulk modify + + +2 +lnkContactContract +yes +bulk delete + + +2 +lnkDocumentContract +yes +read + + +2 +lnkDocumentContract +yes +modify + + +2 +lnkDocumentContract +yes +delete + + +2 +lnkDocumentContract +yes +bulk read + + +2 +lnkDocumentContract +yes +bulk modify + + +2 +lnkDocumentContract +yes +bulk delete + + +2 +bizChangeTicket +yes +read + + +2 +bizChangeTicket +no +modify + + +2 +bizChangeTicket +no +delete + + +2 +bizChangeTicket +yes +bulk read + + +2 +bizChangeTicket +no +bulk modify + + +2 +bizChangeTicket +no +bulk delete + + +2 +lnkInfraChangeTicket +yes +read + + +2 +lnkInfraChangeTicket +yes +modify + + +2 +lnkInfraChangeTicket +yes +delete + + +2 +lnkInfraChangeTicket +yes +bulk read + + +2 +lnkInfraChangeTicket +yes +bulk modify + + +2 +lnkInfraChangeTicket +yes +bulk delete + + +2 +lnkContactChange +yes +read + + +2 +lnkContactChange +yes +modify + + +2 +lnkContactChange +yes +delete + + +2 +lnkContactChange +yes +bulk read + + +2 +lnkContactChange +yes +bulk modify + + +2 +lnkContactChange +yes +bulk delete + + +2 +bizKnownError +yes +read + + +2 +bizKnownError +yes +modify + + +2 +bizKnownError +yes +delete + + +2 +bizKnownError +yes +bulk read + + +2 +bizKnownError +yes +bulk modify + + +2 +bizKnownError +yes +bulk delete + + +2 +lnkInfraError +yes +read + + +2 +lnkInfraError +yes +modify + + +2 +lnkInfraError +yes +delete + + +2 +lnkInfraError +yes +bulk read + + +2 +lnkInfraError +yes +bulk modify + + +2 +lnkInfraError +yes +bulk delete + + +2 +lnkDocumentError +yes +read + + +2 +lnkDocumentError +yes +modify + + +2 +lnkDocumentError +yes +delete + + +2 +lnkDocumentError +yes +bulk read + + +2 +lnkDocumentError +yes +bulk modify + + +2 +lnkDocumentError +yes +bulk delete + + +2 +AuditCategory +yes +read + + +2 +AuditCategory +yes +modify + + +2 +AuditCategory +yes +delete + + +2 +AuditCategory +yes +bulk read + + +2 +AuditCategory +yes +bulk modify + + +2 +AuditCategory +yes +bulk delete + + +2 +AuditRule +yes +read + + +2 +AuditRule +yes +modify + + +2 +AuditRule +yes +delete + + +2 +AuditRule +yes +bulk read + + +2 +AuditRule +yes +bulk modify + + +2 +AuditRule +yes +bulk delete + + +2 +menuNode +yes +read + + +2 +menuNode +yes +modify + + +2 +menuNode +yes +delete + + +2 +menuNode +yes +bulk read + + +2 +menuNode +yes +bulk modify + + +2 +menuNode +yes +bulk delete + + +2 +CMDBChange +yes +read + + +2 +CMDBChange +yes +modify + + +2 +CMDBChange +yes +delete + + +2 +CMDBChange +yes +bulk read + + +2 +CMDBChange +yes +bulk modify + + +2 +CMDBChange +yes +bulk delete + + +2 +CMDBChangeOp +yes +read + + +2 +CMDBChangeOp +yes +modify + + +2 +CMDBChangeOp +yes +delete + + +2 +CMDBChangeOp +yes +bulk read + + +2 +CMDBChangeOp +yes +bulk modify + + +2 +CMDBChangeOp +yes +bulk delete + + +2 +CMDBChangeOpCreate +yes +read + + +2 +CMDBChangeOpCreate +yes +modify + + +2 +CMDBChangeOpCreate +yes +delete + + +2 +CMDBChangeOpCreate +yes +bulk read + + +2 +CMDBChangeOpCreate +yes +bulk modify + + +2 +CMDBChangeOpCreate +yes +bulk delete + + +2 +CMDBChangeOpDelete +yes +read + + +2 +CMDBChangeOpDelete +yes +modify + + +2 +CMDBChangeOpDelete +yes +delete + + +2 +CMDBChangeOpDelete +yes +bulk read + + +2 +CMDBChangeOpDelete +yes +bulk modify + + +2 +CMDBChangeOpDelete +yes +bulk delete + + +2 +CMDBChangeOpSetAttribute +yes +read + + +2 +CMDBChangeOpSetAttribute +yes +modify + + +2 +CMDBChangeOpSetAttribute +yes +delete + + +2 +CMDBChangeOpSetAttribute +yes +bulk read + + +2 +CMDBChangeOpSetAttribute +yes +bulk modify + + +2 +CMDBChangeOpSetAttribute +yes +bulk delete + + diff --git a/setup/data/28.attributegrant.xml b/setup/data/28.attributegrant.xml new file mode 100644 index 0000000000..cf14611677 --- /dev/null +++ b/setup/data/28.attributegrant.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/setup/data/29.stimulusgrant.xml b/setup/data/29.stimulusgrant.xml new file mode 100644 index 0000000000..974b929eab --- /dev/null +++ b/setup/data/29.stimulusgrant.xml @@ -0,0 +1,189 @@ + + + +2 +bizServer +no +ev_store + + +2 +bizServer +no +ev_ship + + +2 +bizServer +no +ev_plug + + +2 +bizServer +no +ev_configuration_finished + + +2 +bizServer +no +ev_val_failed + + +2 +bizServer +no +ev_mtp + + +2 +bizServer +no +ev_start_change + + +2 +bizServer +no +ev_end_change + + +2 +bizServer +no +ev_decommission + + +2 +bizServer +no +ev_obsolete + + +2 +bizServer +no +ev_recycle + + +2 +bizIncidentTicket +no +ev_assign + + +2 +bizIncidentTicket +no +ev_reassign + + +2 +bizIncidentTicket +no +ev_start_working + + +2 +bizIncidentTicket +no +ev_close + + +2 +bizContract +no +ev_freeze_version + + +2 +bizContract +no +ev_sign + + +2 +bizContract +no +ev_begin + + +2 +bizContract +no +ev_notice + + +2 +bizContract +no +ev_terminate + + +2 +bizContract +no +ev_elapsed + + +2 +bizChangeTicket +yes +ev_validate + + +2 +bizChangeTicket +yes +ev_reject + + +2 +bizChangeTicket +no +ev_reopen + + +2 +bizChangeTicket +no +ev_plan + + +2 +bizChangeTicket +yes +ev_approve + + +2 +bizChangeTicket +no +ev_replan + + +2 +bizChangeTicket +yes +ev_notapprove + + +2 +bizChangeTicket +no +ev_implement + + +2 +bizChangeTicket +no +ev_monitor + + +2 +bizChangeTicket +yes +ev_finish + + diff --git a/setup/data/export.cmd b/setup/data/export.cmd index 5c95ce8605..923d30177f 100644 --- a/setup/data/export.cmd +++ b/setup/data/export.cmd @@ -1,27 +1,36 @@ SET WEBROOT=http://localhost:81 SET USER=Erwan SET PWD=Taloc + REM The order (numbering) of the files is important since REM it dictates the order to import them back -wget --output-document=01.organizations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizOrganization&format=xml" -wget --output-document=02.locations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizLocation&format=xml" -wget --output-document=03.persons.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizPerson&format=xml" -wget --output-document=04.teams.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizTeam&format=xml" -wget --output-document=05.pcs.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizPC&format=xml" -wget --output-document=06.servers.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizServer&format=xml" -wget --output-document=07.applications.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizApplication&format=xml" -wget --output-document=08.nw-devices.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizNetworkDevice&format=xml" -wget --output-document=09.links_contacts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactRealObject&format=xml" -wget --output-document=10.workgroups.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizWorkgroup&format=xml" -wget --output-document=11.incidents.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizIncidentTicket&format=xml" -wget --output-document=12.relatedtickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkRelatedTicket&format=xml" -wget --output-document=13.infratickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkInfraTicket&format=xml" -wget --output-document=14.contacttickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactTicket&format=xml" -wget --output-document=15.changetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizChangeTicket&format=xml" -wget --output-document=16.infrachangetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkInfraChangeTicket&format=xml" -wget --output-document=17.contactchangetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactChange&format=xml" -wget --output-document=18.contracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT bizContract&format=xml" -wget --output-document=19.infracontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkInfraContract&format=xml" -wget --output-document=20.contactcontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT lnkContactContract&format=xml" -wget --output-document=21.auditcategories.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT AuditCategory&format=xml" -wget --output-document=22.auditrules.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT AuditRule&format=xml" +wget --output-document=01.organizations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizOrganization&format=xml" +wget --output-document=02.locations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizLocation&format=xml" +wget --output-document=03.persons.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizPerson&format=xml" +wget --output-document=04.teams.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizTeam&format=xml" +wget --output-document=05.pcs.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizPC&format=xml" +wget --output-document=06.servers.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizServer&format=xml" +wget --output-document=07.applications.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizApplication&format=xml" +wget --output-document=08.nw-devices.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizNetworkDevice&format=xml" +wget --output-document=09.links_contacts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkContactRealObject&format=xml" +wget --output-document=10.workgroups.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizWorkgroup&format=xml" +wget --output-document=11.incidents.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizIncidentTicket&format=xml" +wget --output-document=12.relatedtickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkRelatedTicket&format=xml" +wget --output-document=13.infratickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkInfraTicket&format=xml" +wget --output-document=14.contacttickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkContactTicket&format=xml" +wget --output-document=15.changetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizChangeTicket&format=xml" +wget --output-document=16.infrachangetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkInfraChangeTicket&format=xml" +wget --output-document=17.contactchangetickets.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkContactChange&format=xml" +wget --output-document=18.contracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizContract&format=xml" +wget --output-document=19.infracontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkInfraContract&format=xml" +wget --output-document=20.contactcontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkContactContract&format=xml" +wget --output-document=21.auditcategories.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT AuditCategory&format=xml" +wget --output-document=22.auditrules.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT AuditRule&format=xml" +wget --output-document=23.dimensions.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_Dimensions&format=xml" +wget --output-document=24.profiles.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_Profiles&format=xml" +wget --output-document=25.classprojection.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ClassProjection&format=xml" +wget --output-document=26.profileprojection.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ProfileProjection&format=xml" +wget --output-document=27.actiongrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ActionGrant&format=xml" +wget --output-document=28.attributegrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_AttributeGrant&format=xml" +wget --output-document=29.stimulusgrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_StimulusGrant&format=xml" +pause From 12e433846f9cb80ec22be42d91c238b88ac65427 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 21 Aug 2009 13:57:38 +0000 Subject: [PATCH 019/970] session_is_register is deprecated since PHP 5.3.0 SVN:trunk[94] --- application/loginwebpage.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 32e7ad3fbd..29a1e33a4a 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -68,7 +68,7 @@ h1 { $operation = utils::ReadParam('operation', ''); session_start(); - if (!session_is_registered('auth_user') || !session_is_registered('auth_pwd')) + if (!isset($_SESSION['auth_user']) || !isset($_SESSION['auth_pwd'])) { if ($operation == 'login') { From ff6a5a45eee0119f3f725c5c66bdcda9f52805a8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 21 Aug 2009 15:20:35 +0000 Subject: [PATCH 020/970] Fixed issue #28, objects not created by the CSV import because mandatory external keys are not in the column set. SVN:trunk[95] --- core/bulkchange.class.inc.php | 18 ++++++++++++--- pages/csvimport.php | 42 +++++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 491852a22a..89bfdd0fa3 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -335,10 +335,22 @@ class BulkChange } // Check that any external key will have a value proposed - // Could be said once for all rows !!! - foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAtt) + $aMissingKeys = array(); + foreach (MetaModel::GetExternalKeys($this->m_sClass) as $sExtKeyAttCode => $oExtKey) { - if (!$oAtt->IsExternalKey()) continue; + if (!$oExtKey->IsNullAllowed()) + { + if (!array_key_exists($sExtKeyAttCode, $this->m_aExtKeys) && !array_key_exists($sExtKeyAttCode, $this->m_aAttList)) + { + $aMissingKeys[] = $oExtKey->GetLabel(); + } + } + } + if (count($aMissingKeys) > 0) + { + $sMissingKeys = implode(', ', $aMissingKeys); + $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Could not be created, due to missing external key(s): $sMissingKeys"); + return; } // Optionaly record the results diff --git a/pages/csvimport.php b/pages/csvimport.php index 0110e1f53b..4d8911e773 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -393,6 +393,7 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $aSampleData = $oCSVParser->ToArray(array_keys($aFieldMap), 5); $aDisplayConfig = array(); + $aExtKeys = array(); foreach ($aFieldMap as $sFieldId=>$sColDesc) { if (array_key_exists($sFieldId, $aIsReconcKey)) @@ -417,11 +418,16 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $sAttCode = $sColDesc; $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); $aDisplayConfig[$sFieldId] = array("label"=>"$sLabel$sReconcKey", "description"=>""); + if (MetaModel::IsValidKeyAttCode($sClass, $sAttCode)) + { + $aExtKeys[] = $sAttCode; + } } elseif (IsExtKeyField($sColDesc)) { list($sExtKeyAttCode, $sForeignAttCode) = GetExtKeyFieldCodes($sColDesc); $aDisplayConfig[$sFieldId] = array("label"=>MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode), "description"=>""); + $aExtKeys[] = $sExtKeyAttCode; } else { @@ -437,17 +443,39 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) } else { + $oPage->p("

    Column consistency

    "); + $aMissingKeys = array(); + foreach (MetaModel::GetExternalKeys($sClass) as $sExtKeyAttCode => $oExtKey) + { + if (!in_array($sExtKeyAttCode, $aExtKeys) && !$oExtKey->IsNullAllowed()) + { + $aMissingKeys[$sExtKeyAttCode] = $oExtKey; + } + } + if (count($aMissingKeys) > 0) + { + $oPage->p("Warning: the objects could not be created, due to some missing mandatory external keys in the field list: "); + $oPage->p("
      "); + foreach($aMissingKeys as $sAttCode => $oAttDef) + { + $oPage->p("
    • ".$oAttDef->GetLabel()."
    • "); + } + $oPage->p("
    "); + } + $oPage->p("

    Check...

    "); } ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, $oChange); - $oPage->add("
    "); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); $oPage->add_input_hidden("fmap", $aFieldMap); $oPage->add_input_hidden("iskey", $aIsReconcKey); + + return; } function Do_Verify($oPage, $sClass) @@ -459,8 +487,8 @@ function Do_Verify($oPage, $sClass) // FORM started by DoProcessOrVerify... $oPage->add(""); - $oPage->add(""); - $oPage->add(""); + $oPage->add(""); + $oPage->add(""); $oPage->add("
    "); } From 23afe7525eac21ed7e10945534f537b4d03b207d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 22 Aug 2009 09:48:03 +0000 Subject: [PATCH 021/970] Make sure that we catch exceptions that may occur during the setup. SVN:trunk[96] --- setup/index.php | 99 +++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/setup/index.php b/setup/index.php index 20a5472fbd..12b255c38f 100644 --- a/setup/index.php +++ b/setup/index.php @@ -484,53 +484,64 @@ catch(Exception $e) // We'll end here when the tmp config file does not exist. It's normal $oConfig = new Config(TMP_CONFIG_FILE, false /* Don't try to load it */); } -switch($sOperation) +try { - case 'step1': - $oP->log("Info - ========= Wizard step 1 ========"); - DisplayStep1($oP); - break; - - case 'step2': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 2 ========"); - $sDBServer = Utils::ReadParam('db_server'); - $sDBUser = Utils::ReadParam('db_user'); - $sDBPwd = Utils::ReadParam('db_pwd'); - DisplayStep2($oP, $oConfig, $sDBServer, $sDBUser, $sDBPwd); - break; - - case 'step3': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 3 ========"); - $sDBName = Utils::ReadParam('db_name'); - if (empty($sDBName)) + switch($sOperation) { - $sDBName = Utils::ReadParam('new_db_name'); - } - $sDBPrefix = Utils::ReadParam('db_prefix'); - DisplayStep3($oP, $oConfig, $sDBName, $sDBPrefix); - break; - - case 'step4': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 4 ========"); - $sAdminUser = Utils::ReadParam('auth_user'); - $sAdminPwd = Utils::ReadParam('auth_pwd'); - DisplayStep4($oP, $oConfig, $sAdminUser, $sAdminPwd); - break; - - case 'step5': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 5 ========"); - $sAdminUser = Utils::ReadParam('auth_user'); - $sAdminPwd = Utils::ReadParam('auth_pwd'); - DisplayStep5($oP, $oConfig, $sAdminUser, $sAdminPwd); - break; - - default: - $oP->error("Error: unsupported operation '$sOperation'"); + case 'step1': + $oP->log("Info - ========= Wizard step 1 ========"); + DisplayStep1($oP); + break; + + case 'step2': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 2 ========"); + $sDBServer = Utils::ReadParam('db_server'); + $sDBUser = Utils::ReadParam('db_user'); + $sDBPwd = Utils::ReadParam('db_pwd'); + DisplayStep2($oP, $oConfig, $sDBServer, $sDBUser, $sDBPwd); + break; + case 'step3': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 3 ========"); + $sDBName = Utils::ReadParam('db_name'); + if (empty($sDBName)) + { + $sDBName = Utils::ReadParam('new_db_name'); + } + $sDBPrefix = Utils::ReadParam('db_prefix'); + DisplayStep3($oP, $oConfig, $sDBName, $sDBPrefix); + break; + + case 'step4': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 4 ========"); + $sAdminUser = Utils::ReadParam('auth_user'); + $sAdminPwd = Utils::ReadParam('auth_pwd'); + DisplayStep4($oP, $oConfig, $sAdminUser, $sAdminPwd); + break; + + case 'step5': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 5 ========"); + $sAdminUser = Utils::ReadParam('auth_user'); + $sAdminPwd = Utils::ReadParam('auth_pwd'); + DisplayStep5($oP, $oConfig, $sAdminUser, $sAdminPwd); + break; + + default: + $oP->error("Error: unsupported operation '$sOperation'"); + + } +} +catch(Exception $e) +{ + $oP->error("Error: '".$e->getMessage()."'"); +} +catch(CoreException $e) +{ + $oP->error("Error: '".$e->getHtmlDesc()."'"); } $oP->output(); ?> From ef27d068effd513225787b6f30f22720260d3e07 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 22 Aug 2009 09:49:13 +0000 Subject: [PATCH 022/970] Do NOT use PHP short open tags for a better compatibility SVN:trunk[97] --- core/oql/oqlinterpreter.class.inc.php | 2 +- core/oql/oqlquery.class.inc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index ac72a12d9a..ef55d03093 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -1,4 +1,4 @@ - Date: Sat, 22 Aug 2009 09:49:44 +0000 Subject: [PATCH 023/970] Do NOT use PHP short open tags for a better compatibility SVN:trunk[98] --- application/startup.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/startup.inc.php b/application/startup.inc.php index 83ea90c2a8..7f3448f30a 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -1,4 +1,4 @@ - Date: Sat, 22 Aug 2009 09:52:12 +0000 Subject: [PATCH 024/970] - Do NOT rely on PHP short open tags, for a better compatibility - In case of exception, display the whole error string even if it contains HTML tags, sometimes ugly but sometimes VERY useful SVN:trunk[99] --- core/config.class.inc.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index c18c716ffb..ca2d4a5e9d 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -70,8 +70,7 @@ class Config try { ob_start(); - $sCode = str_replace('<'.'?php','<'.'?', $sConfigCode); - eval('?'.'>'.trim($sCode).'<'.'?'); + eval('?'.'>'.trim($sConfigCode)); $sNoise = trim(ob_get_contents()); ob_end_clean(); } @@ -84,7 +83,7 @@ class Config if (strlen($sNoise) > 0) { // Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack) - throw new ConfigException('Syntax error in configuration file', array('file' => $sConfigFile, 'error' => $sNoise)); + throw new ConfigException('Syntax error in configuration file', array('file' => $sConfigFile, 'error' => ''.htmlentities($sNoise).'')); } if (!isset($MySettings) || !is_array($MySettings)) @@ -235,7 +234,7 @@ class Config fwrite($hFile, "\$MySettings = array(\n"); fwrite($hFile, "\t'db_host' => '{$this->m_sDBHost}',\n"); fwrite($hFile, "\t'db_user' => '{$this->m_sDBUser}',\n"); - fwrite($hFile, "\t'db_pwd' => '{$this->m_sDBPwd}',\n"); + fwrite($hFile, "\t'db_pwd' => '".addslashes($this->m_sDBPwd)."',\n"); fwrite($hFile, "\t'db_name' => '{$this->m_sDBName}',\n"); fwrite($hFile, "\t'db_subname' => '{$this->m_sDBSubname}',\n"); fwrite($hFile, ");\n"); From bed32711d69a634d8df9987fcd97157bdd112b4b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 24 Aug 2009 16:40:11 +0000 Subject: [PATCH 025/970] Improved the data load during the setup: - Sample data are clearly separated from the structure data (inc. menus and user profiles) - Both categories of data are enumerated from specific folders SVN:trunk[100] --- setup/data/export.cmd | 8 -- .../{menus.xml => data/structure/1.menus.xml} | 0 .../10.dimensions.xml} | 0 .../11.profiles.xml} | 0 .../12.classprojection.xml} | 0 .../13.profileprojection.xml} | 0 .../14.actiongrant.xml} | 0 .../15.attributegrant.xml} | 0 .../16.stimulusgrant.xml} | 0 setup/{ => data/structure}/export_menus.cmd | 5 +- setup/data/structure/export_profiles.cmd | 13 +++ setup/index.php | 84 ++++++++++++------- setup/setup.js | 9 +- 13 files changed, 72 insertions(+), 47 deletions(-) rename setup/{menus.xml => data/structure/1.menus.xml} (100%) rename setup/data/{23.dimensions.xml => structure/10.dimensions.xml} (100%) rename setup/data/{24.profiles.xml => structure/11.profiles.xml} (100%) rename setup/data/{25.classprojection.xml => structure/12.classprojection.xml} (100%) rename setup/data/{26.profileprojection.xml => structure/13.profileprojection.xml} (100%) rename setup/data/{27.actiongrant.xml => structure/14.actiongrant.xml} (100%) rename setup/data/{28.attributegrant.xml => structure/15.attributegrant.xml} (100%) rename setup/data/{29.stimulusgrant.xml => structure/16.stimulusgrant.xml} (100%) rename setup/{ => data/structure}/export_menus.cmd (90%) create mode 100644 setup/data/structure/export_profiles.cmd diff --git a/setup/data/export.cmd b/setup/data/export.cmd index 923d30177f..d7200a3154 100644 --- a/setup/data/export.cmd +++ b/setup/data/export.cmd @@ -26,11 +26,3 @@ wget --output-document=19.infracontracts.xml --post-data="auth_user=%USER%&auth_ wget --output-document=20.contactcontracts.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkContactContract&format=xml" wget --output-document=21.auditcategories.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT AuditCategory&format=xml" wget --output-document=22.auditrules.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT AuditRule&format=xml" -wget --output-document=23.dimensions.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_Dimensions&format=xml" -wget --output-document=24.profiles.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_Profiles&format=xml" -wget --output-document=25.classprojection.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ClassProjection&format=xml" -wget --output-document=26.profileprojection.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ProfileProjection&format=xml" -wget --output-document=27.actiongrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ActionGrant&format=xml" -wget --output-document=28.attributegrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_AttributeGrant&format=xml" -wget --output-document=29.stimulusgrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_StimulusGrant&format=xml" -pause diff --git a/setup/menus.xml b/setup/data/structure/1.menus.xml similarity index 100% rename from setup/menus.xml rename to setup/data/structure/1.menus.xml diff --git a/setup/data/23.dimensions.xml b/setup/data/structure/10.dimensions.xml similarity index 100% rename from setup/data/23.dimensions.xml rename to setup/data/structure/10.dimensions.xml diff --git a/setup/data/24.profiles.xml b/setup/data/structure/11.profiles.xml similarity index 100% rename from setup/data/24.profiles.xml rename to setup/data/structure/11.profiles.xml diff --git a/setup/data/25.classprojection.xml b/setup/data/structure/12.classprojection.xml similarity index 100% rename from setup/data/25.classprojection.xml rename to setup/data/structure/12.classprojection.xml diff --git a/setup/data/26.profileprojection.xml b/setup/data/structure/13.profileprojection.xml similarity index 100% rename from setup/data/26.profileprojection.xml rename to setup/data/structure/13.profileprojection.xml diff --git a/setup/data/27.actiongrant.xml b/setup/data/structure/14.actiongrant.xml similarity index 100% rename from setup/data/27.actiongrant.xml rename to setup/data/structure/14.actiongrant.xml diff --git a/setup/data/28.attributegrant.xml b/setup/data/structure/15.attributegrant.xml similarity index 100% rename from setup/data/28.attributegrant.xml rename to setup/data/structure/15.attributegrant.xml diff --git a/setup/data/29.stimulusgrant.xml b/setup/data/structure/16.stimulusgrant.xml similarity index 100% rename from setup/data/29.stimulusgrant.xml rename to setup/data/structure/16.stimulusgrant.xml diff --git a/setup/export_menus.cmd b/setup/data/structure/export_menus.cmd similarity index 90% rename from setup/export_menus.cmd rename to setup/data/structure/export_menus.cmd index e7cc514aff..2370101e8c 100644 --- a/setup/export_menus.cmd +++ b/setup/data/structure/export_menus.cmd @@ -1,6 +1,7 @@ SET WEBROOT=http://localhost:81 -SET USER=Erwan -SET PWD=Taloc +SET USER=admin +SET PWD=admin + REM The order (numbering) of the files is important since REM it dictates the order to import them back wget --output-document=1.menus.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%WEBROOT%/pages/export.php?expression=SELECT menuNode WHERE type%%3D%%27application%%27&format=xml" diff --git a/setup/data/structure/export_profiles.cmd b/setup/data/structure/export_profiles.cmd new file mode 100644 index 0000000000..3d2ea0aecc --- /dev/null +++ b/setup/data/structure/export_profiles.cmd @@ -0,0 +1,13 @@ +SET WEBROOT=http://localhost:81 +SET USER=admin +SET PWD=admin + +REM The order (numbering) of the files is important since +REM it dictates the order to import them back +wget --output-document=10.dimensions.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_Dimensions&format=xml" +wget --output-document=11.profiles.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_Profiles&format=xml" +wget --output-document=12.classprojection.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ClassProjection&format=xml" +wget --output-document=13.profileprojection.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ProfileProjection&format=xml" +wget --output-document=14.actiongrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_ActionGrant&format=xml" +wget --output-document=15.attributegrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_AttributeGrant&format=xml" +wget --output-document=16.stimulusgrant.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT URP_StimulusGrant&format=xml" diff --git a/setup/index.php b/setup/index.php index 12b255c38f..0696dfd18b 100644 --- a/setup/index.php +++ b/setup/index.php @@ -8,7 +8,8 @@ require_once('../core/cmdbsource.class.inc.php'); require_once('./setuppage.class.inc.php'); define('TMP_CONFIG_FILE', '../tmp-config-itop.php'); define('FINAL_CONFIG_FILE', '../config-itop.php'); -define('SETUP_DATA_DIR', './data'); +define('SETUP_STRUCTURE_DATA_DIR', './data/structure'); +define('SETUP_SAMPLE_DATA_DIR', './data'); define('PHP_MIN_VERSION', '5.2.0'); define('MYSQL_MIN_VERSION', '5.0.0'); @@ -173,45 +174,66 @@ function CreateAdminAccount(setup_web_page $oP, Config $oConfig, $sAdminUser, $s } } +//aFilesToLoad[aFilesToLoad.length] = './menus.xml'; // First load the menus + +function ListDataFiles($sDirectory, setup_web_page $oP) +{ + $aFilesToLoad = array(); + if ($hDir = @opendir($sDirectory)) + { + // This is the correct way to loop over the directory. (according to the documentation) + while (($sFile = readdir($hDir)) !== false) + { + $sExtension = pathinfo($sFile, PATHINFO_EXTENSION ); + if (strcasecmp($sExtension, 'xml') == 0) + { + $aFilesToLoad[] = $sDirectory.'/'.$sFile; + } + } + closedir($hDir); + // Load order is important we expect the files to be ordered + // like numbered 1.Organizations.xml 2.Locations.xml , etc. + asort($aFilesToLoad); + } + else + { + $oP->error("Data directory (".$sDirectory.") not found or not readable."); + } + return $aFilesToLoad; +} + + /** * Scans the ./data directory for XML files and output them as a Javascript array */ function PopulateDataFilesList(setup_web_page $oP) { - if ($hDir = @opendir(SETUP_DATA_DIR)) + + $oP->add("\n"); + $oP->add("aFilesToLoad[aFilesToLoad.length] = '$sFile';\n"); } - else + + // Sample data - loaded IIF wished by the user + // + $oP->add("if (($(\"#sample_data:checked\").length == 1))"); + $oP->add("{"); + $aSampleDataFiles = ListDataFiles(SETUP_SAMPLE_DATA_DIR, $oP); + foreach($aSampleDataFiles as $sFile) { - $oP->error("Data directory (".SETUP_DATA_DIR.") no found or not readable."); + $oP->add("aFilesToLoad[aFilesToLoad.length] = '$sFile';\n"); } + $oP->add("}\n"); + + $oP->add("}\n"); + $oP->add("\n"); } /** diff --git a/setup/setup.js b/setup/setup.js index f407a199d2..8c9e460089 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -89,12 +89,9 @@ var aFilesToLoad = new Array(); function DoLoadDataAsynchronous() { - // Check if sample data must be loaded, or just the menus - aFilesToLoad[aFilesToLoad.length] = './menus.xml'; // First load the menus - if (($("#sample_data:checked").length == 1)) - { - PopulateDataFilesList(); // Function created in PHP to get the list of XML files on the server - } + // The array aFilesToLoad is populated by this function dynamically written on the server + PopulateDataFilesList(); + $('#setup').block({message: '

    Loading data...

    0%

    '}); $('#progress').progression( {Current:0, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); LoadNextDataFile('', ''); From 802e06888a45fed6218af303e51f22ec916eb870 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 25 Aug 2009 13:52:13 +0000 Subject: [PATCH 026/970] New helper function IsModified SVN:trunk[101] --- core/dbobject.class.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index c9d69a0b90..9a52b562d5 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -533,6 +533,13 @@ abstract class DBObject return $this->ListChangedValues($this->m_aCurrValues); } + // Tells whether or not an object was modified + public function IsModified() + { + $aChanges = $this->ListChanges(); + return (count($aChanges) != 0); + } + // used both by insert/update private function DBWriteLinks() { From 78e5b79893f5031dbd88162dc255e99fa4b98160 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 25 Aug 2009 19:48:37 +0000 Subject: [PATCH 027/970] Fixed the disappearance of tabs when doing a 'reload' SVN:trunk[102] --- application/itopwebpage.class.inc.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 0b69f6669f..b606a68a75 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -91,6 +91,7 @@ class iTopWebPage extends nice_web_page $(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables $(".date-pick").datePicker( {clickInput: false, createButton: true, startDate: '2000-01-01'} ); // Date picker $('#ModalDlg').jqm({ajax: '@href', trigger: 'a.jqmTrigger', overlay:70, modal:true, toTop:true}); // jqModal Window + //$('.display_block').draggable(); // make the blocks draggable EOF ); @@ -198,6 +199,20 @@ EOF echo "\n"; echo "\n"; echo "{$this->s_title}\n"; + // Stylesheets MUST be loaded before any scripts otherwise + // jQuery scripts may face some spurious problems (like failing on a 'reload') + foreach($this->a_linked_stylesheets as $a_stylesheet) + { + if ($a_stylesheet['condition'] != "") + { + echo "\n"; + } + } foreach($this->a_linked_scripts as $s_script) { echo "\n"; @@ -215,18 +230,6 @@ EOF } echo "\n"; } - foreach($this->a_linked_stylesheets as $a_stylesheet) - { - if ($a_stylesheet['condition'] != "") - { - echo "\n"; - } - } if (count($this->a_styles)>0) { From 3c3a1967b6995a6ed03613c0c854461fc81f3431 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 26 Aug 2009 21:32:32 +0000 Subject: [PATCH 028/970] Fixed a missing display template SVN:trunk[103] --- business/incident.business.php | 1 + 1 file changed, 1 insertion(+) diff --git a/business/incident.business.php b/business/incident.business.php index c41d7dd6bf..5a130959a3 100644 --- a/business/incident.business.php +++ b/business/incident.business.php @@ -186,6 +186,7 @@ class lnkRelatedTicket extends cmdbAbstractObject "db_table" => "related_ticket", "db_key_field" => "link_id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); MetaModel::Init_AddAttribute(new AttributeExternalKey("rel_ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Related Ticket id", "description"=>"The related ticket", "allowed_values"=>null, "sql"=>"rel_ticket_id", "is_null_allowed"=>false, "depends_on"=>array()))); From e0b307ef488865517308c1d53f3fa9883c13b08c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 26 Aug 2009 21:52:34 +0000 Subject: [PATCH 029/970] - New user interface to manage n-n links SVN:trunk[104] --- application/application.inc.php | 4 + application/cmdbabstract.class.inc.php | 73 ++++- application/displayblock.class.inc.php | 89 ++++-- application/template.class.inc.php | 19 +- application/ui.linkswidget.class.inc.php | 6 +- application/uilinkswizard.class.inc.php | 380 +++++++++++++++++++++++ business/templates/server.html | 4 +- business/templates/team.html | 2 +- business/templates/ticket.html | 2 +- pages/UI.php | 119 ++++++- pages/ajax.render.php | 34 ++ 11 files changed, 688 insertions(+), 44 deletions(-) create mode 100644 application/uilinkswizard.class.inc.php diff --git a/application/application.inc.php b/application/application.inc.php index 86f3813967..d15464c19e 100644 --- a/application/application.inc.php +++ b/application/application.inc.php @@ -9,4 +9,8 @@ require_once('../application/audit.category.class.inc.php'); require_once('../application/audit.rule.class.inc.php'); //require_once('../application/menunode.class.inc.php'); require_once('../application/utils.inc.php'); + +class ApplicationException extends CoreException +{ +} ?> diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index f95a03eab3..59dd5a48b2 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -51,6 +51,12 @@ abstract class cmdbAbstractObject extends CMDBObject $sHint = htmlentities("$sObjClass::$sObjKey"); return "GetForLink()."\" title=\"$sHint\">$sLabel"; } + + public function GetHyperlink() + { + $aAvailableFields[MetaModel::GetNameAttributeCode(get_class($this))] = $this->GetName(); + return $this->MakeHyperLink(get_class($this), $this->GetKey(), $aAvailableFields); + } public function GetDisplayValue($sAttCode) { @@ -213,13 +219,37 @@ abstract class cmdbAbstractObject extends CMDBObject // Comment by Rom: this helper may be used to display objects of class DBObject // -> I am using this to display the changes history - public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true) + public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false, $iObjectId = 0) { - $oPage->add(self::GetDisplaySet($oPage, $oSet, $sLinkageAttribute, $bDisplayMenu)); + $oPage->add(self::GetDisplaySet($oPage, $oSet, array( 'link_attr' => $sLinkageAttribute, 'object_id' => $iObjectId, 'menu' => $bDisplayMenu, 'selection_mode' => $bSelectMode))); } - public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true) + //public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false) + public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { + static $iListId = 0; + $iListId++; + + // Initialize and check the parameters + $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; + $iLinkedObjectId = isset($aExtraParams['object_id']) ? $aExtraParams['object_id'] : 0; + $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; + if (!empty($sLinkageAttribute)) + { + if($iLinkedObjectId == 0) + { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException("Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template."); + } + if($sTargetAttr == '') + { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException("Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template."); + } + } + $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; + $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; + $sHtml = ''; $oAppContext = new ApplicationContext(); $sClassName = $oSet->GetFilter()->GetClass(); @@ -260,6 +290,10 @@ abstract class cmdbAbstractObject extends CMDBObject } foreach($aList as $sAttCode) { + if ($bSelectMode) + { + $aAttribs['form::select'] = array('label' => "", 'description' => 'Select / Deselect All'); + } $aAttribs['key'] = array('label' => '', 'description' => 'Click to display'); $aAttribs[$sAttCode] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode)); } @@ -267,6 +301,11 @@ abstract class cmdbAbstractObject extends CMDBObject $oSet->Seek(0); while ($oObj = $oSet->Fetch()) { + $aRow['key'] = $oObj->GetKey(); + if ($bSelectMode) + { + $aRow['form::select'] = "GetKey()."\">"; + } $aRow['key'] = $oObj->GetKey(); foreach($aList as $sAttCode) { @@ -280,8 +319,14 @@ abstract class cmdbAbstractObject extends CMDBObject if ($bDisplayMenu) { $sColspan = 'colspan="2"'; + $aMenuExtraParams = array(); + if (!empty($sLinkageAttribute)) + { + //$aMenuExtraParams['linkage'] = $sLinkageAttribute; + $aMenuExtraParams = $aExtraParams; + } $sHtml .= ' '.$oSet->Count().' object(s)'; - $sHtml .= $oMenuBlock->GetRenderContent($oPage, $sLinkageAttribute); + $sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams); $sHtml .= ''; } $sHtml .= ""; @@ -400,7 +445,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sHtml .= "
    \n"; $sHtml .= "

    Search for ".MetaModel::GetName($sClassName)." Objects

    \n"; $oUnlimitedFilter = new DBObjectSearch($sClassName); - $sHtml .= "
    \n"; + $sHtml .= "\n"; $index = 0; $sHtml .= "\n"; $aFilterCriteria = $oSet->GetFilter()->GetCriteria(); @@ -488,7 +533,7 @@ abstract class cmdbAbstractObject extends CMDBObject // OQL query builder $sHtml .= "
    \n"; $sHtml .= "

    OQL Query Builder

    \n"; - $sHtml .= "
    \n"; + $sHtml .= "
    \n"; $sHtml .= "\n"); + foreach($aConfig as $sName=>$void) + { + $oP->add("\n"); + } + $oP->add("\n"); + } + + public function DisplayAddForm(web_page $oP, UserContext $oContext) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); + $sTargetClass = $oAttDef->GetTargetClass(); + $oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); + $oP->add("
    \n"); + $oP->add("
    \n"); + $oP->add("

    Add ".MetaModel::GetName($this->m_sLinkedClass)."s to ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetHyperlink()."

    \n"); + $oP->add("
    \n"); + + $oFilter = $oContext->NewFilter($this->m_sLinkedClass); + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 'SearchFormToAdd', array('open' => true)); + $oP->Add("
    \n"); + $oP->Add("
    \n"); + $oP->Add("

    Use the search form above to search for objects to be added.

    \n"); + $oP->Add("
    \n"); + $oP->add("  "); + $oP->Add("
    \n"); + $oP->Add("\n"); + $oP->add_ready_script("$('div#SearchFormToAdd form').attr('onSubmit', 'var the_form = this; return SearchObjectsToAdd(the_form.id);');"); + } + + public function SearchObjectsToAdd(web_page $oP, UserContext $oContext) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); + + $oFilter = $oContext->NewFilter($this->m_sLinkedClass); + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true)); // Don't display the 'Actions' menu on the results + } + + public function DoAddObjects(web_page $oP, UserContext $oContext, $aLinkedObjectIds = array()) + { + //$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); + //$sTargetClass = $oAttDef->GetTargetClass(); + //$oP->Add("\n"); // Just to make sure it's not empty + $aTable = array(); + foreach($aLinkedObjectIds as $iObjectId) + { + $oLinkedObj = $oContext->GetObject($this->m_sLinkedClass, $iObjectId); + if (is_object($oLinkedObj)) + { + $aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids + $this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId); + } + else + { + echo "Object: $sTargetClass - Id: $iObjectId not found
    \n"; + } + } + //var_dump($aTable); + //$oP->Add("\n"); // Just to make sure it's not empty + } +} +?> diff --git a/business/templates/server.html b/business/templates/server.html index 8f72fd8787..1989ac9359 100644 --- a/business/templates/server.html +++ b/business/templates/server.html @@ -16,10 +16,10 @@ SELECT bizInterface WHERE device_id = $pkey$ - lnkContactRealObject: object_id = $pkey$ + lnkContactRealObject: object_id = $pkey$ - bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + lnkInfraTicket: infra_id = $pkey$ bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) diff --git a/business/templates/team.html b/business/templates/team.html index 12537bdd5e..a9601ee035 100644 --- a/business/templates/team.html +++ b/business/templates/team.html @@ -7,7 +7,7 @@ $class$: pkey = $pkey$ - bizContact: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$) + SELECT lnkContactRealObject WHERE contact_id=$pkey$ bizTeam: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$) diff --git a/business/templates/ticket.html b/business/templates/ticket.html index efce97e2de..b1c0ae548b 100644 --- a/business/templates/ticket.html +++ b/business/templates/ticket.html @@ -10,7 +10,7 @@ lnkInfraTicket: ticket_id = $pkey$ - lnkRelatedTicket: ticket_id = $pkey$ + lnkRelatedTicket: ticket_id = $pkey$ lnkContactTicket: ticket_id = $pkey$ diff --git a/pages/UI.php b/pages/UI.php index 45631dd69f..338fdf406a 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -335,7 +335,7 @@ switch($operation) } if (!is_object($oObj)) { - // new object or or that can't be retrieved (corrupted id or object not allowed to this user) + // new object or that can't be retrieved (corrupted id or object not allowed to this user) $id = ''; $oObj = MetaModel::NewObject($sClass); } @@ -701,6 +701,123 @@ switch($operation) } break; + case 'modify_links': + $sClass = utils::ReadParam('class', ''); + $sLinkAttr = utils::ReadParam('link_attr', ''); + $sTargetClass = utils::ReadParam('target_class', ''); + $id = utils::ReadParam('id', ''); + $bAddObjects = utils::ReadParam('addObjects', false); + if ( empty($sClass) || empty($id) || empty($sLinkAttr) || empty($sTargetClass)) // TO DO: check that the class name is valid ! + { + $oP->set_title("iTop - Error"); + $oP->add("

    4 parameters are mandatory for this operation: class, id, target_class and link_attr.

    \n"); + } + else + { + require_once('../application/uilinkswizard.class.inc.php'); + $oWizard = new UILinksWizard($sClass, $sLinkAttr, $id, $sTargetClass); + $oWizard->Display($oP, $oContext, array('StartWithAdd' => $bAddObjects)); + } + break; + + case 'do_modify_links': + $aLinks = utils::ReadParam('linkId', array(), 'post'); + $sLinksToRemove = trim(utils::ReadParam('linksToRemove', '', 'post')); + $aLinksToRemove = array(); + if (!empty($sLinksToRemove)) + { + $aLinksToRemove = explode(' ', trim($sLinksToRemove)); + } + $sClass = utils::ReadParam('class', '', 'post'); + $sLinkageAtt = utils::ReadParam('linkage', '', 'post'); + $iObjectId = utils::ReadParam('object_id', '', 'post'); + $sLinkingAttCode = utils::ReadParam('linking_attcode', '', 'post'); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + + // Delete links that are to be deleted + foreach($aLinksToRemove as $iLinkId) + { + if ($iLinkId > 0) // Negative IDs are objects that were not even created + { + $oLink = $oContext->GetObject($sClass, $iLinkId); + $oLink->DBDeleteTracked($oMyChange); + } + } + + $aEditableFields = array(); + $aData = array(); + foreach(MetaModel::GetAttributesList($sClass) as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField())) + { + $aEditableFields[] = $sAttCode; + $aData[$sAttCode] = utils::ReadParam('attr_'.$sAttCode, array(), 'post'); + } + } + + // Update existing links or create new links + foreach($aLinks as $iLinkId) + { + if ($iLinkId > 0) + { + // This is an existing link to be modified + $oLink = $oContext->GetObject($sClass, $iLinkId); + + // Update all the attributes of the link + foreach($aEditableFields as $sAttCode) + { + $value = $aData[$sAttCode][$iLinkId]; + $oLink->Set($sAttCode, $value); + } + if ($oLink->IsModified()) + { + $oLink->DBUpdateTracked($oMyChange); + } + //echo "Updated link:
    \n"; + //var_dump($oLink); + } + else + { + // A new link must be created + $oLink = MetaModel::NewObject($sClass); + $oLinkedObjectId = -$iLinkId; + // Set all the attributes of the link + foreach($aEditableFields as $sAttCode) + { + $value = $aData[$sAttCode][$iLinkId]; + $oLink->Set($sAttCode, $value); + } + // And the two external keys + $oLink->Set($sLinkageAtt, $iObjectId); + $oLink->Set($sLinkingAttCode, $oLinkedObjectId); + // then save it + //echo "Created link:
    \n"; + //var_dump($oLink); + $oLink->DBInsertTracked($oMyChange); + } + } + // Display again the details of the linked object + $oAttDef = MetaModel::GetAttributeDef($sClass, $sLinkageAtt); + $sTargetClass = $oAttDef->GetTargetClass(); + $oObj = $oContext->GetObject($sTargetClass, $iObjectId); + + $oSearch = $oContext->NewFilter(get_class($oObj)); + $oBlock = new DisplayBlock($oSearch, 'search', false); + $oBlock->Display($oP, 0); + $oObj->DisplayDetails($oP); + break; default: $oActiveNode->RenderContent($oP, $oAppContext->GetAsHash()); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 35af6a8589..a90059eebc 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -32,6 +32,40 @@ $sStyle = utils::ReadParam('style', 'list'); switch($operation) { + case 'addObjects': + require_once('../application/uilinkswizard.class.inc.php'); + + $sClass = utils::ReadParam('class', '', 'get'); + $sLinkedClass = utils::ReadParam('linkedClass', '', 'get'); + $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); + $iObjectId = utils::ReadParam('objectId', '', 'get'); + $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); + $oLinksWizard->DisplayAddForm($oPage, $oContext); + break; + + case 'searchObjectsToAdd': + require_once('../application/uilinkswizard.class.inc.php'); + + $sClass = utils::ReadParam('class', '', 'get'); + $sLinkedClass = utils::ReadParam('linkedClass', '', 'get'); + $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); + $iObjectId = utils::ReadParam('objectId', '', 'get'); + $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); + $oLinksWizard->SearchObjectsToAdd($oPage, $oContext); + break; + + case 'doAddObjects': + require_once('../application/uilinkswizard.class.inc.php'); + + $sClass = utils::ReadParam('class', '', 'get'); + $sLinkedClass = utils::ReadParam('linkedClass', '', 'get'); + $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); + $iObjectId = utils::ReadParam('objectId', '', 'get'); + $aLinkedObjectIds = utils::ReadParam('selectObject', array(), 'get'); + $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); + $oLinksWizard->DoAddObjects($oPage, $oContext, $aLinkedObjectIds); + break; + case 'wizard_helper_preview': $sJson = utils::ReadParam('json_obj', '', 'post'); $oWizardHelper = WizardHelper::FromJSON($sJson); From e3a402677f23c40299b238bf9a7aed2003255a79 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 27 Aug 2009 12:37:21 +0000 Subject: [PATCH 030/970] Workaround for the missing REQUEST_URI on IIS SVN:trunk[105] --- application/displayblock.class.inc.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 8eebeeae07..8d131427dc 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -775,7 +775,22 @@ class MenuBlock extends DisplayBlock { $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; } - $sPath = $_SERVER['REQUEST_URI']; + // $_SERVER['REQUEST_URI'] is empty when running on IIS + // Let's use Ivan Tcholakov's fix (found on www.dokeos.com) + if (!empty($_SERVER['REQUEST_URI'])) + { + $sPath = $_SERVER['REQUEST_URI']; + } + else + { + $sPath = $_SERVER['SCRIPT_NAME']; + if (!empty($_SERVER['QUERY_STRING'])) + { + $sPath .= '?'.$_SERVER['QUERY_STRING']; + } + $_SERVER['REQUEST_URI'] = $sPath; + } + $sPath = $_SERVER['REQUEST_URI']; $sUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}"; return $sUrl; From e345b516c434e9b0efea25a90c9299d89ff32c06 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 27 Aug 2009 13:52:31 +0000 Subject: [PATCH 031/970] Fixed bug provoking a warning unseen so far but it was causing a fatal error during the installation, with IIS as web server SVN:trunk[106] --- core/cmdbsource.class.inc.php | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 2f76df24f4..ea06e112dd 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -168,23 +168,10 @@ class CMDBSource public static function Query($sSQLQuery) { // Add info into the query as a comment, for easier error tracking - // - //if ($user_contact) $aTraceInf['userID'] = $user_contact->get_key(); - //$aTraceInf['file'] = __FILE__; - if ($_SERVER['REQUEST_URI']) $aTraceInf['requestURI'] = $_SERVER['REQUEST_URI']; - $i = 0; - foreach(debug_backtrace() as $aCallData) - { - $sClass = key_exists("class", $aCallData) ? $aCallData["class"]."::" : ""; - //if ($aCallData['function'] !== 'mysql_simple_query' AND $sClass !== 'r2_set::') - //{ - if ($i == 3) break; - $aTraceInf['function'.$i] = $sClass.$aCallData["function"]." on line ".$aCallData['line']; - $i++; - //} - } // disabled until we need it really! - // $sSQLQuery = $sSQLQuery.MyHelpers::MakeSQLComment($aTraceInf); + // + //$aTraceInf['file'] = __FILE__; + // $sSQLQuery .= MyHelpers::MakeSQLComment($aTraceInf); $mu_t1 = MyHelpers::getmicrotime(); $result = mysql_query($sSQLQuery, self::$m_resDBLink); From 0900b5f6de388be1774d28beffbaa98500ed1c95 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 27 Aug 2009 13:56:57 +0000 Subject: [PATCH 032/970] Changed images paths into relative path (../images where it was still /images) SVN:trunk[107] --- application/itopwebpage.class.inc.php | 2 +- application/itopwizardwebpage.class.inc.php | 2 +- css/blue_green.css | 22 ++++++++++----------- css/jquery.treeview.css | 16 +++++++-------- css/light-grey.css | 8 ++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index b606a68a75..b315d17f43 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -247,7 +247,7 @@ EOF // Display the header echo "
    \n"; echo "
    iTop
    \n"; - //echo "
    \n"; + //echo "
    \n"; $sText = Utils::ReadParam('text', ''); $sOnClick = ""; if (empty($sText)) diff --git a/application/itopwizardwebpage.class.inc.php b/application/itopwizardwebpage.class.inc.php index aadaf30c7d..ba5655ae2d 100644 --- a/application/itopwizardwebpage.class.inc.php +++ b/application/itopwizardwebpage.class.inc.php @@ -24,7 +24,7 @@ class iTopWizardWebPage extends iTopWebPage $sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep'; $aSteps[] = "
    $sStepTitle
    "; } - $sWizardHeader = "

    {$this->s_title}

    \n".implode("
    ", $aSteps)."
    \n"; + $sWizardHeader = "

    {$this->s_title}

    \n".implode("
    ", $aSteps)."
    \n"; $this->s_content = "$sWizardHeader
    ".$this->s_content."
    "; parent::output(); } diff --git a/css/blue_green.css b/css/blue_green.css index 1e51162c33..18585c3cd0 100644 --- a/css/blue_green.css +++ b/css/blue_green.css @@ -151,15 +151,15 @@ ul.dir li { } .treeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; } -.treeview .collapsable { background-image: url(/images/tv-collapsable.gif); } -.treeview .expandable { background-image: url(/images/tv-expandable.gif); } -.treeview .last { background-image: url(/images/tv-item-last.gif); } -.treeview .lastCollapsable { background-image: url(/images/tv-collapsable-last.gif); } -.treeview .lastExpandable { background-image: url(/images/tv-expandable-last.gif); } +.treeview .collapsable { background-image: url(../images/tv-collapsable.gif); } +.treeview .expandable { background-image: url(../images/tv-expandable.gif); } +.treeview .last { background-image: url(../images/tv-item-last.gif); } +.treeview .lastCollapsable { background-image: url(../images/tv-collapsable-last.gif); } +.treeview .lastExpandable { background-image: url(../images/tv-expandable-last.gif); } -#Header { padding: 0; background:#ccc url(/images/bandeau2.gif) repeat-x center;} +#Header { padding: 0; background:#ccc url(../images/bandeau2.gif) repeat-x center;} div.iTopLogo { - background:url(/images/iTop.gif) no-repeat center; + background:url(../images/iTop.gif) no-repeat center; width:100px; height:56px; } @@ -208,15 +208,15 @@ div.iTopLogo span { #MySplitter .vsplitbar { width: 7px; height: 50px; - background: #68a url(/images/vgrabber2.gif) no-repeat center; + background: #68a url(../images/vgrabber2.gif) no-repeat center; } #MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover { - background: #68a url(/images/vgrabber2_active.gif) no-repeat center; + background: #68a url(../images/vgrabber2_active.gif) no-repeat center; } #MySplitter .hsplitbar { height: 8px; - background: #68a url(/images/hgrabber2.gif) no-repeat center; + background: #68a url(../images/hgrabber2.gif) no-repeat center; } #MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover { - background: #68a url(/images/hgrabber2_active.gif) no-repeat center; + background: #68a url(../images/hgrabber2_active.gif) no-repeat center; } diff --git a/css/jquery.treeview.css b/css/jquery.treeview.css index 6ae8d70870..dccad6c76b 100644 --- a/css/jquery.treeview.css +++ b/css/jquery.treeview.css @@ -34,14 +34,14 @@ .treeview .hover { color: red; cursor: pointer; } -.treeview li { background: url(/images/tv-item.gif) 0 0 no-repeat; } -.treeview .collapsable { background-image: url(/images/tv-collapsable.gif); } -.treeview .expandable { background-image: url(/images/tv-expandable.gif); } -.treeview .last { background-image: url(/images/tv-item-last.gif); } -.treeview .lastCollapsable { background-image: url(/images/tv-collapsable-last.gif); } -.treeview .lastExpandable { background-image: url(/images/tv-expandable-last.gif); } +.treeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; } +.treeview .collapsable { background-image: url(../images/tv-collapsable.gif); } +.treeview .expandable { background-image: url(../images/tv-expandable.gif); } +.treeview .last { background-image: url(../images/tv-item-last.gif); } +.treeview .lastCollapsable { background-image: url(../images/tv-collapsable-last.gif); } +.treeview .lastExpandable { background-image: url(../images/tv-expandable-last.gif); } .filetree li { padding: 3px 0 1px 16px; } .filetree span.folder, .filetree span.file { padding-left: 16px; display: block; height: 15px; } -.filetree span.folder { background: url(/images/folder.gif) 0 0 no-repeat; } -.filetree span.file { background: url(/images/file.gif) 0 0 no-repeat; } +.filetree span.folder { background: url(../images/folder.gif) 0 0 no-repeat; } +.filetree span.file { background: url(../images/file.gif) 0 0 no-repeat; } diff --git a/css/light-grey.css b/css/light-grey.css index 85717cb889..6bd8331717 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -482,7 +482,7 @@ ul.jd_menu_itop ul li.jd_menu_hover a.jd_menu_hover { } div.wizActiveStep { height: 29px; - background: url(/images/wizActiveStepLeft.gif) no-repeat left; + background: url(../images/wizActiveStepLeft.gif) no-repeat left; color: #d81515; padding-left: 8px; margin-top: 5px; @@ -491,7 +491,7 @@ div.wizActiveStep { } div.wizActiveStep span { height: 29px; - background: url(/images/wizActiveStepRight.gif) no-repeat top right; + background: url(../images/wizActiveStepRight.gif) no-repeat top right; padding-right: 8px; padding-top: 8px; float:left; @@ -499,7 +499,7 @@ div.wizActiveStep span { div.wizStep { height: 29px; - background: url(/images/wizStepLeft.gif) no-repeat left; + background: url(../images/wizStepLeft.gif) no-repeat left; padding-left: 8px; vertical-align: middle; margin-top: 5px; @@ -507,7 +507,7 @@ div.wizStep { } div.wizStep span { height: 29px; - background: url(/images/wizStepRight.gif) no-repeat top right; + background: url(../images/wizStepRight.gif) no-repeat top right; padding-right: 8px; padding-top: 8px; float:left; From 5df39c5837ad33d85864a3e58ba9ed42b8dad299 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 2 Sep 2009 13:18:59 +0000 Subject: [PATCH 033/970] In the UI, the class names have been replaced by the class labels SVN:trunk[108] --- pages/UI.php | 38 +++++++++++++++++++++++--------------- pages/advanced_search.php | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 338fdf406a..c8708213f0 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -49,6 +49,7 @@ switch($operation) { case 'details': $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); $oSearch = new DBObjectSearch($sClass); $oBlock = new DisplayBlock($oSearch, 'search', false); @@ -62,7 +63,7 @@ switch($operation) $oObj = $oContext->GetObject($sClass, $id); if ($oObj != null) { - $oP->set_title("iTop - ".$oObj->GetDisplayName()." - $sClass details"); + $oP->set_title("iTop - ".$oObj->GetDisplayName()." - $sClassLabel details"); $oObj->DisplayDetails($oP); } else @@ -214,6 +215,7 @@ switch($operation) $oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/jquery.blockUI.js"); $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! { @@ -234,8 +236,8 @@ switch($operation) $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); if( ($oObj != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) { - $oP->set_title("iTop - ".$oObj->GetName()." - $sClass modification"); - $oP->add("

    ".$oObj->GetName()." - $sClass modification

    \n"); + $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel modification"); + $oP->add("

    ".$oObj->GetName()." - $sClassLabel modification

    \n"); $oObj->DisplayModifyForm($oP); } else @@ -248,6 +250,7 @@ switch($operation) case 'clone': $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! { @@ -268,8 +271,8 @@ switch($operation) $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); if( ($oObjToClone != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) { - $oP->set_title("iTop - ".$oObjToClone->GetName()." - $sClass clone"); - $oP->add("

    ".$oObjToClone->GetName()." - $sClass clone

    \n"); + $oP->set_title("iTop - ".$oObjToClone->GetName()." - $sClassLabel clone"); + $oP->add("

    ".$oObjToClone->GetName()." - $sClassLabel clone

    \n"); cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObjToClone); } else @@ -361,6 +364,7 @@ switch($operation) case 'apply_modify': $sClass = utils::ReadPostedParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadPostedParam('id', ''); $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! @@ -376,8 +380,8 @@ switch($operation) $oObj = $oContext->GetObject($sClass, $id); if ($oObj != null) { - $oP->set_title("iTop - ".$oObj->GetName()." - $sClass modification"); - $oP->add("

    ".$oObj->GetName()." - $sClass modification

    \n"); + $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel modification"); + $oP->add("

    ".$oObj->GetName()." - $sClassLabel modification

    \n"); $bObjectModified = false; foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) { @@ -411,7 +415,7 @@ switch($operation) } if (!$bObjectModified) { - $oP->p("No modification detected. ".get_class($oObj)." has not been updated.\n"); + $oP->p("No modification detected. ".MetaModel::GetName(get_class($oObj))." has not been updated.\n"); } else if ($oObj->CheckToUpdate()) { @@ -429,7 +433,7 @@ switch($operation) $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); - $oP->p(get_class($oObj)." updated.\n"); + $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); } else { @@ -448,6 +452,7 @@ switch($operation) case 'delete': $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); $oObj = $oContext->GetObject($sClass, $id); $sName = $oObj->GetName(); @@ -464,7 +469,7 @@ switch($operation) $oMyChange->Set("userinfo", $sUserString); $oMyChange->DBInsert(); $oObj->DBDeleteTracked($oMyChange); - $oP->add("

    ".$sName." - $sClass deleted

    \n"); + $oP->add("

    ".$sName." - $sClassLabel deleted

    \n"); break; case 'apply_new': @@ -474,6 +479,7 @@ switch($operation) case 'apply_clone': $sClass = utils::ReadPostedParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); $iCloneId = utils::ReadPostedParam('clone_id', ''); $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if (!utils::IsTransactionValid($sTransactionId)) @@ -507,7 +513,7 @@ switch($operation) } } $oObj->DBCloneTracked($oMyChange); - $oP->add("

    ".$oObj->GetName()." - $sClass created

    \n"); + $oP->add("

    ".$oObj->GetName()." - $sClassLabel created

    \n"); $oObj->DisplayDetails($oP); } @@ -527,6 +533,7 @@ switch($operation) if (is_object($oObj)) { $sClass = get_class($oObj); + $sClassLabel = MetaModel::GetName($sClass); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); if (UserRights::GetUser() != UserRights::GetRealUser()) @@ -540,8 +547,8 @@ switch($operation) $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); - $oP->set_title("iTop - ".$oObj->GetName()." - $sClass created"); - $oP->add("

    ".$oObj->GetName()." - $sClass created

    \n"); + $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel created"); + $oP->add("

    ".$oObj->GetName()." - $sClassLabel created

    \n"); $oObj->DisplayDetails($oP); } } @@ -645,7 +652,7 @@ switch($operation) } else if (!utils::IsTransactionValid($sTransactionId)) { - $oP->p("Error: object has already be updated!\n"); + $oP->p("Error: object has already been updated!\n"); } else { @@ -688,7 +695,7 @@ switch($operation) $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); - $oP->p(get_class($oObj)." updated.\n"); + $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); } $oObj->DisplayDetails($oP); } @@ -822,5 +829,6 @@ switch($operation) default: $oActiveNode->RenderContent($oP, $oAppContext->GetAsHash()); } +////MetaModel::ShowQueryTrace(); $oP->output(); ?> diff --git a/pages/advanced_search.php b/pages/advanced_search.php index e10a2c0956..c5b5fbfb88 100644 --- a/pages/advanced_search.php +++ b/pages/advanced_search.php @@ -151,7 +151,7 @@ function Page2_ConfigFilters($oPage, $oFilter) $oSubFilter = new CMDBSearchFilter($sRemoteClass); } $oPage->add("
    \n"); - //$oPage->add(" ".MetaModel::GetClassLabel($sRemoteClass)." "); + //$oPage->add(" ".MetaModel::GetName($sRemoteClass)." "); $oPage->add(" Linked to '".MetaModel::GetLinkLabel($sLinkClass, $sExtKeyAttCode)."' by "); $oSubFilter = new CMDBSearchFilter($sRemoteClass); $oPage->add($oSubFilter->__DescribeHTML()); From e1be74457abef094063ccbbad262abe080ef9535 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 2 Sep 2009 13:39:20 +0000 Subject: [PATCH 034/970] Improved the behavior when a link class to not have any other attributes than the external keys. Still, it requires some work to have this configuration working. SVN:trunk[109] --- application/ui.linkswidget.class.inc.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 1db8517e53..de602bdd09 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -21,13 +21,14 @@ class UILinksWidget { $sHTMLValue = ''; $sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode); - $aAllowedValues = MetaModel::GetAllowedValues_att($this->m_sClass, $this->m_sAttCode, array(), ''); + $aAllowedValues = MetaModel::GetAllowedValues_att($this->m_sClass, $this->m_sAttCode, array(), ''); $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); $sDefaultState = MetaModel::GetDefaultState($this->m_sClass); + $aAttributes = array(); $sLinkedClass = $oAttDef->GetLinkedClass(); foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef) { @@ -256,6 +257,7 @@ EOF; $sHTML .= "
    \n"; $index = 0; $aAttrsMap = array(); + $aDetails = array(); foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef) { if ($sStateAttCode == $sAttCode) From 2f26ebe54cce6033f09306b9bdde68915ce4f3fb Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 4 Sep 2009 15:22:40 +0000 Subject: [PATCH 035/970] Finalization of the user management by profile (UI to manage the accounts), and some unrelated changes: - Using class labels in the UI - Data model: you may specify a set of allowed values from a query (see caller_id in bizIncident class), still not 100% used in the UI but does not generate any error - Data model: you may specify a password field (AttributePassword replacing AttributeString) - Setup: calling UserRight::Setup() right after calling UserRight::CreateAdministrator() - Setup: administrator account created with "my organization" and a dedicated contact - Menus: optimized the load of std menus (queries written in OQL to get the benefit of the query cache) - Menus: admin tools, seen only by people having the "admin" profile - Object edition: fixed bug with the display of N-N links in the form SVN:trunk[110] --- .../userrights/userrightsmatrix.class.inc.php | 5 + .../userrights/userrightsnull.class.inc.php | 5 + .../userrightsprofile.class.inc.php | 757 ++++++-- application/cmdbabstract.class.inc.php | 240 +-- application/iotask.class.inc.php | 2 +- application/itopwebpage.class.inc.php | 61 +- application/menunode.class.inc.php | 28 +- application/ui.linkswidget.class.inc.php | 11 +- business/incident.business.php | 4 +- core/attributedef.class.inc.php | 53 +- core/dbobjectsearch.class.php | 34 +- core/expression.class.inc.php | 14 +- core/filterdef.class.inc.php | 66 +- core/metamodel.class.php | 22 +- core/userrights.class.inc.php | 22 + core/valuesetdef.class.inc.php | 85 +- js/linkswidget.js | 2 +- pages/UniversalSearch.php | 2 +- pages/ajax.render.php | 7 +- pages/index.php | 41 +- pages/schema.php | 4 +- pages/usermanagement_userstatus.php | 17 + setup/data/30.profiles.xml | 7 + setup/data/31.profileprojection.xml | 15 + setup/data/32.actiongrant.xml | 1659 +++++++++++++++++ setup/data/33.attributegrant.xml | 3 + setup/data/34.stimulusgrant.xml | 189 ++ setup/data/export.cmd | 6 +- setup/data/structure/1.menus.xml | 407 ++-- setup/data/structure/11.profiles.xml | 4 - setup/data/structure/13.profileprojection.xml | 10 +- setup/data/structure/14.actiongrant.xml | 1656 ---------------- setup/data/structure/16.stimulusgrant.xml | 186 -- setup/data/structure/export_menus.cmd | 5 +- setup/data/structure/export_profiles.cmd | 5 +- setup/index.php | 2 +- 36 files changed, 3112 insertions(+), 2524 deletions(-) create mode 100644 setup/data/30.profiles.xml create mode 100644 setup/data/31.profileprojection.xml create mode 100644 setup/data/32.actiongrant.xml create mode 100644 setup/data/33.attributegrant.xml create mode 100644 setup/data/34.stimulusgrant.xml diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 80a4b97ee0..8341f6cdfa 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -178,6 +178,11 @@ class UserRightsMatrix extends UserRightsAddOnAPI return true; } + public function IsAdministrator($iUserId) + { + return ($iUserId == 1); + } + public function Setup() { // Users must be added manually diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index 0d9656fef0..79a8600a72 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -22,6 +22,11 @@ class UserRightsNull extends UserRightsAddOnAPI return true; } + public function IsAdministrator($iUserId) + { + return true; + } + public function Setup() { return true; diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 10528bc90d..ca8fbaa2f0 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -14,12 +14,14 @@ */ -// It is supposed that this profile does exist in the DB -// Possible improvement: add it when executing the setup procedure -// define('ADMIN_PROFILE_ID', 1); -class URP_Users extends DBObject +class UserRightsBaseClass extends cmdbAbstractObject +{ +} + + +class URP_Users extends UserRightsBaseClass { public static function Init() { @@ -36,23 +38,38 @@ class URP_Users extends DBObject "db_table" => "priv_urp_users", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeInteger("userid", array("label"=>"User id", "description"=>"User identifier (depends on the business model)", "allowed_values"=>null, "sql"=>"userid", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("password", array("label"=>"password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"email", "description"=>"email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("firstname", array("label"=>"firstname", "description"=>"first name", "allowed_values"=>null, "sql"=>"firstname", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("lastname", array("label"=>"lastname", "description"=>"last name", "allowed_values"=>null, "sql"=>"lastname", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"bizPerson", "label"=>"Contact (person)", "description"=>"Personal details from the business data", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("last_name", array("label"=>"Last name", "description"=>"Name of the corresponding contact", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("first_name", array("label"=>"First name", "description"=>"First name of the corresponding contact", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"first_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("email", array("label"=>"Email", "description"=>"Email of the corresponding contact", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"email"))); + + MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"Login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributePassword("password", array("label"=>"Password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profiles", array("label"=>"Profiles", "description"=>"roles, granting rights for that person", "linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); MetaModel::Init_AddFilterFromAttribute("login"); + MetaModel::Init_AddFilterFromAttribute("password"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('userid', 'first_name', 'email', 'login')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'login')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('login', 'userid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('login', 'userid')); // Criteria of the advanced search form } + } -class URP_Profiles extends DBObject + +class URP_Profiles extends UserRightsBaseClass { public static function Init() { @@ -69,19 +86,93 @@ class URP_Profiles extends DBObject "db_table" => "priv_urp_profiles", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("users", array("label"=>"Users", "description"=>"persons having this role", "linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"profileid", "ext_key_to_remote"=>"userid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("description"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'description')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + function GetGrantAsHtml($oUserRights, $sClass, $sAction) + { + $oGrant = $oUserRights->GetClassActionGrant($this->GetKey(), $sClass, $sAction); + if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) + { + return 'yes'; + } + else + { + return 'no'; + } + } + + function DoShowGrantSumary($oPage) + { + + // Note: for sure, we assume that the instance is derived from UserRightsProfile + $oUserRights = UserRights::GetModuleInstance(); + + $aDisplayData = array(); + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + $aStimuli = array(); + foreach (array_keys(MetaModel::EnumStimuli($sClass)) as $sStimulusCode) + { + $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); + if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) + { + $aStimuli[] = $sStimulusCode; + } + } + $sStimuli = implode(', ', $aStimuli); + + $aDisplayData[] = array( + 'class' => MetaModel::GetName($sClass), + 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'), + 'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Read'), + 'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Modify'), + 'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Modify'), + 'stimuli' => $sStimuli, + ); + } + + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); + $aDisplayConfig['read'] = array('label' => 'Read', 'description' => ''); + $aDisplayConfig['bulkread'] = array('label' => 'Bulk read', 'description' => 'List objects or export massively'); + $aDisplayConfig['write'] = array('label' => 'Write', 'description' => 'Create and edit (modify)'); + $aDisplayConfig['bulkwrite'] = array('label' => 'Bulk write', 'description' => 'Massively create/edit (CSV import)'); + $aDisplayConfig['stimuli'] = array('label' => 'Stimuli', 'description' => 'Allowed (compound) actions'); + $oPage->table($aDisplayConfig, $aDisplayData); + } + + function DisplayBareRelations(web_page $oPage) + { + parent::DisplayBareRelations($oPage); + + $oPage->SetCurrentTabContainer('Related Objects'); + + $oPage->SetCurrentTab('Grants matrix'); + $this->DoShowGrantSumary($oPage); } } -class URP_Dimensions extends DBObject + +class URP_Dimensions extends UserRightsBaseClass { public static function Init() { @@ -98,17 +189,25 @@ class URP_Dimensions extends DBObject "db_table" => "priv_urp_dimensions", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"type", "description"=>"class name or data type (projection unit)", "allowed_values"=>new ValueSetEnumClasses('bizmodel', 'String,Integer'), "sql"=>"type", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Type", "description"=>"class name or data type (projection unit)", "allowed_values"=>new ValueSetEnumClasses('bizmodel', 'String,Integer'), "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("description"); MetaModel::Init_AddFilterFromAttribute("type"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description', 'type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'description')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } public function CheckProjectionSpec($oProjectionSpec, $sProjectedClass) @@ -193,39 +292,51 @@ class URP_Dimensions extends DBObject } } -class URP_UserProfile extends DBObject + +class URP_UserProfile extends UserRightsBaseClass { public static function Init() { $aParams = array ( "category" => "addon/userrights", - "name" => "user_profile", + "name" => "User to profile", "description" => "user profiles", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "userid", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_userprofile", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"URP_Users", "jointype"=> "", "label"=>"User", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("label"=>"Login", "description"=>"User's login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason", "description"=>"explain why this person may have this role", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); MetaModel::Init_AddFilterFromAttribute("profileid"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('userid', 'profileid', 'reason')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form } } -class URP_ProfileProjection extends DBObject + +class URP_ProfileProjection extends UserRightsBaseClass { public static function Init() { @@ -236,19 +347,20 @@ class URP_ProfileProjection extends DBObject "description" => "profile projections", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "profileid", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_profileprojection", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant | | +attribute code", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -257,6 +369,13 @@ class URP_ProfileProjection extends DBObject //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("dimensionid"); MetaModel::Init_AddFilterFromAttribute("profileid"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('dimensionid', 'profileid', 'value', 'attribute')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('dimensionid', 'profileid', 'value', 'attribute')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('dimensionid', 'profileid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'profileid')); // Criteria of the advanced search form } public function ProjectUser(URP_Users $oUser) @@ -296,7 +415,8 @@ class URP_ProfileProjection extends DBObject } } -class URP_ClassProjection extends DBObject + +class URP_ClassProjection extends UserRightsBaseClass { public static function Init() { @@ -307,12 +427,13 @@ class URP_ClassProjection extends DBObject "description" => "class projections", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "dimensionid", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_classprojection", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -328,6 +449,13 @@ class URP_ClassProjection extends DBObject MetaModel::Init_AddFilterFromAttribute("dimensionid"); // #@# verifier MetaModel::Init_AddFilterFromAttribute("class"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('dimensionid', 'class', 'value', 'attribute')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('dimensionid', 'class', 'value', 'attribute')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('dimensionid', 'class')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'class')); // Criteria of the advanced search form } public function ProjectObject($oObject) @@ -371,7 +499,8 @@ class URP_ClassProjection extends DBObject } } -class URP_ActionGrant extends DBObject + +class URP_ActionGrant extends UserRightsBaseClass { public static function Init() { @@ -382,12 +511,13 @@ class URP_ActionGrant extends DBObject "description" => "permissions on classes", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "profileid", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_grant_actions", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -395,10 +525,10 @@ class URP_ActionGrant extends DBObject // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"Permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"Action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) @@ -408,10 +538,18 @@ class URP_ActionGrant extends DBObject MetaModel::Init_AddFilterFromAttribute("permission"); MetaModel::Init_AddFilterFromAttribute("action"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'action')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('profileid', 'class', 'permission', 'action')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the advanced search form } } -class URP_StimulusGrant extends DBObject + +class URP_StimulusGrant extends UserRightsBaseClass { public static function Init() { @@ -422,12 +560,13 @@ class URP_StimulusGrant extends DBObject "description" => "permissions on stimilus in the life cycle of the object", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "profileid", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_grant_stimulus", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); @@ -435,10 +574,10 @@ class URP_StimulusGrant extends DBObject // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"Permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"stimulus", "description"=>"stimulus code", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"Stimulus", "description"=>"stimulus code", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) @@ -448,10 +587,18 @@ class URP_StimulusGrant extends DBObject MetaModel::Init_AddFilterFromAttribute("permission"); MetaModel::Init_AddFilterFromAttribute("stimulus"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'stimulus')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('profileid', 'class', 'permission', 'stimulus')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the advanced search form } } -class URP_AttributeGrant extends DBObject + +class URP_AttributeGrant extends UserRightsBaseClass { public static function Init() { @@ -462,22 +609,30 @@ class URP_AttributeGrant extends DBObject "description" => "permissions at the attributes level", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "actiongrantid", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_urp_grant_attributes", "db_key_field" => "id", "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "label"=>"Action grant", "description"=>"action grant", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"Attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("actiongrantid"); MetaModel::Init_AddFilterFromAttribute("attcode"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('actiongrantid', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('actiongrantid', 'attcode')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('actiongrantid', 'attcode')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('actiongrantid', 'attcode')); // Criteria of the advanced search form } } @@ -498,182 +653,68 @@ class UserRightsProfile extends UserRightsAddOnAPI // Installation: create the very first user public function CreateAdministrator($sAdminUser, $sAdminPwd) { + $oOrg = new bizOrganization(); + $oOrg->Set('name', 'My Company/Department'); + $oOrg->Set('code', 'SOMECODE'); + $oOrg->Set('status', 'implementation'); + //$oOrg->Set('parent_id', xxx); + $iOrgId = $oOrg->DBInsertNoReload(); + + // Location : optional + //$oLocation = new bizLocation(); + //$oLocation->Set('name', 'MyOffice'); + //$oLocation->Set('status', 'implementation'); + //$oLocation->Set('org_id', $iOrgId); + //$oLocation->Set('severity', 'high'); + //$oLocation->Set('address', 'my building in my city'); + //$oLocation->Set('country', 'my country'); + //$oLocation->Set('parent_location_id', xxx); + //$iLocationId = $oLocation->DBInsertNoReload(); + + $oContact = new bizPerson(); + $oContact->Set('name', 'My last name'); + $oContact->Set('first_name', 'My first name'); + $oContact->Set('status', 'production'); + $oContact->Set('org_id', $iOrgId); + $oContact->Set('email', 'my.email@foo.org'); + $oContact->Set('phone', ''); + //$oContact->Set('location_id', $iLocationId); + $oContact->Set('employee_number', ''); + $iContactId = $oContact->DBInsertNoReload(); + $oUser = new URP_Users(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); - $oUser->Set('email', 'n/a'); - $oUser->Set('firstname', 'administrator'); - $oUser->Set('lastname', 'itop'); - $oUser->Set('userid', 1); // let's mark it as #1 (for what purpose?) + $oUser->Set('userid', $iContactId); $iUserId = $oUser->DBInsertNoReload(); // Add this user to the very specific 'admin' profile $oUserProfile = new URP_UserProfile(); $oUserProfile->Set('userid', $iUserId); $oUserProfile->Set('profileid', ADMIN_PROFILE_ID); + $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); $oUserProfile->DBInsertNoReload(); return true; } - public function Setup() + public function IsAdministrator($iUserId) { - // Dimensions/Profiles/Classes/Attributes/Stimuli could be added anytime - // This procedure will then update the matrix with expected default values - // - - $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); - while ($oProfile = $oProfileSet->Fetch()) + if (in_array($iUserId, $this->m_aAdmins)) { - $this->SetupProfile($oProfile); - } - - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $this->SetupDimension($oDimension); - } - return true; - } - - protected function SetupDimension($oDimension, $bNewDimension = false) - { - $iDimensionId = $oDimension->GetKey(); - - // Create projections, for any class where it applies - // - foreach(array('bizmodel', 'application', 'gui', 'core/cmdb') as $sCategory) - { - foreach (MetaModel::GetClasses($sCategory) as $sClass) - { - if ($bNewDimension) - { - $bAddCell = true; - } - else - { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection WHERE class = :class AND dimensionid = :dimension"), array(), array('class'=>$sClass, 'dimension'=>$iDimensionId)); - $bAddCell = ($oSet->Count() < 1); - } - if ($bAddCell) - { - // Create a new entry - $oCProj = MetaModel::NewObject("URP_ClassProjection"); - $oCProj->Set("dimensionid", $iDimensionId); - $oCProj->Set("class", $sClass); - $oCProj->Set("value", "true"); - $iId = $oCProj->DBInsertNoReload(); - } - } - } - // Create projections, for any existing profile - // - $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); - while ($oProfile = $oProfileSet->Fetch()) - { - $iProfileId = $oProfile->GetKey(); - if ($bNewDimension) - { - $bAddCell = true; - } - else - { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection WHERE dimensionid = :dimension AND profileid = :profile"), array(), array('dimension'=>$iDimensionId, 'profile'=>$iProfileId)); - $bAddCell = ($oSet->Count() < 1); - } - if ($bAddCell) - { - // Create a new entry - $oDProj = MetaModel::NewObject("URP_ProfileProjection"); - $oDProj->Set("dimensionid", $iDimensionId); - $oDProj->Set("profileid", $iProfileId); - $oDProj->Set("value", "true"); - $iId = $oDProj->DBInsertNoReload(); - } - } - } - - protected function SetupProfile($oProfile, $bNewProfile = false) - { - $iProfileId = $oProfile->GetKey(); - - // Create grant records, for any class where it matters (the rest could be done later on) - // - foreach(array('bizmodel') as $sCategory) - { - foreach (MetaModel::GetClasses($sCategory) as $sClass) - { - foreach (self::$m_aActionCodes as $iActionCode => $sAction) - { - if ($bNewProfile) - { - $bAddCell = true; - } - else - { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile"), array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfileId)); - $bAddCell = ($oSet->Count() < 1); - } - if ($bAddCell) - { - // Create a new entry - $oMyActionGrant = MetaModel::NewObject("URP_ActionGrant"); - $oMyActionGrant->Set("profileid", $iProfileId); - $oMyActionGrant->Set("class", $sClass); - $oMyActionGrant->Set("action", $sAction); - $oMyActionGrant->Set("permission", "yes"); - $iId = $oMyActionGrant->DBInsertNoReload(); - } - } - foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) - { - if ($bNewProfile) - { - $bAddCell = true; - } - else - { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile"), array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfileId)); - $bAddCell = ($oSet->Count() < 1); - } - if ($bAddCell) - { - // Create a new entry - $oMyStGrant = MetaModel::NewObject("URP_StimulusGrant"); - $oMyStGrant->Set("profileid", $iProfileId); - $oMyStGrant->Set("class", $sClass); - $oMyStGrant->Set("stimulus", $sStimulusCode); - $oMyStGrant->Set("permission", "yes"); - $iId = $oMyStGrant->DBInsertNoReload(); - } - } - } - } - // Create the "My Bookmarks" menu item (parent_id = 0, rank = 6) - if ($bNewProfile) - { - $bAddMenu = true; + return true; } else { - //$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT menuNode WHERE type = 'user' AND parent_id = 0 AND user_id = $iUserId")); - //$bAddMenu = ($oSet->Count() < 1); - } - $bAddMenu = false; - if ($bAddMenu) - { - $oMenu = MetaModel::NewObject('menuNode'); - $oMenu->Set('type', 'user'); - $oMenu->Set('parent_id', 0); // It's a toplevel entry - $oMenu->Set('rank', 6); // Located just above the Admin Tools section (=7) - $oMenu->Set('name', 'My Bookmarks'); - $oMenu->Set('label', 'My Favorite Items'); - $oMenu->Set('hyperlink', 'UI.php'); - $oMenu->Set('template', '

    My bookmarks

    This section contains my most favorite search results

    '); - $oMenu->Set('user_id', $iUserId); - $oMenu->DBInsert(); + return false; } } + public function Setup() + { + SetupITILProfiles::DoCreateProfiles(); + return true; + } + public function Init() { MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData')); @@ -689,6 +730,7 @@ class UserRightsProfile extends UserRightsAddOnAPI protected $m_aAdmins = array(); // id of users being linked to the profile #ADMIN_PROFILE_ID protected $m_aClassActionGrants = array(); // profile, class, action -> permission + protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission protected $m_aObjectActionGrants = array(); // userid, class, id, action -> permission, list of attributes public function CacheData() @@ -787,19 +829,33 @@ exit; return $oNullFilter; } - protected function GetClassActionGrant($iProfile, $sClass, $sAction) + // This verb has been made public to allow the development of an accurate feedback for the current configuration + public function GetClassActionGrant($iProfile, $sClass, $sAction) { - $aTest = @$this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; - if (isset($aTest)) return $aTest; + if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) + { + return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; + } // Get the permission for this profile/class/action $oSearch = DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'"); $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile)); - if ($oSet->Count() < 1) + if ($oSet->Count() >= 1) { - return null; + $oGrantRecord = $oSet->Fetch(); + } + else + { + $sParentClass = MetaModel::GetParentPersistentClass($sClass); + if (empty($sParentClass)) + { + $oGrantRecord = null; + } + else + { + $oGrantRecord = $this->GetClassActionGrant($iProfile, $sParentClass, $sAction); + } } - $oGrantRecord = $oSet->Fetch(); $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $oGrantRecord; return $oGrantRecord; @@ -854,8 +910,7 @@ exit; public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances) { - // super admin rights - if (in_array($iUserId, $this->m_aAdmins)) return true; + if ($this->IsAdministrator($iUserId)) return true; $oUser = $this->m_aUsers[$iUserId]; @@ -892,8 +947,7 @@ exit; public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances) { - // super admin rights - if (in_array($iUserId, $this->m_aAdmins)) return true; + if ($this->IsAdministrator($iUserId)) return true; $oUser = $this->m_aUsers[$iUserId]; @@ -936,10 +990,33 @@ exit; } } + // This verb has been made public to allow the development of an accurate feedback for the current configuration + public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode) + { + if (isset($this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode])) + { + return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]; + } + + // Get the permission for this profile/class/stimulus + $oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); + $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); + if ($oSet->Count() >= 1) + { + $oGrantRecord = $oSet->Fetch(); + } + else + { + $oGrantRecord = null; + } + + $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode] = $oGrantRecord; + return $oGrantRecord; + } + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances) { - // super admin rights - if (in_array($iUserId, $this->m_aAdmins)) return true; + if ($this->IsAdministrator($iUserId)) return true; $oUser = $this->m_aUsers[$iUserId]; @@ -1069,6 +1146,274 @@ exit; } } +// +// Here is the code that will create some profiles into our user management model, given an ITIL representation of the user profiles +// +class SetupITILProfiles +{ + /* + Later, maybe ? + + protected static $m_aDimensions = array( + 'organization' => array( + 'description' => '', + 'type' => 'bizOrganization', + ), + 'site' => array( + 'description' => '', + 'type' => '', + ), + ); + */ + protected static $m_aActions = array( + UR_ACTION_READ => 'Read', + UR_ACTION_MODIFY => 'Modify', + UR_ACTION_DELETE => 'Delete', + UR_ACTION_BULK_READ => 'Bulk Read', + UR_ACTION_BULK_MODIFY => 'Bulk Modify', + UR_ACTION_BULK_DELETE => 'Bulk Delete', + ); + + // It is possible to specify the same class in several modules + // + protected static $m_aModules = array( + 'General' => array( + 'bizOrganization', + ), + 'Documentation' => array( + 'bizDocVersion', + 'lnkDocumentRealObject', + 'lnkDocumentContract', + 'lnkDocumentError', + ), + 'Configuration' => array( + 'logRealObject', + 'lnkContactRealObject', + 'lnkInterfaces', + 'ClientServerLinks', + 'lnkInfraGrouping', + ), + 'Incident' => array( + 'bizIncidentTicket', + 'lnkRelatedTicket', + 'lnkInfraTicket', + 'lnkContactTicket', + ), + 'Problem' => array( + 'bizKnownError', + 'lnkInfraError', + 'lnkDocumentError', + ), + 'Change' => array( + 'bizChangeTicket', + 'lnkInfraChangeTicket', + 'lnkContactChange', + ), + 'Service' => array( + 'bizContract', + 'lnkInfraContract', + 'lnkContactContract', + 'lnkDocumentContract', + ), + ); + + protected static $m_aProfiles = array( + 'Configuration Manager' => array( + 'description' => 'Person in charge of the documentation of the managed CIs', + 'write_modules' => 'Documentation,Configuration', + 'stimuli' => array( + 'bizServer' => 'any', + //'bizServer' => 'ev_store,ev_ship,ev_plug,ev_configuration_finished,ev_val_failed,ev_mtp,ev_start_change,ev_end_change,ev_decomission,ev_obsolete,ev_recycle', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'none', + ), + ), + /* 'Requestor pb granularite actions (create/delete)' => array( + 'description' => 'Person notifying an incident', + 'write_modules' => 'Incident', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'none', + ), + ), + */ + 'Service Desk Agent' => array( + 'description' => 'Person in charge of creating incident reports', + 'write_modules' => 'Documentation,Incident', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'ev_assign', + 'bizChangeTicket' => 'none', + ), + ), + 'Support Agent' => array( + 'description' => 'Person analyzing and solving the current incidents or problems', + 'write_modules' => 'Documentation,Incident,Problem', + 'stimuli' => array( + 'bizIncidentTicket' => 'any', + //'bizIncidentTicket' => 'ev_assign,ev_reassign,ev_start_working,ev_close', + ), + ), + 'Change Implementor' => array( + 'description' => 'Person executing the changes', + 'write_modules' => 'Documentation,Configuration,Change', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + ), + ), + 'Change Supervisor' => array( + 'description' => 'Person responsible for the overall change execution', + 'write_modules' => 'Documentation,Change', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'ev_validate,ev_reject,ev_reopen,ev_finish', + ), + ), + 'Change Approver' => array( + 'description' => 'Person who could be impacted by some changes', + 'write_modules' => 'Documentation,Change', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'ev_approve,ev_notapprove', + ), + ), + 'Service Manager' => array( + 'description' => 'Person responsible for the service delivered to the [internal] customer', + 'write_modules' => 'Documentation,Service', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'any', + //'bizContract' => 'ev_freeze_version,ev_sign,ev_begin,ev_notice,ev_terminate,ev_elapsed', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'none', + ), + ), + ); + + + protected static function DoCreateProfileProjection($iProfile, $iDimension) + { + $oNewObj = MetaModel::NewObject("URP_ProfileProjection"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('dimensionid', $iDimension); + $oNewObj->Set('value', ''); + $oNewObj->Set('attribute', ''); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + + protected static function DoCreateActionGrant($iProfile, $iAction, $sClass) + { + $oNewObj = MetaModel::NewObject("URP_ActionGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', true); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('action', self::$m_aActions[$iAction]); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) + { + $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', true); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('stimulus', $sStimulusCode); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateOneProfile($sName, $aProfileData) + { + $sDescription = $aProfileData['description']; + $aWriteModules = explode(',', $aProfileData['write_modules']); + $aStimuli = $aProfileData['stimuli']; + + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', $sName); + $oNewObj->Set('description', $sDescription); + $iProfile = $oNewObj->DBInsertNoReload(); + + // Project in every dimension + // + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + while ($oDimension = $oDimensionSet->Fetch()) + { + $iDimension = $oDimension->GetKey(); + self::DoCreateProfileProjection($iProfile, $iDimension); + } + + // Grant read rights for everything + // + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + self::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass); + } + + // Grant write for given modules + // Start by compiling the information, because some modules may overlap + $aWriteableClasses = array(); + foreach ($aWriteModules as $sModule) + { +// $oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); + foreach (self::$m_aModules[$sModule] as $sClass) + { + $aWriteableClasses[] = $sClass; + } + } + foreach ($aWriteableClasses as $sClass) + { + self::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_DELETE, $sClass); + } + + // Grant stimuli for given classes + foreach ($aStimuli as $sClass => $sAllowedStimuli) + { + if ($sAllowedStimuli == 'any') + { + $aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass)); + } + elseif ($sAllowedStimuli == 'none') + { + $aAllowedStimuli = array(); + } + else + { + $aAllowedStimuli = explode(',', $sAllowedStimuli); + } + foreach ($aAllowedStimuli as $sStimulusCode) + { + self::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass); + } + } + } + + public static function DoCreateProfiles() + { + foreach(self::$m_aProfiles as $sName => $aProfileData) + { + self::DoCreateOneProfile($sName, $aProfileData); + } + } +} + UserRights::SelectModule('UserRightsProfile'); ?> diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 59dd5a48b2..e26d0647ec 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -100,11 +100,71 @@ abstract class cmdbAbstractObject extends CMDBObject return $sDisplayValue; } + function DisplayBareHeader(web_page $oPage) + { + // Standard Header with name, actions menu and history block + // + $oPage->add("
    \n"); + + // action menu + $oSingletonFilter = new DBObjectSearch(get_class($this)); + $oSingletonFilter->AddCondition('pkey', array($this->GetKey())); + $oBlock = new MenuBlock($oSingletonFilter, 'popup', false); + $oBlock->Display($oPage, -1); + + $oPage->add("

    ".MetaModel::GetName(get_class($this)).": ".$this->GetDisplayName()."

    \n"); + + // history block (with toggle) + $oHistoryFilter = new DBObjectSearch('CMDBChangeOpSetAttribute'); + $oHistoryFilter->AddCondition('objkey', $this->GetKey()); + $oBlock = new HistoryBlock($oHistoryFilter, 'toggle', false); + $oBlock->Display($oPage, -1); + + $oPage->add("
    \n"); + } + function DisplayBareDetails(web_page $oPage) { $oPage->add($this->GetBareDetails($oPage)); } + function DisplayBareRelations(web_page $oPage) + { + // Related objects + $oPage->AddTabContainer('Related Objects'); + $oPage->SetCurrentTabContainer('Related Objects'); + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + { + if ((get_class($oAttDef) == 'AttributeLinkedSetIndirect') || (get_class($oAttDef) == 'AttributeLinkedSet')) + { + $oPage->SetCurrentTab($oAttDef->GetLabel()); + $oPage->p($oAttDef->GetDescription()); + + if (get_class($oAttDef) == 'AttributeLinkedSet') + { + $sTargetClass = $oAttDef->GetLinkedClass(); + $oFilter = new DBObjectSearch($sTargetClass); + $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey()); // @@@ condition has same name as field ?? + + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oPage, 0); + } + else // get_class($oAttDef) == 'AttributeLinkedSetIndirect' + { + $sLinkClass = $oAttDef->GetLinkedClass(); + // Transform the DBObjectSet into a CMBDObjectSet !!! + $aLinkedObjects = $this->Get($sAttCode)->ToArray(false); + if (count($aLinkedObjects) > 0) + { + $oSet = CMDBObjectSet::FromArray($sLinkClass, $aLinkedObjects); + $this->DisplaySet($oPage, $oSet, $oAttDef->GetExtKeyToMe(), true, false, $this->GetKey(), $oAttDef->GetExtKeyToRemote()); + } + } + } + } + $oPage->SetCurrentTab(''); + } + function GetDisplayName() { return $this->GetAsHTML(MetaModel::GetNameAttributeCode(get_class($this))); @@ -152,56 +212,11 @@ abstract class cmdbAbstractObject extends CMDBObject } else { - // Standard Header with name, actions menu and history block - $oPage->add("
    \n"); - $oSingletonFilter = new DBObjectSearch(get_class($this)); - $oSingletonFilter->AddCondition('pkey', array($this->GetKey())); - $oBlock = new MenuBlock($oSingletonFilter, 'popup', false); - $oBlock->Display($oPage, -1); - $oPage->add("

    ".Metamodel::GetName(MetaModel::GetName(get_class($this))).": ".$this->GetDisplayName()."

    \n"); - $oHistoryFilter = new DBObjectSearch('CMDBChangeOpSetAttribute'); - $oHistoryFilter->AddCondition('objkey', $this->GetKey()); - $oBlock = new HistoryBlock($oHistoryFilter, 'toggle', false); - $oBlock->Display($oPage, -1); - $oPage->add("
    \n"); - // Object's details // template not found display the object using the *old style* - self::DisplayBareDetails($oPage); - - // Related objects - $oPage->AddTabContainer('Related Objects'); - $oPage->SetCurrentTabContainer('Related Objects'); - foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) - { - if ((get_class($oAttDef) == 'AttributeLinkedSetIndirect') || (get_class($oAttDef) == 'AttributeLinkedSet')) - { - $oPage->SetCurrentTab($oAttDef->GetLabel()); - $oPage->p($oAttDef->GetDescription()); - - if (get_class($oAttDef) == 'AttributeLinkedSet') - { - $sTargetClass = $oAttDef->GetLinkedClass(); - $oFilter = new DBObjectSearch($sTargetClass); - $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey()); // @@@ condition has same name as field ?? - - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oPage, 0); - } - else // get_class($oAttDef) == 'AttributeLinkedSetIndirect' - { - $sLinkClass = $oAttDef->GetLinkedClass(); - // Transform the DBObjectSet into a CMBDObjectSet !!! - $aLinkedObjects = $this->Get($sAttCode)->ToArray(false); - if (count($aLinkedObjects) > 0) - { - $oSet = CMDBObjectSet::FromArray($sLinkClass, $aLinkedObjects); - $this->DisplaySet($oPage, $oSet, $oAttDef->GetExtKeyToMe()); - } - } - } - } - $oPage->SetCurrentTab(''); + $this->DisplayBareHeader($oPage); + $this->DisplayBareDetails($oPage); + $this->DisplayBareRelations($oPage); } } @@ -218,10 +233,10 @@ abstract class cmdbAbstractObject extends CMDBObject } // Comment by Rom: this helper may be used to display objects of class DBObject - // -> I am using this to display the changes history - public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false, $iObjectId = 0) + // -> I am using this to display the changes history + public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false, $iObjectId = 0, $sTargetAttribute = '') { - $oPage->add(self::GetDisplaySet($oPage, $oSet, array( 'link_attr' => $sLinkageAttribute, 'object_id' => $iObjectId, 'menu' => $bDisplayMenu, 'selection_mode' => $bSelectMode))); + $oPage->add(self::GetDisplaySet($oPage, $oSet, array( 'link_attr' => $sLinkageAttribute, 'object_id' => $iObjectId, 'target_attr' => $sTargetAttribute, 'menu' => $bDisplayMenu, 'selection_mode' => $bSelectMode))); } //public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false) @@ -238,7 +253,7 @@ abstract class cmdbAbstractObject extends CMDBObject { if($iLinkedObjectId == 0) { - // if 'links' mode is requested the d of the object to link to must be specified + // if 'links' mode is requested the id of the object to link to must be specified throw new ApplicationException("Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template."); } if($sTargetAttr == '') @@ -438,9 +453,9 @@ abstract class cmdbAbstractObject extends CMDBObject $sClassName = $oSet->GetFilter()->GetClass(); $sHtml .= "\n"; +
  • OQL Query
  • +
  • Simple Search
  • +
    \n"; // Simple search form $sHtml .= "
    \n"; $sHtml .= "

    Search for ".MetaModel::GetName($sClassName)." Objects

    \n"; @@ -487,32 +502,33 @@ abstract class cmdbAbstractObject extends CMDBObject } } } - $aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, array(), ''); - if ($aAllowedValues != null) - { - //Enum field or external key, display a combo - $sValue = "\n"; - $sHtml .= "
    \n"; - } - else - { - // Any value is possible, display an input box - $sHtml .= "\n"; - } + // #@# todo - add context information, otherwise any value will be authorized for external keys + $aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, array()); + if ($aAllowedValues != null) + { + //Enum field or external key, display a combo + $sValue = "\n"; + $sHtml .= "\n"; + } + else + { + // Any value is possible, display an input box + $sHtml .= "\n"; + } $index++; } if (($index % $numCols) != 0) @@ -577,6 +593,10 @@ abstract class cmdbAbstractObject extends CMDBObject $sHTMLValue = ""; break; + case 'Password': + $sHTMLValue = ""; + break; + case 'Text': $sHTMLValue = ""; break; @@ -588,39 +608,41 @@ abstract class cmdbAbstractObject extends CMDBObject case 'String': default: - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array(), ''); - if ($aAllowedValues !== null) - { - //Enum field or external key, display a combo - if (count($aAllowedValues) == 0) + // #@# todo - add context information (depending on dimensions) + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array()); + if ($aAllowedValues !== null) { - $sHTMLValue = ""; - } - else if (count($aAllowedValues) > 50) - { - // too many choices, use an autocomplete - // The input for the auto complete - $sHTMLValue = ""; - // another hidden input to store & pass the object's Id - $sHTMLValue .= "\n"; - $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); + //Enum field or external key, display a combo + if (count($aAllowedValues) == 0) + { + $sHTMLValue = ""; + } + else if (count($aAllowedValues) > 50) + { + // too many choices, use an autocomplete + // The input for the auto complete + $sHTMLValue = ""; + // another hidden input to store & pass the object's Id + $sHTMLValue .= "\n"; + $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); + } + else + { + // Few choices, use a normal 'select' + $sHTMLValue = "\n"; + } } else { - // Few choices, use a normal 'select' - $sHTMLValue = "\n"; + $sHTMLValue = ""; } - } - else - { - $sHTMLValue = ""; - } + break; } } return $sHTMLValue; diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index 32eb33400f..f1f62f8c79 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -31,7 +31,7 @@ class InputOutputTask extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("label"=>"Source Type", "description"=>"Type of data source", "allowed_values"=>new ValueSetEnum('File, Database, Web Service'), "sql"=>"source_type", "default_value"=>"File", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype", array("label"=>"Source Subtype", "description"=>"Subtype of Data Source", "allowed_values"=>new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql"=>"source_subtype", "default_value"=>"CSV", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("source_path", array("label"=>"Source Path", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"source_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "allowed_values"=>new ValueSetEnum('bizOrganization, bizContact, bizTeam, bizPerson, bizLocation, bizServer, bizPC, bizNetworkDevice, bizInterface, bizService, bizContract, bizInfraGroup, bizIncidentTicket, bizSoftware, bizApplication, bizPatch, bizWorkgroup, lnkContactRealObject, lnkInterfaces, bizInfraGrouping' ), "sql"=>"objects_class", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "allowed_values"=>new ValueSetEnumClasses(), "sql"=>"objects_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("label"=>"Test Mode", "description"=>"If set to 'Yes' the modifications are not applied", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"test_mode", "default_value"=>'No', "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("label"=>"Verbose Mode", "description"=>"If set to 'Yes' extra debug information is added to the log", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"verbose_mode", "default_value" => 'No', "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("options", array("label"=>"Options", "description"=>"Reconciliation options", "allowed_values"=>new ValueSetEnum('Full, Update Only, Creation Only'), "sql"=>"options", "default_value"=> 'Full', "is_null_allowed"=>true, "depends_on"=>array()))); diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index b315d17f43..37590b6a37 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -158,29 +158,44 @@ EOF $this->AddToMenu("\n"); $this->AddToMenu("\n"); $this->AddToMenu("
      \n"); - $oAppContext = new ApplicationContext(); - // Display the menu - // 1) Application defined menus - $oSearchFilter = $oContext->NewFilter("menuNode"); - $oSearchFilter->AddCondition('parent_id', 0, '='); - $oSearchFilter->AddCondition('type', 'application', '='); - // There may be more criteria added later to have a specific menu based on the user's profile - $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); - while ($oRootMenuNode = $oSet->Fetch()) - { - $oRootMenuNode->DisplayMenu($this, 'application', $oAppContext->GetAsHash()); - } - // 2) User defined menus (Bookmarks) - $oSearchFilter = $oContext->NewFilter("menuNode"); - $oSearchFilter->AddCondition('parent_id', 0, '='); - $oSearchFilter->AddCondition('type', 'user', '='); - $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); - // There may be more criteria added later to have a specific menu based on the user's profile - $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); - while ($oRootMenuNode = $oSet->Fetch()) - { - $oRootMenuNode->DisplayMenu($this, 'user', $oAppContext->GetAsHash()); - } + + // Display the menu + $oAppContext = new ApplicationContext(); + // 1) Application defined menus + $oSearchFilter = $oContext->NewFilter("menuNode"); + $oSearchFilter->AddCondition('parent_id', 0, '='); + $oSearchFilter->AddCondition('type', 'application', '='); + // There may be more criteria added later to have a specific menu based on the user's profile + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + while ($oRootMenuNode = $oSet->Fetch()) + { + $oRootMenuNode->DisplayMenu($this, 'application', $oAppContext->GetAsHash()); + } + // 2) User defined menus (Bookmarks) + $oSearchFilter = $oContext->NewFilter("menuNode"); + $oSearchFilter->AddCondition('parent_id', 0, '='); + $oSearchFilter->AddCondition('type', 'user', '='); + $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); + // There may be more criteria added later to have a specific menu based on the user's profile + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + while ($oRootMenuNode = $oSet->Fetch()) + { + $oRootMenuNode->DisplayMenu($this, 'user', $oAppContext->GetAsHash()); + } + // 3) Administrator menu + if (userRights::IsAdministrator()) + { + $oSearchFilter = $oContext->NewFilter("menuNode"); + $oSearchFilter->AddCondition('parent_id', 0, '='); + $oSearchFilter->AddCondition('type', 'administrator', '='); + // There may be more criteria added later to have a specific menu based on the user's profile + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + while ($oRootMenuNode = $oSet->Fetch()) + { + $oRootMenuNode->DisplayMenu($this, 'administrator', $oAppContext->GetAsHash()); + } + } + $this->AddToMenu("
    \n"); } diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 72ddfeb230..23cf78eadd 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -42,7 +42,7 @@ class menuNode extends DBObject MetaModel::Init_AddAttribute(new AttributeString("hyperlink", array("label"=>"Hyperlink", "description"=>"Hyperlink to the page", "allowed_values"=>null, "sql"=>"hyperlink", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("icon_path", array("label"=>"Menu Icon", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"icon_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("template", array("label"=>"Template", "description"=>"HTML template for the view", "allowed_values"=>null, "sql"=>"template", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of menu", "allowed_values"=>new ValueSetEnum('application,user'), "sql"=>"type", "default_value"=>"application", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of menu", "allowed_values"=>new ValueSetEnum('application,user,administrator'), "sql"=>"type", "default_value"=>"application", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeInteger("rank", array("label"=>"Display rank", "description"=>"Sort order for displaying the menu", "allowed_values"=>null, "sql"=>"rank", "default_value" => 999, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "sql"=>"parent_id", "targetclass"=>"menuNode", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name"))); @@ -91,17 +91,25 @@ class menuNode extends DBObject public function GetChildNodesSet($sType = null) { - $oSearchFilter = new DBObjectSearch("menuNode"); - $oSearchFilter->AddCondition('parent_id', $this->GetKey(), '='); - if ($sType != null) + $aParams = array(); + + if ($sType == 'user') { - $oSearchFilter->AddCondition('type', $sType, '='); - if ($sType == 'user') - { - $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '='); - } + $sSelectChilds = 'SELECT menuNode AS m WHERE m.parent_id = :parent AND type = :type AND m.user_id = :user'; + $aParams = array('parent' => $this->GetKey(), 'type' => $sType, 'user' => UserRights::GetUserId()); } - $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); + elseif ($sType != null) + { + $sSelectChilds = 'SELECT menuNode AS m WHERE m.parent_id = :parent AND type = :type'; + $aParams = array('parent' => $this->GetKey(), 'type' => $sType); + } + else + { + $sSelectChilds = 'SELECT menuNode AS m WHERE m.parent_id = :parent'; + $aParams = array('parent' => $this->GetKey()); + } + $oSearchFilter = DBObjectSearch::FromOQL($sSelectChilds); + $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true), $aParams); return $oSet; } diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index de602bdd09..5c4bd7d6c1 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -21,6 +21,7 @@ class UILinksWidget { $sHTMLValue = ''; $sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode); + // #@# todo - add context information, otherwise any value will be authorized for external keys $aAllowedValues = MetaModel::GetAllowedValues_att($this->m_sClass, $this->m_sAttCode, array(), ''); $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); @@ -120,6 +121,7 @@ class UILinksWidget */ static public function Autocomplete(web_page $oPage, UserContext $oContext, $sClass, $sAttCode, $sName, $iMaxCount) { + // #@# todo - add context information, otherwise any value will be authorized for external keys $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array() /* $aArgs */, $sName); if ($aAllowedValues != null) { @@ -154,12 +156,13 @@ class UILinksWidget * This static function is called by the Ajax Page display a set of objects being linked * to the object being created * @param $oPage web_page The ajax page used for the put^put (sent back to the browser - * @param $sClass string The name of the class 'linking class' which is the class of the objects to display - * @param $sAttCode string The name of the attribute is the main object being created + * @param $sClass string The name of the 'linking class' which is the class of the objects to display * @param $sSet JSON serialized set of objects + * @param $sExtKeyToMe Name of the attribute in sClass that is pointing to a given object + * @param $iObjectId The id of the object $sExtKeyToMe is pointing to * @return void */ - static public function RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe) + static public function RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe, $sExtKeyToRemote, $iObjectId) { $aSet = json_decode($sJSONSet, true); // true means hash array instead of object $oSet = CMDBObjectSet::FromScratch($sClass); @@ -182,7 +185,7 @@ class UILinksWidget } $oSet->AddObject($oObj); } - cmdbAbstractObject::DisplaySet($oPage, $oSet, $sExtKeyToMe); + cmdbAbstractObject::DisplaySet($oPage, $oSet, $sExtKeyToMe, true /*menu*/, false /*select*/, $iObjectId, $sExtKeyToRemote); } diff --git a/business/incident.business.php b/business/incident.business.php index 5a130959a3..fa275b1509 100644 --- a/business/incident.business.php +++ b/business/incident.business.php @@ -55,7 +55,9 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closed Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>null, "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array()))); + //MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>null, "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->customer_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array('customer_id')))); + //MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = 1'), "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array('customer_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index d1af378a4a..865da06259 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -178,6 +178,13 @@ abstract class AttributeDefinition { return str_replace($sSeparator, $sSepEscape, $sValue); } + + public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') + { + $oValSetDef = $this->GetValuesDef(); + if (!$oValSetDef) return null; + return $oValSetDef->GetValues($aArgs, $sBeginsWith); + } } /** @@ -490,7 +497,10 @@ class AttributeString extends AttributeDBField } public function RealValueToSQLValue($value) { - assert(is_string($value)); + if (!is_string($value)) + { + throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass())); + } return $value; } public function SQLValueToRealValue($value) @@ -499,6 +509,29 @@ class AttributeString extends AttributeDBField } } + +/** + * Map a varchar column (size < ?) to an attribute that must never be shown to the user + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributePassword extends AttributeString +{ + static protected function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "Password";} + public function GetDBFieldType() {return "VARCHAR(64)";} +} + /** * Map a text column (size > ?) to an attribute * @@ -778,7 +811,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid } // overloaded here so that an ext key always have the answer to - // "what are you possible values?" + // "what are your possible values?" public function GetValuesDef() { $oValSetDef = $this->Get("allowed_values"); @@ -788,7 +821,21 @@ class AttributeExternalKey extends AttributeDBFieldVoid $oValSetDef = new ValueSetObjects($this->GetTargetClass()); } return $oValSetDef; - } + } + + public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') + { + try + { + return parent::GetAllowedValues($aArgs, $sBeginsWith); + } + catch (MissingQueryArgument $e) + { + // Some required arguments could not be found, enlarge to any existing value + $oValSetDef = new ValueSetObjects($this->GetTargetClass()); + return $oValSetDef->GetValues($aArgs, $sBeginsWith); + } + } } /** diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index e30d3a5fa8..03020556fb 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -16,7 +16,6 @@ /** * Sibusql - value set start * @package iTopORM - * @info zis is private */ define('VS_START', '{'); /** @@ -465,7 +464,7 @@ class DBObjectSearch public function RenderCondition() { - return $this->m_oSearchCondition->Render($this->m_aParams); + return $this->m_oSearchCondition->Render($this->m_aParams, true); } public function serialize() @@ -507,7 +506,15 @@ class DBObjectSearch $sRelCode = $aRelatedTo['relcode']; $iMaxDepth = $aRelatedTo['maxdepth']; - $sValue .= "T:".$oFilter->serialize().":$sRelCode:$iMaxDepth"; + $sValue .= "T:".$oFilter->serialize().":$sRelCode:$iMaxDepth\n"; + } + if (count($this->m_aParams) > 0) + { + foreach($this->m_aParams as $sName => $sArgValue) + { + // G stands for arGument + $sValue .= "G:$sName:$sArgValue\n"; + } } return base64_encode($sValue); } @@ -554,6 +561,11 @@ class DBObjectSearch $sRelCode = $aCondition[2]; $iMaxDepth = $aCondition[3]; $oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth); + break; + case "G": + $oFilter->m_aParams[$aCondition[1]] = $aCondition[2]; + break; + default: throw new CoreException("invalid filter definition (cannot unserialize the data, clear text = '$sClearText')"); } @@ -600,6 +612,22 @@ class DBObjectSearch public function ToOQL(&$aParams = null) { $bRetrofitParams = (!is_null($aParams)); + if (is_null($aParams)) + { + if (count($this->m_aParams) > 0) + { + $aParams = $this->m_aParams; + } + $bRetrofitParams = false; + } + else + { + if (count($this->m_aParams) > 0) + { + $aParams = array_merge($aParams, $this->m_aParams); + } + $bRetrofitParams = true; + } $sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias(); $sRes .= $this->ToOQL_Joins(); diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index 834ac47b7b..82b3e61b3c 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -11,6 +11,11 @@ * @since 1.0 * @version 1.1.1.1 $ */ + +class MissingQueryArgument extends CoreException +{ +} + abstract class Expression { // recursive translation of identifiers @@ -301,7 +306,7 @@ class VariableExpression extends UnaryExpression // recursive rendering public function Render(&$aArgs = null, $bRetrofitParams = false) { - if (is_null($aArgs) || $bRetrofitParams) + if (is_null($aArgs)) { return ':'.$this->m_sName; } @@ -309,9 +314,14 @@ class VariableExpression extends UnaryExpression { return CMDBSource::Quote($aArgs[$this->m_sName]); } + elseif ($bRetrofitParams) + { + //$aArgs[$this->m_sName] = null; + return ':'.$this->m_sName; + } else { - throw new CoreException('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs)); + throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs)); } } } diff --git a/core/filterdef.class.inc.php b/core/filterdef.class.inc.php index 371eadbd59..8a2d7d0969 100644 --- a/core/filterdef.class.inc.php +++ b/core/filterdef.class.inc.php @@ -209,7 +209,13 @@ class FilterFromAttribute extends FilterDefinition { $oAttDef = $this->Get("refattribute"); return $oAttDef->GetValuesDef(); - } + } + + public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') + { + $oAttDef = $this->Get("refattribute"); + return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); + } public function GetOperators() { @@ -235,62 +241,4 @@ class FilterFromAttribute extends FilterDefinition } } -/** - * Match against a given column (experimental -to be cleaned up later) - * - * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ - */ -class FilterDBValues extends FilterDefinition -{ - static protected function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("dbfield")); - } - - public function GetType() {return "Values from DB";} - public function GetTypeDesc() {return "Match against the existing values in a field";} - - public function GetLabel() - { - return "enum de valeurs DB"; - } - - public function GetValuesDef() - { - return null; - } - - public function GetOperators() - { - return array( - "IN"=>"in", - ); - } - public function GetLooseOperator() - { - return "IN"; - } - - public function GetFilterSQLExpr($sOpCode, $value) - { - $sFieldName = $this->Get("dbfield"); - if (is_array($value) && !empty($value)) - { - $sValueList = "'".implode("', '", $value)."'"; - return "$sFieldName IN ($sValueList)"; - } - return "1=1"; - } - - public function TemporaryGetSQLCol() - { - return $this->Get("dbfield"); - } -} - ?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 5fe13c140c..6d24d113ef 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -665,19 +665,13 @@ abstract class MetaModel public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') { $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - if (!$oAttDef) return null; - $oValSetDef = $oAttDef->GetValuesDef(); - if (!$oValSetDef) return null; - return $oValSetDef->GetValues($aArgs, $sBeginsWith); + return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); } public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') { $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); - if (!$oFltDef) return null; - $oValSetDef = $oFltDef->GetValuesDef(); - if (!$oValSetDef) return null; - return $oValSetDef->GetValues($aArgs, $sBeginsWith); + return $oFltDef->GetAllowedValues($aArgs, $sBeginsWith); } // @@ -820,6 +814,18 @@ abstract class MetaModel self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); } + + // Define defaults values for the standard ZLists + // + foreach (self::$m_aListInfos as $sListCode => $aListConfig) + { + if (!isset(self::$m_aListData[$sClass][$sListCode])) + { + $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); + self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; + //echo "

    $sClass: $sListCode (".count($aAllAttributes)." attributes)

    \n"; + } + } } } diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 25b5469208..0e8cfb21f3 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -45,6 +45,8 @@ define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions abstract class UserRightsAddOnAPI { abstract public function Setup(); // initial installation + abstract public function CreateAdministrator($sAdminUser, $sAdminPwd); // could be used during initial installation + abstract public function Init(); // loads data (possible optimizations) abstract public function CheckCredentials($sLogin, $sPassword); // returns the id of the user or false abstract public function GetUserId($sLogin); // returns the id of the user or false @@ -52,6 +54,7 @@ abstract class UserRightsAddOnAPI abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances); abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances); abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances); + abstract public function IsAdministrator($iUserId); } @@ -94,6 +97,11 @@ class UserRights self::$m_iRealUserId = 0; } + public static function GetModuleInstance() + { + return self::$m_oAddOn; + } + // Installation: create the very first user public static function CreateAdministrator($sAdminUser, $sAdminPwd) { @@ -236,6 +244,20 @@ class UserRights return self::$m_oAddOn->IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstances); } } + + public static function IsAdministrator($iUserId = null) + { + if (!self::CheckLogin()) return false; + + if (is_null($iUserId)) + { + return self::$m_oAddOn->IsAdministrator(self::$m_iUserId); + } + else + { + return self::$m_oAddOn->IsAdministrator($iUserId); + } + } } diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 2adae3aff0..393a06baac 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -19,8 +19,6 @@ abstract class ValueSetDefinition { protected $m_bIsLoaded = false; protected $m_aValues = array(); - protected $m_aArgsObj = array(); - protected $m_aArgsApp = array(); // Displayable description that could be computed out of the std usage context @@ -64,15 +62,6 @@ abstract class ValueSetDefinition return $aRet; } - public function ListArgsFromContextApp() - { - return $this->m_aArgsObj; - } - public function ListArgsFromContextObj() - { - return $this->m_aArgsApp; - } - abstract protected function LoadValues($aArgs); } @@ -109,7 +98,7 @@ class ValueSetObjects extends ValueSetDefinition if (empty($this->m_sValueAttCode)) { - $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); + $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); } $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs); @@ -127,78 +116,6 @@ class ValueSetObjects extends ValueSetDefinition } -/** - * Set of existing values for an attribute, given a search filter and a relation id - * - * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ - */ -class ValueSetRelatedObjects extends ValueSetObjects -{ - public function __construct($sFilterExp, $sRelCode, $sClass, $sValueAttCode = '', $aOrderBy = array()) - { - $sFullFilterExp = "$sClass: RELATED ($sRelCode, 1) TO ($sFilterExp)"; - parent::__construct($sFullFilterExp, $sValueAttCode, $aOrderBy); - } -} - - -/** - * Set oof existing values for an attribute, given a set of objects (AttributeLinkedSet) - * - * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ - */ -class ValueSetRelatedObjectsFromLinkedSet extends ValueSetDefinition -{ - protected $m_sLinkedSetAttCode; - protected $m_sRelCode; - protected $m_sValueAttCode; - protected $m_aOrderBy; - - public function __construct($sLinkedSetAttCode, $sRelCode, $sValueAttCode = '', $aOrderBy = array()) - { - $this->m_sLinkedSetAttCode = $sLinkedSetAttCode; - $this->m_sRelCode = $sRelCode; - $this->m_sValueAttCode = $sValueAttCode; - $this->m_aOrderBy = $aOrderBy; - } - - protected function LoadValues($aArgs) - { - $this->m_aValues = array(); - - if (empty($this->m_sValueAttCode)) - { - $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); - } - - $oCurrentObject = @$aArgs['*this*']; - if (!is_object($oCurrentObject)) return false; - - $oObjects = $oCurrentObject->Get($this->m_sLinkedSetAttCode); - while ($oObject = $oObjects->Fetch()) - { - $this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode); - } - return true; - } - - public function GetValuesDescription() - { - return 'Objects related ('.$this->m_sRelCode.') to objects linked through '.$this->m_sLinkedSetAttCode; - } -} - - /** * Fixed set values (could be hardcoded in the business model) * diff --git a/js/linkswidget.js b/js/linkswidget.js index 61f63247d6..69d8903511 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -29,7 +29,7 @@ function LinksWidget(id, sLinkedClass, sExtKeyToMe, sExtKeyToRemote, aAttributes { sLinks = JSON.stringify(this.aLinks); $('#'+this.id).val(sLinks); - $('#'+this.id+'_values').load('ajax.render.php?operation=ui.linkswidget.linkedset&sclass='+this.sLinkedClass+'&sextkeytome='+this.sExtKeyToMe, + $('#'+this.id+'_values').load('ajax.render.php?operation=ui.linkswidget.linkedset&sclass='+this.sLinkedClass+'&sextkeytome='+this.sExtKeyToMe+'&sextkeytoremote='+this.sExtKeyToRemote+'&myid='+this.id, {'sset' : sLinks}); } } diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index bb4e156473..a12ab5292a 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -96,7 +96,7 @@ EOF; $bChildItem = utils::ReadPostedParam('child_item', false); $oMenuNode->Set('label', $sDescription); $oMenuNode->Set('name', $sLabel); - $oMenuNode->Set('icon_path', '/images/std_view.gif'); + $oMenuNode->Set('icon_path', '../images/std_view.gif'); $oMenuNode->Set('template', $sMenuNodeContent); $oMenuNode->Set('hyperlink', 'UI.php'); $oMenuNode->Set('type', 'user'); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index a90059eebc..4461cef6b0 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -84,6 +84,8 @@ switch($operation) } foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) { + // MetaModel::GetAllowedValues_att() => array(id => value) + // Improvement: what if the list is too long? $oWizardHelper->SetAllowedValuesHtml($sAttCode, "Possible values ($sAttCode)"); } $oPage->add($oWizardHelper->ToJSON()); @@ -192,7 +194,9 @@ switch($operation) $sClass = utils::ReadParam('sclass', 'bizContact'); $sJSONSet = stripslashes(utils::ReadParam('sset', '')); $sExtKeyToMe = utils::ReadParam('sextkeytome', ''); - UILinksWidget::RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe); + $sExtKeyToRemote = utils::ReadParam('sextkeytoremote', ''); + $iObjectId = utils::ReadParam('id', -1); + UILinksWidget::RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe, $sExtKeyToRemote, $iObjectId); break; case 'autocomplete': @@ -208,6 +212,7 @@ switch($operation) if ($oThis = MetaModel::GetObject($sClass, $key)) { $aArgs['*this*'] = $oThis; + $aArgs['this'] = $oThis; } } $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs, $sName); diff --git a/pages/index.php b/pages/index.php index 33c4d77c0b..f8d93ec620 100644 --- a/pages/index.php +++ b/pages/index.php @@ -87,7 +87,7 @@ function DisplayDetails(web_page $oPage, $sClassName, $sKey) global $oContext; //$oObj = MetaModel::GetObject($sClassName, $sKey); $oObj = $oContext->GetObject($sClassName, $sKey); - $oPage->p("Details of ".get_class($oObj)." - $sKey"); + $oPage->p("Details of ".MetaModel::GetName($sClassName)." - $sKey"); $oObj->DisplayDetails($oPage); @@ -140,7 +140,7 @@ function DisplayChangesLog(web_page $oPage, $sClassName, $sKey) global $oContext; //$oObj = MetaModel::GetObject($sClassName, $sKey); $oObj = $oContext->GetObject($sClassName, $sKey); - $oPage->p("Changes log for ".get_class($oObj)." - $sKey"); + $oPage->p("Changes log for ".MetaModel::GetName($sClassName)." - $sKey"); $oObj->DisplayChangesLog($oPage); @@ -219,7 +219,7 @@ function DisplayEditForm(web_page $oPage, $sClassName, $sKey) $oPage->p("You are not allowed to edit this object."); return; } - $oPage->p("Edition of ".get_class($oObj)." - $sKey\n"); + $oPage->p("Edition of ".MetaModel::GetName($sClassName)." - $sKey\n"); $aDetails = array(); $oPage->add("
    \n"); @@ -332,7 +332,7 @@ function UpdateObject(web_page $oPage, $sClassName, $sKey, $aAttributes) $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); - $oPage->p(get_class($oObj)." updated\n"); + $oPage->p(MetaModel::GetName($sClassName)." updated\n"); } else { @@ -342,13 +342,14 @@ function UpdateObject(web_page $oPage, $sClassName, $sKey, $aAttributes) // By Rom // $oObj->DisplayDetails($oPage); // replaced by... - DisplayDetails($oPage, get_class($oObj), $oObj->GetKey()); + DisplayDetails($oPage, $sClassName, $sKey); $oPage->p("Return to main page"); } function DeleteObject(web_page $oPage, $sClassName, $sKey) { global $oContext; + $sClassLabel = MetaModel::GetName($sClassName); //$oObj = MetaModel::GetObject($sClassName, $sKey); $oObj = $oContext->GetObject($sClassName, $sKey); if ($oObj == null) @@ -356,7 +357,7 @@ function DeleteObject(web_page $oPage, $sClassName, $sKey) $oPage->p("You are not allowed to delete this object."); return; } - $oPage->p("Deletion of $sClassName - $sKey"); + $oPage->p("Deletion of $sClassLabel - $sKey"); if ($oObj->CheckToDelete()) { @@ -368,13 +369,13 @@ function DeleteObject(web_page $oPage, $sClassName, $sKey) $iChangeId = $oMyChange->DBInsert(); $oObj->DBDeleteTracked($oMyChange); - $oPage->p("$sClassName deleted\n"); + $oPage->p("$sClassLabel deleted\n"); } else { $oPage->p("Error: object can not be deleted!\n"); // By Rom - DisplayDetails($oPage, get_class($oObj), $oObj->GetKey()); + DisplayDetails($oPage, $sClassName, $sKey); } $oPage->p("Return to main page"); } @@ -382,7 +383,8 @@ function DeleteObject(web_page $oPage, $sClassName, $sKey) function CreateObject(web_page $oPage, $sClassName, $aAttributes) { $oObj = MetaModel::NewObject($sClassName); - $oPage->p("Creation of ".get_class($oObj)." object."); + $sClassLabel = MetaModel::GetName(get_class($oObj)); + $oPage->p("Creation of $sClassLabel object."); foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) { @@ -401,7 +403,7 @@ function CreateObject(web_page $oPage, $sClassName, $aAttributes) $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); - $oPage->p(get_class($oObj)." created\n"); + $oPage->p($sClassLabel." created\n"); // By Rom // $oObj->DisplayDetails($oPage); @@ -419,6 +421,7 @@ function CreateObject(web_page $oPage, $sClassName, $aAttributes) function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKeyToPartner, $sFilter) { global $oContext; + $sClassLabel = MetaModel::GetName($sClassName); //$oObj = MetaModel::GetObject($sClassName, $sKey); $oObj = $oContext->GetObject($sClassName, $sKey); if ($oObj == null) @@ -426,7 +429,7 @@ function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKe $oPage->p("You are not allowed to modify (create links on) this object."); return; } - $oPage->p("Creating links for $sClassName - $sKey"); + $oPage->p("Creating links for $sClassLabel - $sKey"); $oFilter = CMDBSearchFilter::unserialize($sFilter); $oPage->p("Linking to ".$oFilter->__DescribeHTML()); @@ -447,7 +450,7 @@ function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKe $iChangeId = $oMyChange->DBInsert(); $oNewLink->DBInsertTracked($oMyChange); - $oPage->p(get_class($oNewLink)." created\n"); + $oPage->p(MetaModel::GetName($sLinkClass)." created\n"); } else { @@ -595,19 +598,21 @@ switch($operation) $oPage->add("\n"); foreach( $aTopLevelClasses as $sClassName) { + $sClassLabel = MetaModel::GetName($sClassName); $oPage->add("
    "); if (count(MetaModel::GetSubclasses($sClassName)) > 0) { $sActiveSubclass = ReadParam('subclassname', ''); foreach(MetaModel::GetSubclasses($sClassName) as $sSubclassName) { + $sSubclassLabel = MetaModel::GetName($sSubclassName); //$oSearchFilter = new CMDBSearchFilter($sSubclassName); $oSearchFilter = $oContext->NewFilter($sSubclassName); $oSearchFilter ->AddCondition('org_id', $sCurrentOrganization, '='); $oPage->add("
    \n"); $oPage->add("
    \n"); - $oPage->p("$sSubclassName - ".MetaModel::GetClassDescription($sSubclassName)); + $oPage->p("$sSubclassLabel - ".MetaModel::GetClassDescription($sSubclassName)); $oPage->add("\n"); $oPage->add("\n"); $oPage->add("\n"); @@ -634,7 +639,7 @@ switch($operation) $iMatchesCount = $oSet->Count(); if ($iMatchesCount == 0) { - $oPage->p("No $sSubclassName matches these criteria."); + $oPage->p("No $sSubclassLabel matches these criteria."); $oPage->small_p("(".$oSearchFilter->__DescribeHTML().")"); } else @@ -642,7 +647,7 @@ switch($operation) $oPage->p("$iMatchesCount item(s) found."); cmdbAbstractObject::DisplaySet($oPage, $oSet); } - $oPage->p("Create a new $sSubclassName\n"); + $oPage->p("Create a new $sSubclassLabel\n"); $oPage->add("
    \n"); } } @@ -655,7 +660,7 @@ switch($operation) $oPage->add("
    \n"); $oPage->add("
    \n"); - $oPage->p("$sClassName - ".MetaModel::GetClassDescription($sClassName)); + $oPage->p("$sClassLabel - ".MetaModel::GetClassDescription($sClassName)); $oPage->add("\n"); $oPage->add("\n"); $oPage->add("\n"); @@ -681,7 +686,7 @@ switch($operation) $iMatchesCount = $oSet->Count(); if ($iMatchesCount == 0) { - $oPage->p("No $sClassName matches these criteria."); + $oPage->p("No $sClassLabel matches these criteria."); $oPage->small_p("(".$oSearchFilter->__DescribeHTML().")"); } else @@ -690,7 +695,7 @@ switch($operation) cmdbAbstractObject::DisplaySet($oPage, $oSet); $oPage->small_p("(".$oSearchFilter->__DescribeHTML().")"); } - $oPage->p("Create a new $sClassName\n"); + $oPage->p("Create a new $sClassLabel\n"); $oPage->add("
    \n"); $oPage->add("
    \n"); } diff --git a/pages/schema.php b/pages/schema.php index f3a4ba52ed..e985d19081 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -333,8 +333,8 @@ function DisplayClassDetails($oPage, $sClass) { $sValue = $oAttDef->GetDescription(); } - $sType = $oAttDef->GetType().' ('.$oAttDef->GetTypeDesc().')'; - $sOrigin = MetaModel::GetAttributeOrigin($sClass, $sAttCode); + $sType = $oAttDef->GetType().' ('.$oAttDef->GetTypeDesc().')'; + $sOrigin = MetaModel::GetAttributeOrigin($sClass, $sAttCode); $sAllowedValues = ""; $oAllowedValuesDef = $oAttDef->GetValuesDef(); $sMoreInfo = ""; diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index 8b243ff2ac..4468a0cca0 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -263,6 +263,23 @@ if ($iUser == -1) } else { + $oPage->p('

    How is it computing the user rights?

    '); + + $oPage->p('

    1st, find the profiles that apply

    '); + $oPage->p('

    Project the current object in every existing dimension

    '); + $oPage->p('

    Project the observed profile in every existing dimension (might depend on the user)

    '); + $oPage->p('

    If an overlap is found in any dimension, then the profile applies

    '); + + $oPage->p('

    2nd, interpret the profiles

    '); + $oPage->p('

    Note: granting rights for specific attributes is not fully implemented. It is still not taking into account the inheritance of rights AND the UI will not take that information into account!

    '); + $oPage->p('

    Actions: looks into URP_ActionGrant for a permission (yes or no) and goes up into the class hierarchy until an answer is found, defaults to no

    '); + $oPage->p('

    Stimuli: looks into URP_StimulusGrant for a permission (yes or no), defaults to no

    '); + + + $oPage->p('

    3rd, keep the most permissive one

    '); + $oPage->p('

    If one profile says YES, then the answer is YES

    '); + + $oUser = MetaModel::GetObject('URP_Users', $iUser); $oPage->p('

    Projections for user '.$oUser->GetName().'

    '); diff --git a/setup/data/30.profiles.xml b/setup/data/30.profiles.xml new file mode 100644 index 0000000000..85c32a0273 --- /dev/null +++ b/setup/data/30.profiles.xml @@ -0,0 +1,7 @@ + + + +Delivery Manager France +Persons in charge of the operations for French customers + + diff --git a/setup/data/31.profileprojection.xml b/setup/data/31.profileprojection.xml new file mode 100644 index 0000000000..4a16290fe3 --- /dev/null +++ b/setup/data/31.profileprojection.xml @@ -0,0 +1,15 @@ + + + +1 +100 +1;2 + + + +2 +100 +<any> + + + diff --git a/setup/data/32.actiongrant.xml b/setup/data/32.actiongrant.xml new file mode 100644 index 0000000000..6d1d836333 --- /dev/null +++ b/setup/data/32.actiongrant.xml @@ -0,0 +1,1659 @@ + + + +100 +bizOrganization +yes +read + + +100 +bizOrganization +yes +modify + + +100 +bizOrganization +yes +delete + + +100 +bizOrganization +yes +bulk read + + +100 +bizOrganization +yes +bulk modify + + +100 +bizOrganization +yes +bulk delete + + +100 +logRealObject +yes +read + + +100 +logRealObject +yes +modify + + +100 +logRealObject +yes +delete + + +100 +logRealObject +yes +bulk read + + +100 +logRealObject +yes +bulk modify + + +100 +logRealObject +yes +bulk delete + + +100 +bizContact +yes +read + + +100 +bizContact +yes +modify + + +100 +bizContact +yes +delete + + +100 +bizContact +yes +bulk read + + +100 +bizContact +yes +bulk modify + + +100 +bizContact +yes +bulk delete + + +100 +bizPerson +yes +read + + +100 +bizPerson +yes +modify + + +100 +bizPerson +yes +delete + + +100 +bizPerson +yes +bulk read + + +100 +bizPerson +yes +bulk modify + + +100 +bizPerson +yes +bulk delete + + +100 +bizTeam +yes +read + + +100 +bizTeam +yes +modify + + +100 +bizTeam +yes +delete + + +100 +bizTeam +yes +bulk read + + +100 +bizTeam +yes +bulk modify + + +100 +bizTeam +yes +bulk delete + + +100 +bizDocument +yes +read + + +100 +bizDocument +yes +modify + + +100 +bizDocument +yes +delete + + +100 +bizDocument +yes +bulk read + + +100 +bizDocument +yes +bulk modify + + +100 +bizDocument +yes +bulk delete + + +100 +bizDocVersion +yes +read + + +100 +bizDocVersion +yes +modify + + +100 +bizDocVersion +yes +delete + + +100 +bizDocVersion +yes +bulk read + + +100 +bizDocVersion +yes +bulk modify + + +100 +bizDocVersion +yes +bulk delete + + +100 +lnkDocumentRealObject +yes +read + + +100 +lnkDocumentRealObject +yes +modify + + +100 +lnkDocumentRealObject +yes +delete + + +100 +lnkDocumentRealObject +yes +bulk read + + +100 +lnkDocumentRealObject +yes +bulk modify + + +100 +lnkDocumentRealObject +yes +bulk delete + + +100 +lnkContactRealObject +yes +read + + +100 +lnkContactRealObject +yes +modify + + +100 +lnkContactRealObject +yes +delete + + +100 +lnkContactRealObject +yes +bulk read + + +100 +lnkContactRealObject +yes +bulk modify + + +100 +lnkContactRealObject +yes +bulk delete + + +100 +logInfra +yes +read + + +100 +logInfra +yes +modify + + +100 +logInfra +yes +delete + + +100 +logInfra +yes +bulk read + + +100 +logInfra +yes +bulk modify + + +100 +logInfra +yes +bulk delete + + +100 +bizLocation +yes +read + + +100 +bizLocation +yes +modify + + +100 +bizLocation +yes +delete + + +100 +bizLocation +yes +bulk read + + +100 +bizLocation +yes +bulk modify + + +100 +bizLocation +yes +bulk delete + + +100 +bizCircuit +yes +read + + +100 +bizCircuit +yes +modify + + +100 +bizCircuit +yes +delete + + +100 +bizCircuit +yes +bulk read + + +100 +bizCircuit +yes +bulk modify + + +100 +bizCircuit +yes +bulk delete + + +100 +bizInterface +yes +read + + +100 +bizInterface +yes +modify + + +100 +bizInterface +yes +delete + + +100 +bizInterface +yes +bulk read + + +100 +bizInterface +yes +bulk modify + + +100 +bizInterface +yes +bulk delete + + +100 +lnkInterfaces +yes +read + + +100 +lnkInterfaces +yes +modify + + +100 +lnkInterfaces +yes +delete + + +100 +lnkInterfaces +yes +bulk read + + +100 +lnkInterfaces +yes +bulk modify + + +100 +lnkInterfaces +yes +bulk delete + + +100 +bizDevice +yes +read + + +100 +bizDevice +yes +modify + + +100 +bizDevice +yes +delete + + +100 +bizDevice +yes +bulk read + + +100 +bizDevice +yes +bulk modify + + +100 +bizDevice +yes +bulk delete + + +100 +bizPC +yes +read + + +100 +bizPC +yes +modify + + +100 +bizPC +yes +delete + + +100 +bizPC +yes +bulk read + + +100 +bizPC +yes +bulk modify + + +100 +bizPC +yes +bulk delete + + +100 +bizServer +yes +read + + +100 +bizServer +yes +modify + + +100 +bizServer +yes +delete + + +100 +bizServer +yes +bulk read + + +100 +bizServer +yes +bulk modify + + +100 +bizServer +yes +bulk delete + + +100 +bizNetworkDevice +yes +read + + +100 +bizNetworkDevice +yes +modify + + +100 +bizNetworkDevice +yes +delete + + +100 +bizNetworkDevice +yes +bulk read + + +100 +bizNetworkDevice +yes +bulk modify + + +100 +bizNetworkDevice +yes +bulk delete + + +100 +bizInfraGroup +yes +read + + +100 +bizInfraGroup +yes +modify + + +100 +bizInfraGroup +yes +delete + + +100 +bizInfraGroup +yes +bulk read + + +100 +bizInfraGroup +yes +bulk modify + + +100 +bizInfraGroup +yes +bulk delete + + +100 +bizApplication +yes +read + + +100 +bizApplication +yes +modify + + +100 +bizApplication +yes +delete + + +100 +bizApplication +yes +bulk read + + +100 +bizApplication +yes +bulk modify + + +100 +bizApplication +yes +bulk delete + + +100 +lnkInfraGrouping +yes +read + + +100 +lnkInfraGrouping +yes +modify + + +100 +lnkInfraGrouping +yes +delete + + +100 +lnkInfraGrouping +yes +bulk read + + +100 +lnkInfraGrouping +yes +bulk modify + + +100 +lnkInfraGrouping +yes +bulk delete + + +100 +lnkClientServer +yes +read + + +100 +lnkClientServer +yes +modify + + +100 +lnkClientServer +yes +delete + + +100 +lnkClientServer +yes +bulk read + + +100 +lnkClientServer +yes +bulk modify + + +100 +lnkClientServer +yes +bulk delete + + +100 +bizPatch +yes +read + + +100 +bizPatch +yes +modify + + +100 +bizPatch +yes +delete + + +100 +bizPatch +yes +bulk read + + +100 +bizPatch +yes +bulk modify + + +100 +bizPatch +yes +bulk delete + + +100 +bizIncidentTicket +yes +read + + +100 +bizIncidentTicket +no +modify + + +100 +bizIncidentTicket +no +delete + + +100 +bizIncidentTicket +yes +bulk read + + +100 +bizIncidentTicket +no +bulk modify + + +100 +bizIncidentTicket +no +bulk delete + + +100 +lnkRelatedTicket +yes +read + + +100 +lnkRelatedTicket +yes +modify + + +100 +lnkRelatedTicket +yes +delete + + +100 +lnkRelatedTicket +yes +bulk read + + +100 +lnkRelatedTicket +yes +bulk modify + + +100 +lnkRelatedTicket +yes +bulk delete + + +100 +lnkInfraTicket +yes +read + + +100 +lnkInfraTicket +yes +modify + + +100 +lnkInfraTicket +yes +delete + + +100 +lnkInfraTicket +yes +bulk read + + +100 +lnkInfraTicket +yes +bulk modify + + +100 +lnkInfraTicket +yes +bulk delete + + +100 +lnkContactTicket +yes +read + + +100 +lnkContactTicket +yes +modify + + +100 +lnkContactTicket +yes +delete + + +100 +lnkContactTicket +yes +bulk read + + +100 +lnkContactTicket +yes +bulk modify + + +100 +lnkContactTicket +yes +bulk delete + + +100 +bizWorkgroup +yes +read + + +100 +bizWorkgroup +yes +modify + + +100 +bizWorkgroup +yes +delete + + +100 +bizWorkgroup +yes +bulk read + + +100 +bizWorkgroup +yes +bulk modify + + +100 +bizWorkgroup +yes +bulk delete + + +100 +bizContract +yes +read + + +100 +bizContract +yes +modify + + +100 +bizContract +yes +delete + + +100 +bizContract +yes +bulk read + + +100 +bizContract +yes +bulk modify + + +100 +bizContract +yes +bulk delete + + +100 +lnkInfraContract +yes +read + + +100 +lnkInfraContract +yes +modify + + +100 +lnkInfraContract +yes +delete + + +100 +lnkInfraContract +yes +bulk read + + +100 +lnkInfraContract +yes +bulk modify + + +100 +lnkInfraContract +yes +bulk delete + + +100 +lnkContactContract +yes +read + + +100 +lnkContactContract +yes +modify + + +100 +lnkContactContract +yes +delete + + +100 +lnkContactContract +yes +bulk read + + +100 +lnkContactContract +yes +bulk modify + + +100 +lnkContactContract +yes +bulk delete + + +100 +lnkDocumentContract +yes +read + + +100 +lnkDocumentContract +yes +modify + + +100 +lnkDocumentContract +yes +delete + + +100 +lnkDocumentContract +yes +bulk read + + +100 +lnkDocumentContract +yes +bulk modify + + +100 +lnkDocumentContract +yes +bulk delete + + +100 +bizChangeTicket +yes +read + + +100 +bizChangeTicket +no +modify + + +100 +bizChangeTicket +no +delete + + +100 +bizChangeTicket +yes +bulk read + + +100 +bizChangeTicket +no +bulk modify + + +100 +bizChangeTicket +no +bulk delete + + +100 +lnkInfraChangeTicket +yes +read + + +100 +lnkInfraChangeTicket +yes +modify + + +100 +lnkInfraChangeTicket +yes +delete + + +100 +lnkInfraChangeTicket +yes +bulk read + + +100 +lnkInfraChangeTicket +yes +bulk modify + + +100 +lnkInfraChangeTicket +yes +bulk delete + + +100 +lnkContactChange +yes +read + + +100 +lnkContactChange +yes +modify + + +100 +lnkContactChange +yes +delete + + +100 +lnkContactChange +yes +bulk read + + +100 +lnkContactChange +yes +bulk modify + + +100 +lnkContactChange +yes +bulk delete + + +100 +bizKnownError +yes +read + + +100 +bizKnownError +yes +modify + + +100 +bizKnownError +yes +delete + + +100 +bizKnownError +yes +bulk read + + +100 +bizKnownError +yes +bulk modify + + +100 +bizKnownError +yes +bulk delete + + +100 +lnkInfraError +yes +read + + +100 +lnkInfraError +yes +modify + + +100 +lnkInfraError +yes +delete + + +100 +lnkInfraError +yes +bulk read + + +100 +lnkInfraError +yes +bulk modify + + +100 +lnkInfraError +yes +bulk delete + + +100 +lnkDocumentError +yes +read + + +100 +lnkDocumentError +yes +modify + + +100 +lnkDocumentError +yes +delete + + +100 +lnkDocumentError +yes +bulk read + + +100 +lnkDocumentError +yes +bulk modify + + +100 +lnkDocumentError +yes +bulk delete + + +100 +AuditCategory +yes +read + + +100 +AuditCategory +yes +modify + + +100 +AuditCategory +yes +delete + + +100 +AuditCategory +yes +bulk read + + +100 +AuditCategory +yes +bulk modify + + +100 +AuditCategory +yes +bulk delete + + +100 +AuditRule +yes +read + + +100 +AuditRule +yes +modify + + +100 +AuditRule +yes +delete + + +100 +AuditRule +yes +bulk read + + +100 +AuditRule +yes +bulk modify + + +100 +AuditRule +yes +bulk delete + + +100 +menuNode +yes +read + + +100 +menuNode +yes +modify + + +100 +menuNode +yes +delete + + +100 +menuNode +yes +bulk read + + +100 +menuNode +yes +bulk modify + + +100 +menuNode +yes +bulk delete + + +100 +CMDBChange +yes +read + + +100 +CMDBChange +yes +modify + + +100 +CMDBChange +yes +delete + + +100 +CMDBChange +yes +bulk read + + +100 +CMDBChange +yes +bulk modify + + +100 +CMDBChange +yes +bulk delete + + +100 +CMDBChangeOp +yes +read + + +100 +CMDBChangeOp +yes +modify + + +100 +CMDBChangeOp +yes +delete + + +100 +CMDBChangeOp +yes +bulk read + + +100 +CMDBChangeOp +yes +bulk modify + + +100 +CMDBChangeOp +yes +bulk delete + + +100 +CMDBChangeOpCreate +yes +read + + +100 +CMDBChangeOpCreate +yes +modify + + +100 +CMDBChangeOpCreate +yes +delete + + +100 +CMDBChangeOpCreate +yes +bulk read + + +100 +CMDBChangeOpCreate +yes +bulk modify + + +100 +CMDBChangeOpCreate +yes +bulk delete + + +100 +CMDBChangeOpDelete +yes +read + + +100 +CMDBChangeOpDelete +yes +modify + + +100 +CMDBChangeOpDelete +yes +delete + + +100 +CMDBChangeOpDelete +yes +bulk read + + +100 +CMDBChangeOpDelete +yes +bulk modify + + +100 +CMDBChangeOpDelete +yes +bulk delete + + +100 +CMDBChangeOpSetAttribute +yes +read + + +100 +CMDBChangeOpSetAttribute +yes +modify + + +100 +CMDBChangeOpSetAttribute +yes +delete + + +100 +CMDBChangeOpSetAttribute +yes +bulk read + + +100 +CMDBChangeOpSetAttribute +yes +bulk modify + + +100 +CMDBChangeOpSetAttribute +yes +bulk delete + + diff --git a/setup/data/33.attributegrant.xml b/setup/data/33.attributegrant.xml new file mode 100644 index 0000000000..cf14611677 --- /dev/null +++ b/setup/data/33.attributegrant.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/setup/data/34.stimulusgrant.xml b/setup/data/34.stimulusgrant.xml new file mode 100644 index 0000000000..47e7e7ecdb --- /dev/null +++ b/setup/data/34.stimulusgrant.xml @@ -0,0 +1,189 @@ + + + +100 +bizServer +no +ev_store + + +100 +bizServer +no +ev_ship + + +100 +bizServer +no +ev_plug + + +100 +bizServer +no +ev_configuration_finished + + +100 +bizServer +no +ev_val_failed + + +100 +bizServer +no +ev_mtp + + +100 +bizServer +no +ev_start_change + + +100 +bizServer +no +ev_end_change + + +100 +bizServer +no +ev_decommission + + +100 +bizServer +no +ev_obsolete + + +100 +bizServer +no +ev_recycle + + +100 +bizIncidentTicket +no +ev_assign + + +100 +bizIncidentTicket +no +ev_reassign + + +100 +bizIncidentTicket +no +ev_start_working + + +100 +bizIncidentTicket +no +ev_close + + +100 +bizContract +no +ev_freeze_version + + +100 +bizContract +no +ev_sign + + +100 +bizContract +no +ev_begin + + +100 +bizContract +no +ev_notice + + +100 +bizContract +no +ev_terminate + + +100 +bizContract +no +ev_elapsed + + +100 +bizChangeTicket +yes +ev_validate + + +100 +bizChangeTicket +yes +ev_reject + + +100 +bizChangeTicket +no +ev_reopen + + +100 +bizChangeTicket +no +ev_plan + + +100 +bizChangeTicket +yes +ev_approve + + +100 +bizChangeTicket +no +ev_replan + + +100 +bizChangeTicket +yes +ev_notapprove + + +100 +bizChangeTicket +no +ev_implement + + +100 +bizChangeTicket +no +ev_monitor + + +100 +bizChangeTicket +yes +ev_finish + + diff --git a/setup/data/export.cmd b/setup/data/export.cmd index d7200a3154..5e54a4dec4 100644 --- a/setup/data/export.cmd +++ b/setup/data/export.cmd @@ -1,6 +1,8 @@ SET WEBROOT=http://localhost:81 -SET USER=Erwan -SET PWD=Taloc +SET EXPORT=%WEBROOT%/webservices/export.php + +SET USER=admin +SET PWD=admin REM The order (numbering) of the files is important since REM it dictates the order to import them back diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index 756cfd4ba9..cfea4a7928 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -1,25 +1,25 @@ - -0 -Admin Tools - + +Tools + UI.php + -7 application +7 +0 +0 - -5 + All Applications UI.php + -999 application +999 +14 +0 - -5 + All Circuits UI.php + -999 application +999 +14 +0 - -64 + All Contracts ./UI.php + -2 application +2 +31 +0 - -5 + All Interfaces UI.php + -999 application +999 +14 +0 - -5 + All Network devices UI.php + -999 application +999 +14 +0 - -5 + All Patches UI.php + -999 application +999 +14 +0 - -5 + All PCs UI.php + -999 application +999 +14 +0 - -5 + All Servers UI.php + -999 application +999 +14 +0 - -1 + Audit ./audit.php + -4 application +4 +15 +0 - -0 + Change Management ./UI.php + -4 application +4 +0 +0 - -66 + Closed Changes UI.php + -2 application +2 +11 +0 - -61 + Closed Incident ./UI.php + -2 application +2 +22 +0 - -1 + Configuration Items UI.php + -2 application +2 +15 +0 - -0 + Configuration Management UI.php + -2 application +2 +0 +0 - -1 + Contacts UI.php - +application +999 +14 +0 + + +All Subnets + +UI.php + + application From e2d60e7c8f5a2b639b1db3e3f1f9354974864c5b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 11 Sep 2009 20:35:16 +0000 Subject: [PATCH 073/970] Implementation of AllowedValues for fields that depend on other fields SVN:trunk[148] --- application/cmdbabstract.class.inc.php | 12 +++++----- application/uiwizard.class.inc.php | 1 + application/wizardhelper.class.inc.php | 12 +++++++++- js/forms-json-utils.js | 11 ++++++++- js/wizardhelper.js | 31 +++++++++++++++++++++++++- pages/UI.php | 2 ++ pages/ajax.render.php | 22 ++++++++---------- 7 files changed, 69 insertions(+), 22 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 10664f92d9..146f88e42f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -601,7 +601,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sHtml; } - public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0) + public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array()) { static $iInputId = 0; if (!empty($iId)) @@ -630,7 +630,7 @@ abstract class cmdbAbstractObject extends CMDBObject break; case 'Password': - $sHTMLValue = ""; + $sHTMLValue = ""; break; case 'Text': @@ -645,19 +645,19 @@ abstract class cmdbAbstractObject extends CMDBObject case 'String': default: // #@# todo - add context information (depending on dimensions) - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array()); + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); if ($aAllowedValues !== null) { //Enum field or external key, display a combo if (count($aAllowedValues) == 0) { - $sHTMLValue = ""; + $sHTMLValue = ""; } else if (count($aAllowedValues) > 50) { // too many choices, use an autocomplete // The input for the auto complete - $sHTMLValue = ""; + $sHTMLValue = ""; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); @@ -678,7 +678,7 @@ abstract class cmdbAbstractObject extends CMDBObject } else { - $sHTMLValue = ""; + $sHTMLValue = ""; } break; } diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 886b8fd395..f49228dbcc 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -89,6 +89,7 @@ class UIWizard function OnEnterStep{$iStepIndex}() { oWizardHelper.ResetQuery(); + oWizardHelper.UpdateWizard(); $sJSHandlerCode diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 3ab7b5c1cd..578932e181 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -60,7 +60,7 @@ class WizardHelper $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); $oObj->Set($sAttCode, $oSet); } - else if (($oAttDef->IsExternalKey()) && ($value != '') ) + else if (($oAttDef->IsExternalKey()) && (!empty($value)) ) { // For external keys: load the target object so that external fields // get filled too @@ -176,6 +176,16 @@ class WizardHelper return $aFields; } + public function GetTargetClass() + { + return $this->m_aData['m_sClass']; + } + + public function GetIdForField($sFieldName) + { + return $this->m_aData['m_oFieldsMap'][$sFieldName]; + } + static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet) { $aSet = json_decode($sJsonSet, true); // true means hash array instead of object diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index 1c6b5b1e30..60754bd078 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -57,7 +57,16 @@ function ReloadObjectFromServer(sJSON) function GoToStep(iCurrentStep, iNextStep) { var oCurrentStep = document.getElementById('wizStep'+iCurrentStep); - if (CheckMandatoryFields('wizStep'+iCurrentStep)) + if (iNextStep > iCurrentStep) + { + // Check the values when moving forward + if (CheckMandatoryFields('wizStep'+iCurrentStep)) + { + oCurrentStep.style.display = 'none'; + ActivateStep(iNextStep); + } + } + else { oCurrentStep.style.display = 'none'; ActivateStep(iNextStep); diff --git a/js/wizardhelper.js b/js/wizardhelper.js index e1b50bcba5..89377316b5 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -9,6 +9,7 @@ function WizardHelper(sClass) 'm_aAllowedValuesRequested': [], 'm_aDefaultValue': [], 'm_aAllowedValues': [], + 'm_iFieldsCount' : 0, }; this.m_oData.m_sClass = sClass; @@ -18,6 +19,12 @@ function WizardHelper(sClass) this.m_oData.m_oFieldsMap = oFieldsMap; } + this.SetFieldsCount = function (count) + { + this.m_oData.m_iFieldsCount = count; + + } + this.RequestDefaultValue = function (sFieldName) { currentValue = this.UpdateCurrentValue(sFieldName); @@ -50,12 +57,20 @@ function WizardHelper(sClass) this.ResetQuery = function () { this.m_oData.m_aDefaultValueRequested = []; + this.m_oData.m_aDefaultValue = []; this.m_oData.m_aAllowedValuesRequested = []; + this.m_oData.m_aAllowedValues = []; } this.UpdateFields = function () { //console.log('** UpdateFields **') + for(i=0; i< this.m_oData.m_aAllowedValuesRequested.length; i++) + { + sAttCode = this.m_oData.m_aAllowedValuesRequested[i]; + sFieldId = this.m_oData.m_oFieldsMap[sAttCode]; + $('#field_'+sFieldId).html(this.m_oData.m_aAllowedValues[sFieldId]); + } for(i=0; i< this.m_oData.m_aDefaultValueRequested.length; i++) { sAttCode = this.m_oData.m_aDefaultValueRequested[i]; @@ -67,10 +82,24 @@ function WizardHelper(sClass) } } + this.UpdateWizard = function () + { + //console.log('** UpdateWizard **') + for(i=0; i< this.m_oData.m_iFieldsCount; i++) + { + value = $('#att_'+i).val(); + if (value == '') + { + value = null; + } + this.m_oData.m_aCurrentValues[i] = value; + } + } + this.AjaxQueryServer = function () { //console.log('data sent:', this.ToJSON()); - //console.log('oWizard:', this); + console.log('oWizard:', this); $.get('ajax.render.php?json_obj=' + this.ToJSON(), { operation: 'wizard_helper' }, function(json_data){ diff --git a/pages/UI.php b/pages/UI.php index ec6136aaca..0de789f0d8 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -368,12 +368,14 @@ switch($operation) { $aNewFieldsMap[$sFieldCode] = $id; } + $iFieldsCount = count($aFieldsMap); $sJsonFieldsMap = json_encode($aNewFieldsMap); $oP->add(" // Initializes the object once at the beginning of the page... var oWizardHelper = new WizardHelper('$sClass'); oWizardHelper.SetFieldsMap($sJsonFieldsMap); + oWizardHelper.SetFieldsCount($iFieldsCount); ActivateStep(1); \n"); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 9d4365f76d..b5e892a448 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -77,25 +77,21 @@ switch($operation) $sJson = utils::ReadParam('json_obj', ''); $oWizardHelper = WizardHelper::FromJSON($sJson); $oObj = $oWizardHelper->GetTargetObject(); + $sClass = $oWizardHelper->GetTargetClass(); foreach($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode) { - $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); - $oWizardHelper->SetDefaultValue($sAttCode, $oAttDef->GetDefaultValue()); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $defaultValue = $oAttDef->GetDefaultValue(); + $oWizardHelper->SetDefaultValue($sAttCode, $defaultValue); + $oObj->Set($sAttCode, $defaultValue); } foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) { - $aAllowedValues = MetaModel::GetAllowedValues_att(get_class($oObj), $sAttCode, array('this' => $oObj)); - // Few choices, use a normal 'select' - $sHTMLValue = "\n"; + $sId = $oWizardHelper->GetIdForField($sAttCode); + $value = $oObj->Get($sAttCode); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, '', 'att_'.$sId, '', 0, array('this' => $oObj)); - // Improvement: what if the list is too long? $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } $oPage->add($oWizardHelper->ToJSON()); From 3c8f7cf08ab7ea65fa11088f10a974f1d0e42704 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 11 Sep 2009 20:36:34 +0000 Subject: [PATCH 074/970] - Automatic adjustment of the width and height of the ModalDlg to fit the document's size SVN:trunk[149] --- application/uilinkswizard.class.inc.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index ff56db516d..7fdc3a5081 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -115,6 +115,8 @@ class UILinksWizard function(data) { $('#ModalDlg').html(data); + dlgWidth = $(document).width() - 100; + $('#ModalDlg').css('width', dlgWidth); $('#ModalDlg').jqmShow(); }, 'html' @@ -149,8 +151,13 @@ class UILinksWizard nb_rows = $('#SearchResultsToAdd table.listResults tr').length; if(nb_rows > 10) { - $('#SearchResultsToAdd table.listResults tbody').attr('height', '300px'); - $('#SearchResultsToAdd .listResults tbody').css('overflow', 'auto'); + yOffset = $('#ModalDlg').height() - $('#SearchResultsToAdd table.listResults tbody').height(); + tbodyHeight = $(document).height() - 100 - yOffset; + if ($('#ModalDlg').height() > ($(document).height() - 100)) + { + $('#SearchResultsToAdd table.listResults tbody').attr('height', tbodyHeight); + $('#SearchResultsToAdd .listResults tbody').css('overflow', 'auto'); + } } }, From e4f4b66722633fe7eac19151e5d7c1961903fe43 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 11 Sep 2009 21:20:45 +0000 Subject: [PATCH 075/970] Field validation now preserves background images SVN:trunk[150] --- js/forms-json-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index 60754bd078..2a0140fff9 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -127,7 +127,7 @@ function CheckMandatoryFields(sFormId) $('#'+sFormId+' :input.mandatory').each( function() { if (( this.value == '') || (this.value == 0)) { - this.style.background = '#fcc'; + this.style.backgroundColor = '#fcc'; iErrorsCount++; if (iErrorsCount == 1) { @@ -136,7 +136,7 @@ function CheckMandatoryFields(sFormId) } else { - this.style.background = '#fff'; + this.style.backgroundColor = '#fff'; } } ); From 61df1d7babea6877ac987b17561606a2bde5ca59 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 11 Sep 2009 21:23:24 +0000 Subject: [PATCH 076/970] - 'Add...' butotn is now grayed when there is nothing to Add - Better handling of AllowedValues, supporting autocompletes - Cleanup unused code in wizard.utils.js SVN:trunk[151] --- application/ui.linkswidget.class.inc.php | 15 ++++++++++----- css/jqModal.css | 11 ++++++----- js/linkswidget.js | 4 ++++ js/wizard.utils.js | 17 ----------------- js/wizardhelper.js | 2 +- pages/ajax.render.php | 2 +- 6 files changed, 22 insertions(+), 29 deletions(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 929403adf2..e2e5aa72df 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -75,14 +75,15 @@ class UILinksWidget $sHTMLValue .= "\n"; $sHTMLValue .= $this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk'); $sHTMLValue .= $this->GetLinkObjectDialog($oPage, $this->m_iInputId); - $sHTMLValue .= "m_iInputId}\" size=\"35\" name=\"\" value=\"\" title=\"Type the first 3 characters\" />"; - $sHTMLValue .= "m_iInputId}.AddObject();\"/>"; + $sHTMLValue .= "m_iInputId}\" size=\"35\" name=\"\" value=\"\" title=\"Type the first 3 characters\"/>"; + $sHTMLValue .= "m_iInputId}\" value=\" Add... \" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; // another hidden input to store & pass the object's Id - $sHTMLValue .= "m_iInputId}\"/>\n"; + $sHTMLValue .= "m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n"; $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n"; $oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\noLinkWidget{$this->m_iInputId}.Init();\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});"); - $oPage->add_ready_script("\$('#ac_{$this->m_iInputId}').result( function(event, data, formatted) { if (data) { $('#id_ac_{$this->m_iInputId}').val(data[1]); } } );"); + $oPage->add_ready_script("\$('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled');"); + $oPage->add_ready_script("\$('#ac_{$this->m_iInputId}').result( function(event, data, formatted) { if (data) { $('#id_ac_{$this->m_iInputId}').val(data[1]); $('#ac_add_{$this->m_iInputId}').attr('disabled', ''); } else { $('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled'); } } );"); } else { @@ -221,6 +222,7 @@ class UILinksWidget { $sHTML = <<< EOF
    +
    "; + $sHTMLValue = ""; break; case 'Text': - $sHTMLValue = ""; + $sHTMLValue = ""; break; case 'List': - $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId); + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sNameSuffix); $sHTMLValue = $oWidget->Display($oPage, $value); break; @@ -549,7 +594,7 @@ abstract class cmdbAbstractObject extends CMDBObject //Enum field or external key, display a combo if (count($aAllowedValues) == 0) { - $sHTMLValue = ""; + $sHTMLValue = ""; } else if (count($aAllowedValues) > 50) { @@ -557,13 +602,13 @@ abstract class cmdbAbstractObject extends CMDBObject // The input for the auto complete $sHTMLValue = ""; // another hidden input to store & pass the object's Id - $sHTMLValue .= "\n"; + $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); } else { // Few choices, use a normal 'select' - $sHTMLValue = "\n"; foreach($aAllowedValues as $key => $display_value) { $sSelected = ($value == $key) ? ' selected' : ''; @@ -574,7 +619,7 @@ abstract class cmdbAbstractObject extends CMDBObject } else { - $sHTMLValue = ""; + $sHTMLValue = ""; } } } diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 33e639952d..8eebeeae07 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -85,11 +85,15 @@ class DisplayBlock { $sEncoding = strtolower($aMatches[1]); } - if (preg_match('/ linkage="(.*)"/U',$sITopTag, $aMatches)) + if (preg_match('/ link_attr="(.*)"/U',$sITopTag, $aMatches)) { // The list to display is a list of links to the specified object - $sExtKey = strtolower($aMatches[1]); - $aParams['linkage'] = $sExtKey; // Name of the Ext. Key that make this linkage + $aParams['link_attr'] = $aMatches[1]; // Name of the Ext. Key that make this linkage + } + if (preg_match('/ object_id="(.*)"/U',$sITopTag, $aMatches)) + { + // The list to display is a list of links to the specified object + $aParams['object_id'] = $aMatches[1]; // Id of the object to be linked to } // Parameters contains a list of extra parameters for the block // the syntax is param_name1:value1;param_name2:value2;... @@ -106,6 +110,21 @@ class DisplayBlock } } } + if (!empty($aParams['link_attr'])) + { + // Check that all mandatory parameters are present: + if(empty($aParams['object_id'])) + { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException("Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template."); + } + if(empty($aParams['target_attr'])) + { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException("Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template."); + } + + } switch($sEncoding) { case 'text/serialize': @@ -245,17 +264,16 @@ class DisplayBlock break; case 'list': - $bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false; if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) { - $sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : ''; - $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $sLinkage, !$bDashboardMode /* bDisplayMenu */); + $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams); } else { $sHtml .= $oPage->GetP("No object to display."); $sClass = $this->m_oFilter->GetClass(); - if (!$bDashboardMode) + $bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true; + if ($bDisplayMenu) { if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES) { @@ -265,12 +283,37 @@ class DisplayBlock } break; + case 'links': + //$bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false; + //$bSelectMode = isset($aExtraParams['select']) ? ($aExtraParams['select'] == 'true') : false; + if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) + { + //$sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : ''; + $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams); + } + else + { + $sHtml .= $oPage->GetP("No object to display."); + $sClass = $this->m_oFilter->GetClass(); + $bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true; + if ($bDisplayMenu) + { + if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $this->m_aParams['target_attr']); + $sTargetClass = $oAttDef->GetTargetClass(); + $sHtml .= $oPage->GetP("Click here to add new ".Metamodel::GetName($sTargetClass)."s\n"); + } + } + } + break; + case 'details': if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) { while($oObj = $this->m_oSet->Fetch()) { - $sHtml .= $oObj->GetDetails($oPage); + $sHtml .= $oObj->GetDetails($oPage); // Still used ??? } } break; @@ -311,7 +354,7 @@ class DisplayBlock $oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); $sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams); $sHtml .= "\n"; - $sHtml .= "
    \n"; + $sHtml .= "
    \n"; $sHtml .= "
    Search
    \n"; break; @@ -635,12 +678,15 @@ class MenuBlock extends DisplayBlock $bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); // Just one object in the set, possible actions are "new / clone / modify and delete" - if (isset($aExtraParams['linkage'])) + if (isset($aExtraParams['link_attr'])) { - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add #...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone #...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); } - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify #...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } - if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove #', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } + $id = $aExtraParams['object_id']; + $sTargetAttr = $aExtraParams['target_attr']; + $oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr); + $sTargetClass = $oAttDef->GetTargetClass(); + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Manage...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); } + if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } } else { @@ -681,14 +727,17 @@ class MenuBlock extends DisplayBlock $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); $bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); - if (isset($aExtraParams['linkage'])) + if (isset($aExtraParams['link_attr'])) { + $id = $aExtraParams['object_id']; + $sTargetAttr = $aExtraParams['target_attr']; + $oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr); + $sTargetClass = $oAttDef->GetTargetClass(); $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add #...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } - if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove #', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify #...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } - if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All #...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } - if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All #...', 'url' => "#"); } + if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); } + //if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&linkage=".$aExtraParams['linkage']."&id=$id&addObjects=true&$sContext"); } + if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Manage...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); } + if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#"); } } else { diff --git a/application/template.class.inc.php b/application/template.class.inc.php index db768c3af1..6436dfc521 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -134,7 +134,7 @@ class DisplayTemplate break; case 'itoptab': - $oPage->SetCurrentTab($aAttributes['name']); + $oPage->SetCurrentTab(str_replace('_', ' ', $aAttributes['name'])); $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied //$oPage->p('iTop Tab Content:
    '.htmlentities($sContent).'
    '); @@ -153,9 +153,22 @@ class DisplayTemplate $sBlockClass = $aAttributes['blockclass']; $sBlockType = $aAttributes['type']; $aExtraParams = array(); - if (isset($aAttributes['linkage'])) + if (isset($aAttributes['link_attr'])) { - $aExtraParams['linkage'] = $aAttributes['linkage']; + $aExtraParams['link_attr'] = $aAttributes['link_attr']; + // Check that all mandatory parameters are present: + if(empty($aAttributes['object_id'])) + { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException("Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template."); + } + if(empty($aAttributes['target_attr'])) + { + // if 'links' mode is requested the d of the object to link to must be specified + throw new ApplicationException("Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template."); + } + $aExtraParams['object_id'] = $aAttributes['object_id']; + $aExtraParams['target_attr'] = $aAttributes['target_attr']; } switch($aAttributes['encoding']) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 31be4bef4a..1db8517e53 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -6,12 +6,14 @@ class UILinksWidget { protected $m_sClass; protected $m_sAttCode; + protected $m_sNameSuffix; protected $m_iInputId; - public function __construct($sClass, $sAttCode, $iInputId) + public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '') { $this->m_sClass = $sClass; $this->m_sAttCode = $sAttCode; + $this->m_sNameSuffix = $sNameSuffix; $this->m_iInputId = $iInputId; } @@ -76,7 +78,7 @@ class UILinksWidget $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; // another hidden input to store & pass the object's Id $sHTMLValue .= "m_iInputId}\"/>\n"; - $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}\" value=\"\"/>\n"; + $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n"; $oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});"); } else diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php new file mode 100644 index 0000000000..3a4cb450b0 --- /dev/null +++ b/application/uilinkswizard.class.inc.php @@ -0,0 +1,380 @@ +m_sClass = $sClass; + $this->m_sLinkageAttr = $sLinkageAttr; + $this->m_iObjectId = $iObjectId; + $this->m_sLinkedClass = $sLinkedClass; // Will try to guess below, if it's empty + $this->m_sLinkingAttCode = ''; // Will be filled once we've found the attribute corresponding to the linked class + + $this->m_aEditableFields = array(); + $this->m_aTableConfig = array(); + $this->m_aTableConfig['form::checkbox'] = array( 'label' => "", 'description' => "Select / Deselect All"); + foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + if ($oAttDef->IsExternalKey() && ($sAttCode != $this->m_sLinkageAttr)) + { + if (empty($this->m_sLinkedClass)) + { + // This is a class of objects we can manage ! + // Since nothing was specify, any class will do ! + $this->m_sLinkedClass = $oAttDef->GetTargetClass(); + $this->m_sLinkingAttCode = $sAttCode; + } + else if ($this->m_sLinkedClass == $oAttDef->GetTargetClass()) + { + // This is the class of objects we want to manage ! + $this->m_sLinkingAttCode = $sAttCode; + } + } + else if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField())) + { + $this->m_aEditableFields[] = $sAttCode; + $this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); + } + } + if (empty($this->m_sLinkedClass)) + { + throw( new Exception("Incorrect link definition: the class of objects to manage: '$sLinkedClass' was not found as an external key in the class '$sClass'")); + } + foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); + $this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); + } + } + + public function Display(web_page $oP, UserContext $oContext, $aExtraParams = array()) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); + $sTargetClass = $oAttDef->GetTargetClass(); + $oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); + + $oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetName()); + $oP->add("
    \n"); + $oP->add("\n"); + $oP->add("
    \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("m_sClass}\">\n"); + $oP->add("m_sLinkageAttr}\">\n"); + $oP->add("m_iObjectId}\">\n"); + $oP->add("m_sLinkingAttCode}\">\n"); + $oP->add("

    Manage ".MetaModel::GetName($this->m_sLinkedClass)."s linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetHyperlink()."

    \n"); + $oP->add("
    \n"); + $oP->add("\n"); + $oP->add_ready_script("InitForm();"); + $oFilter = $oContext->NewFilter($this->m_sClass); + $oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '='); + $oSet = new DBObjectSet($oFilter); + $aForm = array(); + while($oCurrentLink = $oSet->Fetch()) + { + $aRow = array(); + $key = $oCurrentLink->GetKey(); + $oLinkedObj = $oContext->GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode)); + + $aForm[$key] = $this->GetFormRow($oP, $oLinkedObj, $oCurrentLink); + } + //var_dump($aTableLabels); + //var_dump($aForm); + $this->DisplayFormTable($oP, $this->m_aTableConfig, $aForm); + $oP->add("     m_sLinkedClass)."s \" onClick=\"RemoveSelected();\" >"); + $oP->add("   m_sLinkedClass)."s... \" onClick=\"AddObjects();\">\n"); + $oP->add(""); + $oP->add("   \n"); + $oP->add("

     

    \n"); + $oP->add("
    \n"); + $oP->add("\n"); + if (isset($aExtraParams['StartWithAdd']) && ($aExtraParams['StartWithAdd'])) + { + $oP->add_ready_script("AddObjects();"); + } + } + + protected function GetFormRow($oP, $oLinkedObj, $currentLink = null ) + { + $aRow = array(); + if(is_object($currentLink)) + { + $key = $currentLink->GetKey(); + $sNameSuffix = "[$key]"; // To make a tabular form + $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] .= ""; + foreach($this->m_aEditableFields as $sFieldCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode); + $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, $currentLink->Get($sFieldCode), '' /* DisplayValue */, $key, $sNameSuffix); + } + } + else + { + // form for creating a new record + $sNameSuffix = "[$currentLink]"; // To make a tabular form + $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] .= ""; + foreach($this->m_aEditableFields as $sFieldCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode); + $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue */, '' /* DisplayValue */, '' /* id */, $sNameSuffix); + } + } + foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode) + { + $aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode); + } + return $aRow; + } + + protected function DisplayFormTable(web_page $oP, $aConfig, $aData) + { + $oP->add("\n"); + // Header + $oP->add("\n"); + $oP->add("\n"); + foreach($aConfig as $sName=>$aDef) + { + $oP->add("\n"); + } + $oP->add("\n"); + $oP->add("\n"); + + // Content + $oP->add("\n"); + if (count($aData) == 0) + { + $oP->add(""); + } + else + { + foreach($aData as $iRowId => $aRow) + { + $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId); + } + } + $oP->add("\n"); + + // Footer + $oP->add("
    ".$aDef['label']."
    The list is empty, use 'Add...' to add elements.
    \n"); + } + + protected function DisplayFormRow(web_page $oP, $aConfig, $aRow, $iRowId) + { + $oP->add("
    ".$aRow[$sName]."
    $sValue$sValue
    @@ -248,6 +250,7 @@ class UILinksWidget
    +
    EOF; $oPage->add_ready_script("$('#ManageObjectsDlg_$this->m_iInputId').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window //$oPage->add_ready_script("UpdateObjectList('$sClass');"); @@ -265,6 +268,7 @@ EOF; $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); $sHTML = "
    \n"; + $sHTML .= "
    \n"; $sHTML .= "

    $sLinkedClass attributes

    \n"; $sHTML .= "\n"; $index = 0; @@ -306,9 +310,10 @@ EOF; } } $sHTML .= $oPage->GetDetails($aDetails); - $sHTML .= "     \n"; + $sHTML .= "     \n"; $sHTML .= "\n"; $sHTML .= "
    \n"; + $sHTML .= "
    \n"; $oPage->add_ready_script("$('#LinkDlg_$sId').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window //$oPage->add_ready_script("UpdateObjectList('$sClass');"); return $sHTML; diff --git a/css/jqModal.css b/css/jqModal.css index db45b7039b..288981f2a5 100644 --- a/css/jqModal.css +++ b/css/jqModal.css @@ -11,11 +11,12 @@ display: none; position: fixed; - top: 10%; - left: 20%; + top: 25px; + left: 25px; - margin-left: -100px; - width: 800px; + nomargin-left: 25px; + nomargin-right: 25px; + nowidth: 800px; background-color: #FFF; color: #333; @@ -37,4 +38,4 @@ * removed.html .jqmWindow { position: absolute; top: expression((document.documentElement.scrollTop || document.body.scrollTop) + Math.round(17 * (document.documentElement.offsetHeight || document.body.clientHeight) / 100) + 'px'); -} \ No newline at end of file +} diff --git a/js/linkswidget.js b/js/linkswidget.js index 47f65fc3bc..d7027f77e2 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -74,12 +74,16 @@ function LinksWidget(id, sLinkedClass, sExtKeyToMe, sExtKeyToRemote, aAttributes this.aLinks.push(oLink); } this.Refresh(); + // Grey out the 'Add...' button + $('#ac_add_'+this.id).attr('disabled', 'disabled'); } this.OnLinkCancel = function() { // Restore the links to their previous value (just in case) this.aLinks = this.aPreviousLinks; + // Grey out the 'Add...' button + $('#ac_add_'+this.id).attr('disabled', 'disabled'); } this.RemoveLink = function(index) diff --git a/js/wizard.utils.js b/js/wizard.utils.js index 21cdd6e779..dc58f8b5f7 100644 --- a/js/wizard.utils.js +++ b/js/wizard.utils.js @@ -24,22 +24,6 @@ function AddObject(sClass, sId, sExtKeyToRemote) // Display the additional dialog $('#LinkDlg_'+sId).jqmShow(); return; - - // TO BE REWRITTEN - var sRelatedObjectIds = new String($('#related_object_ids_'+sId).val()); - if (sRelatedObjectIds.length > 0) - { - aRelatedObjectIds = sRelatedObjectIds.split(' '); - } - else - { - aRelatedObjectIds = new Array(); - } - // To do: check if the ID is not already in the list... - aRelatedObjectIds[aRelatedObjectIds.length] = sCurrentObjectId; - // Update the form & reload the list - $('#related_object_ids').val(aRelatedObjectIds.join(' ')); - UpdateObjectList(sClass, sExtKeyToRemote); } function ManageObjects(sTitle, sClass, sId, sExtKeyToRemote) @@ -158,6 +142,5 @@ function GetObjectIds(sInputId, sExtKeyToRemote) } } return aLinkedIds; - } diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 89377316b5..57ff917614 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -104,7 +104,7 @@ function WizardHelper(sClass) { operation: 'wizard_helper' }, function(json_data){ //console.log('data received:', json_data); - oWizardHelper.FromJSON(json_data); + //oWizardHelper.FromJSON(json_data); oWizardHelper.UpdateFields(); //console.log(oWizardHelper); $('#wizStep'+ G_iCurrentStep).unblock( {fadeOut: 0} ); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index b5e892a448..c43924d601 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -94,7 +94,7 @@ switch($operation) $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } - $oPage->add($oWizardHelper->ToJSON()); + $oPage->add("\n"); break; case 'ajax': From b98b1219eeffb4ac914e86fb89d8268632eb9fc1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 12 Sep 2009 10:08:13 +0000 Subject: [PATCH 077/970] - Removed debug output into the console thats breaks when Firebug is not installed (or on IE) SVN:trunk[152] --- js/wizardhelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 57ff917614..0315715925 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -99,7 +99,7 @@ function WizardHelper(sClass) this.AjaxQueryServer = function () { //console.log('data sent:', this.ToJSON()); - console.log('oWizard:', this); + //console.log('oWizard:', this); $.get('ajax.render.php?json_obj=' + this.ToJSON(), { operation: 'wizard_helper' }, function(json_data){ From c45352bcc5f923c952043f841d8d1d636713760b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 12 Sep 2009 19:58:20 +0000 Subject: [PATCH 078/970] Disabled automatic population of Zlists which was buggy. Not all ZLists are made of AttCode ! SVN:trunk[153] --- core/metamodel.class.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 285db94609..51aaadbdb2 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -817,15 +817,15 @@ abstract class MetaModel // Define defaults values for the standard ZLists // - foreach (self::$m_aListInfos as $sListCode => $aListConfig) - { - if (!isset(self::$m_aListData[$sClass][$sListCode])) - { - $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); - self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; - //echo "

    $sClass: $sListCode (".count($aAllAttributes)." attributes)

    \n"; - } - } + //foreach (self::$m_aListInfos as $sListCode => $aListConfig) + //{ + // if (!isset(self::$m_aListData[$sClass][$sListCode])) + // { + // $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); + // self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; + // //echo "

    $sClass: $sListCode (".count($aAllAttributes)." attributes)

    \n"; + // } + //} } } From 9ca940a41ef0ae789f27636fcc597619af937a41 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Sep 2009 08:37:10 +0000 Subject: [PATCH 079/970] Revised the CSV import: fixed issues with the external keys, added hyperlinks to the found objects, obsoleted the word 'pkey', replaced by 'id' SVN:trunk[154] --- application/cmdbabstract.class.inc.php | 6 - core/bulkchange.class.inc.php | 103 ++++++++++++----- core/cmdbobject.class.inc.php | 29 ++++- core/dbobject.class.php | 32 ++++-- core/metamodel.class.php | 6 + pages/csvimport.php | 153 +++++++++++++++++-------- 6 files changed, 231 insertions(+), 98 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 146f88e42f..bc1008ed37 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -74,12 +74,6 @@ abstract class cmdbAbstractObject extends CMDBObject return "GetForLink()."\" title=\"$sHint\">$sLabel"; } - public function GetHyperlink() - { - $aAvailableFields[MetaModel::GetNameAttributeCode(get_class($this))] = $this->GetName(); - return $this->MakeHyperLink(get_class($this), $this->GetKey(), $aAvailableFields); - } - public function GetDisplayValue($sAttCode) { $sDisplayValue = ""; diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 89bfdd0fa3..6b4332d52c 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -37,36 +37,55 @@ abstract class CellChangeSpec $this->m_proposedValue = $proposedValue; } - public function GetValue() + static protected function ValueAsHtml($value) { - return $this->m_proposedValue; + if (MetaModel::IsValidObject($value)) + { + return $value->GetHyperLink(); + } + else + { + return htmlentities($value); + } } - abstract public function GetDescription(); + public function GetValue($bHtml = false) + { + if ($bHtml) + { + return self::ValueAsHtml($this->m_proposedValue); + } + else + { + return $this->m_proposedValue; + } + } + + abstract public function GetDescription($bHtml = false); } class CellChangeSpec_Void extends CellChangeSpec { - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue(); + return $this->GetValue($bHtml); } } class CellChangeSpec_Unchanged extends CellChangeSpec { - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue()." (unchanged)"; + return $this->GetValue($bHtml)." (unchanged)"; } } class CellChangeSpec_Init extends CellChangeSpec { - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue(); + return $this->GetValue($bHtml); } } @@ -80,9 +99,9 @@ class CellChangeSpec_Modify extends CellChangeSpec parent::__construct($proposedValue); } - public function GetDescription() + public function GetDescription($bHtml = false) { - return $this->GetValue()." (previous: ".$this->m_previousValue.")"; + return $this->GetValue($bHtml)." (previous: ".self::ValueAsHtml($this->m_previousValue).")"; } } @@ -96,13 +115,13 @@ class CellChangeSpec_Issue extends CellChangeSpec_Modify parent::__construct($proposedValue, $previousValue); } - public function GetDescription() + public function GetDescription($bHtml = false) { if (is_null($this->m_proposedValue)) { return 'Could not be changed - reason: '.$this->m_sReason; } - return 'Could not be changed to "'.$this->GetValue().'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')'; + return 'Could not be changed to "'.$this->GetValue($bHtml).'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')'; } } @@ -124,12 +143,12 @@ abstract class RowStatus { } - abstract public function GetDescription(); + abstract public function GetDescription($bHtml = false); } class RowStatus_NoChange extends RowStatus { - public function GetDescription() + public function GetDescription($bHtml = false) { return "unchanged"; } @@ -139,12 +158,13 @@ class RowStatus_NewObj extends RowStatus { protected $m_iObjKey; - public function __construct($iObjKey = null) + public function __construct($sClass = '', $iObjKey = null) { $this->m_iObjKey = $iObjKey; + $this->m_sClass = $sClass; } - public function GetDescription() + public function GetDescription($bHtml = false) { if (is_null($this->m_iObjKey)) { @@ -152,7 +172,15 @@ class RowStatus_NewObj extends RowStatus } else { - return 'Created ('.$this->m_iObjKey.')'; + if (empty($this->m_sClass)) + { + $oObj = MetaModel::GetObject($this->m_sClass, $this->m_iObjKey); + return 'Created '.$oObj->GetHyperLink(); + } + else + { + return 'Created (id: '.$this->m_iObjKey.')'; + } } } } @@ -166,7 +194,7 @@ class RowStatus_Modify extends RowStatus $this->m_iChanged = $iChanged; } - public function GetDescription() + public function GetDescription($bHtml = false) { return "update ".$this->m_iChanged." cols"; } @@ -181,7 +209,7 @@ class RowStatus_Issue extends RowStatus $this->m_sReason = $sReason; } - public function GetDescription() + public function GetDescription($bHtml = false) { return 'Skipped - reason:'.$this->m_sReason; } @@ -217,6 +245,19 @@ class BulkChange $this->m_aExtKeys = $aExtKeys; } + static protected function MakeSpecObject($sClass, $iId) + { + $oObj = MetaModel::GetObject($sClass, $iId); + if (is_null($oObj)) + { + return $iId; + } + else + { + return $oObj; + } + } + protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors) { $aResults = array(); @@ -250,23 +291,26 @@ class BulkChange // Report it if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { + if ($oTargetObj->IsNew()) { - $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj); } else { - $aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->GetOriginal($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj, $previousValue); } } else { - $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oTargetObj->Get($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oForeignObj); } break; default: $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), "Found ".$oExtObjects->Count()." matches"); + $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $previousValue, "Found ".$oExtObjects->Count()." matches"); } } @@ -291,18 +335,17 @@ class BulkChange { if (isset($aErrors[$sAttCode])) { - $aResults["col$iCol"]= new CellChangeSpec_Issue($aRowData[$iCol], $oTargetObj->Get($sAttCode), $aErrors[$sAttCode]); + $aResults["col$iCol"]= new CellChangeSpec_Issue($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode), $aErrors[$sAttCode]); } elseif (array_key_exists($sAttCode, $aChangedFields)) { - $originalValue = $oTargetObj->GetOriginal($sAttCode); if ($oTargetObj->IsNew()) { - $aResults["col$iCol"]= new CellChangeSpec_Init($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue); + $aResults["col$iCol"]= new CellChangeSpec_Init($oTargetObj->Get($sAttCode)); } else { - $aResults["col$iCol"]= new CellChangeSpec_Modify($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue); + $aResults["col$iCol"]= new CellChangeSpec_Modify($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); } } else @@ -358,7 +401,7 @@ class BulkChange if ($oChange) { $newID = $oTargetObj->DBInsertTrackedNoReload($oChange); - $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($newID); + $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($this->m_sClass, $newID); } else { @@ -424,7 +467,7 @@ class BulkChange case 1: $oTargetObj = $oReconciliationSet->Fetch(); $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); - $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetKey(); + $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetHyperLink(); // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject break; default: diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index d49f7280b5..6608f32185 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -154,7 +154,7 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("objkey", $objkey); $iId = $oMyChangeOp->DBInsertNoReload(); } - private function RecordAttChanges(CMDBChange $oChange, array $aValues) + private function RecordAttChanges(CMDBChange $oChange, array $aValues, array $aOrigValues) { // $aValues is an array of $sAttCode => $value // @@ -167,7 +167,15 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("objclass", get_class($this)); $oMyChangeOp->Set("objkey", $this->GetKey()); $oMyChangeOp->Set("attcode", $sAttCode); - $oMyChangeOp->Set("oldvalue", $this->GetOriginal($sAttCode)); + if (array_key_exists($sAttCode, $aOrigValues)) + { + $sOriginalValue = $aOrigValues[$sAttCode]; + } + else + { + $sOriginalValue = 'undefined'; + } + $oMyChangeOp->Set("oldvalue", $sOriginalValue); $oMyChangeOp->Set("newvalue", $value); $iId = $oMyChangeOp->DBInsertNoReload(); } @@ -262,9 +270,11 @@ abstract class CMDBObject extends DBObject throw new CoreWarning("Attempting to update an unchanged object"); return; } - + + // Save the original values (will be reset to the new values when the object get written to the DB) + $aOriginalValues = $this->m_aOrigValues; $ret = parent::DBUpdate(); - $this->RecordAttChanges(self::$m_oCurrChange, $aChanges); + $this->RecordAttChanges(self::$m_oCurrChange, $aChanges, $aOriginalValues); return $ret; } @@ -355,14 +365,23 @@ abstract class CMDBObject extends DBObject $oObjSet = new CMDBObjectSet($oFilter); $oObjSet->Load(); + // Keep track of the previous values (will be overwritten when the objects are synchronized with the DB) + $aOriginalValues = array(); + $oObjSet->Rewind(); + while ($oItem = $oObjSet->Fetch()) + { + $aOriginalValues[$oItem->GetKey()] = $oItem->m_aOrigValues; + } + // Update in one single efficient query $ret = parent::BulkUpdate($oFilter, $aValues); // Record... in many queries !!! + $oObjSet->Rewind(); while ($oItem = $oObjSet->Fetch()) { $aChangedValues = $oItem->ListChangedValues($aValues); - $oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues); + $oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues, $aOriginalValues[$oItem->GetKey()]); } return $ret; } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index eaca961f48..c746527554 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -33,7 +33,7 @@ abstract class DBObject private $m_bIsInDB = false; // true IIF the object is mapped to a DB record private $m_iKey = null; private $m_aCurrValues = array(); - private $m_aOrigValues = array(); + protected $m_aOrigValues = array(); private $m_bFullyLoaded = false; // Compound objects can be partially loaded private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode @@ -217,6 +217,12 @@ abstract class DBObject public function Set($sAttCode, $value) { + if ($sAttCode == 'finalclass') + { + // Ignore it - this attribute is set upon object creation and that's it + //throw new CoreWarning('Attempting to set the value for the internal attribute \"finalclass\"', array('current value'=>$this->Get('finalclass'), 'new value'=>$value)); + return; + } if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this)))) { throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this)); @@ -348,15 +354,7 @@ abstract class DBObject } $sTargetClass = $oAtt->GetTargetClass(EXTKEY_ABSOLUTE); - $aMakeHLink = array(get_class($this), 'MakeHyperLink'); - if (is_callable($aMakeHLink)) - { - return call_user_func($aMakeHLink, $sTargetClass, $this->Get($sAttCode), $aAvailableFields); - } - else - { - return $this->Get($sAttCode); - } + return $this->MakeHyperLink($sTargetClass, $this->Get($sAttCode), $aAvailableFields); } // That's a standard attribute (might be an ext field or a direct field, etc.) @@ -375,6 +373,20 @@ abstract class DBObject return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sSepEscape); } + protected static function MakeHyperLink($sObjClass, $sObjKey, $aAvailableFields) + { + if ($sObjKey == 0) return 'undefined'; + + return MetaModel::GetName($sObjClass)."::$sObjKey"; + } + + public function GetHyperlink() + { + $aAvailableFields[MetaModel::GetNameAttributeCode(get_class($this))] = $this->GetName(); + return $this->MakeHyperLink(get_class($this), $this->GetKey(), $aAvailableFields); + } + + // could be in the metamodel ? public static function IsValidPKey($value) { diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 51aaadbdb2..39d116f493 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2636,6 +2636,12 @@ abstract class MetaModel return self::GetObjectByRow($sClass, $aRow); } + public static function GetHyperLink($sTargetClass, $sOldValue) + { + $oObj = self::GetObject($sTargetClass, $sOldValue); + return $oObj->GetHyperLink(); + } + public static function NewObject($sClass) { self::_check_subclass($sClass); diff --git a/pages/csvimport.php b/pages/csvimport.php index 4d8911e773..24bf03798d 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -38,9 +38,17 @@ function GetExtKeyFieldCodes($sColDesc) function MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode) { $oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); - $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); + if ($sForeignAttCode == 'id') + { + $sForeignAttLabel = 'id'; + } + else + { + $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); + $sForeignAttLabel = $oForeignAtt->GetLabel(); + } - return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$oForeignAtt->GetLabel(); + return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$sForeignAttLabel; } function MakeExtFieldSelectValue($sAttCode, $sExtAttCode) @@ -67,27 +75,33 @@ function ShowTableForm($oPage, $oCSVParser, $sClass) $aFields = array(); foreach($oCSVParser->ListFields() as $iFieldIndex=>$sFieldName) { - $sSelField = ""; - - $sCHECKED = ($sFieldName == "pkey" || MetaModel::IsReconcKey($sClass, $sFoundAttCode)) ? " CHECKED" : ""; - $sSelField .= " "; - $aFields["field$iFieldIndex"]["label"] = $sSelField; - $aFields["field$iFieldIndex"]["value"] = $aColToRow[$iFieldIndex]; + // Find the best match + $iMin = strlen($sFieldName); + $sBestValue = null; + foreach ($aOptions as $sValue => $aData) + { + $iDist = levenshtein(strtolower($sFieldName), strtolower($aData['LabelRef'])); + if (($iDist != -1) && ($iDist < $iMin)) + { + $iMin = $iDist; + $sBestValue = $sValue; + } + } + + $sSelField = ""; + $aFields["field$iFieldIndex"]["label"] = $sSelField; + + $sCHECKED = ($sFieldName == "id" || MetaModel::IsReconcKey($sClass, $sFoundAttCode)) ? " CHECKED" : ""; + $aFields["field$iFieldIndex"]["label"] .= ""; + + if (array_key_exists($iFieldIndex, $aColToRow)) + { + $aFields["field$iFieldIndex"]["value"] = $aColToRow[$iFieldIndex]; + } } $oPage->details($aFields); } @@ -151,7 +209,7 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM foreach($aFieldMap as $sFieldId=>$sColDesc) { $iFieldId = (int) substr($sFieldId, strlen("field")); - if ($sColDesc == "pkey") + if ($sColDesc == "id") { // Skip ! $iPKeyId = $iFieldId; @@ -162,18 +220,19 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM } elseif (IsExtKeyField($sColDesc)) { + // This field is value to search on, to find a value for an external key list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); $aExtKeys[$sExtKeyAttCode][$sExtReconcKeyAttCode] = $iFieldId; } elseif (array_key_exists($sFieldId, $aIsReconcKey)) { + // This value is a reconciliation key $aReconcilKeys[$sColDesc] = $iFieldId; $aAttList[$sColDesc] = $iFieldId; // A reconciliation key is also a field } else { // $sColDesc is an attribute code - // $aAttList[$sColDesc] = $iFieldId; } } @@ -182,10 +241,10 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM // $aDisplayConfig = array(); $aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>""); - $aDisplayConfig["__STATUS__"] = array("label"=>"Status", "description"=>""); + $aDisplayConfig["__STATUS__"] = array("label"=>"Import status", "description"=>""); if (isset($iPKeyId)) { - $aDisplayConfig["col$iPKeyId"] = array("label"=>"pkey", "description"=>""); + $aDisplayConfig["col$iPKeyId"] = array("label"=>"id", "description"=>""); } foreach($aReconcilKeys as $sAttCode => $iCol) { @@ -223,13 +282,12 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM $aExtKeys ); $aRes = $oBulk->Process($oChange); - $aResultDisp = array(); // to be displayed foreach($aRes as $iRow => $aRowData) { $aRowDisp = array(); $aRowDisp["__RECONCILIATION__"] = $aRowData["__RECONCILIATION__"]; - $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(); + $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(true); foreach($aRowData as $sKey => $value) { if ($sKey == '__RECONCILIATION__') continue; @@ -255,11 +313,11 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM } if (empty($sClass)) { - $aRowDisp[$sKey] = $value->GetDescription(); + $aRowDisp[$sKey] = $value->GetDescription(true); } else { - $aRowDisp[$sKey] = "
    ".$value->GetDescription()."
    "; + $aRowDisp[$sKey] = "
    ".$value->GetDescription(true)."
    "; } } $aResultDisp[$iRow] = $aRowDisp; @@ -297,7 +355,7 @@ function Do_Welcome($oPage, $sClass) $aList = MetaModel::GetZListItems($sClassName, 'details'); $aHeader = array(); // $aHeader[] = MetaModel::GetKeyLabel($sClassName); - $aHeader[] = 'pkey'; // Should be what's coded on the line above... but there is a bug + $aHeader[] = 'id'; // Should be what's coded on the line above... but there is a bug foreach($aList as $sAttCode) { $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); @@ -347,12 +405,9 @@ function Do_Format($oPage, $sClass) $iTarget = count($aData); if ($iTarget == 0) { - $oPage->add("Empty data set..."); - $oPage->add("
    "); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add("
    "); + $oPage->p("Empty data set..., please provide some data!"); + $oPage->add("\n"); + return; } // Guess the format : @@ -405,9 +460,9 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $sReconcKey = ""; } - if ($sColDesc == "pkey") + if ($sColDesc == "id") { - $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>"blah pkey"); + $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>""); } elseif ($sColDesc == "__none__") { @@ -455,12 +510,16 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) if (count($aMissingKeys) > 0) { $oPage->p("Warning: the objects could not be created, due to some missing mandatory external keys in the field list: "); - $oPage->p("
      "); + $oPage->add("
        "); foreach($aMissingKeys as $sAttCode => $oAttDef) { - $oPage->p("
      • ".$oAttDef->GetLabel()."
      • "); + $oPage->add("
      • ".$oAttDef->GetLabel()."
      • "); } - $oPage->p("
      "); + $oPage->add("
    "); + } + else + { + $oPage->p("ok - required external keys (if any) have been found in the field list"); } $oPage->p("

    Check...

    "); From 85ad73419eee94247ac7cd06db7fa206b5134b64 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Sep 2009 08:38:52 +0000 Subject: [PATCH 080/970] Hyperlinks in the history logs presentation SVN:trunk[155] --- core/cmdbchangeop.class.inc.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 2c09139103..c7ff31d5de 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -193,6 +193,9 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp */ public function GetDescription() { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + $sResult = ''; $oTargetObjectClass = $this->Get('objclass'); $oTargetObjectKey = $this->Get('objkey'); @@ -225,9 +228,16 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; } } + elseif($bIsHtml && $oAttDef->IsExternalKey()) + { + $sTargetClass = $oAttDef->GetTargetClass(); + $sFrom = MetaModel::GetHyperLink($sTargetClass, $sOldValue); + $sTo = MetaModel::GetHyperLink($sTargetClass, $sNewValue); + $sResult = "$sAttName set to $sTo (previous: $sFrom)"; + } else { - $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + $sResult = "$sAttName set too $sNewValue (previous value: $sOldValue)"; } } return $sResult; From 0e4e0f07a0646442ca88c54b23b6e8ba19898049 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Sep 2009 08:40:28 +0000 Subject: [PATCH 081/970] Fixed bug: could not empty a field SVN:trunk[156] --- pages/UI.php | 8 ++++++-- pages/incident.php | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 0de789f0d8..6a02f911c8 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -410,6 +410,10 @@ switch($operation) { // Non-visible, or read-only attribute, do nothing } + else if ($sAttCode == 'finalclass') + { + // This very specific field is read-only + } else if ($oAttDef->IsLinkSet()) { // Link set, the data is a set of link objects, encoded in JSON @@ -424,9 +428,9 @@ switch($operation) } else if (!$oAttDef->IsExternalField()) { - $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); + $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", null)); $previousValue = $oObj->Get($sAttCode); - if (!empty($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) + if (!is_null($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) { $oObj->Set($sAttCode, $aAttributes[$sAttCode]); $bObjectModified = true; diff --git a/pages/incident.php b/pages/incident.php index bea0a89511..ff9fe3df97 100644 --- a/pages/incident.php +++ b/pages/incident.php @@ -712,11 +712,15 @@ switch($operation) { // Non-visible, or read-only attribute, do nothing } + else if ($sAttCode == 'finalclass') + { + // This very specific field is read-only + } else if (!$oAttDef->IsExternalField()) { - $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); + $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", null)); $previousValue = $oObj->Get($sAttCode); - if (!empty($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) + if (!is_null($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) { $oObj->Set($sAttCode, $aAttributes[$sAttCode]); $bObjectModified = true; From 13600893ba00aba942b1e9a82477b69f84e1d81d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Sep 2009 08:55:41 +0000 Subject: [PATCH 082/970] Cosmetic: display the stimulus label in the profile grants sumary SVN:trunk[157] --- addons/userrights/userrightsprofile.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index cb2aa797de..a0fe498c7f 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -136,12 +136,12 @@ class URP_Profiles extends UserRightsBaseClass foreach (MetaModel::GetClasses('bizmodel') as $sClass) { $aStimuli = array(); - foreach (array_keys(MetaModel::EnumStimuli($sClass)) as $sStimulusCode) + foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) { $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) { - $aStimuli[] = $sStimulusCode; + $aStimuli[] = ''.htmlentities($oStimulus->Get('label')).''; } } $sStimuli = implode(', ', $aStimuli); From 7ed603b7e94f8d4dd18f3b9a27e9c204adfdb36c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Sep 2009 16:04:12 +0000 Subject: [PATCH 083/970] New business model (Erwan) SVN:trunk[158] --- business/ChangeMgmt.business.php | 29 ++++++++-------- business/ServiceDesk.business.php | 11 +++--- business/ServiceMgmt.business.php | 46 ++++++++++++-------------- business/incidentMgmt.business.php | 35 ++++++++++++-------- business/itop.business.class.inc.php | 11 ++++-- business/templates/application.html | 11 ++++++ business/templates/interface.html | 6 +++- business/templates/network.device.html | 8 ++--- business/templates/pc.html | 3 ++ business/templates/person.html | 8 ++++- business/templates/server.html | 7 ++-- business/templates/team.html | 4 +++ 12 files changed, 111 insertions(+), 68 deletions(-) diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index 8ed0ead722..412af9cbf0 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -27,7 +27,7 @@ class bizChangeTicket extends cmdbAbstractObject //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Ticket Ref", "description"=>"Refence number ofr this change", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Change", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Change Type", "description"=>"Type of the Change", "allowed_values"=>new ValueSetEnum("Routine, Normal, Emergency"), "sql"=>"type", "default_value"=>"Routine", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Change Type", "description"=>"Type of the Change", "allowed_values"=>new ValueSetEnum("Routine, Normal, Emergency"), "sql"=>"type", "default_value"=>"Routine", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("domain", array("label"=>"Domain", "description"=>"Domain for the Change", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"domain", "default_value"=>"Desktop", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason For Change", "description"=>"Reason for the Change", "allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -47,7 +47,7 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Start Date", "description"=>"Time the change is expected to start", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"End Date", "description"=>"Date when the change is supposed to end", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("close_date", array("label"=>"Closure Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Risk Assessment", "description"=>"Impact of the change", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Risk Assessment", "description"=>"Impact of the change", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "depends_on"=>array('workgroup_id')))); @@ -62,7 +62,7 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalField("managergroup_name", array("label"=>"Manager Group", "description"=>"name of workgroup approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'managergroup_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Manager", "description"=>"who is approving the ticket", "allowed_values"=>null, "sql"=>"manager_id", "is_null_allowed"=>true, "depends_on"=>array('managergroup_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("manager_mail", array("label"=>"Manager", "description"=>"name of agent approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'manager_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("label"=>"Planned Outage", "description"=>"Flag to define if there is a planned outage", "allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("label"=>"Planned Outage", "description"=>"Flag to define if there is a planned outage", "allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("change_request", array("label"=>"Change Request", "description"=>"Description of Change required", "allowed_values"=>null, "sql"=>"change_req", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -108,22 +108,23 @@ class bizChangeTicket extends cmdbAbstractObject // State machine MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_MANDATORY, 'title' => OPT_ATT_MANDATORY, 'reason' => OPT_ATT_MANDATORY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, - 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_HIDDEN, 'supervisorgroup_id' => OPT_ATT_HIDDEN, 'managergroup_id' => OPT_ATT_HIDDEN, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN ))); MetaModel::Init_DefineState("Validated", array("label"=>"Validated", "description"=>"Ticket is approved", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'managergroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY))); + "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY, 'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, + 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY, 'managergroup_id' => OPT_ATT_MANDATORY, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN))); MetaModel::Init_DefineState("Rejected", array("label"=>"Rejected", "description"=>"This ticket is not approved", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); MetaModel::Init_DefineState("PlannedScheduled", array("label"=>"Planned&Scheduled", "description"=>"Evaluation is done for this change", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_MANDATORY, 'workgroup_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MUSTCHANGE,'fallback' => OPT_ATT_MANDATORY))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_MANDATORY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_MANDATORY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_MANDATORY, 'manager_id' => OPT_ATT_MANDATORY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); MetaModel::Init_DefineState("Approved", array("label"=>"Approved", "description"=>"Ticket is approved by CAB", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); MetaModel::Init_DefineState("NotApproved", array("label"=>"Not Approved", "description"=>"Ticket has not been approved by CAB", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); MetaModel::Init_DefineState("Implemented", array("label"=>"Implementation", "description"=>"Work is in progress for this ticket", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); MetaModel::Init_DefineState("Monitored", array("label"=>"Monitored", "description"=>"Change performed is now monitored", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); - MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('org_id' => OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_READONLY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_READONLY, 'change_log' => OPT_ATT_READONLY,'fallback' => OPT_ATT_READONLY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); + MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'close_date' => OPT_ATT_READONLY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_READONLY, 'change_log' => OPT_ATT_READONLY,'fallback' => OPT_ATT_READONLY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); MetaModel::Init_DefineStimulus("ev_validate", new StimulusUserAction(array("label"=>"Validate this change", "description"=>"Make sure it is a valid change request"))); MetaModel::Init_DefineStimulus("ev_reject", new StimulusUserAction(array("label"=>"Reject this change", "description"=>"This change request is rejected because it is a non valid one"))); @@ -145,8 +146,8 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_DefineTransition("NotApproved", "ev_replan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Approved", "ev_implement", array("target_state"=>"Implemented", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Implemented", "ev_monitor", array("target_state"=>"Monitored", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Implemented", "ev_finish", array("target_state"=>"Closed", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Monitored", "ev_finish", array("target_state"=>"Closed", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Implemented", "ev_finish", array("target_state"=>"Closed", "actions"=>array('SetClosureDate','SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Monitored", "ev_finish", array("target_state"=>"Closed", "actions"=>array('SetClosureDate','SetLastUpDate'), "user_restriction"=>null)); } public function Generate(cmdbDataGenerator $oGenerator) diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index 9ecd9022ab..26d4a724f4 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -45,7 +45,7 @@ class bizServiceCall extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"Customer concerned by this service call", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer raising this service call", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("call_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress,Closed"), "sql"=>"call_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("call_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress,Resolved,Closed"), "sql"=>"call_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); MetaModel::Init_AddAttribute(new AttributeText("call_description", array("label"=>"Description", "description"=>"Description of the call as describe by caller", "allowed_values"=>null, "sql"=>"call_description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("label"=>"Creation date", "description"=>"Call creation date", "allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -104,18 +104,21 @@ class bizServiceCall extends cmdbAbstractObject MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Call is assigned to somebody", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "call_description"=>OPT_ATT_READONLY, "creation_date"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_MUSTCHANGE, "agent_id"=>OPT_ATT_MUSTCHANGE))); MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "call_description"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "creation_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Call is closed", "attribute_inherit"=>null, "attribute_list"=>array("workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY, "resolution"=>OPT_ATT_MANDATORY, "end_date"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Resolved", array("label"=>"Resolved", "description"=>"Call is resolved", "attribute_inherit"=>null, "attribute_list"=>array("workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY, "resolution"=>OPT_ATT_MANDATORY, "end_date"=>OPT_ATT_MANDATORY,"resolution"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Call is closed", "attribute_inherit"=>null, "attribute_list"=>array("workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY, "resolution"=>OPT_ATT_READONLY, "end_date"=>OPT_ATT_READONLY))); MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this call", "description"=>"Assign this call to a group and an agent"))); MetaModel::Init_DefineStimulus("ev_reassign", new StimulusUserAction(array("label"=>"Reassign this call", "description"=>"Reassign this call to a different group and agent"))); MetaModel::Init_DefineStimulus("ev_start_working", new StimulusUserAction(array("label"=>"Work on this call", "description"=>"Start working on this call"))); - MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this call", "description"=>"Close/resolve this call"))); + MetaModel::Init_DefineStimulus("ev_resolve", new StimulusUserAction(array("label"=>"Resolve this call", "description"=>"Resolve this call"))); + MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this call", "description"=>"Close this call"))); MetaModel::Init_DefineTransition("New", "ev_assign", array("target_state"=>"Assigned", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Assigned", "ev_start_working", array("target_state"=>"WorkInProgress", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("WorkInProgress", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("WorkInProgress", "ev_close", array("target_state"=>"Closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("WorkInProgress", "ev_resolve", array("target_state"=>"Resolved", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Resolved", "ev_close", array("target_state"=>"Closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); } diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index 9f6d6621c0..cfc028b3c5 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -110,17 +110,17 @@ class bizContract extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"bizService", "label"=>"Service", "description"=>"Provider for this contract", "allowed_values"=>null, "sql"=>"service_id", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("label"=>"Provider", "description"=>"name of the service provider", "allowed_values"=>null, "extkey_attcode"=> 'service_id', "target_attcode"=>"provider_name"))); MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("label"=>"Service", "description"=>"name of the service", "allowed_values"=>null, "extkey_attcode"=> 'service_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team managing this contract", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team managing this contract", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team", "description"=>"name of the team managing this contract", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("service_level", array("label"=>"Service Level", "description"=>"Level of service for this contract", "allowed_values"=>new ValueSetEnum("Gold,Silver,Bronze"), "sql"=>"service_level", "default_value"=>"Bronze", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("cost_unit", array("label"=>"Cost Unit", "description"=>"Cost unit to compute global cost for this contract", "allowed_values"=>new ValueSetEnum("Devices,Persons,Applications,Global"), "sql"=>"cost_unit", "default_value"=>"Global", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("cost_freq", array("label"=>"Billing frequency", "description"=>"Frequency of cost for this contract", "allowed_values"=>new ValueSetEnum("Monthly,Yearly,Once"), "sql"=>"cost_freq", "default_value"=>"Once", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("service_level", array("label"=>"Service Level", "description"=>"Level of service for this contract", "allowed_values"=>new ValueSetEnum("Gold,Silver,Bronze"), "sql"=>"service_level", "default_value"=>"Bronze", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("cost_unit", array("label"=>"Cost Unit", "description"=>"Cost unit to compute global cost for this contract", "allowed_values"=>new ValueSetEnum("Devices,Persons,Applications,Global"), "sql"=>"cost_unit", "default_value"=>"Global", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("cost_freq", array("label"=>"Billing frequency", "description"=>"Frequency of cost for this contract", "allowed_values"=>new ValueSetEnum("Monthly,Yearly,Once"), "sql"=>"cost_freq", "default_value"=>"Once", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("cost", array("label"=>"Cost", "description"=>"Cost of this contract", "allowed_values"=>null, "sql"=>"cost", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("label"=>"Currency", "description"=>"Currency of cost for this contract", "allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("label"=>"Currency", "description"=>"Currency of cost for this contract", "allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("label"=>"Date of Move To Production", "description"=>"Date when the contract is on production", "allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("label"=>"Date of Move To Production", "description"=>"Date when the contract is on production", "allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("end_prod", array("label"=>"Date of End Of Production", "description"=>"Date when the contract is stopped", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the contract", "allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production, Notice, Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the contract", "allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production,Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the contract", "allowed_values"=>new ValueSetEnum("Hardware,Software,Support,Licence"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeInteger("version_number", array("label"=>"Version", "description"=>"Revision number for this contract", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); @@ -141,37 +141,27 @@ class bizContract extends cmdbAbstractObject // Life cycle MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created contract", "attribute_inherit"=>null, - "attribute_list"=>array())); + "attribute_list"=>array('name' => OPT_ATT_MANDATORY,'org_id' => OPT_ATT_MANDATORY, 'service_id' => OPT_ATT_MANDATORY,'type' => OPT_ATT_MANDATORY, 'description' => OPT_ATT_MANDATORY))); MetaModel::Init_DefineState("Negotiating", array("label"=>"Negotiating", "description"=>"The contract is being worked on", "attribute_inherit"=>null, - "attribute_list"=>array())); + "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY))); MetaModel::Init_DefineState("Signed", array("label"=>"Signed", "description"=>"The contract has been signed", "attribute_inherit"=>null, - "attribute_list"=>array())); + "attribute_list"=>array( 'name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'service_id' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, 'service_level' => OPT_ATT_MANDATORY , 'cost_unit' => OPT_ATT_MANDATORY , 'cost_freq' => OPT_ATT_MANDATORY , 'cost' => OPT_ATT_MANDATORY, 'currency' => OPT_ATT_MANDATORY))); MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The contract is effective in production", "attribute_inherit"=>null, - "attribute_list"=>array())); - MetaModel::Init_DefineState("Notice", array("label"=>"Notice", "description"=>"The contract is about to be terminated", "attribute_inherit"=>null, - "attribute_list"=>array())); + "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'service_id' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, 'service_level' => OPT_ATT_READONLY , 'cost_unit' => OPT_ATT_READONLY , 'cost_freq' => OPT_ATT_READONLY , 'cost' => OPT_ATT_READONLY, 'currency' => OPT_ATT_READONLY,'move2prod_date' => OPT_ATT_MANDATORY,'end_prod' => OPT_ATT_MANDATORY))); MetaModel::Init_DefineState("Finished", array("label"=>"Finished", "description"=>"The contract is terminated", "attribute_inherit"=>null, - "attribute_list"=>array())); + "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'service_id' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, 'service_level' => OPT_ATT_READONLY , 'cost_unit' => OPT_ATT_READONLY , 'cost_freq' => OPT_ATT_READONLY , 'cost' => OPT_ATT_READONLY, 'currency' => OPT_ATT_READONLY,'move2prod_date' => OPT_ATT_READONLY,'end_prod' => OPT_ATT_READONLY,'team_id' => OPT_ATT_READONLY,'description' => OPT_ATT_READONLY))); - MetaModel::Init_DefineStimulus("ev_freeze_version", new StimulusUserAction(array("label"=>"Freeze this version", "description"=>"This version of the contract is published"))); + MetaModel::Init_DefineStimulus("ev_negociate", new StimulusUserAction(array("label"=>"Negociate this contract", "description"=>"This version of the contract is published"))); MetaModel::Init_DefineStimulus("ev_sign", new StimulusUserAction(array("label"=>"Sign this contract", "description"=>"This contract is being signed"))); MetaModel::Init_DefineStimulus("ev_begin", new StimulusUserAction(array("label"=>"Move to production", "description"=>"The contract becomes applicable in production"))); - MetaModel::Init_DefineStimulus("ev_notice", new StimulusUserAction(array("label"=>"Start notice period", "description"=>"The end date of the contract is approaching"))); MetaModel::Init_DefineStimulus("ev_terminate", new StimulusUserAction(array("label"=>"Ends this contract", "description"=>"The contract is ending"))); - MetaModel::Init_DefineStimulus("ev_elapsed", new StimulusUserAction(array("label"=>"Times up [Do not click!]", "description"=>"The contract over"))); - MetaModel::Init_DefineTransition("New", "ev_freeze_version", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Negotiating", "ev_freeze_version", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("New", "ev_negociate", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Negotiating", "ev_sign", array("target_state"=>"Signed", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Negotiating", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Signed", "ev_freeze_version", array("target_state"=>"Signed", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Signed", "ev_begin", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Signed", "ev_begin", array("target_state"=>"Production", "actions"=>array('SetProdDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Signed", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Production", "ev_freeze_version", array("target_state"=>"Production", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Production", "ev_elapsed", array("target_state"=>"Notice", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Production", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Notice", "ev_elapsed", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Notice", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'service_id','provider_name','type','description','team_id','service_level','cost','currency','cost_unit','cost_freq','move2prod_date','end_prod', 'version_number')); // Attributes to be displayed for the complete details @@ -188,6 +178,12 @@ class bizContract extends cmdbAbstractObject $this->Set('version_number', $this->Get('version_number') + 1); return true; } + + public function SetProdDate($sStimulusCode) + { + $this->Set('move2prod_date', time()); + return true; + } } diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index bccc74ba88..77f1c04771 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -45,21 +45,21 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Resolved, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("label"=>"Initial Situation", "description"=>"Initial situation of the Incident", "allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("label"=>"Current Situation", "description"=>"Current situation of the Incident", "allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closure Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array('org_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "depends_on"=>array("workgroup_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"mail of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); @@ -101,22 +101,25 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'next_update' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_HIDDEN, "title"=>OPT_ATT_MANDATORY, "org_id"=>OPT_ATT_MANDATORY, "caller_id"=>OPT_ATT_MANDATORY, "initial_situation"=>OPT_ATT_MANDATORY, "start_date"=>OPT_ATT_MANDATORY, "workgroup_id"=>OPT_ATT_MANDATORY, - "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT))); + "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT,"resolution"=>OPT_ATT_HIDDEN))); MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Ticket is assigned to somebody", "attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_MUSTCHANGE, "agent_id"=>OPT_ATT_MUSTCHANGE))); - MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array("workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY, "resolution"=>OPT_ATT_MANDATORY, "end_date"=>OPT_ATT_MANDATORY))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_MUSTCHANGE, "agent_id"=>OPT_ATT_MUSTCHANGE,"resolution"=>OPT_ATT_HIDDEN))); + MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY,"action_log"=>OPT_ATT_MANDATORY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY))); + MetaModel::Init_DefineState("Resolved", array("label"=>"Resolved", "description"=>"Ticket is resolved", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_MUSTCHANGE))); + MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_READONLY))); MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this ticket", "description"=>"Assign this ticket to a group and an agent"))); MetaModel::Init_DefineStimulus("ev_reassign", new StimulusUserAction(array("label"=>"Reassign this ticket", "description"=>"Reassign this ticket to a different group and agent"))); MetaModel::Init_DefineStimulus("ev_start_working", new StimulusUserAction(array("label"=>"Work on this ticket", "description"=>"Start working on this ticket"))); - MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this ticket", "description"=>"Close/resolve this ticket"))); + MetaModel::Init_DefineStimulus("ev_resolve", new StimulusUserAction(array("label"=>"Resolve this ticket", "description"=>"Resolve this ticket"))); + MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this ticket", "description"=>"Close this ticket"))); MetaModel::Init_DefineTransition("New", "ev_assign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Assigned", "ev_start_working", array("target_state"=>"WorkInProgress", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("WorkInProgress", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("WorkInProgress", "ev_close", array("target_state"=>"Closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount','SetLastUpdate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Assigned", "ev_start_working", array("target_state"=>"WorkInProgress", "actions"=>array('SetLastUpdate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("WorkInProgress", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount','SetLastUpdate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("WorkInProgress", "ev_resolve", array("target_state"=>"Resolved", "actions"=>array('SetLastUpdate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Resolved", "ev_close", array("target_state"=>"Closed", "actions"=>array('SetClosureDate','SetLastUpdate'), "user_restriction"=>null)); } @@ -149,6 +152,12 @@ class bizIncidentTicket extends cmdbAbstractObject return true; } + public function SetLastUpdate($sStimulusCode) + { + $this->Set('last_update', time()); + return true; + } + public function ComputeValues() { $iKey = $this->GetKey(); diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index dca260fc02..bdc2327b9b 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -879,7 +879,10 @@ class bizInterface extends logInfra MetaModel::Init_AddAttribute(new AttributeString("mac", array("label"=>"MAC address", "description"=>"MAC address for this interface", "allowed_values"=>null, "sql"=>"mac", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"Speed (Kb/s)", "description"=>"speed of this interface", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("label"=>"Duplex", "description"=>"Duplex configured for this interface", "allowed_values"=>new ValueSetEnum("half,full,unknown"), "sql"=>"duplex", "default_value"=>"unknown", "is_null_allowed"=>true, "depends_on"=>array()))); - + MetaModel::Init_AddAttribute(new AttributeExternalKey("if_connected_id", array("targetclass"=>"bizInterface", "label"=>"Connected interface", "description"=>"interface connected to this one", "allowed_values"=>null, "sql"=>"ext_if_id", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("if_connected_name", array("label"=>"Connected interface", "description"=>"name of the interface connected to this one", "allowed_values"=>null, "extkey_attcode"=> 'if_connected_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("if_connected_device", array("label"=>"Connected device", "description"=>"name of the device connected to this interface", "allowed_values"=>null, "extkey_attcode"=> 'if_connected_id', "target_attcode"=>"device_name"))); + MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("device_id"); MetaModel::Init_AddFilterFromAttribute("device_name"); @@ -888,8 +891,10 @@ class bizInterface extends logInfra MetaModel::Init_AddFilterFromAttribute("physical_type"); MetaModel::Init_AddFilterFromAttribute("ip_address"); MetaModel::Init_AddFilterFromAttribute("mac"); + MetaModel::Init_AddFilterFromAttribute("if_connected_id"); + // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'device_id', 'device_location_id','severity','logical_type','physical_type','ip_address','mask','mac','speed','duplex')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'device_id', 'device_location_id','severity','logical_type','physical_type','ip_address','mask','mac','speed','duplex','if_connected_name','if_connected_device')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'device_id','severity')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'ip_address','mac','device_id')); // Criteria of the std search form @@ -937,6 +942,7 @@ class bizInterface extends logInfra * n-n link between any Interfaces */ //////////////////////////////////////////////////////////////////////////////////// +/* class lnkInterfaces extends cmdbAbstractObject { public static function Init() @@ -979,6 +985,7 @@ class lnkInterfaces extends cmdbAbstractObject MetaModel::Init_SetZListItems('list', array('interface1_id', 'interface1_device_id', 'interface2_id', 'interface2_device_id', 'link_type')); // Attributes to be displayed for the complete details } } +*/ //////////////////////////////////////////////////////////////////////////////////// /** diff --git a/business/templates/application.html b/business/templates/application.html index 9540a63bd2..eeb4afd9b1 100644 --- a/business/templates/application.html +++ b/business/templates/application.html @@ -12,8 +12,19 @@ SELECT lnkClientServer WHERE client_id = $pkey$ + + lnkInfraTicket: infra_id = $pkey$ + + + bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) + + SELECT lnkInfraContract WHERE infra_id = $pkey$ + + SELECT lnkInfraGrouping WHERE infra_id = $pkey$ + +
    diff --git a/business/templates/interface.html b/business/templates/interface.html index dca953a932..1911d01df8 100644 --- a/business/templates/interface.html +++ b/business/templates/interface.html @@ -7,7 +7,11 @@ SELECT bizInterface WHERE id = $pkey$ - SELECT lnkInterfaces WHERE interface1_id = $pkey$ + SELECT bizInterface WHERE if_connected_id = $pkey$ + + SELECT lnkInfraGrouping WHERE infra_id = $pkey$ + + diff --git a/business/templates/network.device.html b/business/templates/network.device.html index 276247d601..56f9000a2e 100644 --- a/business/templates/network.device.html +++ b/business/templates/network.device.html @@ -18,11 +18,11 @@ bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) + + SELECT lnkInfraGrouping WHERE infra_id = $pkey$ + SELECT lnkDocumentRealObject WHERE object_id = $pkey$ - - - - + diff --git a/business/templates/pc.html b/business/templates/pc.html index f283c13e94..fcb8427b95 100644 --- a/business/templates/pc.html +++ b/business/templates/pc.html @@ -21,6 +21,9 @@ bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + + SELECT lnkInfraGrouping WHERE infra_id = $pkey$ + SELECT lnkDocumentRealObject WHERE object_id = $pkey$ diff --git a/business/templates/person.html b/business/templates/person.html index ad36b55e2e..15b519fc99 100644 --- a/business/templates/person.html +++ b/business/templates/person.html @@ -9,7 +9,13 @@ SELECT lnkContactTeam WHERE contact_id = $pkey$ - SELECT lnkContactRealObject WHERE contact_id = $pkey$ + + SELECT lnkContactRealObject WHERE contact_id = $pkey$ + + + SELECT bizServiceCall WHERE caller_id = $pkey$ + + SELECT lnkDocumentRealObject WHERE object_id = $pkey$ diff --git a/business/templates/server.html b/business/templates/server.html index c0bf054762..30597b06d3 100644 --- a/business/templates/server.html +++ b/business/templates/server.html @@ -24,12 +24,11 @@ bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) + + SELECT lnkInfraGrouping WHERE infra_id = $pkey$ + SELECT lnkDocumentRealObject WHERE object_id = $pkey$ - - - - diff --git a/business/templates/team.html b/business/templates/team.html index 042a984acc..5a0edcf372 100644 --- a/business/templates/team.html +++ b/business/templates/team.html @@ -12,6 +12,10 @@ SELECT lnkContactInfra WHERE contact_id = $pkey$ + + SELECT bizWorkgroup WHERE team_id = $pkey$ + + SELECT lnkDocumentRealObject WHERE object_id = $pkey$ From 043552536d3a734230139a56418966196a35703e Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Sep 2009 16:06:04 +0000 Subject: [PATCH 084/970] Cosmetics on the bulk load (followed new test plan) SVN:trunk[159] --- core/bulkchange.class.inc.php | 4 +- core/metamodel.class.php | 20 ++++++--- pages/csvimport.php | 80 +++++++++++++++++++++++------------ 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 6b4332d52c..fae8db1ebb 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -172,7 +172,7 @@ class RowStatus_NewObj extends RowStatus } else { - if (empty($this->m_sClass)) + if (!empty($this->m_sClass)) { $oObj = MetaModel::GetObject($this->m_sClass, $this->m_iObjKey); return 'Created '.$oObj->GetHyperLink(); @@ -281,7 +281,7 @@ class BulkChange { case 0: $aErrors[$sAttCode] = "Object not found"; - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found'); + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found - check the spelling (no space before/after)'); break; case 1: // Do change the external key attribute diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 39d116f493..f93f8a8972 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2564,7 +2564,7 @@ abstract class MetaModel return $iTotalHits.' ('.implode(', ', $aRes).')'; } - public static function MakeSingleRow($sClass, $iKey) + public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true) { if (!array_key_exists($sClass, self::$aQueryCacheGetObject)) { @@ -2591,7 +2591,7 @@ abstract class MetaModel $aRow = CMDBSource::FetchArray($res); CMDBSource::FreeResult($res); - if (empty($aRow)) + if ($bMustBeFound && empty($aRow)) { throw new CoreException("No result for the single row query: '$sSQL'"); } @@ -2625,10 +2625,10 @@ abstract class MetaModel return new $sClass($aRow); } - public static function GetObject($sClass, $iKey) + public static function GetObject($sClass, $iKey, $bMustBeFound = true) { self::_check_subclass($sClass); - $aRow = self::MakeSingleRow($sClass, $iKey); + $aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound); if (empty($aRow)) { return null; @@ -2636,9 +2636,17 @@ abstract class MetaModel return self::GetObjectByRow($sClass, $aRow); } - public static function GetHyperLink($sTargetClass, $sOldValue) + public static function GetHyperLink($sTargetClass, $iKey) { - $oObj = self::GetObject($sTargetClass, $sOldValue); + if ($iKey < 0) + { + return "$sTargetClass: $iKey (invalid value)"; + } + $oObj = self::GetObject($sTargetClass, $iKey, false); + if (is_null($oObj)) + { + return "$sTargetClass: $iKey (not found)"; + } return $oObj->GetHyperLink(); } diff --git a/pages/csvimport.php b/pages/csvimport.php index 24bf03798d..256aa2957a 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -75,6 +75,8 @@ function ShowTableForm($oPage, $oCSVParser, $sClass) $aFields = array(); foreach($oCSVParser->ListFields() as $iFieldIndex=>$sFieldName) { + $sFieldName = trim($sFieldName); + $aOptions = array(); $aOptions['id'] = array( 'LabelHtml' => "Private key", @@ -336,18 +338,17 @@ function Do_Welcome($oPage, $sClass) $sCSVData = utils::ReadPostedParam('csvdata'); - $oPage->add("
    "); - $oPage->MakeClassesSelect("class", $sClass, 50); - //$oPage->Add(""); - $oPage->add("
    "); - $oPage->add(""); - $oPage->add("
    "); - $oPage->add(""); - $oPage->add("
    \n"); - $oPage->add("
    "); + $oPage->add("
    "); + $oPage->MakeClassesSelect("class", $sClass, 50); + $oPage->add("
    "); + $oPage->add(""); + $oPage->add("
    "); + $oPage->add(""); + $oPage->add("
    \n"); + $oPage->add("
    "); - // As a help to the end-user, let's display the list of possible fields - // for a class, that can be copied/pasted into the CSV area. + // As a help to the end-user, let's display the list of possible fields + // for a class, that can be copied/pasted into the CSV area. $sCurrentList = ""; $aHeadersList = array(); foreach(MetaModel::GetClasses('bizmodel') as $sClassName) @@ -385,8 +386,8 @@ function Do_Welcome($oPage, $sClass) \n"); $oPage->add_ready_script("$('#select_class').change( function() {DisplayFields(this.value);} );"); - - $oPage->add("Fields for this object: "); + $oPage->add("
    "); + $oPage->add("Fields for this object
    "); } @@ -437,6 +438,15 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $aFieldMap = utils::ReadPostedParam('fmap'); $aIsReconcKey = utils::ReadPostedParam('iskey'); + if (empty($aIsReconcKey)) + { + $oPage->p("Error: no reconciliation key has been specified. Please specify which field(s) will be used to identify the object"); + + $oPage->add("\n"); + $oPage->add("\n"); + return; + } + $oCSVParser = new CSVParser($sCSVData); $oCSVParser->SetSeparator($sSep); $oCSVParser->SetSkipLines($iSkip); @@ -521,6 +531,7 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) { $oPage->p("ok - required external keys (if any) have been found in the field list"); } + $oPage->p("Note: the procedure will fail if any line has not the same number of columns as the first line"); $oPage->p("

    Check...

    "); } @@ -534,7 +545,7 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $oPage->add_input_hidden("fmap", $aFieldMap); $oPage->add_input_hidden("iskey", $aIsReconcKey); - return; + return true; } function Do_Verify($oPage, $sClass) @@ -542,13 +553,14 @@ function Do_Verify($oPage, $sClass) $oPage->p("

    Bulk load from CSV data / step 3

    "); $sWiztep = "3_verify"; - DoProcessOrVerify($oPage, $sClass, null); - // FORM started by DoProcessOrVerify... - - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); + if (DoProcessOrVerify($oPage, $sClass, null)) + { + // FORM started by DoProcessOrVerify... + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + } } function Do_Execute($oPage, $sClass) @@ -558,15 +570,27 @@ function Do_Execute($oPage, $sClass) $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "CSV Import"); + $iUser = UserRights::GetContactId(); + if ($iUser != null) + { + // Ok, that's dirty, I admit :-) + $oUser = MetaModel::GetObject('bizContact', $iUser); + $sUser = $oUser->GetName(); + $oMyChange->Set("userinfo", "CSV Import, by ".$sUser); + } + else + { + $oMyChange->Set("userinfo", "CSV Import"); + } $iChangeId = $oMyChange->DBInsert(); - DoProcessOrVerify($oPage, $sClass, $oMyChange); - - // FORM started by DoProcessOrVerify... - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); + if (DoProcessOrVerify($oPage, $sClass, $oMyChange)) + { + // FORM started by DoProcessOrVerify... + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + } } From 6e4da54706276226218edc3a6dc98a0af7360571 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 14 Sep 2009 16:50:51 +0000 Subject: [PATCH 085/970] - New cache management for the lifecycle graphs SVN:trunk[160] --- pages/graphviz.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/graphviz.php b/pages/graphviz.php index 32181c4ce5..c049191444 100644 --- a/pages/graphviz.php +++ b/pages/graphviz.php @@ -65,8 +65,8 @@ function GraphvizLifecycle($sClass) $sClass = utils::ReadParam('class', 'bizIncidentTicket'); $sDir = dirname(__FILE__); -$sImageFilePath = realpath($sDir."/../images/lifecycle/$sClass.png"); -if (!file_exists($sImageFilePath)) +$sImageFilePath = $sDir."\\..\\images\\lifecycle\\".$sClass.".png"; +if (file_exists("/iTop/Graphviz/bin/dot.exe")) { // create the file with Graphviz $sDotDescription = GraphvizLifecycle($sClass); From 7b6295f0a0c0f5124d5dd0ea40a8f24d80c092f3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 07:53:12 +0000 Subject: [PATCH 086/970] Fixed regression: server status was reset SVN:trunk[161] --- core/metamodel.class.php | 4 ++++ pages/UI.php | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index f93f8a8972..6299b46a39 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -649,6 +649,10 @@ abstract class MetaModel if (!empty($sStateAttCode)) { $aStates = MetaModel::EnumStates($sClass); + if (!array_key_exists($sState, $aStates)) + { + throw new CoreException("Invalid state '$sState' for class '$sClass', expecting a value in {".implode(', ', array_keys($aStates))."}"); + } $aCurrentState = $aStates[$sState]; if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) ) { diff --git a/pages/UI.php b/pages/UI.php index 6a02f911c8..fc2700b710 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -428,12 +428,16 @@ switch($operation) } else if (!$oAttDef->IsExternalField()) { - $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", null)); - $previousValue = $oObj->Get($sAttCode); - if (!is_null($aAttributes[$sAttCode]) && ($previousValue != $aAttributes[$sAttCode])) + $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); + if (!is_null($rawValue)) { - $oObj->Set($sAttCode, $aAttributes[$sAttCode]); - $bObjectModified = true; + $aAttributes[$sAttCode] = trim($rawValue); + $previousValue = $oObj->Get($sAttCode); + if ($previousValue != $aAttributes[$sAttCode]) + { + $oObj->Set($sAttCode, $aAttributes[$sAttCode]); + $bObjectModified = true; + } } } } From 66f1622554005070a081ce845ee88f4cbe5bdc65 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 07:56:14 +0000 Subject: [PATCH 087/970] Fixed regression: ticket "impact" was reset ; resulting from a conflict in the form fields names (impact used both in the main detail form and into hidden links forms) SVN:trunk[162] --- application/ui.linkswidget.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index e2e5aa72df..074207ff2e 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -303,7 +303,7 @@ EOF; $sSubId = $sId.'_'.$index; $aAttrsMap[$sAttCode] = $sSubId; $index++; - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sLinkedClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sSubId); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sLinkedClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sSubId, $this->m_sAttCode); } $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } From 0669db07f3a41d8e8a0ebecc001fab86c8c57817 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 07:59:11 +0000 Subject: [PATCH 088/970] Disabled the features not finalized (cloning, bookmarking, bulk operations) SVN:trunk[163] --- application/displayblock.class.inc.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index d7840f6c82..b78267081f 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -689,16 +689,16 @@ class MenuBlock extends DisplayBlock $sTargetClass = $oAttDef->GetTargetClass(); if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); } if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Manage...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); } - if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } + //if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); } } else { $sUrl = self::GetAbsoluteUrl(); $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); - $aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); + //$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } - if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); } + //if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); } if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Delete', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } } @@ -740,7 +740,7 @@ class MenuBlock extends DisplayBlock if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); } //if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&linkage=".$aExtraParams['linkage']."&id=$id&addObjects=true&$sContext"); } if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Manage...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); } - if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#"); } + //if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#"); } } else { @@ -748,10 +748,10 @@ class MenuBlock extends DisplayBlock $sUrl = self::GetAbsoluteUrl(); $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); - $aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); + //$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); } - if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "../pages/$sUIPage?operation=modify_all&filter=$sFilter&$sContext"); } - if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Delete All', 'url' => "../pages/$sUIPage?operation=delete_all&filter=$sFilter&$sContext"); } + //if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "../pages/$sUIPage?operation=modify_all&filter=$sFilter&$sContext"); } + //if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Delete All', 'url' => "../pages/$sUIPage?operation=delete_all&filter=$sFilter&$sContext"); } } } $sHtml .= "
      \n
    • Actions\n
        \n"; From 347021676765c6c36239c2cf3f55cf83661e9043 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 08:03:42 +0000 Subject: [PATCH 089/970] Fixed a regression on the management of the allowed values and attribute dependencies (symptom: Browse button not working, error when creating a ticket with no CI attached to it) SVN:trunk[164] --- js/wizardhelper.js | 9 ++++++--- pages/ajax.render.php | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 0315715925..09ad3556ee 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -64,7 +64,8 @@ function WizardHelper(sClass) this.UpdateFields = function () { - //console.log('** UpdateFields **') + //console.log('** UpdateFields **'); + //console.log(this.m_oData); for(i=0; i< this.m_oData.m_aAllowedValuesRequested.length; i++) { sAttCode = this.m_oData.m_aAllowedValuesRequested[i]; @@ -79,6 +80,7 @@ function WizardHelper(sClass) //console.log('Setting field:'+sFieldId+' ('+sAttCode+') to: '+defaultValue); var oElement = document.getElementById('att_'+sFieldId); oElement.value = defaultValue; + //console.log('att_'+sFieldId+', set to '+defaultValue); } } @@ -102,8 +104,9 @@ function WizardHelper(sClass) //console.log('oWizard:', this); $.get('ajax.render.php?json_obj=' + this.ToJSON(), { operation: 'wizard_helper' }, - function(json_data){ - //console.log('data received:', json_data); + function(html){ + $('body').append(html); + console.log('data received:', oWizardHelper); //oWizardHelper.FromJSON(json_data); oWizardHelper.UpdateFields(); //console.log(oWizardHelper); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index c43924d601..0edce8c5e3 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -94,7 +94,7 @@ switch($operation) $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } - $oPage->add("\n"); + $oPage->add("\n"); break; case 'ajax': From 93e629593c07c84bb86274e4af3cd4cc603bca3a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 08:39:02 +0000 Subject: [PATCH 090/970] New menus (source: Erwan) SVN:trunk[165] --- setup/data/structure/1.menus.xml | 1625 +++++++++++++------------ setup/data/structure/export_menus.cmd | 4 +- 2 files changed, 865 insertions(+), 764 deletions(-) diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index 4b7198209f..79c989337e 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -1,762 +1,863 @@ - - - -Tools - -UI.php - - -application -7 -0 -0 - - -All Applications - -UI.php - - -application -999 -14 -0 - - -All Circuits - -UI.php - - -application -999 -14 -0 - - -All Contracts - -./UI.php - - -application -2 -31 -0 - - -All Interfaces - -UI.php - - -application -999 -14 -0 - - -All Network devices - -UI.php - - -application -999 -14 -0 - - -All Patches - -UI.php - - -application -999 -14 -0 - - -All PCs - -UI.php - - -application -999 -14 -0 - - -All Servers - -UI.php - - -application -999 -14 -0 - - -All Subnets - -UI.php - - -application -999 -14 -0 - - -Audit - -./audit.php - - -application -4 -15 -0 - - -Change Management - -./UI.php - - -application -4 -0 -0 - - -Closed Changes - -UI.php - - -application -2 -11 -0 - - -Closed Incident - -./UI.php - - -application -2 -22 -0 - - -Configuration Items - -UI.php - - -application -2 -15 -0 - - -Configuration Management - -UI.php - - -application -2 -0 -0 - - -Contacts - -UI.php - - -application -1 -15 -0 - - -CSV import - -csvimport.php - - -application -999 -1 -0 - - -Document - -UI.php - - -application -6 -15 -0 - - - -Grouping - -UI.php - - -application -3 -15 -0 - - -Incident Management - -./UI.php - - -application -3 -0 -0 - - -Known Errors - -./UI.php - - -application -999 -22 -0 - - -Locations - -UI.php - - -application -5 -15 -0 - - -Negociating contracts - -UI.php - - -application -1 -31 -0 - - -Open Changes - -./UI.php - - -application -1 -11 -0 - - -Open Incidents - -UI.php - - -application -1 -22 -0 - - -Persons - -UI.php - - -application -7 -16 -0 - - - -Scheduled Outages - -./UI.php - - -application -999 -11 -0 - - -Service Management - -./UI.php - - -application -5 -0 -0 - - -Teams - -UI.php - - -application -8 -16 -0 - - - -Welcome - -./UI.php - - -application -1 -0 -0 - - - -Admin tools - -UI.php - - -administrator -1000 -0 -0 - - - -Data Model - -schema.php - - -administrator -1500 -100 -0 - - - -Universal Search - -UniversalSearch.php - - -administrator -1600 -100 -0 - - - -User management - -UI.php - - -administrator -1 -100 -0 - - - -User logins - -UI.php -../images/std_view.gif - -administrator -10 -110 -0 - - - -Profiles - -UI.php -../images/std_view.gif - -administrator -11 -110 -0 - - - -Export - -../webservices/export.php - - -administrator -1001 -100 -0 - - - -Run queries - -./run_query.php - - -administrator -1002 -100 -0 - - - + + + +Admin tools + +UI.php + + +administrator +1000 +0 +0 + + +All Applications + +UI.php + + +application +999 +15 +0 + + +All Circuits + +UI.php + + +application +999 +15 +0 + + +All Contracts + +./UI.php + + +application +2 +33 +0 + + +All Interfaces + +UI.php + + +application +999 +15 +0 + + +All Network devices + +UI.php + + +application +999 +15 +0 + + +All Patches + +UI.php + + +application +999 +15 +0 + + +All PCs + +UI.php + + +application +999 +15 +0 + + +All Servers + +UI.php + + +application +999 +15 +0 + + +All Services + +UI.php + + +application +1 +33 +0 + + +All Subnets + +UI.php + + +application +999 +15 +0 + + +Audit + +./audit.php + + +application +4 +16 +0 + + +Change Management + +./UI.php + + +application +7 +0 +0 + + +Closed Changes + +UI.php + + +application +5 +12 +0 + + +Closed Incident + +./UI.php + + +application +5 +21 +0 + + +Configuration Items + +UI.php + + +application +2 +16 +0 + + +Configuration Management + +UI.php + + +application +2 +0 +0 + + +Contacts + +UI.php + + +application +1 +16 +0 + + +CSV import + +csvimport.php + + +application +999 +35 +0 + + +Data Model + +schema.php + + +administrator +1500 +37 +0 + + +Document + +UI.php + + +application +6 +16 +0 + + +Export + +../webservices/export.php + + +administrator +1001 +37 +0 + + +Grouping + +UI.php + + +application +3 +16 +0 + + +Incident Management + +./UI.php + + +application +5 +0 +0 + + +Known Errors + +./UI.php + + +application +999 +21 +0 + + +Locations + +UI.php + + +application +5 +16 +0 + + +My Changes + +UI.php + + +application +1 +12 +0 + + +My Incidents + +UI.php + + +application +1 +21 +0 + + +My Service calls + +UI.php + + +application +1 +32 +0 + + +Open Changes + +./UI.php + + +application +3 +12 +0 + + +Open Incidents + +UI.php + + +application +3 +21 +0 + + +Open Service calls + +UI.php + + +application +2 +32 +0 + + +Persons + +UI.php + + +application +7 +17 +0 + + +Profiles + +UI.php +../images/std_view.gif + +administrator +11 +40 +0 + + +Run queries + +./run_query.php + + +administrator +1002 +37 +0 + + +Scheduled Outages + +./UI.php + + +application +7 +12 +0 + + +Service Desk + +UI.php + + +application +3 +0 +0 + + +Service Management + +./UI.php + + +application +8 +0 +0 + + +Teams + +UI.php + + +application +8 +17 +0 + + +Tools + +UI.php + + +application +9 +0 +0 + + +Universal Search + +UniversalSearch.php + + +administrator +1600 +37 +0 + + +User logins + +UI.php +../images/std_view.gif + +administrator +10 +40 +0 + + +User management + +UI.php + + +administrator +1 +37 +0 + + +Welcome + +./UI.php + + +application +1 +0 +0 + + \ No newline at end of file diff --git a/setup/data/structure/export_menus.cmd b/setup/data/structure/export_menus.cmd index c034b0bb68..29ac490fe6 100644 --- a/setup/data/structure/export_menus.cmd +++ b/setup/data/structure/export_menus.cmd @@ -1,4 +1,4 @@ -SET WEBROOT=http://localhost:81 +SET WEBROOT=http://localhost:81/trunk SET EXPORT=%WEBROOT%/webservices/export.php SET USER=admin @@ -6,5 +6,5 @@ SET PWD=admin REM The order (numbering) of the files is important since REM it dictates the order to import them back -wget --output-document=1.menus.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT menuNode WHERE type%%3D%%27application%%27&format=xml" +wget --output-document=1.menus.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT menuNode WHERE type%%3D%%27application%%27 OR type%%3D%%27administrator%%27&format=xml" pause \ No newline at end of file From 84c636fc0768abe8c07831ebd10acb2220406049 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 08:48:54 +0000 Subject: [PATCH 091/970] Fixed wrong file path for the graphs (lifecyle), could not work but on a Windows platform SVN:trunk[166] --- pages/graphviz.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/graphviz.php b/pages/graphviz.php index c049191444..6552db0850 100644 --- a/pages/graphviz.php +++ b/pages/graphviz.php @@ -65,7 +65,7 @@ function GraphvizLifecycle($sClass) $sClass = utils::ReadParam('class', 'bizIncidentTicket'); $sDir = dirname(__FILE__); -$sImageFilePath = $sDir."\\..\\images\\lifecycle\\".$sClass.".png"; +$sImageFilePath = $sDir."/../images/lifecycle/".$sClass.".png"; if (file_exists("/iTop/Graphviz/bin/dot.exe")) { // create the file with Graphviz From 6964ca3003fb73e323bdbfe0fad35485631fec30 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 16 Sep 2009 14:14:15 +0000 Subject: [PATCH 092/970] - removed debug trace SVN:trunk[167] --- js/wizardhelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 09ad3556ee..13f0e78e91 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -106,7 +106,7 @@ function WizardHelper(sClass) { operation: 'wizard_helper' }, function(html){ $('body').append(html); - console.log('data received:', oWizardHelper); + //console.log('data received:', oWizardHelper); //oWizardHelper.FromJSON(json_data); oWizardHelper.UpdateFields(); //console.log(oWizardHelper); From 5efd4f10f5c2d8b90d9b2d78235019f706cb0539 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 15:00:40 +0000 Subject: [PATCH 093/970] Fixed a regression: autocomplete not working due to a change in the execution of the script rendered in Ajax SVN:trunk[168] --- application/cmdbabstract.class.inc.php | 4 ++-- js/wizardhelper.js | 2 +- pages/ajax.render.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index bc1008ed37..3f9a3cd002 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -645,13 +645,13 @@ abstract class cmdbAbstractObject extends CMDBObject //Enum field or external key, display a combo if (count($aAllowedValues) == 0) { - $sHTMLValue = ""; + $sHTMLValue = ""; } else if (count($aAllowedValues) > 50) { // too many choices, use an autocomplete // The input for the auto complete - $sHTMLValue = ""; + $sHTMLValue = ""; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 13f0e78e91..3a3d870d88 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -108,7 +108,7 @@ function WizardHelper(sClass) $('body').append(html); //console.log('data received:', oWizardHelper); //oWizardHelper.FromJSON(json_data); - oWizardHelper.UpdateFields(); + //oWizardHelper.UpdateFields(); // Is done directly in the html provided by ajax.render.php //console.log(oWizardHelper); $('#wizStep'+ G_iCurrentStep).unblock( {fadeOut: 0} ); }); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 0edce8c5e3..c3d7833409 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -94,7 +94,7 @@ switch($operation) $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } - $oPage->add("\n"); + $oPage->add("\n"); break; case 'ajax': From 0232d2dadced04e143cd3430a867e57931649f39 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 16 Sep 2009 16:01:12 +0000 Subject: [PATCH 094/970] User Management: removed limitation on the user rights that could not be established without a given object set, and added a feedback in the user details page (grant summary, based on the results returned by the std user management API, thus is reliable) SVN:trunk[169] --- .../userrights/userrightsmatrix.class.inc.php | 6 +- .../userrights/userrightsnull.class.inc.php | 6 +- .../userrightsprofile.class.inc.php | 215 ++++++++++++++---- application/nicewebpage.class.inc.php | 9 +- core/userrights.class.inc.php | 24 +- pages/advanced_search.php | 2 +- pages/csvimport.php | 2 +- 7 files changed, 198 insertions(+), 66 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index aacdd2020a..08b9eab174 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -348,7 +348,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $oNullFilter; } - public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstanceSet = null) { if (!array_key_exists($iActionCode, self::$m_aActionCodes)) { @@ -376,7 +376,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $iRetCode; } - public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) { if (!array_key_exists($iActionCode, self::$m_aActionCodes)) { @@ -404,7 +404,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $iRetCode; } - public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances) + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstanceSet = null) { $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND userid = '$iUserId'")); if ($oSet->Count() < 1) diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index 2d58eac418..981ef8ccf3 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -59,17 +59,17 @@ class UserRightsNull extends UserRightsAddOnAPI return $oNullFilter; } - public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstanceSet = null) { return UR_ALLOWED_YES; } - public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances) + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstanceSet = null) { return UR_ALLOWED_YES; } - public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances) + public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) { return UR_ALLOWED_YES; } diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index a0fe498c7f..9edf1b1537 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -66,6 +66,70 @@ class URP_Users extends UserRightsBaseClass MetaModel::Init_SetZListItems('advanced_search', array('login', 'userid')); // Criteria of the advanced search form } + function GetGrantAsHtml($sClass, $iAction) + { + if (UserRights::IsActionAllowed($sClass, $iAction, null, $this->GetKey())) + { + return 'yes'; + } + else + { + return 'no'; + } + } + + function DoShowGrantSumary($oPage) + { + $iUserId = $this->GetKey(); + if (UserRights::IsAdministrator($iUserId)) + { + // Looks dirty, but ok that's THE ONE + $oPage->p('Has Read/Write access to any object in the database.'); + return; + } + + $aDisplayData = array(); + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + $aStimuli = array(); + foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + if (UserRights::IsStimulusAllowed($sClass, $sStimulusCode, null, $iUserId)) + { + $aStimuli[] = ''.htmlentities($oStimulus->Get('label')).''; + } + } + $sStimuli = implode(', ', $aStimuli); + + $aDisplayData[] = array( + 'class' => MetaModel::GetName($sClass), + 'read' => $this->GetGrantAsHtml($sClass, UR_ACTION_READ), + 'bulkread' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_READ), + 'write' => $this->GetGrantAsHtml($sClass, UR_ACTION_MODIFY), + 'bulkwrite' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_MODIFY), + 'stimuli' => $sStimuli, + ); + } + + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); + $aDisplayConfig['read'] = array('label' => 'Read', 'description' => ''); + $aDisplayConfig['bulkread'] = array('label' => 'Bulk read', 'description' => 'List objects or export massively'); + $aDisplayConfig['write'] = array('label' => 'Write', 'description' => 'Create and edit (modify)'); + $aDisplayConfig['bulkwrite'] = array('label' => 'Bulk write', 'description' => 'Massively create/edit (CSV import)'); + $aDisplayConfig['stimuli'] = array('label' => 'Stimuli', 'description' => 'Allowed (compound) actions'); + $oPage->table($aDisplayConfig, $aDisplayData); + } + + function DisplayBareRelations(web_page $oPage) + { + parent::DisplayBareRelations($oPage); + + $oPage->SetCurrentTabContainer('Related Objects'); + + $oPage->SetCurrentTab('Grants matrix'); + $this->DoShowGrantSumary($oPage); + } } @@ -881,18 +945,26 @@ exit; return $oGrantRecord; } - protected function GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject) + protected function GetObjectActionGrant($oUser, $sClass, $iActionCode, /*DBObject*/ $oObject = null) { + if(is_null($oObject)) + { + $iObjectRef = -999; + } + else + { + $iObjectRef = $oObject->GetKey(); + } // load and cache permissions for the current user on the given object // - $aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$oObject->GetKey][$iActionCode]; + $aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode]; if (is_array($aTest)) return $aTest; $sAction = self::$m_aActionCodes[$iActionCode]; $iInstancePermission = UR_ALLOWED_NO; $aAttributes = array(); - foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile) + foreach($this->GetMatchingProfiles($oUser, $sClass, $oObject) as $iProfile) { $oGrantRecord = $this->GetClassActionGrant($iProfile, $sClass, $sAction); if (is_null($oGrantRecord)) @@ -924,18 +996,24 @@ exit; 'permission' => $iInstancePermission, 'attributes' => $aAttributes, ); - $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$oObject->GetKey()][$iActionCode] = $aRes; + $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode] = $aRes; return $aRes; } - public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances) + public function IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstanceSet = null) { if ($this->IsAdministrator($iUserId)) return true; $oUser = $this->m_aUsers[$iUserId]; - $oInstances->Rewind(); - while($oObject = $oInstances->Fetch()) + if (is_null($oInstanceSet)) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode); + return $aObjectPermissions['permission']; + } + + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) { $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); @@ -953,7 +1031,7 @@ exit; $iGlobalPermission = $iInstancePermission; } } - $oInstances->Rewind(); + $oInstanceSet->Rewind(); if (isset($iGlobalPermission)) { @@ -965,14 +1043,28 @@ exit; } } - public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances) + public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) { if ($this->IsAdministrator($iUserId)) return true; $oUser = $this->m_aUsers[$iUserId]; - $oInstances->Rewind(); - while($oObject = $oInstances->Fetch()) + if (is_null($oInstanceSet)) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode); + $aAttributes = $aObjectPermissions['attributes']; + if (in_array($sAttCode, $aAttributes)) + { + return $aObjectPermissions['permission']; + } + else + { + return UR_ALLOWED_NO; + } + } + + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) { $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); @@ -998,7 +1090,7 @@ exit; $iGlobalPermission = $iInstancePermission; } } - $oInstances->Rewind(); + $oInstanceSet->Rewind(); if (isset($iGlobalPermission)) { @@ -1034,7 +1126,7 @@ exit; return $oGrantRecord; } - public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances) + public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstanceSet = null) { if ($this->IsAdministrator($iUserId)) return true; @@ -1042,21 +1134,33 @@ exit; // Note: this code is VERY close to the code of IsActionAllowed() - $oInstances->Rewind(); - while($oObject = $oInstances->Fetch()) + if (is_null($oInstanceSet)) { $iInstancePermission = UR_ALLOWED_NO; - foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile) + foreach($this->GetMatchingProfiles($oUser, $sClass) as $iProfile) { - // Get the permission for this profile/class/stimulus - $oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); - $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); - if ($oSet->Count() < 1) + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (!is_null($oGrantRecord)) { - return UR_ALLOWED_NO; + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; + } + } + return $iInstancePermission; + } + + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) + { + $iInstancePermission = UR_ALLOWED_NO; + foreach($this->GetMatchingProfiles($oUser, $sClass, $oObject) as $iProfile) + { + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (is_null($oGrantRecord)) + { + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; } - // no need to fetch the record, we've requested the records having permission = 'yes' - $iInstancePermission = UR_ALLOWED_YES; } if (isset($iGlobalPermission)) { @@ -1070,7 +1174,7 @@ exit; $iGlobalPermission = $iInstancePermission; } } - $oInstances->Rewind(); + $oInstanceSet->Rewind(); if (isset($iGlobalPermission)) { @@ -1095,7 +1199,7 @@ exit; $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); $aRes = array(); - if (array_key_exists($iUser, $this->m_aUserProfiles) > 0) + if (array_key_exists($iUser, $this->m_aUserProfiles)) { foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) { @@ -1128,40 +1232,65 @@ exit; protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object - protected function GetMatchingProfiles($oUser, $oObject) + protected function GetMatchingProfiles($oUser, $sClass, /*DBObject*/ $oObject = null) { $iUser = $oUser->GetKey(); - $sClass = get_class($oObject); - $iObject = $oObject->GetKey(); + + if(is_null($oObject)) + { + $iObjectRef = -999; + } + else + { + $iObjectRef = $oObject->GetKey(); + } + // // List profiles for which the user projection overlaps the object projection in each and every dimension // Caches the result // - $aTest = @$this->m_aMatchingProfiles[$iUser][$sClass][$iObject]; + $aTest = @$this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef]; if (is_array($aTest)) { return $aTest; } - $aProfileRes = array(); - foreach ($this->m_aDimensions as $iDimension => $oDimension) + if (is_null($oObject)) { - foreach ($this->GetMatchingProfilesByDim($oUser, $oObject, $oDimension) as $iProfile) + if (array_key_exists($iUser, $this->m_aUserProfiles)) { - @$aProfileRes[$iProfile] += 1; + $aRes = array_keys($this->m_aUserProfiles[$iUser]); + } + else + { + // no profile has been defined for this user + $aRes = array(); + } + } + else + { + $aProfileRes = array(); + foreach ($this->m_aDimensions as $iDimension => $oDimension) + { + foreach ($this->GetMatchingProfilesByDim($oUser, $oObject, $oDimension) as $iProfile) + { + @$aProfileRes[$iProfile] += 1; + } + } + + $aRes = array(); + $iDimCount = count($this->m_aDimensions); + foreach ($aProfileRes as $iProfile => $iMatches) + { + if ($iMatches == $iDimCount) + { + $aRes[] = $iProfile; + } } } - $aRes = array(); - $iDimCount = count($this->m_aDimensions); - foreach ($aProfileRes as $iProfile => $iMatches) - { - if ($iMatches == $iDimCount) - { - $aRes[] = $iProfile; - } - } - $this->m_aMatchingProfiles[$iUser][$sClass][$iObject] = $aRes; + // store into the cache + $this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef] = $aRes; return $aRes; } } diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index bd29966ed5..63cfd4cc8d 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -30,15 +30,18 @@ class nice_web_page extends web_page } // By Rom, used by CSVImport and Advanced search - public function MakeClassesSelect($sName, $sDefaultValue, $iWidthPx) + public function MakeClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null) { // $aTopLevelClasses = array('bizService', 'bizContact', 'logInfra', 'bizDocument'); // These are classes wich root class is cmdbAbstractObject ! $this->add(""); } diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index e831e93639..d3071333bc 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -52,9 +52,9 @@ abstract class UserRightsAddOnAPI abstract public function GetUserId($sLogin); // returns the id of the user or false abstract public function GetContactId($sLogin); // returns the id of the "business" user or false abstract public function GetFilter($sLogin, $sClass); // returns a filter object - abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances); - abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances); - abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances); + abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null); + abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null); + abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsAdministrator($iUserId); } @@ -211,48 +211,48 @@ class UserRights return self::$m_oAddOn->GetFilter(self::$m_iUserId, $sClass); } - public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $oInstances, $iUserId = null) + public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $iUserId = null) { if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; if (is_null($iUserId)) { - return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $oInstances); + return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $oInstanceSet); } else { - return self::$m_oAddOn->IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstances); + return self::$m_oAddOn->IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstanceSet); } } - public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $oInstances, $iUserId = null) + public static function IsStimulusAllowed($sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null, $iUserId = null) { if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; if (is_null($iUserId)) { - return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $oInstances); + return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $oInstanceSet); } else { - return self::$m_oAddOn->IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstances); + return self::$m_oAddOn->IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstanceSet); } } - public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances, $iUserId = null) + public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $iUserId = null) { if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; if (!self::CheckLogin()) return false; if (is_null($iUserId)) { - return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $oInstances); + return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $oInstanceSet); } else { - return self::$m_oAddOn->IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstances); + return self::$m_oAddOn->IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstanceSet); } } diff --git a/pages/advanced_search.php b/pages/advanced_search.php index c5b5fbfb88..fa29c2c88b 100644 --- a/pages/advanced_search.php +++ b/pages/advanced_search.php @@ -31,7 +31,7 @@ function Page1_AskClass($oPage) $oPage->add("
        \n"); //$oPage->add(""); $oPage->p("Please select the type of object that you want to look for:"); - $oPage->MakeClassesSelect("class", "", 50); + $oPage->MakeClassesSelect("class", "", 50, UR_ACTION_READ); $oPage->add("\n"); $oPage->add("
        \n"); } diff --git a/pages/csvimport.php b/pages/csvimport.php index 256aa2957a..e18aa4bda5 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -339,7 +339,7 @@ function Do_Welcome($oPage, $sClass) $sCSVData = utils::ReadPostedParam('csvdata'); $oPage->add("
        "); - $oPage->MakeClassesSelect("class", $sClass, 50); + $oPage->MakeClassesSelect("class", $sClass, 50, UR_ACTION_MODIFY); $oPage->add("
        "); $oPage->add(""); $oPage->add("
        "); From 7c00f42d5c5e3ca29ad464c891c664d4a4fc9626 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 16 Sep 2009 16:07:28 +0000 Subject: [PATCH 095/970] - Fix for Internet Explorer: Arrays must be properly closed! SVN:trunk[170] --- js/wizardhelper.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 3a3d870d88..c42beded44 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -1,5 +1,4 @@ // Wizard Helper JavaScript class to communicate with the WizardHelper PHP class - function WizardHelper(sClass) { this.m_oData = { 'm_sClass' : '', @@ -9,7 +8,7 @@ function WizardHelper(sClass) 'm_aAllowedValuesRequested': [], 'm_aDefaultValue': [], 'm_aAllowedValues': [], - 'm_iFieldsCount' : 0, + 'm_iFieldsCount' : 0 }; this.m_oData.m_sClass = sClass; From d3b76d3b238ab10408bdd0a44fba3208b9f70639 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 16 Sep 2009 16:08:52 +0000 Subject: [PATCH 096/970] - Allowed values is now implemented, so I removed the comment ! SVN:trunk[171] --- application/cmdbabstract.class.inc.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3f9a3cd002..ad852fc797 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -523,8 +523,7 @@ abstract class cmdbAbstractObject extends CMDBObject } } } - // #@# todo - add context information, otherwise any value will be authorized for external keys - $aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, array()); + $aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, $aExtraParams); if ($aAllowedValues != null) { //Enum field or external key, display a combo From 127e77858ebace9ed1c8d4803a1a69c54389427f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 16 Sep 2009 16:09:46 +0000 Subject: [PATCH 097/970] Correct "script" tag... just in case... SVN:trunk[172] --- pages/UI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/UI.php b/pages/UI.php index fc2700b710..b1324936c4 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -360,7 +360,7 @@ switch($operation) $id = ''; $oObj = MetaModel::NewObject($sClass); } - $oP->add("\n"; $sHTMLValue .= $this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk'); $sHTMLValue .= $this->GetLinkObjectDialog($oPage, $this->m_iInputId); - $sHTMLValue .= "m_iInputId}\" size=\"35\" name=\"\" value=\"\" title=\"Type the first 3 characters\"/>"; + $sHTMLValue .= "m_iInputId}\" size=\"35\" value=\"\" title=\"Type the first 3 characters\"/>"; $sHTMLValue .= "m_iInputId}\" value=\" Add... \" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; // another hidden input to store & pass the object's Id @@ -275,7 +275,7 @@ EOF; $sHTML = "
        \n"; $sHTML .= "
        \n"; $sHTML .= "

        ".MetaModel::GetName($sLinkedClass)." attributes

        \n"; - $sHTML .= "\n"; + $sHTML .= "\n"; $index = 0; $aAttrsMap = array(); $aDetails = array(); diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index 7fdc3a5081..db57e20753 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -117,6 +117,8 @@ class UILinksWizard $('#ModalDlg').html(data); dlgWidth = $(document).width() - 100; $('#ModalDlg').css('width', dlgWidth); + $('#ModalDlg').css('left', 50); + $('#ModalDlg').css('top', 50); $('#ModalDlg').jqmShow(); }, 'html' @@ -355,7 +357,7 @@ class UILinksWizard $oP->add("  "); $oP->Add("
        \n"); $oP->Add("\n"); - $oP->add_ready_script("$('div#SearchFormToAdd form').attr('onSubmit', 'var the_form = this; return SearchObjectsToAdd(the_form.id);');"); + $oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit', function() {var the_form = this; SearchObjectsToAdd(the_form.id); return false;});"); } public function SearchObjectsToAdd(web_page $oP, UserContext $oContext) diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index f49228dbcc..307bdb50a1 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -80,12 +80,12 @@ class UIWizard $sDisabled = $bFinishEnabled ? '' : 'disabled'; $nbSteps = count($this->m_aWizardSteps['mandatory']) + count($this->m_aWizardSteps['optional']); $this->m_oPage->add("
        - - - + + +
        \n"); $this->m_oPage->add(" -\n"); $this->m_oPage->add("
        \n"); $this->m_oPage->add("
        \n"); - $this->m_oPage->add("m_sClass)."\">\n"); + $this->m_oPage->add("m_sClass)."\" />\n"); $this->m_oPage->add("\n"); $this->m_oPage->add("
        \n"); } diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index cdf31ab800..3474033fab 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -41,7 +41,7 @@ class CMDBSource self::$m_sDBName = $sSource; if (!self::$m_resDBLink = @mysql_pconnect($sServer, $sUser, $sPwd)) { - throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer, 'user'=>$sUser)); + throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer)); } if (!empty($sSource)) { diff --git a/css/jqModal.css b/css/jqModal.css index 288981f2a5..41e0593437 100644 --- a/css/jqModal.css +++ b/css/jqModal.css @@ -2,32 +2,30 @@ Brice Burgess */ /* The Window's CSS z-index value is respected (takes priority). If none is supplied, - the Window's z-index value will be set to 3000 by default (in jqModal.js). You - can change this value by either; - a) supplying one via CSS - b) passing the "zIndex" parameter. E.g. (window).jqm({zIndex: 500}); */ + the Window's z-index value will be set to 3000 by default (via jqModal.js). */ .jqmWindow { display: none; position: fixed; - top: 25px; - left: 25px; + no.top: 17%; + no.left: 50%; - nomargin-left: 25px; - nomargin-right: 25px; - nowidth: 800px; + no.margin-left: -300px; + no.width: 700px; - background-color: #FFF; + background-color: #EEE; color: #333; border: 1px solid black; padding: 12px; + + z-index:9999; } -.jqmOverlay { background-color: #333; } +.jqmOverlay { background-color: #000; } /* Background iframe styling for IE6. Prevents ActiveX bleed-through ("); } } + + protected function get_base_tag() + { + $sTag = ''; + if (($this->a_base['href'] != '') || ($this->a_base['target'] != '')) + { + $sTag = 'a_base['href'] != '')) + { + $sTag .= "href =\"{$this->a_base['href']}\" "; + } + if (($this->a_base['target'] != '')) + { + $sTag .= "target =\"{$this->a_base['target']}\" "; + } + $sTag .= " />\n"; + } + return $sTag; + } } ?> diff --git a/webservices/export.php b/webservices/export.php index b7c7ad2e7d..912ddebfa2 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -32,6 +32,21 @@ if (!empty($sExpression)) { case 'html': $oP = new nice_web_page("iTop - Export"); + // The HTML output is made for pages located in the /pages/ folder + // since this page is in a different folder, let's adjust the HTML 'base' attribute + // to make the relative hyperlinks in the page work + $sServerName = $_SERVER['SERVER_NAME']; + $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + if ($sProtocol == 'http') + { + $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + else + { + $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + $sUrl = "$sProtocol://{$sServerName}{$sPort}/pages/"; + $oP->set_base($sUrl); cmdbAbstractObject::DisplaySet($oP, $oSet, array('menu' => false)); break; From 89396151c51afb7c0a3ab58d82c4b5271acc88bf Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 21 Dec 2009 15:52:23 +0000 Subject: [PATCH 123/970] Fixed bug (Trac #53), I don't know exactly what I did (!) but I've copied what's done in __DescribeHTML and this makes the full text search work for organizations ! Note that __DescribeHTML actually modifies the objects it is applied to (via a referenced passed to the Describe method) SVN:trunk[200] --- core/dbobjectsearch.class.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 03020556fb..ae1ffbdff8 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -275,6 +275,8 @@ class DBObjectSearch public function AddCondition_FullText($sFullText) { $this->m_aFullText[] = $sFullText; + $index = count($this->m_aParams) + 1; + $this->m_aParams['param'.$index] = 1; } protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation) From 97761de870ff431b5d0e29abffbfedcb693c5926 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 21 Dec 2009 19:16:27 +0000 Subject: [PATCH 124/970] - completed the implementaiton o the 'is dirty' flag to prevent reloading an object being modified. - added a protection against reading an uninitialized field that triggered a PHP 'notice' error. SVN:trunk[201] --- core/dbobject.class.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 431132097c..00568551c9 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -302,6 +302,7 @@ abstract class DBObject } } $this->m_aCurrValues[$sAttCode] = $oAttDef->MakeRealValue($value); + $this->RegisterAsDirty(); // Make sure we do not reload it anymore... before saving it } public function Get($sAttCode) @@ -310,13 +311,13 @@ abstract class DBObject { throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this)); } - if ($this->m_bIsInDB && !$this->m_aLoadedAtt[$sAttCode]) + if ($this->m_bIsInDB && !$this->m_aLoadedAtt[$sAttCode] && !$this->m_bDirty) { // #@# non-scalar attributes.... handle that differentely $this->Reload(); } $this->ComputeFields(); - return $this->m_aCurrValues[$sAttCode]; + return isset($this->m_aCurrValues[$sAttCode]) ? $this->m_aCurrValues[$sAttCode] : ''; } public function GetOriginal($sAttCode) @@ -696,6 +697,7 @@ abstract class DBObject public function DBInsert() { $this->DBInsertNoReload(); + $this->m_bDirty = false; $this->Reload(); return $this->m_iKey; } @@ -741,9 +743,13 @@ abstract class DBObject } $this->DBWriteLinks(); + $this->m_bDirty = false; // Reload to get the external attributes - if ($bHasANewExternalKeyValue) $this->Reload(); + if ($bHasANewExternalKeyValue) + { + $this->Reload(); + } return $this->m_iKey; } From 8bb9176d3613511840d8927bfdc5f2935dee9af1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 21 Dec 2009 19:53:13 +0000 Subject: [PATCH 125/970] - fixed the display of the history log of the 'user' class (Trac #48), and more generally the history log of all classes not derived from logRealObject. SVN:trunk[202] --- application/cmdbabstract.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index a707dff3e1..85334ffb05 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -133,6 +133,7 @@ abstract class cmdbAbstractObject extends CMDBObject // history block (with toggle) $oHistoryFilter = new DBObjectSearch('CMDBChangeOpSetAttribute'); $oHistoryFilter->AddCondition('objkey', $this->GetKey()); + $oHistoryFilter->AddCondition('objclass', get_class($this)); $oBlock = new HistoryBlock($oHistoryFilter, 'toggle', false); $oBlock->Display($oPage, -1); From d3d617fabe53d2480f114c25d6667551b857cad5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 21 Dec 2009 20:11:29 +0000 Subject: [PATCH 126/970] - fixed bug #52 (display of lnkInfraError objects) by adding a protection in GetDisplayName against objects that actually do not have any name. SVN:trunk[203] --- application/cmdbabstract.class.inc.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 85334ffb05..18d2f4a939 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -189,7 +189,12 @@ abstract class cmdbAbstractObject extends CMDBObject function GetDisplayName() { - return $this->GetAsHTML(MetaModel::GetNameAttributeCode(get_class($this))); + $sDisplayName = ''; + if (MetaModel::GetNameAttributeCode(get_class($this)) != '') + { + $sDisplayName = $this->GetAsHTML(MetaModel::GetNameAttributeCode(get_class($this))); + } + return $sDisplayName; } function GetBareDetails(web_page $oPage) From 37c5880ced0fab20e101acca182d0247c944c0e0 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sat, 26 Dec 2009 13:06:28 +0000 Subject: [PATCH 127/970] #53 Global search always returning all organization, reviewed Denis's fix SVN:trunk[204] --- core/dbobjectsearch.class.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index ae1ffbdff8..8cf4ccf156 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -275,8 +275,6 @@ class DBObjectSearch public function AddCondition_FullText($sFullText) { $this->m_aFullText[] = $sFullText; - $index = count($this->m_aParams) + 1; - $this->m_aParams['param'.$index] = 1; } protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation) @@ -466,7 +464,7 @@ class DBObjectSearch public function RenderCondition() { - return $this->m_oSearchCondition->Render($this->m_aParams, true); + return $this->m_oSearchCondition->Render($this->m_aParams, false); } public function serialize() @@ -634,6 +632,12 @@ class DBObjectSearch $sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias(); $sRes .= $this->ToOQL_Joins(); $sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams); + + // Temporary: add more info about other conditions, necessary to avoid strange behaviors with the cache + foreach($this->m_aFullText as $sFullText) + { + $sRes .= " AND MATCHES '$sFullText'"; + } return $sRes; } From ae1e7e224a07b5621f2fe01a306b6dc985f20456 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sat, 26 Dec 2009 13:18:56 +0000 Subject: [PATCH 128/970] Reviewed and changed a bug fix (No TRAC entry) on the display of external fields when the external key is null. The actual fix is to consider that null is a value. Isset() should be used with care because one could not differentiate a missing value (in an array or a value that is null) SVN:trunk[205] --- core/dbobject.class.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 00568551c9..45b02c0c15 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -209,7 +209,9 @@ abstract class DBObject // Note: we assume that, for a given attribute, if it can be loaded, // then one column will be found with an empty suffix, the others have a suffix - if (isset($aRow[$sAttCode])) + // Take care: the function isset will return false in case the value is null, + // which is something that could happen on open joins + if (array_key_exists($sAttCode, $aRow)) { $value = $oAttDef->FromSQLToValue($aRow, $sAttCode); @@ -317,7 +319,7 @@ abstract class DBObject $this->Reload(); } $this->ComputeFields(); - return isset($this->m_aCurrValues[$sAttCode]) ? $this->m_aCurrValues[$sAttCode] : ''; + return $this->m_aCurrValues[$sAttCode]; } public function GetOriginal($sAttCode) From 193aef8a63044d3321a148ea17650217891b6b87 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sat, 26 Dec 2009 13:30:16 +0000 Subject: [PATCH 129/970] Trac #51 - Implemented external key feed by the mean of reconciliation on external fields SVN:trunk[206] --- core/bulkchange.class.inc.php | 4 ++-- webservices/import.php | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 995bce92b2..f2641afeb8 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -229,7 +229,8 @@ class BulkChange protected $m_sClass; protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string) // #@# todo: rename the variables to sColIndex - protected $m_aAttList; // attcode => iCol protected $m_aReconcilKeys;// iCol => attcode (attcode = 'id' for the pkey) + protected $m_aAttList; // attcode => iCol + protected $m_aReconcilKeys;// iCol => attcode (attcode = 'id' for the pkey) protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol; public function __construct($sClass, $aData, $aAttList, $aReconcilKeys, $aExtKeys) @@ -268,7 +269,6 @@ class BulkChange foreach ($aKeyConfig as $sForeignAttCode => $iCol) { // The foreign attribute is one of our reconciliation key - $sFieldId = MakeExtFieldSelectValue($sAttCode, $sForeignAttCode); $oReconFilter->AddCondition($sForeignAttCode, $aRowData[$iCol], '='); $aResults["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); } diff --git a/webservices/import.php b/webservices/import.php index 59b9a2cc68..9d91bc016a 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -15,7 +15,7 @@ // // Known limitations // - output still in html, because the errors are not displayed in xml -// - could not set the external keys by the mean of a reconciliation (the exact key must be given) +// - only external fields attributes could be used as reconciliation keys for external keys // - reconciliation is made on the first column // - no option to force 'always create' or 'never create' // @@ -35,6 +35,11 @@ require_once('../application/xmlpage.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); + +class WebServiceException extends Exception +{ +} + login_web_page::DoLogin(); // Check user rights and prompt if needed $oContext = new UserContext(); @@ -46,6 +51,7 @@ $oAppContext = new ApplicationContext(); //$oP = new XMLPage("iTop - Bulk import"); $oP = new web_page("iTop - Bulk import"); +$oP->add('This is a prototype, I repeat: PRO-TO-TYPE, therefore it suffers bugs and limitations, documented in the code. Next step: specify...'); try { $sClass = utils::ReadParam('class', ''); @@ -59,11 +65,30 @@ try // Limitation: as the attribute list is in the first line, we can not match external key by an third-party attribute $sRawFieldList = $oCSVParser->ListFields(); $aAttList = array(); - foreach($sRawFieldList as $iField => $sFieldName) - { - $aAttList[$sFieldName] = $iField; - } $aExtKeys = array(); + foreach($sRawFieldList as $iFieldId => $sFieldName) + { + if (!MetaModel::IsValidAttCode($sClass, $sFieldName)) + { + throw new WebServiceException("Unknown attribute '$sFieldName' (class: '$sClass')"); + } + $oAtt = MetaModel::GetAttributeDef($sClass, $sFieldName); + if ($oAtt->IsExternalKey()) + { + $aExtKeys[$sFieldName]['id'] = $iFieldId; + $aAttList[$sFieldName] = $iFieldId; + } + elseif ($oAtt->IsExternalField()) + { + $sExtKeyAttCode = $oAtt->GetKeyAttCode(); + $sRemoteAttCode = $oAtt->GetExtAttCode(); + $aExtKeys[$sExtKeyAttCode][$sRemoteAttCode] = $iFieldId; + } + else + { + $aAttList[$sFieldName] = $iFieldId; + } + } // Limitation: the reconciliation key is the first attribute $aReconcilKeys = array($sRawFieldList[0]); From 18f209a2f74bfa4a333cb9a86fb5f90a93a2dc5a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sat, 26 Dec 2009 13:36:59 +0000 Subject: [PATCH 130/970] Trac #50 - Interactive bulk load (CSV) was not doing well when using several reconciliation keys SVN:trunk[207] --- business/itop.business.class.inc.php | 48 ++++++++++++++-------------- pages/csvimport.php | 35 +++++++++++++------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index caed4161ba..a863167f4a 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -191,7 +191,7 @@ class bizContact extends logRealObject "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "contacts", "db_key_field" => "id", "db_finalclass_field" => "", @@ -249,8 +249,8 @@ class bizPerson extends bizContact "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "first_name", "name"), // comment en dfinir plusieurs - // "reconc_keys" => array("org_name", "employee_number"), + "reconc_keys" => array("org_id", "first_name", "name"), // comment en dfinir plusieurs + // "reconc_keys" => array("org_id", "employee_number"), "db_table" => "persons", // Can it use the same physical DB table as any contact ? "db_key_field" => "id", "db_finalclass_field" => "", @@ -260,7 +260,7 @@ class bizPerson extends bizContact MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"First Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("employee_number", array("label"=>"Employee Number", "description"=>"employee number", "allowed_values"=>null, "sql"=>"employee_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedEmployeeStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedEmployeeStatuses, "sql"=>"status", "default_value"=>"available", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("first_name"); @@ -303,7 +303,7 @@ class bizTeam extends bizContact "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "teams", "db_key_field" => "id", "db_finalclass_field" => "", @@ -340,7 +340,7 @@ class lnkContactTeam extends cmdbAbstractObject "key_label" => "link_id", "name_attcode" => "role", "state_attcode" => "", - "reconc_keys" => array("contact_name", "team_name"), + "reconc_keys" => array("contact_id", "team_name"), "db_table" => "teams_links", "db_key_field" => "link_id", "db_finalclass_field" => "", @@ -387,7 +387,7 @@ class bizDocument extends logRealObject "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "documents", "db_key_field" => "id", "db_finalclass_field" => "", @@ -434,7 +434,7 @@ class bizDocVersion extends cmdbAbstractObject "key_label" => "id", "name_attcode" => "version_number", "state_attcode" => "", - "reconc_keys" => array("docname", "version_number"), + "reconc_keys" => array("document", "version_number"), "db_table" => "document_versions", "db_key_field" => "id", "db_finalclass_field" => "", @@ -482,7 +482,7 @@ class lnkDocumentRealObject extends cmdbAbstractObject "key_label" => "link_id", "name_attcode" => "link_type", "state_attcode" => "", - "reconc_keys" => array("doc_name", "object_name"), + "reconc_keys" => array("doc_id", "object_name"), "db_table" => "documents_links", "db_key_field" => "link_id", "db_finalclass_field" => "", @@ -528,7 +528,7 @@ class lnkContactRealObject extends cmdbAbstractObject "key_label" => "link_id", "name_attcode" => "role", "state_attcode" => "", - "reconc_keys" => array("contact_name", "object_name"), + "reconc_keys" => array("contact_id", "object_name"), "db_table" => "contacts_links", "db_key_field" => "link_id", "db_finalclass_field" => "", @@ -577,7 +577,7 @@ class logInfra extends logRealObject "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "infra", "db_key_field" => "id", "db_finalclass_field" => "", @@ -610,7 +610,7 @@ class lnkContactInfra extends cmdbAbstractObject "key_label" => "link_id", "name_attcode" => "role", "state_attcode" => "", - "reconc_keys" => array("contact_name", "object_name"), + "reconc_keys" => array("contact_id", "infra_id"), "db_table" => "contacts_infra_links", "db_key_field" => "link_id", "db_finalclass_field" => "", @@ -657,7 +657,7 @@ class bizLocation extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "location", "db_key_field" => "id", "db_finalclass_field" => "", @@ -789,7 +789,7 @@ class bizCircuit extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "carrier_name", "carrier_ref", "name"), // inherited attributes + "reconc_keys" => array("org_id", "provider_id", "carrier_ref", "name"), // inherited attributes "db_table" => "circuits", "db_key_field" => "id", "db_finalclass_field" => "", @@ -872,7 +872,7 @@ class bizInterface extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "device_name", "name"), + "reconc_keys" => array("org_id", "device_id", "name"), "db_table" => "interfaces", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1018,7 +1018,7 @@ class bizSubnet extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "subnets", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1101,7 +1101,7 @@ class bizDevice extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "devices", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1174,7 +1174,7 @@ class bizPC extends bizDevice "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "pcs", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1268,7 +1268,7 @@ class bizServer extends bizDevice "key_label" => "id", "name_attcode" => "name", "state_attcode" => "status", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "servers", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1276,7 +1276,7 @@ class bizServer extends bizDevice ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("InStore,Shipped,Plugged,ProductionCandidate,InProduction,Being Deconfigured,Obsolete"), "sql"=>"status", "default_value"=>"InStore", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("InStore,Shipped,Plugged,ProductionCandidate,InProduction,BeingDeconfigured,Obsolete"), "sql"=>"status", "default_value"=>"InStore", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"CPU type", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("number_of_cpus", array("label"=>"Number of CPUs", "description"=>"Number of CPUs", "allowed_values"=>null, "sql"=>"number_of_cpus", "default_value"=>"1", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -1442,7 +1442,7 @@ class bizNetworkDevice extends bizDevice "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "network_devices", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1506,7 +1506,7 @@ class bizInfraGroup extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_name", "name"), // inherited attributes + "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "infra_group", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1589,7 +1589,7 @@ class bizApplication extends logInfra "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("device_name", "name"), // inherited attributes + "reconc_keys" => array("device_id", "name"), // inherited attributes "db_table" => "applications", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1776,7 +1776,7 @@ class bizPatch extends logRealObject "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("device_name", "name"), // inherited attributes + "reconc_keys" => array("device_id", "name"), // inherited attributes "db_table" => "patches", "db_key_field" => "id", "db_finalclass_field" => "", diff --git a/pages/csvimport.php b/pages/csvimport.php index f20e28eb5a..b617192e97 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -210,13 +210,25 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM foreach($aFieldMap as $sFieldId=>$sColDesc) { $iFieldId = (int) substr($sFieldId, strlen("field")); + + if (array_key_exists($sFieldId, $aIsReconcKey)) + { + // This column will be used as a reconciliation key + + if (IsExtKeyField($sColDesc)) + { + list($sAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); + } + else + { + $sAttCode = $sColDesc; + } + $aReconcilKeys[$sAttCode] = $iFieldId; + } + if ($sColDesc == "id") { $aAttList['id'] = $iFieldId; - if (array_key_exists($sFieldId, $aIsReconcKey)) - { - $aReconcilKeys['id'] = $iFieldId; - } } elseif ($sColDesc == "__none__") { @@ -226,14 +238,12 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM { // This field is value to search on, to find a value for an external key list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); + if ($sExtKeyAttCode == $sExtReconcKeyAttCode) + { + $aAttList[$sExtKeyAttCode] = $iFieldId; + } $aExtKeys[$sExtKeyAttCode][$sExtReconcKeyAttCode] = $iFieldId; } - elseif (array_key_exists($sFieldId, $aIsReconcKey)) - { - // This value is a reconciliation key - $aReconcilKeys[$sColDesc] = $iFieldId; - $aAttList[$sColDesc] = $iFieldId; // A reconciliation key is also a field - } else { // $sColDesc is an attribute code @@ -471,7 +481,7 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) { if (array_key_exists($sFieldId, $aIsReconcKey)) { - $sReconcKey = " [search]"; + $sReconcKey = "
        [key]"; } else { @@ -499,7 +509,8 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) elseif (IsExtKeyField($sColDesc)) { list($sExtKeyAttCode, $sForeignAttCode) = GetExtKeyFieldCodes($sColDesc); - $aDisplayConfig[$sFieldId] = array("label"=>MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode), "description"=>""); + $sLabel = MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode); + $aDisplayConfig[$sFieldId] = array("label"=>"$sLabel$sReconcKey", "description"=>""); $aExtKeys[] = $sExtKeyAttCode; } else From 002c1bf869841baed88bd720c838bbcdade73e60 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sat, 26 Dec 2009 13:39:16 +0000 Subject: [PATCH 131/970] Fixed bug impacting only unit testing procedures SVN:trunk[208] --- addons/userrights/userrightsnull.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index da3f35548e..cb8d351d26 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -74,7 +74,7 @@ class UserRightsNull extends UserRightsAddOnAPI return UR_ALLOWED_YES; } - public static function FlushPrivileges() + public function FlushPrivileges() { } } From d8269cc11bee4c33fd5da21570feeaf538afa50c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 28 Dec 2009 11:22:54 +0000 Subject: [PATCH 132/970] Trac #56 - Implemented and usable but still not a complete and dynamically generated WSDL file. Unit tests are in place (through client/server SOAP protocol, or directly onto the internal API) SVN:trunk[209] --- core/test.class.inc.php | 30 ++- pages/testlist.inc.php | 156 +++++++++++-- webservices/itop.wsdl | 4 + webservices/soapserver.php | 56 +++++ webservices/webservices.class.inc.php | 323 ++++++++++++++++++++++++++ 5 files changed, 552 insertions(+), 17 deletions(-) create mode 100644 webservices/itop.wsdl create mode 100644 webservices/soapserver.php create mode 100644 webservices/webservices.class.inc.php diff --git a/core/test.class.inc.php b/core/test.class.inc.php index f20ebec079..905e7ad063 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -16,6 +16,7 @@ require_once('dbobjectset.class.php'); require_once('userrights.class.inc.php'); +require_once('../webservices/webservices.class.inc.php'); // Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?) @@ -201,15 +202,16 @@ abstract class TestWebServices extends TestHandler { // simply overload DoExecute (temporary) - static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = '', $sOptionnalHeaders = null) + static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null) { $aDataAndAuth = $aData; $aDataAndAuth['operation'] = 'login'; $aDataAndAuth['auth_user'] = $sLogin; $aDataAndAuth['auth_pwd'] = $sPassword; - - $sHost = $GLOBALS['_SERVER']['HTTP_HOST']; - $sUrl = "http://$sHost/$sRelativeUrl"; + $sHost = $_SERVER['HTTP_HOST']; + $sRawPath = $_SERVER['SCRIPT_NAME']; + $sPath = dirname($sRawPath); + $sUrl = "http://$sHost/$sPath/$sRelativeUrl"; return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders); } @@ -248,6 +250,26 @@ abstract class TestWebServices extends TestHandler } } +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +abstract class TestSoapWebService extends TestHandler +{ + // simply overload DoExecute (temporary) + + function __construct() + { + parent::__construct(); + } +} + /** * Test to check that a function outputs some values depending on its input * diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index a0332e1f78..81803829d9 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -591,18 +591,8 @@ class TestMyBizModel extends TestBizModel // Test a complex biz model on the fly /////////////////////////////////////////////////////////////////////////// -class TestQueriesOnFarm extends TestBizModel +abstract class MyFarm extends TestBizModel { - static public function GetName() - { - return 'Farm test'; - } - - static public function GetDescription() - { - return 'A series of tests on the farm business model (SQL generation)'; - } - static public function GetConfigFile() {return '../config-test-farm.php';} protected function DoPrepare() @@ -673,6 +663,20 @@ class TestQueriesOnFarm extends TestBizModel $iId = $oNew->DBInsertNoReload(); return $iId; } +} + + +class TestQueriesOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test'; + } + + static public function GetDescription() + { + return 'A series of tests on the farm business model (SQL generation)'; + } protected function CheckQuery($sQuery, $bIsCorrectQuery) { @@ -940,6 +944,45 @@ class TestBulkChangeOnFarm extends TestBizModel } +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestFullTextSearchOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test - full text search'; + } + + static public function GetDescription() + { + return 'Focus on the full text search feature'; + } + + protected function DoExecute() + { + echo "

        Create protagonists...

        "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + echo "

        Search...

        "; + $oSearch = new DBObjectSearch('Mammal'); + $oSearch->AddCondition_FullText('manof'); + //$oResultSet = new DBObjectSet($oSearch); + $this->search_and_show_list($oSearch); + return true; + } +} + + /////////////////////////////////////////////////////////////////////////// // Benchmark queries /////////////////////////////////////////////////////////////////////////// @@ -1087,7 +1130,7 @@ class TestItopWebServices extends TestWebServices $sCsvData = $aLoadSpec['csvdata']; $aPostData = array('class' => $sClass, 'csvdata' => $sCsvData); - $sRes = self::DoPostRequestAuth('webservices/import.php', $aPostData); + $sRes = self::DoPostRequestAuth('../webservices/import.php', $aPostData); echo "

        $sTitle

        $sCsvData
        $sRes
        "; } @@ -1110,7 +1153,7 @@ class TestItopWebServices extends TestWebServices ), array( 'class' => 'bizTeam', - 'csvdata' => "name;org_id;location_id\nSquadra Azzura;1;1" + 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris" ), array( 'class' => 'bizWorkgroup', @@ -1130,4 +1173,91 @@ class TestItopWebServices extends TestWebServices return true; } } + +$aWebServices = array( + array( + 'verb' => 'CreateIncidentTicket', + 'args' => array( + 'desc of ticket', /* sDescription */ + 'initial situation blah blah blah', /* sInitialSituation */ + array('id' => 1), /* aCallerDesc */ + array('id' => 2), /* aCustomerDesc */ + array('id' => 1), /* aWorkgroupDesc */ + array( + array( + 'class' => 'logInfra', + 'search' => array('id' => 108), + 'link_values' => array('impactoche' => 'plus que critique'), + ), + array( + 'class' => 'bizDevice', + 'search' => array('name' => 'Router03'), + 'link_values' => array('impact' => 'ouais bof'), + ), + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), +); + +class TestSoap extends TestSoapWebService +{ + static public function GetName() {return 'Test SOAP';} + static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';} + + protected function DoExecute() + { + $this->m_SoapClient = new SoapClient( +// null, + "http://localhost:81/trunk/webservices/Itop.wsdl", + array( + //'location' => 'http://localhost:81/trunk/webservices/soapserver.php', + //'uri' => 'http://test-itop/', + 'login' => 'admin', + 'password' => 'admin', + // note: using the classmap functionality lead to APACHE fault on the server side + //'classmap' => array('stdClass' => 'ItopError'), + 'trace' => 1, + ) + ); + + global $aWebServices; + $aWebService = $aWebServices[0]; + +// $oRes = $this->m_SoapClient->CreateIncidentTicket(); + $oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']); + + echo "
        \n";
        +		print_r($oRes);
        +		echo "
        \n"; + +print "
        \n"; 
        +print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
        +print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
        +print "
        "; + + return true; + } +} + +class TestWebServicesDirect extends TestBizModel +{ + static public function GetName() {return 'Test web services locally';} + static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoExecute() + { + global $aWebServices; + $aWebService = $aWebServices[0]; + + $oWebServices = new WebServices(); + $oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']); + echo "
        \n";
        +		print_r($oRes);
        +		echo "
        \n"; + return true; + } +} ?> diff --git a/webservices/itop.wsdl b/webservices/itop.wsdl new file mode 100644 index 0000000000..ba90273376 --- /dev/null +++ b/webservices/itop.wsdl @@ -0,0 +1,4 @@ + + +Create an incident ticket from a monitoring system +Some CIs might be specified (by their name/IP) \ No newline at end of file diff --git a/webservices/soapserver.php b/webservices/soapserver.php new file mode 100644 index 0000000000..c644cb1b37 --- /dev/null +++ b/webservices/soapserver.php @@ -0,0 +1,56 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +// Important note: if some required includes are missing, this might result +// in the error "looks like we got no XML document"... + +require_once('../application/application.inc.php'); +require_once('../application/startup.inc.php'); + +require('./webservices.class.inc.php'); + + +// pb ? - login_web_page::DoLogin(); // Check user rights and prompt if needed + +// Main program + +$oSoapServer = new SoapServer( + null, + //"http://localhost:81/trunk/webservices/Itop.wsdl", // to be a file generated dynamically with location = here + array( + 'uri' => 'http://test-itop/', + // note: using the classmap and no WSDL spec causes a fault in APACHE (looks like an infinite loop) + //'classmap' => array('ItopErrorSOAP' => 'ItopError') + ) +); +// $oSoapServer->setPersistence(SOAP_PERSISTENCE_SESSION); +$oSoapServer->setClass('WebServices', null); + +if ($_SERVER["REQUEST_METHOD"] == "POST") +{ + $oSoapServer->handle(); +} +else +{ + echo "This SOAP server can handle the following functions: "; + $aFunctions = $oSoapServer->getFunctions(); + echo "
          \n"; + foreach($aFunctions as $sFunc) + { + echo "
        • $sFunc
        • \n"; + } + echo "
        \n"; + echo ""; +} +?> diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php new file mode 100644 index 0000000000..f8f1864ecf --- /dev/null +++ b/webservices/webservices.class.inc.php @@ -0,0 +1,323 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + +class WebServiceResult +{ + + /** + * Overall status + * + * @var m_bStatus + */ + public $m_bStatus; + + /** + * Error log + * + * @var m_aErrors + */ + public $m_aErrors; + + /** + * Warning log + * + * @var m_aWarnings + */ + public $m_aWarnings; + + /** + * Information log + * + * @var m_aInfos + */ + public $m_aInfos; + + /** + * Constructor + * + * @param status $bStatus + */ + public function __construct() + { + $this->m_bStatus = true; + $this->m_aErrors = array(); + $this->m_aWarnings = array(); + $this->m_aInfos = array(); + } + + /** + * Did the current processing encounter a stopper issue ? + * + * @return bool + */ + public function IsOk() + { + return $this->m_bStatus; + } + + /** + * Log an error + * + * @param description $sDescription + */ + public function LogError($sDescription) + { + $this->m_aErrors[] = $sDescription; + $this->m_bStatus = false; + } + + /** + * Log a warning + * + * @param description $sDescription + */ + public function LogWarning($sDescription) + { + $this->m_aWarnings[] = $sDescription; + } + + /** + * Log an error or a warning + * + * @param string $sDescription + * @param boolean $bIsStopper + */ + public function LogIssue($sDescription, $bIsStopper = true) + { + if ($bIsStopper) $this->LogError($sDescription); + else $this->LogWarning($sDescription); + } + + /** + * Log operation details + * + * @param description $sDescription + */ + public function LogInfo($sDescription) + { + $this->m_aInfos[] = $sDescription; + } +} + +class WebServices +{ + /** + * Helper to set an external key + * + * @param string sAttCode + * @param array aCallerDesc + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + */ + protected function SetExternalKey($sAttCode, $aExtKeyDesc, &$oTargetObj, &$oRes) + { + $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); + + $bIsMandatory = !$oExtKey->IsNullAllowed(); + if (count($aExtKeyDesc) == 0) + { + $oRes->LogIssue("Ext key $sAttCode: no data was given to give a value to the key", $bIsMandatory); + return; + } + + $sKeyClass = $oExtKey->GetTargetClass(); + $oReconFilter = new CMDBSearchFilter($sKeyClass); + foreach ($aExtKeyDesc as $sForeignAttCode => $value) + { + if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) + { + $sMsg = "Ext key $sAttCode: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass'"; + $oRes->LogIssue($sMsg, $bIsMandatory); + } + // The foreign attribute is one of our reconciliation key + $oReconFilter->AddCondition($sForeignAttCode, $value, '='); + } + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch($oExtObjects->Count()) + { + case 0: + $sMsg = "External key $sAttCode could not be found (searched: '".$oReconFilter->ToOQL()."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + break; + case 1: + // Do change the external key attribute + $oForeignObj = $oExtObjects->Fetch(); + $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); + + // Report it (no need to report if the object already had this value + if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) + { + $oRes->LogInfo("$sAttCode has been set to ".$oForeignObj->GetKey()); + } + break; + default: + $sMsg = "Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')"; + $oRes->LogIssue($sMsg, $bIsMandatory); + } + } + + /** + * Helper to link objects + * + * @param string sLinkAttCode + * @param string sLinkedClass + * @param array $aLinkList + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + * @return array List of objects that could not be found + */ + protected function AddLinkedObjects($sLinkAttCode, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) + { + $oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode); + $sLinkClass = $oLinkAtt->GetLinkedClass(); + $sExtKeyToItem = $oLinkAtt->GetExtKeyToRemote(); + + $aItemsFound = array(); + $aItemsNotFound = array(); + foreach ($aLinkList as $aItemData) + { + $sTargetClass = $aItemData['class']; + if (!MetaModel::IsValidClass($sTargetClass)) + { + $oRes->LogError("Invalid class $sTargetClass for impacted item"); + continue; // skip + } + if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) + { + $oRes->LogError("$sTargetClass is not a child class of $sLinkedClass"); + continue; // skip + } + $oReconFilter = new CMDBSearchFilter($sTargetClass); + $aCIStringDesc = array(); + foreach ($aItemData['search'] as $sAttCode => $value) + { + if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) + { + $oRes->LogError("Invalid filter code $sAttCode for class $sTargetClass"); + continue; // skip + } + $aCIStringDesc[] = "$sAttCode: $value"; + + // The attribute is one of our reconciliation key + $oReconFilter->AddCondition($sAttCode, $value, '='); + } + if (count($aCIStringDesc) == 1) + { + // take the last and unique value to describe the object + $sItemDesc = $value; + } + else + { + // describe the object by the given keys + $sItemDesc = $sTargetClass.'('.implode('/', $aCIStringDesc).')'; + } + + $oExtObjects = new CMDBObjectSet($oReconFilter); + switch($oExtObjects->Count()) + { + case 0: + $oRes->LogWarning("Object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL()."')"); + $aItemsNotFound[] = $sItemDesc; + break; + case 1: + $aItemsFound[] = array ( + 'object' => $oExtObjects->Fetch(), + 'link_values' => @$aItemData['link_values'], + 'desc' => $sItemDesc, + ); + break; + default: + $oRes->LogWarning("Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')"); + $aItemsNotFound[] = $sItemDesc; + } + } + + if (count($aItemsFound) > 0) + { + $aLinks = array(); + foreach($aItemsFound as $aItemData) + { + $oLink = MetaModel::NewObject($sLinkClass); + $oLink->Set($sExtKeyToItem, $aItemData['object']->GetKey()); + foreach($aItemData['link_values'] as $sKey => $value) + { + if(!MetaModel::IsValidAttCode($sLinkClass, $sKey)) + { + $oRes->LogWarning("Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); + } + else + { + $oLink->Set($sKey, $value); + } + } + $aLinks[] = $oLink; + } + $oImpactedInfraSet = DBObjectSet::FromArray($sLinkClass, $aLinks); + $oTargetObj->Set($sLinkAttCode, $oImpactedInfraSet); + } + + return $aItemsNotFound; + } + + /** + * Create an incident ticket from a monitoring system + * Some CIs might be specified (by their name/IP) + * + * @param string sDecription + * @param string sInitialSituation + * @param array aCallerDesc + * @param array aCustomerDesc + * @param array aWorkgroupDesc + * @param array aImpactedCIs + * @param string sSeverity + * + * @return WebServiceResult + */ + function CreateIncidentTicket($sDescription, $sInitialSituation, $aCallerDesc, $aCustomerDesc, $aWorkgroupDesc, $aImpactedCIs, $sSeverity) + { + $oRes = new WebServiceResult(); + + new CMDBChange(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Administrator"); + $iChangeId = $oMyChange->DBInsertNoReload(); + + $oNewTicket = MetaModel::NewObject('bizIncidentTicket'); + $oNewTicket->Set('title', $sDescription); + $oNewTicket->Set('initial_situation', $sInitialSituation); + $oNewTicket->Set('severity', $sSeverity); + + $this->SetExternalKey('org_id', $aCustomerDesc, $oNewTicket, $oRes); + $this->SetExternalKey('caller_id', $aCallerDesc, $oNewTicket, $oRes); + $this->SetExternalKey('workgroup_id', $aWorkgroupDesc, $oNewTicket, $oRes); + + $aDevicesNotFound = $this->AddLinkedObjects('impacted_infra_manual', 'logInfra', $aImpactedCIs, $oNewTicket, $oRes); + if (count($aDevicesNotFound) > 0) + { + $oTargetObj->Set('impact', implode(', ', $aDevicesNotFound)); + } + + if ($oRes->IsOk()) + { + $iId = $oNewTicket->DBInsertTrackedNoReload($oMyChange); + $oRes->LogInfo("Created ticket #$iId"); + } + return $oRes; + } +} +?> From 9e475054478965fd5bd4b150491fe5b01224c7a3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 28 Dec 2009 16:51:58 +0000 Subject: [PATCH 133/970] Fixed regression on the bug fix [207] SVN:trunk[210] --- pages/csvimport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index b617192e97..377b2a7c34 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -238,7 +238,7 @@ function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CM { // This field is value to search on, to find a value for an external key list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); - if ($sExtKeyAttCode == $sExtReconcKeyAttCode) + if ($sExtReconcKeyAttCode == 'id') { $aAttList[$sExtKeyAttCode] = $iFieldId; } From 4d1d6bf6b0e4573a7672216a226b3336062c55fe Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 13:38:02 +0000 Subject: [PATCH 134/970] - Track the changes/history when creating the administrator account (Trac #48) SVN:trunk[211] --- addons/userrights/userrightsmatrix.class.inc.php | 10 +++++++++- addons/userrights/userrightsprofile.class.inc.php | 14 ++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index dfb8bb7aa5..eda5978114 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -173,7 +173,15 @@ class UserRightsMatrix extends UserRightsAddOnAPI $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); $oUser->Set('userid', 1); // one is for root ! - $iUserId = $oUser->DBInsertNoReload(); + + // Create a change to record the history of the User object + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + + // Now record the admin user object + $iUserId = $oUser->DBInsertTrackedNoReload($oChange); $this->SetupUser($iUserId, true); return true; } diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index d648dc06b9..64eaedfafb 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -757,12 +757,18 @@ class UserRightsProfile extends UserRightsAddOnAPI // Installation: create the very first user public function CreateAdministrator($sAdminUser, $sAdminPwd) { + // Create a change to record the history of the User object + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + $oOrg = new bizOrganization(); $oOrg->Set('name', 'My Company/Department'); $oOrg->Set('code', 'SOMECODE'); $oOrg->Set('status', 'implementation'); //$oOrg->Set('parent_id', xxx); - $iOrgId = $oOrg->DBInsertNoReload(); + $iOrgId = $oOrg->DBInsertTrackedNoReload($oChange); // Location : optional //$oLocation = new bizLocation(); @@ -784,20 +790,20 @@ class UserRightsProfile extends UserRightsAddOnAPI $oContact->Set('phone', ''); //$oContact->Set('location_id', $iLocationId); $oContact->Set('employee_number', ''); - $iContactId = $oContact->DBInsertNoReload(); + $iContactId = $oContact->DBInsertTrackedNoReload($oChange); $oUser = new URP_Users(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); $oUser->Set('userid', $iContactId); - $iUserId = $oUser->DBInsertNoReload(); + $iUserId = $oUser->DBInsertTrackedNoReload($oChange); // Add this user to the very specific 'admin' profile $oUserProfile = new URP_UserProfile(); $oUserProfile->Set('userid', $iUserId); $oUserProfile->Set('profileid', ADMIN_PROFILE_ID); $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); - $oUserProfile->DBInsertNoReload(); + $oUserProfile->DBInsertTrackedNoReload($oChange); return true; } From adeb7b17b1d77caf2007f0354191e8af226ea070 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 13:40:29 +0000 Subject: [PATCH 135/970] - Added 2 new parameters to the config file to manage the display_limit (Trac #61) SVN:trunk[212] --- core/config.class.inc.php | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index ca2d4a5e9d..6d6e5e9827 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -16,6 +16,9 @@ class ConfigException extends CoreException { } +define ('DEFAULT_MIN_DISPLAY_LIMIT', 10); +define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); + class Config { //protected $m_bIsLoaded = false; @@ -31,6 +34,15 @@ class Config protected $m_sDBName; protected $m_sDBSubname; + /** + * @var integer Number of elements to be displayed when there are more than m_iMaxDisplayLimit elements + */ + protected $m_iMinDisplayLimit; + /** + * @var integer Max number of elements before truncating the display + */ + protected $m_iMaxDisplayLimit; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; @@ -43,6 +55,8 @@ class Config $this->m_sDBPwd = ''; $this->m_sDBName = ''; $this->m_sDBSubname = ''; + $this->m_iMinDisplayLimit = DEFAULT_MIN_DISPLAY_LIMIT; + $this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT; if ($bLoadConfig) { $this->Load($sConfigFile); @@ -119,6 +133,9 @@ class Config $this->m_sDBPwd = trim($MySettings['db_pwd']); $this->m_sDBName = trim($MySettings['db_name']); $this->m_sDBSubname = trim($MySettings['db_subname']); + + $this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT; + $this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT; } protected function Verify() @@ -177,6 +194,16 @@ class Config return $this->m_sDBPwd; } + public function GetMinDisplayLimit() + { + return $this->m_iMinDisplayLimit; + } + + public function GetMaxDisplayLimit() + { + return $this->m_iMaxDisplayLimit; + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -201,6 +228,17 @@ class Config { $this->m_sDBPwd = $sPwd; } + + public function SetMinDisplayLimit($iMinDisplayLimit) + { + $this->m_iMinDisplayLimit = $iMinDisplayLimit; + } + + public function SetMaxDisplayLimit($iMaxDisplayLimit) + { + $this->m_iMaxDisplayLimit = $iMaxDisplayLimit; + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -237,6 +275,9 @@ class Config fwrite($hFile, "\t'db_pwd' => '".addslashes($this->m_sDBPwd)."',\n"); fwrite($hFile, "\t'db_name' => '{$this->m_sDBName}',\n"); fwrite($hFile, "\t'db_subname' => '{$this->m_sDBSubname}',\n"); + fwrite($hFile, "\n"); + fwrite($hFile, "\t'min_display_limit' => {$this->m_iMinDisplayLimit},\n"); + fwrite($hFile, "\t'max_display_limit' => {$this->m_iMaxDisplayLimit},\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n/**\n"); From e91b81c777f393e33bf19d2a567a863c62a323b1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 13:41:44 +0000 Subject: [PATCH 136/970] SVN:trunk[213] --- images/Thumbs.db | Bin 187392 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 images/Thumbs.db diff --git a/images/Thumbs.db b/images/Thumbs.db deleted file mode 100644 index 812e6e80ae43e6019a8a2e828d2a6b65fde2019f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187392 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 T5Sn+Y{v-=Ye$#Y9t?>T Date: Tue, 29 Dec 2009 14:02:18 +0000 Subject: [PATCH 137/970] - completed the fix of the user object history (Trac #48) - completed the implementation of truncated lists (Trac #61) - Fixed the handling of the search form in the details page (Trac #29) SVN:trunk[214] --- application/cmdbabstract.class.inc.php | 41 +++++++++++++++++++++---- application/displayblock.class.inc.php | 9 +++++- application/itopwebpage.class.inc.php | 1 + application/startup.inc.php | 2 ++ application/uilinkswizard.class.inc.php | 7 +++-- css/light-grey.css | 21 +++++++++++++ js/utils.js | 19 ++++++++++++ pages/UI.php | 41 ++++++++++++++++++++++--- pages/UniversalSearch.php | 4 +-- pages/ajax.render.php | 8 ++++- pages/run_query.php | 4 +-- webservices/export.php | 2 +- 12 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 js/utils.js diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 18d2f4a939..c288b7b266 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -131,7 +131,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("

        ".MetaModel::GetName(get_class($this)).": ".$this->GetDisplayName()."

        \n"); // history block (with toggle) - $oHistoryFilter = new DBObjectSearch('CMDBChangeOpSetAttribute'); + $oHistoryFilter = new DBObjectSearch('CMDBChangeOp'); $oHistoryFilter->AddCondition('objkey', $this->GetKey()); $oHistoryFilter->AddCondition('objclass', get_class($this)); $oBlock = new HistoryBlock($oHistoryFilter, 'toggle', false); @@ -269,6 +269,8 @@ abstract class cmdbAbstractObject extends CMDBObject //public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false) public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { + global $g_oConfig; + static $iListId = 0; $iListId++; @@ -341,7 +343,16 @@ abstract class cmdbAbstractObject extends CMDBObject } $aValues = array(); $oSet->Seek(0); - while ($oObj = $oSet->Fetch()) + $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; + $iMaxObjects = -1; + if ($bDisplayLimit) + { + if ($oSet->Count() > $g_oConfig->GetMaxDisplayLimit()) + { + $iMaxObjects = $g_oConfig->GetMinDisplayLimit(); + } + } + while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0)) { $aRow['key'] = $oObj->GetKey(); if ($bSelectMode) @@ -354,6 +365,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aRow[$sAttCode] = $oObj->GetAsHTML($sAttCode); } $aValues[] = $aRow; + $iMaxObjects--; } $oMenuBlock = new MenuBlock($oSet->GetFilter()); $sHtml .= ''; @@ -367,7 +379,22 @@ abstract class cmdbAbstractObject extends CMDBObject //$aMenuExtraParams['linkage'] = $sLinkageAttribute; $aMenuExtraParams = $aExtraParams; } - $sHtml .= ''; } @@ -570,9 +597,11 @@ abstract class cmdbAbstractObject extends CMDBObject $sHtml .= "
         '.$oSet->Count().' object(s)'; + if ($bDisplayLimit && ($oSet->Count() > $g_oConfig->GetMaxDisplayLimit())) + { + // list truncated + $divId = $aExtraParams['block_id']; + $sFilter = $oSet->GetFilter()->serialize(); + $aExtraParams['display_limit'] = false; // To expand the full list + $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them + $sHtml .= '
        '.$g_oConfig->GetMinDisplayLimit().' object(s) displayed out of '.$oSet->Count().'  Display All'; + $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); + $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); + } + else + { + // Full list + $sHtml .= '
         '.$oSet->Count().' object(s)'; + } $sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams); $sHtml .= '
        \n"; foreach($aExtraParams as $sName => $sValue) { - $sHtml .= "\n"; + $sHtml .= "\n"; } - $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; + $sHtml .= "\n"; $sHtml .= "\n"; $sHtml .= "
    \n"; @@ -598,7 +627,7 @@ abstract class cmdbAbstractObject extends CMDBObject { $sHtml .= "\n"; } - $sHtml .= "\n"; + $sHtml .= "\n"; $sHtml .= "\n"; $sHtml .= "
    \n"; return $sHtml; diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index f05b65e6a0..c006bd91a8 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -144,7 +144,10 @@ class DisplayBlock public function Display(web_page $oPage, $sId, $aExtraParams = array()) { + $oPage->add($this->GetDisplay($oPage, $sId, $aExtraParams)); + /* $aExtraParams = array_merge($aExtraParams, $this->m_aParams); + $aExtraParams['block_id'] = $sId; if (!$this->m_bAsynchronous) { // render now @@ -171,12 +174,14 @@ class DisplayBlock ); '); // TO DO: add support for $aExtraParams in asynchronous/Ajax mode } + */ } public function GetDisplay(web_page $oPage, $sId, $aExtraParams = array()) { $sHtml = ''; $aExtraParams = array_merge($aExtraParams, $this->m_aParams); + $aExtraParams['block_id'] = $sId; if (!$this->m_bAsynchronous) { // render now @@ -188,17 +193,19 @@ class DisplayBlock { // render it as an Ajax (asynchronous) call $sFilter = $this->m_oFilter->serialize(); + $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them $sHtml .= "
    \n"; $sHtml .= $oPage->GetP(" Loading..."); $sHtml .= "
    \n"; $sHtml .= ' '; // TO DO: add support for $aExtraParams in asynchronous/Ajax mode diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 1b1bca3d80..6f8698deb1 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -35,6 +35,7 @@ class iTopWebPage extends nice_web_page $this->add_linked_script("../js/date.js"); $this->add_linked_script("../js/jquery.date.picker.js"); $this->add_linked_script("../js/jquery.tablesorter.min.js"); + $this->add_linked_script("../js/utils.js"); //$this->add_linked_script("../js/jquery-ui-personalized-1.5.3.js"); $this->add_linked_script("../js/swfobject.js"); $this->add_linked_stylesheet("../css/jquery.treeview.css"); diff --git a/application/startup.inc.php b/application/startup.inc.php index 7f3448f30a..89b0911179 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -2,6 +2,8 @@ require_once('../application/utils.inc.php'); +$g_oConfig = new Config('../config-itop.php'); + MetaModel::Startup('../config-itop.php'); ?> diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index db57e20753..3779efac12 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -159,6 +159,7 @@ class UILinksWizard { $('#SearchResultsToAdd table.listResults tbody').attr('height', tbodyHeight); $('#SearchResultsToAdd .listResults tbody').css('overflow', 'auto'); + $('#SearchResultsToAdd .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables } } @@ -214,7 +215,7 @@ class UILinksWizard } $('.listResults tbody').append(data); $('.listResults').trigger('update'); - $('.listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('.listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables }, 'html' ); @@ -362,12 +363,12 @@ class UILinksWizard public function SearchObjectsToAdd(web_page $oP, UserContext $oContext) { - $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); + //$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); $oFilter = $oContext->NewFilter($this->m_sLinkedClass); $oSet = new CMDBObjectSet($oFilter); $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true)); // Don't display the 'Actions' menu on the results + $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results } public function DoAddObjects(web_page $oP, UserContext $oContext, $aLinkedObjectIds = array()) diff --git a/css/light-grey.css b/css/light-grey.css index 994bf7a3b9..ac9df52d88 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -663,3 +663,24 @@ div.HRDrawer { .mandatory { border: 1px solid #f00; } +table.listResults tr td.truncated { + background: transparent; +} + +table.listResults tr td.truncated { + background: url(../images/truncated.png) bottom repeat-x; + margin-bottom: -3px; +} + +table.listResults tr.even td.truncated { + background: #f9f9f1 url(../images/truncated.png) bottom repeat-x; +} + +table.listResults tr.even td.truncated.hover { + background: #E8FFD3 url(../images/truncated.png) bottom repeat-x; +} + +table.listResults.truncated { + border-bottom: 0; + padding-bottom: 0; +} diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000000..9cdf7f9fd7 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,19 @@ +// Some general purpose JS functions for the iTop application +/** + * Reload a truncated list + */ +function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) +{ + $('#'+divId).addClass('loading'); + //$('#'+divId).blockUI(); + $.get('ajax.render.php?filter='+sSerializedFilter+'&style=list', + { operation: 'ajax', extra_params: sExtraParams }, + function(data){ + $('#'+divId).empty(); + $('#'+divId).append(data); + $('#'+divId).removeClass('loading'); + $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + //$('#'+divId).unblockUI(); + } + ); +} diff --git a/pages/UI.php b/pages/UI.php index b20da68082..aeaf59fde0 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -74,7 +74,7 @@ switch($operation) } break; - case 'search_form': + case 'search_oql': $sOQLClass = utils::ReadParam('oql_class', ''); $sOQLClause = utils::ReadParam('oql_clause', ''); $sFormat = utils::ReadParam('format', ''); @@ -128,7 +128,39 @@ switch($operation) } } break; - + case 'search_form': + $sClass = utils::ReadParam('class', ''); + $sFormat = utils::ReadParam('format', 'html'); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sClass)) + { + $oP->set_title("iTop - Error"); + $oP->add("

    'class' must be specifed for this operation.

    \n"); + } + else + { + $oP->set_title("iTop - Search results"); + $oFilter = $oContext->NewFilter($sClass); + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, array('open' => true)); + $oBlock->Display($oP, 0); + } + if (strtolower($sFormat) == 'csv') + { + $oBlock = new DisplayBlock($oFilter, 'csv', false); + $oBlock->Display($oP, 1); + $oP->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block + } + else + { + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 1); + } + } + break; + case 'search': $sFilter = utils::ReadParam('filter', ''); $sFormat = utils::ReadParam('format', ''); @@ -158,7 +190,7 @@ switch($operation) else { $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oP, 0); + $oBlock->Display($oP, 1); } } break; @@ -173,6 +205,7 @@ switch($operation) { $oP->p("

    Results for '$sFullText':

    \n"); $iCount = 0; + $iBlock = 0; // Search in full text mode in all the classes foreach(MetaModel::GetClasses('bizmodel') as $sClassName) { @@ -198,7 +231,7 @@ switch($operation) $oP->add("\n"); $oLeafsFilter->AddCondition('pkey', $aLeafs, 'IN'); $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); - $oBlock->Display($oP, 0); + $oBlock->Display($oP, $iBlock++); } } } diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index 3fe8fe246f..05333aa7a0 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -73,11 +73,11 @@ if ($oFilter != null) { $oSet = new CMDBObjectSet($oFilter); $oBlock = new DisplayBlock($oFilter, 'search', false); - $oBlock->Display($oP, 0); + $oBlock->Display($oP, 0, array('open' => true)); // Search results $oResultBlock = new DisplayBlock($oFilter, 'list', false); - $oResultBlock->RenderContent($oP); + $oResultBlock->Display($oP, 1); // Menu node $sFilter = $oFilter->ToOQL(); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index c3d7833409..682e3efca1 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -100,6 +100,12 @@ switch($operation) case 'ajax': if ($sFilter != "") { + $sExtraParams = stripslashes(utils::ReadParam('extra_params', '')); + $aExtraParams = array(); + if (!empty($sExtraParams)) + { + $aExtraParams = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); + } if ($sEncoding == 'sibusql') { $oFilter = CMDBSearchFilter::FromSibusQL($sFilter); @@ -109,7 +115,7 @@ switch($operation) $oFilter = CMDBSearchFilter::unserialize($sFilter); } $oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false); - $oDisplayBlock->RenderContent($oPage); + $oDisplayBlock->RenderContent($oPage, $aExtraParams); } else { diff --git a/pages/run_query.php b/pages/run_query.php index 8214bd8edf..2457121bfb 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -109,8 +109,8 @@ try { $oP->add("

    Query results

    \n"); - $oSet = new CMDBObjectSet($oFilter); - cmdbAbstractObject::DisplaySet($oP, $oSet); + $oResultBlock = new DisplayBlock($oFilter, 'list', false); + $oResultBlock->Display($oP, 1); $oP->p(''); $oP->StartCollapsibleSection('More info on the query', false); diff --git a/webservices/export.php b/webservices/export.php index 912ddebfa2..302b885667 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -47,7 +47,7 @@ if (!empty($sExpression)) } $sUrl = "$sProtocol://{$sServerName}{$sPort}/pages/"; $oP->set_base($sUrl); - cmdbAbstractObject::DisplaySet($oP, $oSet, array('menu' => false)); + cmdbAbstractObject::DisplaySet($oP, $oSet, array('menu' => false, 'display_limit' => false)); // no menu, no truncated list break; case 'csv': From c9f8417f7689250870b06a5f32fbd226616d59f9 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 15:15:36 +0000 Subject: [PATCH 138/970] _ Fixed Trac #46: when a mandatory field has no valid value, an empty drop-down list is displayed, this prevents the user from validating the from. SVN:trunk[215] --- application/cmdbabstract.class.inc.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index c288b7b266..26080b1d0c 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -681,11 +681,12 @@ abstract class cmdbAbstractObject extends CMDBObject if ($aAllowedValues !== null) { //Enum field or external key, display a combo - if (count($aAllowedValues) == 0) - { - $sHTMLValue = ""; - } - else if (count($aAllowedValues) > 50) + //if (count($aAllowedValues) == 0) + //{ + // $sHTMLValue = ""; + //} + //else if (count($aAllowedValues) > 50) + if (count($aAllowedValues) > 50) { // too many choices, use an autocomplete // The input for the auto complete @@ -698,6 +699,7 @@ abstract class cmdbAbstractObject extends CMDBObject else { // Few choices, use a normal 'select' + // In case there are no valid values, the select will be empty, thus blocking the user from validating the form $sHTMLValue = "m_iInputId}\" size=\"35\" value=\"\" title=\"Type the first 3 characters\"/>"; $sHTMLValue .= "m_iInputId}\" value=\" Add... \" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; @@ -94,10 +94,13 @@ class UILinksWidget { // Few choices, use a normal 'select' $sHTMLValue = "\n"; } @@ -274,7 +277,7 @@ EOF; $sHTML = "
    \n"; $sHTML .= "
    \n"; - $sHTML .= "

    ".MetaModel::GetName($sLinkedClass)." attributes

    \n"; + $sHTML .= "

    ".MetaModel::GetName($sLinkedClass)." attributes

    \n"; $sHTML .= "
    \n"; $index = 0; $aAttrsMap = array(); diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index e33e7235c3..b7cafa9406 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -14,6 +14,7 @@ class web_page { protected $s_title; protected $s_content; + protected $s_deferred_content; protected $a_scripts; protected $a_styles; protected $a_include_scripts; @@ -25,6 +26,7 @@ class web_page { $this->s_title = $s_title; $this->s_content = ""; + $this->s_deferred_content = ''; $this->a_scripts = array(); $this->a_styles = array(); $this->a_linked_scripts = array(); @@ -59,6 +61,16 @@ class web_page $this->s_content .= $s_html; } + /** + * Add any text or HTML fragment at the end of the body of the page + * This is useful to add hidden content, DIVs or FORMs that should not + * be embedded into each other. + */ + public function add_at_the_end($s_html) + { + $this->s_deferred_content .= $s_html; + } + /** * Add a paragraph to the body of the page */ @@ -281,6 +293,7 @@ class web_page { echo "
    $s_captured_output
    \n"; } + echo $this->s_deferred_content; echo "\n"; echo "\n"; } From 7efeec5acdc9423880660ec89d2afd98c8fee743 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 17:08:04 +0000 Subject: [PATCH 140/970] - Fixed bug Trac #65: incorrect display of non-ASCII characters. htmlentities must be aware of the charset used ! SVN:trunk[217] --- core/MyHelpers.class.inc.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php index 8ee89a5ff3..16e149cd93 100644 --- a/core/MyHelpers.class.inc.php +++ b/core/MyHelpers.class.inc.php @@ -434,9 +434,10 @@ class Str } public static function pure2html($pure, $maxLength = false) { + // Check for HTML entities, but be careful the DB is in UTF-8 return $maxLength - ? htmlentities(substr($pure, 0, $maxLength)) - : htmlentities($pure); + ? htmlentities(substr($pure, 0, $maxLength), ENT_COMPAT, 'UTF-8') + : htmlentities($pure, ENT_COMPAT, 'UTF-8'); } public static function pure2sql($pure, $maxLength = false) { From 61874fbea30b4a7c3d338efea6afa01372ccc254 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 17:32:18 +0000 Subject: [PATCH 141/970] - Fixed bug Trac #43 ('links attribute' dialog can now be validated by pressing Enter) SVN:trunk[218] --- application/ui.linkswidget.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 8109dc6495..8100f9ee8e 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -253,7 +253,7 @@ class UILinksWidget -       +       @@ -318,7 +318,7 @@ EOF; } } $sHTML .= $oPage->GetDetails($aDetails); - $sHTML .= "     \n"; + $sHTML .= "     \n"; $sHTML .= "\n"; $sHTML .= "
    \n"; $sHTML .= "
    \n"; From 9247b326c87e3e960ca3a54d17467f5dd6c95ca8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 29 Dec 2009 18:18:17 +0000 Subject: [PATCH 142/970] - Fixed bug (Trac #49) display of user's grant matrix not refreshed properly. Actually it was not a display issue but the cache was not flushed at the right time and the flush was just loading more entries in the cache without clearing the current ones. SVN:trunk[219] --- .../userrightsprofile.class.inc.php | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 64eaedfafb..bc99de3a2f 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -18,6 +18,25 @@ define('ADMIN_PROFILE_ID', 1); class UserRightsBaseClass extends cmdbAbstractObject { + // Whenever something changes, reload the privileges + + public function DBInsertTracked(CMDBChange $oChange) + { + parent::DBInsertTracked($oChange); + UserRights::FlushPrivileges(); + } + + public function DBUpdateTracked(CMDBChange $oChange) + { + parent::DBUpdateTracked($oChange); + UserRights::FlushPrivileges(); + } + + public function DBDeleteTracked(CMDBChange $oChange) + { + parent::DBDeleteTracked($oChange); + UserRights::FlushPrivileges(); + } } @@ -131,9 +150,6 @@ class URP_Users extends UserRightsBaseClass function DisplayBareRelations(web_page $oPage) { - // We may have just added a user, then we have to reset any existing cache - UserRights::FlushPrivileges(); - parent::DisplayBareRelations($oPage); $oPage->SetCurrentTabContainer('Related Objects'); @@ -258,9 +274,6 @@ class URP_Profiles extends UserRightsBaseClass function DisplayBareRelations(web_page $oPage) { - // We may have just added a user, then we have to reset any existing cache - UserRights::FlushPrivileges(); - parent::DisplayBareRelations($oPage); $oPage->SetCurrentTabContainer('Related Objects'); @@ -851,6 +864,8 @@ class UserRightsProfile extends UserRightsAddOnAPI // Could be loaded in a shared memory (?) $oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users")); + $this->m_aUsers = array(); + $this->m_aLogin2UserId = array(); while ($oUser = $oUserSet->Fetch()) { $this->m_aUsers[$oUser->GetKey()] = $oUser; @@ -858,24 +873,29 @@ class UserRightsProfile extends UserRightsAddOnAPI } $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + $this->m_aDimensions = array(); while ($oDimension = $oDimensionSet->Fetch()) { $this->m_aDimensions[$oDimension->GetKey()] = $oDimension; } $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); + $this->m_aClassProjs = array(); while ($oClassProj = $oClassProjSet->Fetch()) { $this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; } $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); + $this->m_aProfiles = array(); while ($oProfile = $oProfileSet->Fetch()) { $this->m_aProfiles[$oProfile->GetKey()] = $oProfile; } $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile")); + $this->m_aUserProfiles = array(); + $this->m_aAdmins = array(); while ($oUserProfile = $oUserProfileSet->Fetch()) { $this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; @@ -886,6 +906,7 @@ class UserRightsProfile extends UserRightsAddOnAPI } $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); + $this->m_aProPros = array(); while ($oProPro = $oProProSet->Fetch()) { $this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; From 966d4820caf658621a6f14791de8c52332b5bc0e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 31 Dec 2009 09:08:09 +0000 Subject: [PATCH 143/970] - First implementation of documents - small cleanup to remove a global Config variable (and removed some deprecated utils:: functions) - removed the 'DocumentVersion' object since previous versions are now tracked as normal change objects. SVN:trunk[220] --- application/cmdbabstract.class.inc.php | 50 ++++++--- application/startup.inc.php | 4 +- application/uiwizard.class.inc.php | 9 +- application/utils.inc.php | 147 ++++++++++++++++++------- application/wizardhelper.class.inc.php | 22 +++- business/itop.business.class.inc.php | 51 +-------- business/templates/document.html | 6 - core/attributedef.class.inc.php | 15 ++- core/cmdbchangeop.class.inc.php | 6 +- core/cmdbsource.class.inc.php | 17 +++ core/ormdocument.class.inc.php | 77 +++++++++++-- js/utils.js | 12 ++ js/wizardhelper.js | 1 + pages/UI.php | 13 ++- pages/ajax.render.php | 51 +++++++++ setup/index.php | 54 ++++++++- 16 files changed, 403 insertions(+), 132 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 26080b1d0c..ccd5c9e55c 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -225,6 +225,21 @@ abstract class cmdbAbstractObject extends CMDBObject } } $sHtml .= $oPage->GetDetails($aDetails); + // Documents displayed inline (when possible: images, html...) + foreach($aList as $sAttCode) + { + $oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode); + if ( $oAttDef->GetEditClass() == 'Document') + { + $oDoc = $this->Get($sAttCode); + if (is_object($oDoc) && !$oDoc->IsEmpty()) + { + $sHtml .= "

    Open in New Window: ".$oDoc->GetDisplayLink($sClass, $this->GetKey(), $sAttCode).", \n"; + $sHtml .= "Download: ".$oDoc->GetDownloadLink($sClass, $this->GetKey(), $sAttCode)."

    \n"; + $sHtml .= "
    ".$oDoc->GetDisplayInline($sClass, $this->GetKey(), $sAttCode)."
    \n"; + } + } + } return $sHtml; } @@ -269,8 +284,6 @@ abstract class cmdbAbstractObject extends CMDBObject //public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false) public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { - global $g_oConfig; - static $iListId = 0; $iListId++; @@ -347,9 +360,9 @@ abstract class cmdbAbstractObject extends CMDBObject $iMaxObjects = -1; if ($bDisplayLimit) { - if ($oSet->Count() > $g_oConfig->GetMaxDisplayLimit()) + if ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit()) { - $iMaxObjects = $g_oConfig->GetMinDisplayLimit(); + $iMaxObjects = utils::GetConfig()->GetMinDisplayLimit(); } } while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0)) @@ -379,14 +392,14 @@ abstract class cmdbAbstractObject extends CMDBObject //$aMenuExtraParams['linkage'] = $sLinkageAttribute; $aMenuExtraParams = $aExtraParams; } - if ($bDisplayLimit && ($oSet->Count() > $g_oConfig->GetMaxDisplayLimit())) + if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) { // list truncated $divId = $aExtraParams['block_id']; $sFilter = $oSet->GetFilter()->serialize(); $aExtraParams['display_limit'] = false; // To expand the full list $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them - $sHtml .= ''.$g_oConfig->GetMinDisplayLimit().' object(s) displayed out of '.$oSet->Count().'  
    Display All'; + $sHtml .= ''.utils::GetConfig()->GetMinDisplayLimit().' object(s) displayed out of '.$oSet->Count().'  Display All'; $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); } @@ -434,14 +447,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aRow[] = $oObj->GetKey(); foreach($aList as $sAttCode) { - if (strstr($oObj->Get($sAttCode), $sSeparator)) // Escape the text only when it contains the separator - { - $aRow[] = $sTextQualifier.$oObj->Get($sAttCode).$sTextQualifier; - } - else - { - $aRow[] = $oObj->Get($sAttCode); - } + $aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, '\\'); } $sHtml .= implode($sSeparator, $aRow)."\n"; } @@ -674,6 +680,20 @@ abstract class cmdbAbstractObject extends CMDBObject $sHTMLValue = $oWidget->Display($oPage, $value); break; + case 'Document': + $oDocument = $value; // Value is an ormDocument object + $sFileName = ''; + if (is_object($oDocument)) + { + $sFileName = $oDocument->GetFileName(); + } + $iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); + $sHTMLValue = "\n"; + $sHTMLValue .= "\n"; + $sHTMLValue .= "$sFileName
    \n"; + $sHTMLValue .= "\n"; + break; + case 'String': default: // #@# todo - add context information (depending on dimensions) @@ -728,7 +748,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); $iKey = $this->GetKey(); $aDetails = array(); - $oPage->add("
    \n"); + $oPage->add("\n"); foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) { if ('finalclass' == $sAttCode) // finalclass is a reserved word, hardcoded ! diff --git a/application/startup.inc.php b/application/startup.inc.php index 89b0911179..97969a34be 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -2,8 +2,6 @@ require_once('../application/utils.inc.php'); -$g_oConfig = new Config('../config-itop.php'); - -MetaModel::Startup('../config-itop.php'); +MetaModel::Startup(ITOP_CONFIG_FILE); ?> diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 307bdb50a1..1ae24cabe7 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -27,6 +27,10 @@ class UIWizard */ public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false) { + if ($iStepIndex == 1) // one big form that contains everything, to make sure that the uploaded files are posted too + { + $this->m_oPage->add("\n"); + } $this->m_oPage->add("
    \n"); $this->m_oPage->add("\n"); $aStates = MetaModel::EnumStates($this->m_sClass); @@ -34,7 +38,7 @@ class UIWizard $sJSHandlerCode = ''; // Javascript code to be executed each time this step of the wizard is entered foreach($aStep as $sAttCode) { - if ($sAttCode != 'finalclass') // Do not displa the attribute that stores the actual class name + if ($sAttCode != 'finalclass') // Do not display the attribute that stores the actual class name { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); $sAttLabel = $oAttDef->GetLabel(); @@ -107,7 +111,6 @@ $sJSHandlerCode $this->m_oPage->add("\n"); + $this->m_oPage->add("\n"); } /** * Compute the order of the fields & pages in the wizard diff --git a/application/utils.inc.php b/application/utils.inc.php index 00aa4c8d3a..3330714fb9 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1,10 +1,15 @@ 0)) + { + $sMimeType = $sType; + } + } + @finfo_close($finfo); + } + $oDocument = new ormDocument($doc_content, $sMimeType, $_FILES[$sName]['name']); + break; + + case UPLOAD_ERR_NO_FILE: + // no file to load, it's a normal case, just return an empty document + break; + + case UPLOAD_ERR_FORM_SIZE: + case UPLOAD_ERR_INI_SIZE: + throw new FileUploadException("Uploaded file is too big. (Max allowed size is ".ini_get('upload_max_filesize').". Check you PHP configuration for upload_max_filesize."); + break; + + case UPLOAD_ERR_PARTIAL: + throw new FileUploadException("File upload failed, file has been truncated."); + break; + + case UPLOAD_ERR_NO_TMP_DIR: + throw new FileUploadException("Missing a temporary folder."); + break; + + case UPLOAD_ERR_CANT_WRITE: + throw new FileUploadException("Unable to write the temporary file to the disk (upload_tmp_dir = ".ini_get('upload_tmp_dir').")"); + break; + + case UPLOAD_ERR_EXTENSION: + throw new FileUploadException("File upload stopped by extension. (Original file name: ".$_FILES[$sName]['name'].")"); + break; + + default: + throw new FileUploadException("File upload failed, unknown cause (Error code = ".$_FILES[$sName]['error'].")"); + break; + + } + } + return $oDocument; + } + public static function GetNewTransactionId() { // TO DO implement the real mechanism here @@ -34,47 +108,44 @@ class utils return file_get_contents($sFileName); } - public static function ReadConfig() + /** + * Get access to the application config file + * @param none + * @return Config The Config object initialized from the application config file + */ + public static function GetConfig() { - self::$m_aConfig = array(); - - $sConfigContents = self::ReadFromFile(CONFIGFILE); - if (!$sConfigContents) throw new Exception("Could not load file ".CONFIGFILE); - - foreach (explode("\n", $sConfigContents) as $sLine) + if (self::$m_oConfig == null) { - $sLine = trim($sLine); - if (($iPos = strpos($sLine, '#')) !== false) - { - // strip out the end of the line right after the # - $sLine = substr($sLine, 0, $iPos); - } - - $aMatches = array(); - if (preg_match("@(\\S+.*)=\s*(\S+.*)@", $sLine, $aMatches)) - { - $sParamName = trim($aMatches[1]); - $sParamValue = trim($aMatches[2]); - self::$m_aConfig[$sParamName] = $sParamValue; - } + self::$m_oConfig = new Config(ITOP_CONFIG_FILE); } + return self::$m_oConfig; } - - public static function GetConfig($sParamName, $defaultValue = "") + /** + * Helper function to convert a value expressed in a 'user friendly format' + * as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes + * @param mixed $value The value as read from php.ini + * @return number + */ + public static function ConvertToBytes( $value ) { - if (is_null(self::$m_aConfig)) + $iReturn = $value; + if ( !is_numeric( $value ) ) { - self::ReadConfig(); - } - - if (array_key_exists($sParamName, self::$m_aConfig)) - { - return self::$m_aConfig[$sParamName]; - } - else - { - return $defaultValue; - } - } + $iLength = strlen( $value ); + $iReturn = substr( $value, 0, $iLength - 1 ); + $sUnit = strtoupper( substr( $value, $iLength - 1 ) ); + switch ( $sUnit ) + { + case 'G': + $iReturn *= 1024; + case 'M': + $iReturn *= 1024; + case 'K': + $iReturn *= 1024; + } + } + return $iReturn; + } } ?> diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 578932e181..656b745890 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -8,8 +8,12 @@ class WizardHelper public function __construct() { } - - public function GetTargetObject() + /** + * Constructs the PHP target object from the parameters sent to the web page by the wizard + * @param boolean $bReadUploadedFiles True to also ready any uploaded file (for blob/document fields) + * @return object + */ + public function GetTargetObject($bReadUploadedFiles = false) { $oObj = MetaModel::NewObject($this->m_aData['m_sClass']); foreach($this->m_aData['m_aCurrentValues'] as $iIndex => $value) @@ -60,6 +64,20 @@ class WizardHelper $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); $oObj->Set($sAttCode, $oSet); } + else if ( $oAttDef->GetEditClass() == 'Document' ) + { + if ($bReadUploadedFiles) + { + $oDocument = utils::ReadPostedDocument('file_'.$sAttCode); + $oObj->Set($sAttCode, $oDocument); + } + else + { + // Create a new empty document, just for displaying the file name + $oDocument = new ormDocument(null, '', $value); + $oObj->Set($sAttCode, $oDocument); + } + } else if (($oAttDef->IsExternalKey()) && (!empty($value)) ) { // For external keys: load the target object so that external fields diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index a863167f4a..1ce2709252 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -406,7 +406,7 @@ class bizDocument extends logRealObject MetaModel::Init_AddFilterFromAttribute("description"); MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope', 'description', 'contents')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope', 'contents')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'scope')); // Criteria of the advanced search form @@ -415,55 +415,6 @@ class bizDocument extends logRealObject } -//////////////////////////////////////////////////////////////////////////////////// -/** -* A version of an electronic document -*/ -//////////////////////////////////////////////////////////////////////////////////// -class bizDocVersion extends cmdbAbstractObject -{ - public static function Init() - { - global $oAllowedStatuses; - $aParams = array - ( - "category" => "bizmodel,searchable", - "name" => "DocumentVersion", - "description" => "A version of a document", - "key_type" => "autoincrement", - "key_label" => "id", - "name_attcode" => "version_number", - "state_attcode" => "", - "reconc_keys" => array("document", "version_number"), - "db_table" => "document_versions", - "db_key_field" => "id", - "db_finalclass_field" => "", - "display_template" => "../business/templates/default.html", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("document", array("targetclass"=>"bizDocument", "label"=>"Document", "description"=>"The main document", "allowed_values"=>null, "sql"=>"document_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("docname", array("label"=>"Document Name", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'document', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("version_number", array("label"=>"Version", "description"=>"Version identifier", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type", "allowed_values"=>new ValueSetEnum("local,draft"), "sql"=>"type", "default_value"=>"local", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeURL("url", array("label"=>"URL", "description"=>"Hyperlink to the version", "allowed_values"=>null, "target"=>"_blank", "sql"=>"url", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - - MetaModel::Init_AddFilterFromAttribute("document"); - MetaModel::Init_AddFilterFromAttribute("docname"); - MetaModel::Init_AddFilterFromAttribute("version_number"); - MetaModel::Init_AddFilterFromAttribute("status"); - MetaModel::Init_AddFilterFromAttribute("type"); - MetaModel::Init_AddFilterFromAttribute("description"); - - MetaModel::Init_SetZListItems('details', array('document', 'status', 'version_number', 'type','url','description')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('version_number', 'status', 'type', 'url')); // Attributes to be displayed for a list - // Search criteria - MetaModel::Init_SetZListItems('standard_search', array('docname', 'type')); // Criteria of the std search form - MetaModel::Init_SetZListItems('advanced_search', array('docname', 'type')); // Criteria o - } -} - //////////////////////////////////////////////////////////////////////////////////// /** * n-n link between any Object and a Document diff --git a/business/templates/document.html b/business/templates/document.html index 125c2b00e2..a0c5a8322a 100644 --- a/business/templates/document.html +++ b/business/templates/document.html @@ -5,9 +5,3 @@
    SELECT $class$ WHERE id = $pkey$ - - - SELECT bizDocVersion WHERE document = $pkey$ - - - diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index df2db249dd..6f920f7bc7 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1233,7 +1233,20 @@ class AttributeBlob extends AttributeDefinition public function GetAsHTML($value) { - return $value->GetAsHTML(); + if (is_object($value)) + { + return $value->GetAsHTML(); + } + } + + public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') + { + return ''; // Not exportable in CSV ! + } + + public function GetAsXML($value) + { + return ''; // Not exportable in XML, or as CDATA + some subtags ?? } } diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 5c4fc1a071..ebbfc173bc 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -347,7 +347,11 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute $sAttName = $oAttDef->GetLabel(); $oPrevDoc = $this->Get('prevdata'); $sDocView = $oPrevDoc->GetAsHtml(); - $sResult = "$sAttName changed (previous value: $sDocView)"; + $sDocView .= "
    Open in New Window: ".$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata').", \n"; + $sDocView .= "Download: ".$oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata')."\n"; + + //$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata'); + $sResult = "$sAttName changed, previous value: $sDocView"; } return $sResult; } diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 3474033fab..b68474d3e4 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -428,6 +428,23 @@ class CMDBSource mysql_free_result($result); return $aRows; } + + /** + * Returns the value of the specified server variable + * @param string $sVarName Name of the server variable + * @return mixed Current value of the variable + */ + public static function GetServerVariable($sVarName) + { + $result = ''; + $sSql = "SELECT @@$sVarName as theVar"; + $aRows = self::QueryToArray($sSql); + if (count($aRows) > 0) + { + $result = $aRows[0]['theVar']; + } + return $result; + } } diff --git a/core/ormdocument.class.inc.php b/core/ormdocument.class.inc.php index 54b8cecb7e..4ffb3ace49 100644 --- a/core/ormdocument.class.inc.php +++ b/core/ormdocument.class.inc.php @@ -33,6 +33,11 @@ class ormDocument return MyHelpers::beautifulstr($this->m_data, 100, true); } + public function IsEmpty() + { + return ($this->m_data == null); + } + public function GetMimeType() { return $this->m_sMimeType; @@ -59,20 +64,72 @@ class ormDocument public function GetAsHTML() { - $data = $this->GetData(); - + $sResult = ''; + if ($this->IsEmpty()) + { + // If the filename is not empty, display it, this is used + // by the creation wizard while the file has not yet been uploaded + $sResult = $this->GetFileName(); + } + else + { + $data = $this->GetData(); + $sResult = $this->GetFileName().' [ '.$this->GetMimeType().', size: '.strlen($data).' byte(s) ]'; + } + return $sResult; + } + + /** + * Returns an HTML fragment that will display the document *inline* (if possible) + * @return string + */ + public function GetDisplayInline($sClass, $Id, $sAttCode) + { switch ($this->GetMainMimeType()) { - case 'text': - return "
    ".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."
    \n"; + case 'text': + case 'html': + $data = $this->GetData(); + switch($this->GetMimeType()) + { + case 'text/html': + case 'text/xml': + return "\n"; + + default: + return "
    ".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."
    \n"; + } + break; // Not really needed, but... - case 'application': - return "binary data for ".$this->GetMimeType().', size: '.strlen($data).' byte(s).'; - - case 'html': - default: - return "
    ".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."
    \n"; + case 'application': + switch($this->GetMimeType()) + { + case 'application/pdf': + return "\n"; + } + break; + + case 'image': + return "\n"; } } + + /** + * Returns an hyperlink to display the document *inline* + * @return string + */ + public function GetDisplayLink($sClass, $Id, $sAttCode) + { + return "
    ".$this->GetFileName()."\n"; + } + + /** + * Returns an hyperlink to download the document (content-disposition: attachment) + * @return string + */ + public function GetDownloadLink($sClass, $Id, $sAttCode) + { + return "".$this->GetFileName()."\n"; + } } ?> diff --git a/js/utils.js b/js/utils.js index 9cdf7f9fd7..7efa4a2081 100644 --- a/js/utils.js +++ b/js/utils.js @@ -17,3 +17,15 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) } ); } + +/** + * Update the display and value of a file input widget when the user picks a new file + */ +function UpdateFileName(id, sNewFileName) +{ + var aPath = sNewFileName.split('\\'); + var sNewFileName = aPath[aPath.length-1]; + + $('#'+id).val(sNewFileName); + $('#name_'+id).text(sNewFileName); +} diff --git a/js/wizardhelper.js b/js/wizardhelper.js index c42beded44..9e8831ee94 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -18,6 +18,7 @@ function WizardHelper(sClass) this.m_oData.m_oFieldsMap = oFieldsMap; } + this.SetFieldsCount = function (count) { this.m_oData.m_iFieldsCount = count; diff --git a/pages/UI.php b/pages/UI.php index aeaf59fde0..ba8dc89692 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -459,6 +459,17 @@ switch($operation) $bObjectModified = true; } } + else if ($oAttDef->GetEditClass() == 'Document') + { + // There should be an uploaded file with the named attr_ + $oDocument = utils::ReadPostedDocument('file_'.$sAttCode); + if (!$oDocument->IsEmpty()) + { + // A new file has been uploaded + $oObj->Set($sAttCode, $oDocument); + $bObjectModified = true; + } + } else if (!$oAttDef->IsExternalField()) { $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); @@ -826,7 +837,7 @@ switch($operation) } else { - $oObj = $oWizardHelper->GetTargetObject(); + $oObj = $oWizardHelper->GetTargetObject(true /* read uploaded files */); if (is_object($oObj)) { $sClass = get_class($oObj); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 682e3efca1..0c52e4d154 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -269,9 +269,60 @@ switch($operation) $oPage->add(''); } break; + + case 'display_document': + $id = utils::ReadParam('id', ''); + $sField = utils::ReadParam('field', ''); + if (!empty($sClass) && !empty($id) && !empty($sField)) + { + DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'inline'); + } + break; + + case 'download_document': + $id = utils::ReadParam('id', ''); + $sField = utils::ReadParam('field', ''); + if (!empty($sClass) && !empty($id) && !empty($sField)) + { + DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'attachement'); + } + break; default: $oPage->p("Invalid query."); } $oPage->output(); + +/** + * Downloads a document to the browser, either as 'inline' or 'attachment' + * + * @param web_page $oPage The web page for the output + * @param UserContext $oContext The current User/security context to retreive the objects + * @param string $sClass Class name of the object + * @param mixed $id Identifier of the object + * @param string $sAttCode Name of the attribute containing the document to download + * @param string $sContentDisposition Either 'inline' or 'attachment' + * @return none + */ +function DownloadDocument(web_page $oPage, UserContext $oContext, $sClass, $id, $sAttCode, $sContentDisposition = 'attachement') +{ + try + { + $oObj = $oContext->GetObject($sClass, $id); + if (is_object($oObj)) + { + $oDocument = $oObj->Get($sAttCode); + if (is_object($oDocument)) + { + $oPage->add_header('Content-type: '.$oDocument->GetMimeType()); + $oPage->add_header('Content-Disposition: '.$sContentDisposition.'; filename="'.$oDocument->GetFileName().'"'); + $oPage->add($oDocument->GetData()); + } + } + } + catch(Exception $e) + { + $oPage->p($e->getMessage()); + } +} ?> diff --git a/setup/index.php b/setup/index.php index 91fd34717e..7cecf8f2d7 100644 --- a/setup/index.php +++ b/setup/index.php @@ -23,6 +23,7 @@ $oP = new setup_web_page('iTop configuration wizard'); */ function CheckPHPVersion(setup_web_page $oP) { + $bResult = true; $oP->log('Info - CheckPHPVersion'); if (version_compare(phpversion(), PHP_MIN_VERSION, '>=')) { @@ -57,9 +58,38 @@ function CheckPHPVersion(setup_web_page $oP) if (count($aMissingExtensions) > 0) { $oP->error("Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks)."."); - return false; + $bResult = false; } - return true; + // Check some ini settings here + if (!ini_get('file_uploads')) + { + $oP->error("Files upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."); + $bResult = false; + } + + $sUploadTmpDir = ini_get('upload_tmp_dir'); + if (empty($sUploadTmpDir)) + { + $oP->error("Temporary directory for files upload is not defined (upload_tmp_dir)"); + $bResult = false; + } + + if (!ini_get('upload_max_filesize')) + { + $oP->error("File upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."); + } + + $iMaxFileUploads = ini_get('max_file_uploads'); + if (!empty($iMaxFileUploads) && ($iMaxFileUploads < 1)) + { + $oP->error("File upload is not allowed on this server (max_file_uploads = ".ini_get('max_file_uploads').")."); + $bResult = false; + } + $oP->log("Info - upload_max_filesize: ".ini_get('upload_max_filesize')); + $oP->log("Info - upload_tmp_dir: $sUploadTmpDir"); + $oP->log("Info - max_file_uploads: ".ini_get('max_file_uploads')); + + return $bResult; } /** @@ -80,6 +110,26 @@ function CheckServerConnection(setup_web_page $oP, $sDBServer, $sDBUser, $sDBPwd if (version_compare($sDBVersion, MYSQL_MIN_VERSION, '>=')) { $oP->ok("Current MySQL version ($sDBVersion), greater than minimum required version (".MYSQL_MIN_VERSION.")"); + // Check some server variables + $iMaxAllowedPacket = $oDBSource->GetServerVariable('max_allowed_packet'); + $iMaxUploadSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); + if ($iMaxAllowedPacket >= (500 + $iMaxUploadSize)) // Allow some space for the query + the file to upload + { + $oP->ok("MySQL server's max_allowed_packet is big enough."); + } + else if($iMaxAllowedPacket < $iMaxUploadSize) + { + $oP->warning("MySQL server's max_allowed_packet ($iMaxAllowedPacket) is not big enough."); + $oP->warning("Consider setting it to at least ".(500 + $iMaxUploadSize)."."); + } + $oP->log("Info - MySQL max_allowed_packet: $iMaxAllowedPacket"); + $iMaxConnections = $oDBSource->GetServerVariable('max_connections'); + if ($iMaxConnections < 5) + { + $oP->warning("MySQL server's max_connections ($iMaxConnections) is not enough."); + $oP->warning("Consider setting it to at least 5."); + } + $oP->log("Info - MySQL max_connections: ".($oDBSource->GetServerVariable('max_connections'))); } else { From 015f44784d1306a64d92995aec0f97b04e4f0bc1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 31 Dec 2009 09:36:57 +0000 Subject: [PATCH 144/970] - Check that the temporary directory used by files upload exists and is writable from PHP. (As required in Trac #15). SVN:trunk[221] --- setup/index.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/setup/index.php b/setup/index.php index 7cecf8f2d7..8d429361a2 100644 --- a/setup/index.php +++ b/setup/index.php @@ -73,6 +73,25 @@ function CheckPHPVersion(setup_web_page $oP) $oP->error("Temporary directory for files upload is not defined (upload_tmp_dir)"); $bResult = false; } + // check that the upload directory is indeed writable from PHP + if (!empty($sUploadTmpDir)) + { + if (!file_exists($sUploadTmpDir)) + { + $oP->error("Temporary directory for files upload ($sUploadTmpDir) does not exist or cannot be read by PHP."); + $bResult = false; + } + else if (!is_writable($sUploadTmpDir)) + { + $oP->error("Temporary directory for files upload ($sUploadTmpDir) is not writable."); + $bResult = false; + } + else + { + $oP->log("Info - Temporary directory for files upload ($sUploadTmpDir) is writable."); + } + } + if (!ini_get('upload_max_filesize')) { @@ -86,7 +105,6 @@ function CheckPHPVersion(setup_web_page $oP) $bResult = false; } $oP->log("Info - upload_max_filesize: ".ini_get('upload_max_filesize')); - $oP->log("Info - upload_tmp_dir: $sUploadTmpDir"); $oP->log("Info - max_file_uploads: ".ini_get('max_file_uploads')); return $bResult; From 95b1ce9030946cc3952dada4f2286cebb98fc273 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 3 Jan 2010 13:39:56 +0000 Subject: [PATCH 145/970] - Fix the dialog to manage n-n links: don't display again objects that are already linked. - Now use OQL instead of SibusQL SVN:trunk[222] --- js/wizard.utils.js | 4 ++-- pages/ajax.render.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/wizard.utils.js b/js/wizard.utils.js index fcb8738630..02a7d00c4d 100644 --- a/js/wizard.utils.js +++ b/js/wizard.utils.js @@ -34,8 +34,8 @@ function ManageObjects(sTitle, sClass, sId, sExtKeyToRemote) { aObjList[0] = 0; } - Manage_LoadSelect('selected_objects_'+sId, sClass+': pkey IN {' + aObjList.join(', ') + '}'); - Manage_LoadSelect('available_objects_'+sId, sClass); + Manage_LoadSelect('selected_objects_'+sId, 'SELECT '+sClass+' WHERE id IN (' + aObjList.join(', ') + ')'); // id is a reserved keyword always representing the primary key + Manage_LoadSelect('available_objects_'+sId, 'SELECT '+sClass+' WHERE id NOT IN (' + aObjList.join(', ') + ')'); $('#ManageObjectsDlg_'+sId).jqmShow(); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 0c52e4d154..3ac20e6a97 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -262,7 +262,7 @@ switch($operation) break; case 'combo_options': - $oFilter = CMDBSearchFilter::FromSibusQL($sFilter); + $oFilter = CMDBSearchFilter::FromOQL($sFilter); $oSet = new CMDBObjectSet($oFilter); while( $oObj = $oSet->fetch()) { From 905b32eac43b4cac935c4a2dca3e12ce92029c91 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 3 Jan 2010 19:14:10 +0000 Subject: [PATCH 146/970] - First (partial) implementation of context and default values. SVN:trunk[223] --- application/applicationcontext.class.inc.php | 30 ++++++++++++++++---- application/cmdbabstract.class.inc.php | 16 +++++++++-- application/displayblock.class.inc.php | 12 ++++++-- application/uiwizard.class.inc.php | 6 ++-- pages/UI.php | 8 +++--- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index e3422e02e9..e23ddd390b 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -14,6 +14,7 @@ class ApplicationContext { protected $aNames; protected $aValues; + protected static $aDefaultValues; // Cache shared among all instances public function __construct() { @@ -29,16 +30,20 @@ class ApplicationContext */ protected function ReadContext() { - $this->aValues = array(); - foreach($this->aNames as $sName) + if (empty(self::$aDefaultValues)) { - $sValue = utils::ReadParam($sName, ''); - // TO DO: check if some of the context parameters are mandatory (or have default values) - if (!empty($sValue)) + self::$aDefaultValues = array(); + foreach($this->aNames as $sName) { - $this->aValues[$sName] = $sValue; + $sValue = utils::ReadParam($sName, ''); + // TO DO: check if some of the context parameters are mandatory (or have default values) + if (!empty($sValue)) + { + self::$aDefaultValues[$sName] = $sValue; + } } } + $this->aValues = self::$aDefaultValues; } /** @@ -77,5 +82,18 @@ class ApplicationContext { return $this->aValues; } + /** + * Removes the specified parameter from the context, for example when the same parameter + * is already a search parameter + * @param string $sParamName Name of the parameter to remove + * @return none + */ + public function Reset($sParamName) + { + if (isset($this->aValues[$sParamName])) + { + unset($this->aValues[$sParamName]); + } + } } ?> diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index ccd5c9e55c..dd4782afcd 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -507,12 +507,13 @@ abstract class cmdbAbstractObject extends CMDBObject public static function GetSearchForm(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { static $iSearchFormId = 0; + $oAppContext = new ApplicationContext(); $sHtml = ''; $numCols=4; $iSearchFormId++; $sClassName = $oSet->GetFilter()->GetClass(); - // Romain: temporariy removed the tab "OQL query" because it was not finalized + // Romain: temporarily removed the tab "OQL query" because it was not finalized // (especially when used to add a link) /* $sHtml .= "
      @@ -536,6 +537,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aList = MetaModel::GetZListItems($sClassName, 'standard_search'); foreach($aList as $sFilterCode) { + $oAppContext->Reset($sFilterCode); // Make sure the same parameter will not be passed twice if (($index % $numCols) == 0) { if ($index != 0) @@ -608,6 +610,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sHtml .= "\n"; $sHtml .= "\n"; $sHtml .= "\n"; + $sHtml .= $oAppContext->GetForForm(); $sHtml .= "\n"; $sHtml .= "
    \n"; @@ -634,6 +637,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sHtml .= "\n"; } $sHtml .= "\n"; + $sHtml .= $oAppContext->GetForForm(); $sHtml .= "\n"; $sHtml .= "\n"; return $sHtml; @@ -642,6 +646,12 @@ abstract class cmdbAbstractObject extends CMDBObject public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array()) { static $iInputId = 0; + if (isset($aArgs[$sAttCode]) && empty($value)) + { + // default value passed by the context (either the app context of the operation) + $value = $aArgs[$sAttCode]; + } + if (!empty($iId)) { $iInputId = $iId; @@ -797,7 +807,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("\n"); } - public static function DisplayCreationForm(web_page $oPage, $sClass, $oObjectToClone = null) + public static function DisplayCreationForm(web_page $oPage, $sClass, $oObjectToClone = null, $aArgs = array()) { static $iCreationFormId = 0; @@ -835,7 +845,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetDisplayValue($sAttCode); $iOptions = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; - $sHTMLValue = self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, '', '', $iOptions); + $sHTMLValue = self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, '', '', $iOptions, $aArgs); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } } diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index c006bd91a8..f744cf6c45 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -257,10 +257,12 @@ class DisplayBlock } $sFilter = urlencode($this->m_oFilter->serialize()); $aData = array(); + $oAppContext = new ApplicationContext(); + $sParams = $oAppContext->GetForLink(); foreach($aGroupBy as $sValue => $iCount) { $aData[] = array ( 'group' => $sValue, - 'value' => "$iCount"); // TO DO: add the context information + 'value' => "$iCount"); // TO DO: add the context information } $sHtml .= $oPage->GetTable(array('group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''), 'value' => array('label'=>'Count', 'description' => 'Number of elements')), $aData); } @@ -287,7 +289,9 @@ class DisplayBlock { if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) { - $sHtml .= $oPage->GetP("Click here to create a new ".Metamodel::GetName($sClass)."\n"); + $oAppContext = new ApplicationContext(); + $sParams = $oAppContext->GetForLink(); + $sHtml .= $oPage->GetP("Click here to create a new ".Metamodel::GetName($sClass)."\n"); } } } @@ -312,7 +316,9 @@ class DisplayBlock { if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) { - $sHtml .= $oPage->GetP("Click here to add new ".Metamodel::GetName($sTargetClass)."s\n"); + $oAppContext = new ApplicationContext(); + $sParams = $oAppContext->GetForLink(); + $sHtml .= $oPage->GetP("Click here to add new ".Metamodel::GetName($sTargetClass)."s\n"); } } } diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 1ae24cabe7..9ee6c1d835 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -25,7 +25,7 @@ class UIWizard /** * Displays one step of the wizard */ - public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false) + public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false, $aArgs = array()) { if ($iStepIndex == 1) // one big form that contains everything, to make sure that the uploaded files are posted too { @@ -60,7 +60,7 @@ class UIWizard $sFieldFlag = ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) ? ' *' : ''; $oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId", '', $iOptions); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId", '', $iOptions, $aArgs); $aFieldsMap[$iMaxInputId] = $sAttCode; $aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "
    $sHTMLValue
    "); if ($oAttDef->GetValuesDef() != null) @@ -108,6 +108,7 @@ $sJSHandlerCode */ public function DisplayFinalStep($iStepIndex, $aFieldsMap) { + $oAppContext = new ApplicationContext(); $this->m_oPage->add("\n"); $this->m_oPage->add("\n"); diff --git a/pages/UI.php b/pages/UI.php index ba8dc89692..7478805d34 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -350,6 +350,8 @@ switch($operation) $oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/jquery.blockUI.js"); $oWizard = new UIWizard($oP, $sClass, $sStateCode); + $oContext = new UserContext(); + $aArgs = array_merge($oAppContext->GetAsHash(), utils::ReadParam('default', array())); $sStateCode = $oWizard->GetTargetState(); // Will computes the default state if none was supplied $sClassLabel = MetaModel::GetName($sClass); $oP->add("

    Creation of a new $sClassLabel

    "); @@ -367,21 +369,19 @@ switch($operation) foreach($aWizardSteps['mandatory'] as $aSteps) { $oP->SetCurrentTab("Step $iStepIndex *"); - $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap); + $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, false /* no finish button */, $aArgs); //$oP->add("\n"); $iStepIndex++; } foreach($aWizardSteps['optional'] as $aSteps) { $oP->SetCurrentTab("Step $iStepIndex *"); - $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, true); // true means enable the finish button + $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, true, $aArgs); // true means enable the finish button //$oP->add("\n"); $iStepIndex++; } $oWizard->DisplayFinalStep($iStepIndex, $aFieldsMap); - $oAppContext = new ApplicationContext(); - $oContext = new UserContext(); $oObj = null; if (!empty($id)) { From 644d936c2653f0cb0125692b4c8645fb73ed0abd Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 5 Jan 2010 09:05:12 +0000 Subject: [PATCH 147/970] #47 Replaced every occurence of deprecated verb split() by explode() SVN:trunk[224] --- core/csvparser.class.inc.php | 10 +++++----- core/data.generator.class.inc.php | 4 ++-- core/dbobjectsearch.class.php | 15 +++++++++++---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php index c1e49f10ff..b6c6334a36 100644 --- a/core/csvparser.class.inc.php +++ b/core/csvparser.class.inc.php @@ -70,7 +70,7 @@ class CSVParser $aStatsBySeparator[$sSep] = array(); } - foreach(split("\n", $this->m_sCSVData) as $sLine) + foreach(explode("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; @@ -106,13 +106,13 @@ class CSVParser // Take the FIRST -valuable- LINE ONLY // If there is a number, then for sure this is not a header line // Otherwise, we may consider that there is one line to skip - foreach(split("\n", $this->m_sCSVData) as $sLine) + foreach(explode("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; if (empty($sLine)) continue; - foreach (split($this->m_sSep, $sLine) as $value) + foreach (explode($this->m_sSep, $sLine) as $value) { if (is_numeric($value)) { @@ -133,7 +133,7 @@ class CSVParser $iCount = 0; $iSkipped = 0; - foreach(split("\n", $this->m_sCSVData) as $sLine) + foreach(explode("\n", $this->m_sCSVData) as $sLine) { $sLine = trim($sLine); if (substr($sLine, 0, 1) == '#') continue; @@ -145,7 +145,7 @@ class CSVParser continue; } - foreach (split($this->m_sSep, $sLine) as $iCol=>$sValue) + foreach (explode($this->m_sSep, $sLine) as $iCol=>$sValue) { if (is_array($aFieldMap)) $sColRef = $aFieldMap[$iCol]; else $sColRef = $iCol; diff --git a/core/data.generator.class.inc.php b/core/data.generator.class.inc.php index f9d284e3e2..03cb9db644 100644 --- a/core/data.generator.class.inc.php +++ b/core/data.generator.class.inc.php @@ -206,7 +206,7 @@ class cmdbDataGenerator function GenerateString($sTemplate) { $sResult = ""; - $aParts = split("\|", $sTemplate); + $aParts = explode("\|", $sTemplate); foreach($aParts as $sPart) { if (preg_match("/domain\(\)/", $sPart, $aMatches)) @@ -216,7 +216,7 @@ class cmdbDataGenerator elseif (preg_match("/enum\((.+)\)/", $sPart, $aMatches)) { $sEnumValues = $aMatches[1]; - $aEnumValues = split(",", $sEnumValues); + $aEnumValues = explode(",", $sEnumValues); $sResult .= $aEnumValues[rand(0, count($aEnumValues) - 1)]; } elseif (preg_match("/number\((\d+)-(\d+)\)/", $sPart, $aMatches)) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 8cf4ccf156..71dda95089 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -163,7 +163,14 @@ class DBObjectSearch public function __DescribeHTML() { - $sConditionDesc = $this->DescribeConditions(); + try + { + $sConditionDesc = $this->DescribeConditions(); + } + catch (MissingQueryArgument $e) + { + $sConditionDesc = '?missing query argument?'; + } if (!empty($sConditionDesc)) { return "Objects of class '$this->m_sClass', $sConditionDesc"; @@ -525,14 +532,14 @@ class DBObjectSearch // -> return (unserialize(base64_decode($sValue))); $sClearText = base64_decode($sValue); - $aValues = split("\n", $sClearText); + $aValues = explode("\n", $sClearText); $i = 0; $sClass = $aValues[$i++]; $sClassAlias = $aValues[$i++]; $oFilter = new DBObjectSearch($sClass, $sClassAlias); while($i < count($aValues) && !empty($aValues[$i])) { - $aCondition = split(":", $aValues[$i++]); + $aCondition = explode(":", $aValues[$i++]); switch ($aCondition[0]) { case "A": @@ -940,7 +947,7 @@ class DBObjectSearch { $sClass = trim(substr($sQuery, 0, $iSepPos)); $sConds = trim(substr($sQuery, $iSepPos + 1)); - $aValues = split(" AND ", $sConds); + $aValues = explode(" AND ", $sConds); $oFilter = new DBObjectSearch($sClass); From 5a15dcb8490d1a3ecc8c64a0bf66b723d7db28d3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 6 Jan 2010 08:51:08 +0000 Subject: [PATCH 148/970] Trac #57 - Implemented beta version of email notifications (triggers and actions) SVN:trunk[225] --- application/template.class.inc.php | 17 +----- business/itop.business.class.inc.php | 2 +- core/attributedef.class.inc.php | 62 +++++++++++++++++++++- core/cmdbobject.class.inc.php | 10 ++++ core/config.class.inc.php | 8 +++ core/dbobject.class.php | 38 +++++++++++-- core/metamodel.class.php | 24 ++++++--- pages/schema.php | 13 +++++ setup/data/structure/1.menus.xml | 79 ++++++++++++++++++++++++++++ 9 files changed, 224 insertions(+), 29 deletions(-) diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 6436dfc521..20d57620c3 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -16,7 +16,7 @@ class DisplayTemplate public function Render(web_page $oPage, $aParams = array()) { - $this->ApplyParams($aParams); + $this->m_sTemplate = MetaModel::ApplyParams($this->m_sTemplate, $aParams); $iStart = 0; $iEnd = strlen($this->m_sTemplate); $iCount = 0; @@ -40,21 +40,6 @@ class DisplayTemplate $oPage->add(substr($this->m_sTemplate, $iAfterTagPos)); } - /** - * Replaces all the parameters by the values passed in the hash array - */ - public function ApplyParams($aParams) - { - $aSearches = array(); - $aReplacements = array(); - foreach($aParams as $sSearch => $sReplace) - { - $aSearches[] = '$'.$sSearch.'$'; - $aReplacements[] = $sReplace; - } - $this->m_sTemplate = str_replace($aSearches, $aReplacements, $this->m_sTemplate); - } - public function GetNextTag(&$iStartPos, &$iEndPos) { $iChunkStartPos = $iStartPos; diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 1ce2709252..ceda3e8e23 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -200,7 +200,7 @@ class bizContact extends logRealObject MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department of the contact", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEmailAddress("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("phone", array("label"=>"Phone", "description"=>"Telephone", "allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"Id of the location where the contact is located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"Name of the location where the contact is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 6f920f7bc7..c734792689 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -547,7 +547,7 @@ class AttributeString extends AttributeDBField } public function ScalarToSQL($value) { - if (!is_string($value)) + if (!is_string($value) && !is_null($value)) { throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass())); } @@ -615,6 +615,66 @@ class AttributeText extends AttributeString } } +/** + * Specialization of a string: email + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeEmailAddress extends AttributeString +{ + public function GetTypeDesc() {return "Email address(es)";} +} + +/** + * Specialization of a string: OQL expression + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeOQL extends AttributeString +{ + public function GetTypeDesc() {return "OQL expression";} +} + +/** + * Specialization of a string: template + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeTemplateString extends AttributeString +{ + public function GetTypeDesc() {return "Template string";} +} + +/** + * Specialization of a text: template + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeTemplateText extends AttributeText +{ + public function GetTypeDesc() {return "Multiline template string";} +} + /** * Map a enum column to an attribute * diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 79b448071d..997f605d4d 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -37,9 +37,19 @@ require_once('dbobject.class.php'); require_once('dbobjectsearch.class.php'); require_once('dbobjectset.class.php'); +// db change tracking data model require_once('cmdbchange.class.inc.php'); require_once('cmdbchangeop.class.inc.php'); +// customization data model +// Romain: temporary moved into application.inc.php (see explanations there) +//require_once('trigger.class.inc.php'); +//require_once('action.class.inc.php'); + +// application log +// Romain: temporary moved into application.inc.php (see explanations there) +//require_once('event.class.inc.php'); + require_once('csvparser.class.inc.php'); require_once('bulkchange.class.inc.php'); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 6d6e5e9827..aa7aa5289e 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -289,6 +289,14 @@ class Config fwrite($hFile, "\t'application' => array (\n"); fwrite($hFile, "\t\t'../application/menunode.class.inc.php',\n"); fwrite($hFile, "\t\t'../application/audit.rule.class.inc.php',\n"); +// Romain - That's dirty, because those 3 classes are in fact part of the core +// but I needed those classes to be derived from cmdbAbstractObject +// (to be managed via the GUI) and this class in not really known from +// the core, PLUS I needed the includes to be there also for the setup +// to create the tables. + fwrite($hFile, "\t\t'../core/event.class.inc.php',\n"); + fwrite($hFile, "\t\t'../core/action.class.inc.php',\n"); + fwrite($hFile, "\t\t'../core/trigger.class.inc.php',\n"); fwrite($hFile, "\t\t// to be continued...\n"); fwrite($hFile, "\t),\n"); fwrite($hFile, "\t'business' => array (\n"); diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 45b02c0c15..d309ca4db1 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -611,7 +611,7 @@ abstract class DBObject if (!empty($this->m_iKey) && ($this->m_iKey >= 0)) { // Add it to the list of fields to write - $aFieldsToWrite[] = MetaModel::DBGetKey($sTableClass); + $aFieldsToWrite[] = '`'.MetaModel::DBGetKey($sTableClass).'`'; $aValuesToWrite[] = CMDBSource::Quote($this->m_iKey); } @@ -622,7 +622,7 @@ abstract class DBObject $aAttColumns = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]); foreach($aAttColumns as $sColumn => $sValue) { - $aFieldsToWrite[] = $sColumn; + $aFieldsToWrite[] = "`$sColumn`"; $aValuesToWrite[] = CMDBSource::Quote($sValue); } } @@ -803,7 +803,9 @@ abstract class DBObject // Change the state before proceeding to the actions, this is necessary because an action might // trigger another stimuli (alternative: push the stimuli into a queue) - $this->Set($sStateAttCode, $aTransitionDef['target_state']); + $sPreviousState = $this->Get($sStateAttCode); + $sNewState = $aTransitionDef['target_state']; + $this->Set($sStateAttCode, $sNewState); // $aTransitionDef is an // array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD @@ -824,9 +826,39 @@ abstract class DBObject if (!$bRet) $bSuccess = false; } + // Change state triggers... + $sClass = get_class($this); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class='$sClass' AND t.state='$sPreviousState'")); + while ($oTrigger = $oSet->Fetch()) + { + $oTrigger->DoActivate($this->ToArgs('this')); + } + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class='$sClass' AND t.state='$sNewState'")); + while ($oTrigger = $oSet->Fetch()) + { + $oTrigger->DoActivate($this->ToArgs('this')); + } + return $bSuccess; } + // Make standard context arguments + public function ToArgs($sArgName) + { + $aScalarArgs = array(); + $aScalarArgs[$sArgName] = $this->GetKey(); + $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); + + $sClass = get_class($this); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + $aScalarArgs[$sArgName.'->'.$sAttCode] = $this->Get($sAttCode); + } + return $aScalarArgs; + } + + // Return an empty set for the parent of all public static function GetRelationQueries($sRelCode) { diff --git a/core/metamodel.class.php b/core/metamodel.class.php index c40bc220b7..c54e828f98 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1222,14 +1222,7 @@ abstract class MetaModel { if (self::IsValidObject($value)) { - $aScalarArgs[$sArgName] = $value->GetKey(); - $aScalarArgs[$sArgName.'->id'] = $value->GetKey(); - - $sClass = get_class($value); - foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - $aScalarArgs[$sArgName.'->'.$sAttCode] = $value->Get($sAttCode); - } + $aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName)); } else { @@ -2849,6 +2842,21 @@ abstract class MetaModel return self::GetLabel($sLinkClass, $sAttCode); } + /** + * Replaces all the parameters by the values passed in the hash array + */ + static public function ApplyParams($aInput, $aParams) + { + $aSearches = array(); + $aReplacements = array(); + foreach($aParams as $sSearch => $sReplace) + { + $aSearches[] = '$'.$sSearch.'$'; + $aReplacements[] = $sReplace; + } + return str_replace($aSearches, $aReplacements, $aInput); + } + } // class MetaModel diff --git a/pages/schema.php b/pages/schema.php index b7162f5f45..1162f1ecdf 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -231,6 +231,16 @@ function DisplayLifecycle($oPage, $sClass) } +/** + * Helper for the trigger + */ +function DisplayTriggers($oPage, $sClass) +{ + $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateChange WHERE target_class = '$sClass'")); + cmdbAbstractObject::DisplaySet($oPage, $oSet); +} + + /** * Display the list of classes from the business model */ @@ -402,6 +412,9 @@ function DisplayClassDetails($oPage, $sClass) $oPage->SetCurrentTab('Lifecycle'); DisplayLifecycle($oPage, $sClass); + $oPage->SetCurrentTab('Triggers'); + DisplayTriggers($oPage, $sClass); + $oPage->SetCurrentTab(); $oPage->SetCurrentTabContainer(); } diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index df61e6cb1e..7c78a33e01 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -918,4 +918,83 @@ text-align:left; 0 0 + +Customization + +UI.php + + +administrator +2 +1 +0 + + +Triggers - entering + +UI.php +../images/std_view.gif + +administrator +2 +500 +0 + + +Triggers - leaving + +UI.php +../images/std_view.gif + +administrator +2 +500 +0 + + +Actions + +UI.php +../images/std_view.gif + +administrator +2 +500 +0 + + +Application log + +UI.php +../images/std_view.gif + +administrator +2 +1 +0 + From 9440b599214b35610fab32d31e94df0cd25fdd2f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 6 Jan 2010 12:57:40 +0000 Subject: [PATCH 149/970] Forgot to add 3 files in the previous commit SVN:trunk[226] --- core/action.class.inc.php | 238 +++++++++++++++++++++++++++++++++++++ core/event.class.inc.php | 94 +++++++++++++++ core/trigger.class.inc.php | 204 +++++++++++++++++++++++++++++++ 3 files changed, 536 insertions(+) create mode 100644 core/action.class.inc.php create mode 100644 core/event.class.inc.php create mode 100644 core/trigger.class.inc.php diff --git a/core/action.class.inc.php b/core/action.class.inc.php new file mode 100644 index 0000000000..80bdb5861b --- /dev/null +++ b/core/action.class.inc.php @@ -0,0 +1,238 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class Action extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "action", + "description" => "Custom action", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_action", + "db_key_field" => "id", + "db_finalclass_field" => "realclass", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("name"); + MetaModel::Init_AddFilterFromAttribute("description"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'description')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + abstract public function DoExecute($oTrigger, $aContextArgs); +} + +/** + * A notification + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class ActionNotification extends Action +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "notification", + "description" => "Notification (abstract)", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_action_notification", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} + +/** + * An email notification + * + * @package iTopORM + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class ActionEmail extends ActionNotification +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "email notification", + "description" => "Action: Email notification", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_action_email", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeString("from", array("label"=>"From", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("label"=>"Reply to", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("to", array("label"=>"To", "description"=>"Destination of the email", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("label"=>"Cc", "description"=>"Carbon Copy", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("label"=>"bcc", "description"=>"Blind Carbon Copy", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("label"=>"subject", "description"=>"Title of the email", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("label"=>"body", "description"=>"Contents of the email", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("label"=>"importance", "description"=>"Importance flag", "allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'to', 'subject')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + // args: a search object + // returns an array of emails + protected function FindRecipients($sAttCode, $aArgs) + { + $sOQL = $this->Get($sAttCode); + if (strlen($sOQL) == '') return ''; + + $oSearch = DBObjectSearch::FromOQL($sOQL); + $sClass = $oSearch->GetClass(); + // Determine the email attribute (the first one will be our choice) + foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + if ($oAttDef instanceof AttributeEmailAddress) + { + $sEmailAttCode = $sAttCode; + // we've got one, exit the loop + break; + } + } + + $oSet = new DBObjectSet($oSearch, array() /* order */, $aArgs); + $aRecipients = array(); + while ($oObj = $oSet->Fetch()) + { + $aRecipients[] = $oObj->Get($sEmailAttCode); + } + return implode(', ', $aRecipients); + } + + public function DoExecute($oTrigger, $aContextArgs) + { + // Determine recicipients + // + $sTo = $this->FindRecipients('to', $aContextArgs); + $sCC = $this->FindRecipients('cc', $aContextArgs); + $sBCC = $this->FindRecipients('bcc', $aContextArgs); + + $sFrom = $this->Get('from'); + $sReplyTo = $this->Get('reply_to'); + + $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs); + $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs); + + // To send HTML mail, the Content-type header must be set + $sHeaders = 'MIME-Version: 1.0' . "\r\n"; + $sHeaders .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n"; + + // Additional headers + if (strlen($sFrom) > 0) + { + $sHeaders .= "From: $sFrom\r\n"; + // This is required on Windows because otherwise I would get the error + // "sendmail_from" not set in php.ini" even if it is correctly working + // (apparently, once it worked the SMTP server won't claim anymore for it) + ini_set("sendmail_from", $sFrom); + } + if (strlen($sReplyTo) > 0) + { + $sHeaders .= "Reply-To: $sReplyTo\r\n"; + } + if (strlen($sCC) > 0) + { + $sHeaders .= "Cc: $sCC\r\n"; + } + if (strlen($sBCC) > 0) + { + $sHeaders .= "Bcc: $sBCC\r\n"; + } + + $sOverview = "TO:$sTo, FROM:$sFrom, REPLY-TO:$sReplyTo, CC:$sCC, BCC:$sBCC, SUBJECT:$sSubject, BODY:$sBody"; + + // Mail it + if (mail($sTo, $sSubject, $sBody, $sHeaders)) + { + $oLog = new EventNotification(); + $oLog->Set('message', 'Email sent successfully'); + $oLog->Set('userinfo', UserRights::GetUser()); + $oLog->Set('trigger_id', $oTrigger->GetKey()); + $oLog->Set('action_id', $this->GetKey()); + $oLog->Set('object_id', $aContextArgs['this->id']); + $oLog->Set('overview', $sOverview); + $oLog->DBInsertNoReload(); + } + else + { + throw new CoreException('mail not sent', array('action'=>$this->GetKey(), 'to'=>$sTo, 'subject'=>$sSubject, 'headers'=>$sHeaders)); + } + } +} +?> diff --git a/core/event.class.inc.php b/core/event.class.inc.php new file mode 100644 index 0000000000..b50e7a4c57 --- /dev/null +++ b/core/event.class.inc.php @@ -0,0 +1,94 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class Event extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Log Event", + "description" => "An application internal event", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_event", + "db_key_field" => "id", + "db_finalclass_field" => "realclass", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("message", array("label"=>"Message", "description"=>"one line description", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"misc. info", "description"=>"caller's defined information", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("message"); + MetaModel::Init_AddFilterFromAttribute("date"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('finalclass', 'date', 'message')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'date', 'message')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} + +class EventNotification extends Event +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Notification event", + "description" => "Trace of a notification that has been sent", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_event_notification", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> "", "label"=>"Trigger", "description"=>"user account", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("object_id", array("label"=>"Object id", "description"=>"object id (class defined by the trigger ?)", "allowed_values"=>null, "sql"=>"object_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("overview", array("label"=>"Overview", "description"=>"Complete view of the resulting email", "allowed_values"=>null, "sql"=>"overview", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("trigger_id"); + MetaModel::Init_AddFilterFromAttribute("action_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'message', 'userinfo', 'trigger_id', 'action_id', 'object_id', 'overview')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'message')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + +} + + +?> diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php new file mode 100644 index 0000000000..ffc043a9f9 --- /dev/null +++ b/core/trigger.class.inc.php @@ -0,0 +1,204 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class Trigger extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "trigger", + "description" => "Custom event handler", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_trigger", + "db_key_field" => "id", + "db_finalclass_field" => "realclass", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("linked_actions", array("label"=>"Triggered actions", "description"=>"Actions performed when the trigger is activated", "linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"trigger_id", "ext_key_to_remote"=>"action_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + + //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("description"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('finalclass', 'description')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'description')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + public function DoActivate($aContextArgs) + { + // Find the related + $oLinkedActions = $this->Get('linked_actions'); + while ($oLink = $oLinkedActions->Fetch()) + { + $iActionId = $oLink->Get('action_id'); + $oAction = MetaModel::GetObject('Action', $iActionId); + $oAction->DoExecute($this, $aContextArgs); + } + } +} + +class TriggerOnStateChange extends Trigger +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Trigger on object state change", + "description" => "Trigger on object state change", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_trigger_onstatechange", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("target_class", array("label"=>"Target class", "description"=>"label", "allowed_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("state", array("label"=>"State", "description"=>"label", "allowed_values"=>null, "sql"=>"state", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("target_class"); + MetaModel::Init_AddFilterFromAttribute("state"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'state')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'state')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + +} + +class TriggerOnStateEnter extends TriggerOnStateChange +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Trigger on object entering a state", + "description" => "Trigger on object state change - entering", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_trigger_onstateenter", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'state')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} + +class TriggerOnStateLeave extends TriggerOnStateChange +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Trigger on object leaving a state", + "description" => "Trigger on object state change - leaving", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_trigger_onstateleave", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'state')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('')); // Criteria of the advanced search form + } +} + +class lnkTriggerAction extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Actions-Trigger", + "description" => "Link between a trigger and an action", + "key_type" => "autoincrement", + "key_label" => "Link ID", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(""), + "db_table" => "priv_link_action_trigger", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> '', "label"=>"Action", "description"=>"The action to be executed", "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("action_name", array("label"=>"Action Name", "description"=>"Name of the action", "allowed_values"=>null, "extkey_attcode"=> 'action_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> '', "label"=>"Trigger", "description"=>"Trigger", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("order", array("label"=>"Order", "description"=>"Actions execution order", "allowed_values"=>null, "sql"=>"order", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddFilterFromAttribute("action_id"); + MetaModel::Init_AddFilterFromAttribute("trigger_id"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('action_id', 'trigger_id', 'order')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form + } +} +?> From 58ab73c7181cc955fb75d8bd9054477e746eb29d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 6 Jan 2010 19:27:24 +0000 Subject: [PATCH 150/970] - For truncated lists SVN:trunk[227] --- images/truncated.png | Bin 0 -> 1429 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/truncated.png diff --git a/images/truncated.png b/images/truncated.png new file mode 100644 index 0000000000000000000000000000000000000000..57d3e77eae6a4956ecde31a0268b844cd70b6c98 GIT binary patch literal 1429 zcmc(e`!^E`0LLeV>u}W~Q99^xN~f-rlj|fYn)P6jBDW6i*64&hN=4rGWDKKim{+!v zM_$9so6Y-~VY7^R??E2JJnsIB`#tCT{haU5-}6O796*}t`sx4xKoe|l>!cu5VO=$4 zMRsEJ|4`r{+~%PT08qldeh9Om=z76+uEEa!FM~ro17QFgsJ|D?5bWy-hdIGKp&tS| zU^f5&B?RQbW4nlmh}qd$u~_``=g*Xsl-1Q$JRVOV5D*9iolZxgP*G7)xw*MZOH1M5 z;a|Rd85$ZI92{gY7%M9)ySuw}b#){XiAtr8j*cRc$f>ER@$vEA-rmT_$c2Rk91a&9 z9W4@x&}j6=#s-haYieprO-*fYZ~y-Ndqze^Yinz4Y%B(Y+1uNjnVDH!TwGpWPE1VX z^ZCii$rBS3)6>%l2?_D>@g*fCGMOwZE32%mjLYSgmX>B_W|GO|_4W0+xw(dhhP1S_ zk&zLhP}tYk_x0;n7K??&VvCB33JMBpYHEs$i`i`UuV25Ko12+T=I76!b8>Rx;^L%I zX-`iNg+i&WuAZNt&&$gzEG#4viE_ETs;cVu@84@{Ye`8-9UUDKi6kZ_Mj#Lj4-a>B zbu~6N?(FPzc6L@&RM2R&+S=NIfdLMO)6&w?*4DPYySSPyr*L6HFOQGI?hEaMm zf~30!rdwJnb$9qi(xedfz8Y{~uSmR|?tj%Jaz`1;T|ttZqb42KsWbWNI|M5#DPm~( zFGjYR8(f$R2lDm~ZGF&ttTa*Rq%_m?-sEx=*oPTv4s)ij-!U%HOuqH8AbiPhuwI-X zzMx@u<^HXB%DuyRP;z79?V+Q8n-gD~5LCS^F+R`VZMhb2CP~183xG~NJ4=%z+SAH~ zE+5Zdbz^N*w3#JM&96Pl86|oX?)mgZ%k4?$;JW?ZCLRYZ(#o)bH8f%?TnMzr87ajX z8*nbA^wsNEVFf47J}=64X3tB%gsST4x=ultCAipnnTG6f`qxMd%?9reM-%m)PU}Y@?vopKDb}|geQ`v zvfY37EG+oYDKq^4uWU)XD%d6dj_pa+}#Sss3snu zW*0-XSM#c9*Fg=?G2cRnw+{bZSXUZdSJ&JOG-AZPj$8wyFsbEZWhw>d$(jY-kke2; zrCp! Date: Wed, 6 Jan 2010 19:39:03 +0000 Subject: [PATCH 151/970] - Fixing styles issues found on the truncated lists (Ticket #61) SVN:trunk[228] --- css/light-grey.css | 4 ++++ js/utils.js | 1 + 2 files changed, 5 insertions(+) diff --git a/css/light-grey.css b/css/light-grey.css index ac9df52d88..ea901d9fe8 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -680,6 +680,10 @@ table.listResults tr.even td.truncated.hover { background: #E8FFD3 url(../images/truncated.png) bottom repeat-x; } +table.listResults tr.odd td.truncated.hover { + background: #E8FFD3 url(../images/truncated.png) bottom repeat-x; +} + table.listResults.truncated { border-bottom: 0; padding-bottom: 0; diff --git a/js/utils.js b/js/utils.js index 7efa4a2081..0f2e795ab9 100644 --- a/js/utils.js +++ b/js/utils.js @@ -12,6 +12,7 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) $('#'+divId).empty(); $('#'+divId).append(data); $('#'+divId).removeClass('loading'); + $('#'+divId+' .listResults').tableHover(); // hover tables $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables //$('#'+divId).unblockUI(); } From 9fc06ff6e4af5ca8e8e4ac34a53846de2a9f6cee Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 6 Jan 2010 20:06:16 +0000 Subject: [PATCH 152/970] - Fixing file upload/download troubles: make sure that magic_quotes_gpc is turned off and that the output is not truncated by AjaxWebPage. SVN:trunk[229] --- application/ajaxwebpage.class.inc.php | 2 +- setup/index.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 3d061f69a5..fbacc00584 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -42,7 +42,7 @@ class ajax_page extends web_page } $s_captured_output = ob_get_contents(); ob_end_clean(); - echo trim($this->s_content); + echo $this->s_content; if (!empty($this->m_sReadyScript)) { echo "\n"; - $sHtml .= "
    Here goes the chart
    \n"; + $sHtml .= "
    \n"; $iChartCounter++; break; From bdaf63c681c19edb90792564f6bcf109d2b7d36f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 12 Jan 2010 08:58:42 +0000 Subject: [PATCH 158/970] Data model changed: incident::workgroup becomes mandatory, and document::scope replaced by document::type SVN:trunk[235] --- business/incidentMgmt.business.php | 2 +- business/itop.business.class.inc.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index 0e386c1740..2a44a46727 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -59,7 +59,7 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"mail of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index ceda3e8e23..49c6ed8d3d 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -396,20 +396,20 @@ class bizDocument extends logRealObject MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning the document", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("scope", array("label"=>"scope", "description"=>"Scope of this document", "allowed_values"=>new ValueSetEnum("organization,hardware support"), "sql"=>"scope", "default_value"=>"organization", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"type", "description"=>"type of this document", "allowed_values"=>new ValueSetEnum("documentation,contract,working instruction,network maps,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("label"=>"Contents", "description"=>"File content", "depends_on"=>array()))); MetaModel::Init_InheritFilters(); - MetaModel::Init_AddFilterFromAttribute("scope"); + MetaModel::Init_AddFilterFromAttribute("type"); MetaModel::Init_AddFilterFromAttribute("description"); - MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope', 'description', 'contents')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope', 'contents')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'type', 'description', 'contents')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'type', 'contents')); // Attributes to be displayed for a list // Search criteria - MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope')); // Criteria of the std search form - MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'scope')); // Criteria of the advanced search form + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'type')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type')); // Criteria of the advanced search form } From 3b8928bf743771606b5a225d5eaf02bb3c0a0bb6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 12 Jan 2010 09:05:50 +0000 Subject: [PATCH 159/970] Fixed typo in the enums of the new field document::type SVN:trunk[236] --- business/itop.business.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 49c6ed8d3d..e870ec4f4a 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -396,7 +396,7 @@ class bizDocument extends logRealObject MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning the document", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"type", "description"=>"type of this document", "allowed_values"=>new ValueSetEnum("documentation,contract,working instruction,network maps,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"type", "description"=>"usage of the document", "allowed_values"=>new ValueSetEnum("documentation,contract,working instructions,network map,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("label"=>"Contents", "description"=>"File content", "depends_on"=>array()))); From 2565d21c402f5cf562165ba81cd40904274797fe Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 10:13:33 +0000 Subject: [PATCH 160/970] New menu for the configuration of notifications SVN:trunk[237] --- setup/data/structure/1.menus.xml | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index 7c78a33e01..64820cb60c 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -997,4 +997,59 @@ text-align:left; 1 0 + +Notifications + +UI.php + + +administrator +2 +1 +0 + From 29a4ce205192a9eeb0403ced70539a142fd48ab3 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 10:16:45 +0000 Subject: [PATCH 161/970] Factorized the processing of templates SVN:trunk[238] --- application/menunode.class.inc.php | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index d6337c718e..19b54b2452 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -116,30 +116,9 @@ class menuNode extends DBObject public function RenderContent(web_page $oPage, $aExtraParams = array()) { $sTemplate = $this->Get('template'); - $this->ProcessTemplate($sTemplate, $oPage, $aExtraParams); - } - - protected function ProcessTemplate($sTemplate, web_page $oPage, $aExtraParams = array()) - { - $iStartPos = stripos($sTemplate, '<'.DisplayBlock::TAG_BLOCK.' ',0); - $index = 0; - while(($iStartPos = stripos($sTemplate, '<'.DisplayBlock::TAG_BLOCK.' ',0)) !== false) - { - $iEndPos = stripos($sTemplate, '', $iStartPos); - - $sBlockDefinition = substr($sTemplate, $iStartPos, $iEndPos - $iStartPos + strlen('')); - $oBlock = DisplayBlock::FromTemplate($sBlockDefinition); - - $oPage->add(substr($sTemplate, 0, $iStartPos)); - if ($oBlock) // Protects agains invalid XML templates - { - $oBlock->Display($oPage, "block{$index}", $aExtraParams); // Values from $aExtraParams have precedence over $aParams - } - $index++; - $sTemplate = substr($sTemplate, $iEndPos + strlen('')); - } - // What remains is purely static (without any block inside), just output as it is - $oPage->add($sTemplate); + $oTemplate = new DisplayTemplate($sTemplate); + $oTemplate->Render($oPage, $aExtraParams); + //$this->ProcessTemplate($sTemplate, $oPage, $aExtraParams); } public function DisplayMenu(iTopWebPage $oP, $sType, $aExtraParams) From 423216e4aebdd2ce21727d1345beca91f326c0af Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 10:18:18 +0000 Subject: [PATCH 162/970] The label of the active menu is now used as the title of the web page. SVN:trunk[239] --- pages/UI.php | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/UI.php b/pages/UI.php index 7478805d34..e3a34c6238 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1139,6 +1139,7 @@ switch($operation) if (is_object($oActiveNode)) { $oActiveNode->RenderContent($oP, $oAppContext->GetAsHash()); + $oP->set_title($oActiveNode->Get('label')); } } ////MetaModel::ShowQueryTrace(); From 532ce97fd117d53fec44d60d8d2f11a055a46f3a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 10:20:01 +0000 Subject: [PATCH 163/970] Cosmetic bug fix: the triangle image of the collapsible section was not displayed properly if the "section" was initially open. SVN:trunk[240] --- application/template.class.inc.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/application/template.class.inc.php b/application/template.class.inc.php index e7b30dcae1..4ebfdf4294 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -141,7 +141,9 @@ class DisplayTemplate break; case 'itoptoggle': - $oPage->StartCollapsibleSection($aAttributes['name']); + $sName = isset($aAttributes['name']) ? $aAttributes['name'] : 'Tagada'; + $bOpen = isset($aAttributes['open']) ? $aAttributes['open'] : true; + $oPage->StartCollapsibleSection($sName, $bOpen); $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied //$oPage->p('iTop Tab Content:
    '.htmlentities($sContent).'
    '); From 068c6916d6f0dcddd978dd005b7f1d0ef599bc52 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 20:12:00 +0000 Subject: [PATCH 164/970] - Removed the organization "My Company/Department" since it is always created by the setup. - Do not set a parent for each organization (was usually set to themselves) SVN:trunk[241] --- setup/data/01.organizations.xml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/setup/data/01.organizations.xml b/setup/data/01.organizations.xml index 9c43f85be6..a8426abf19 100644 --- a/setup/data/01.organizations.xml +++ b/setup/data/01.organizations.xml @@ -4,24 +4,18 @@ Demo MCO implementation -2 +0 - + France Telecom FT production 0 - -My Company/Department -SOMECODE -implementation -0 - - + Oracle ORA production -3 +0 - \ No newline at end of file + From 2fe861a0d554af7cd26c39ae7177816d6a403049 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 20:14:13 +0000 Subject: [PATCH 165/970] - Exclude "My Company/Department" from the export of organizations, since this organization is always created by the setup program. SVN:trunk[242] --- setup/data/export.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/data/export.cmd b/setup/data/export.cmd index 33a1e6d2bb..c36e5a35ce 100644 --- a/setup/data/export.cmd +++ b/setup/data/export.cmd @@ -6,7 +6,7 @@ SET PWD=admin REM The order (numbering) of the files is important since REM it dictates the order to import them back -wget --output-document=01.organizations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizOrganization&format=xml" +wget --output-document=01.organizations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizOrganization WHERE name !='My Company/Department'&format=xml" wget --output-document=02.locations.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizLocation&format=xml" wget --output-document=03.persons.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizPerson WHERE id !%%3D 1&format=xml" wget --output-document=04.teams.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT bizTeam&format=xml" From c800b9d41e7f5941ba0ff144f98e2cf41916e5f6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 21:02:42 +0000 Subject: [PATCH 166/970] - Added some variables about the version, build revision and build date to be populated automatically by the build system. SVN:trunk[243] --- application/startup.inc.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/startup.inc.php b/application/startup.inc.php index 97969a34be..3b4f86b88e 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -1,4 +1,7 @@ Date: Tue, 12 Jan 2010 21:10:08 +0000 Subject: [PATCH 167/970] - Added display of the version / revision and build date for "official builds" - Fixed the display of the triangle image of the "collapsible section" when it is initially "open" SVN:trunk[244] --- application/itopwebpage.class.inc.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index dfe51047e7..16e897823d 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -262,8 +262,18 @@ EOF echo "\n"; // Display the header + if (ITOP_REVISION == '$WCREV$') + { + // This is NOT a version built using the buil system, just display the main version + $sVersionString = "iTop Version ".ITOP_VERSION; + } + else + { + // This is a build made from SVN, let display the full information + $sVersionString = "iTop Version ".ITOP_VERSION." revision ".ITOP_REVISION.", built on: ".ITOP_BUILD_DATE; + } echo "
    \n"; - echo "
    iTop
    \n"; + echo "
    iTop
    \n"; //echo "
    \n"; $sText = Utils::ReadParam('text', ''); $sOnClick = ""; @@ -384,7 +394,8 @@ EOF { $sHtml = ''; static $iSectionId = 0; - $sHtml .= "$sSectionLabel
    \n"; + $sImgStyle = $bOpen ? ' open' : ''; + $sHtml .= "$sSectionLabel
    \n"; $sStyle = $bOpen ? '' : 'style="display:none" '; $sHtml .= "
    "; $this->add_ready_script("\$(\"#LnkCollapse_$iSectionId\").click(function() {\$(\"#Collapse_$iSectionId\").slideToggle('normal'); $(\"#LnkCollapse_$iSectionId\").toggleClass('open');});"); From 89fa70d755b49e3e206310c486553526abdd89e0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 12 Jan 2010 21:47:14 +0000 Subject: [PATCH 168/970] New icon for the notifications SVN:trunk[245] --- images/bell.png | Bin 0 -> 12978 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/bell.png diff --git a/images/bell.png b/images/bell.png new file mode 100644 index 0000000000000000000000000000000000000000..34fb9b5d8683eee635fa9ea4124c829d2e640166 GIT binary patch literal 12978 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 literal 0 HcmV?d00001 From 8964b13a2e7b1440d0495bfc6f215a224ca3a7d8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 13 Jan 2010 13:30:08 +0000 Subject: [PATCH 169/970] - Fixed bug with change tracking of TEXT attributes - Log any email notification (successful or not) - Class selection always made through a Combo box - Automatic creation of indexes for external keys - New trigger: on object creation - Application log: added a status string - Added documentation to the WSDL (+ anyType replaced by string) - SOAP: improved handling of enumeration attributes - SOAP: returned logs to mention the name of the parameter as advertised in the WSDL file (different than the name of the attribute in Itop) - Finalized SOAP tests - Added a SOAP client example SVN:trunk[246] --- .../userrightsprofile.class.inc.php | 2 +- application/iotask.class.inc.php | 2 +- core/action.class.inc.php | 29 ++-- core/attributedef.class.inc.php | 24 ++++ core/cmdbchangeop.class.inc.php | 70 ++++++++- core/cmdbobject.class.inc.php | 20 +++ core/cmdbsource.class.inc.php | 10 ++ core/dbobject.class.php | 27 +++- core/event.class.inc.php | 6 +- core/metamodel.class.php | 30 ++-- core/trigger.class.inc.php | 38 ++++- core/valuesetdef.class.inc.php | 42 +++--- pages/testlist.inc.php | 115 ++++++++++++--- webservices/itop.wsdl.tpl | 51 +++++-- webservices/itopsoap.examples.php | 69 +++++++++ webservices/webservices.class.inc.php | 136 +++++++++++++----- 16 files changed, 554 insertions(+), 117 deletions(-) create mode 100644 webservices/itopsoap.examples.php diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index bc99de3a2f..d6c33e24ce 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -307,7 +307,7 @@ class URP_Dimensions extends UserRightsBaseClass //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Type", "description"=>"class name or data type (projection unit)", "allowed_values"=>new ValueSetEnumClasses('bizmodel', 'String,Integer'), "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("type", array("label"=>"Type", "description"=>"class name or data type (projection unit)", "class_category"=>"bizmodel", "more_values"=>"String,Integer", "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index f1f62f8c79..e882a48ee2 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -31,7 +31,7 @@ class InputOutputTask extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("label"=>"Source Type", "description"=>"Type of data source", "allowed_values"=>new ValueSetEnum('File, Database, Web Service'), "sql"=>"source_type", "default_value"=>"File", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype", array("label"=>"Source Subtype", "description"=>"Subtype of Data Source", "allowed_values"=>new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql"=>"source_subtype", "default_value"=>"CSV", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("source_path", array("label"=>"Source Path", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"source_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "allowed_values"=>new ValueSetEnumClasses(), "sql"=>"objects_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "class_category"=>"", "more_values"=>"", "sql"=>"objects_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("label"=>"Test Mode", "description"=>"If set to 'Yes' the modifications are not applied", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"test_mode", "default_value"=>'No', "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("label"=>"Verbose Mode", "description"=>"If set to 'Yes' extra debug information is added to the log", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"verbose_mode", "default_value" => 'No', "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("options", array("label"=>"Options", "description"=>"Reconciliation options", "allowed_values"=>new ValueSetEnum('Full, Update Only, Creation Only'), "sql"=>"options", "default_value"=> 'Full', "is_null_allowed"=>true, "depends_on"=>array()))); diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 68c12a7596..51cee9af5e 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -215,25 +215,28 @@ class ActionEmail extends ActionNotification $sHeaders .= "Bcc: $sBCC\r\n"; } - // Mail it + $oLog = new EventNotificationEmail(); if (mail($sTo, $sSubject, $sBody, $sHeaders)) { - $oLog = new EventNotificationEmail(); - $oLog->Set('userinfo', UserRights::GetUser()); - $oLog->Set('trigger_id', $oTrigger->GetKey()); - $oLog->Set('action_id', $this->GetKey()); - $oLog->Set('object_id', $aContextArgs['this->id']); - $oLog->Set('to', $sTo); - $oLog->Set('cc', $sCC); - $oLog->Set('bcc', $sBCC); - $oLog->Set('subject', $sSubject); - $oLog->Set('body', $sBody); - $oLog->DBInsertNoReload(); + $oLog->Set('message', 'Notification sent'); } else { - throw new CoreException('mail not sent', array('action'=>$this->GetKey(), 'to'=>$sTo, 'subject'=>$sSubject, 'headers'=>$sHeaders)); + $aLastError = error_get_last(); + $oLog->Set('message', 'Mail could not be sent: '.$aLastError['message']); + //throw new CoreException('mail not sent', array('action'=>$this->GetKey(), 'to'=>$sTo, 'subject'=>$sSubject, 'headers'=>$sHeaders)); } + + $oLog->Set('userinfo', UserRights::GetUser()); + $oLog->Set('trigger_id', $oTrigger->GetKey()); + $oLog->Set('action_id', $this->GetKey()); + $oLog->Set('object_id', $aContextArgs['this->id']); + $oLog->Set('to', $sTo); + $oLog->Set('cc', $sCC); + $oLog->Set('bcc', $sBCC); + $oLog->Set('subject', $sSubject); + $oLog->Set('body', $sBody); + $oLog->DBInsertNoReload(); } } ?> diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 6beed31b2c..145020461a 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -600,6 +600,30 @@ class AttributeString extends AttributeDBField } } +/** + * An attibute that matches an object class + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeClass extends AttributeString +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("class_category", "more_values")); + } + + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']); + parent::__construct($sCode, $aParams); + } +} /** * Map a varchar column (size < ?) to an attribute that must never be shown to the user diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index ebbfc173bc..2835992edf 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -304,8 +304,8 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute $aParams = array ( "category" => "core/cmdb", - "name" => "object data change", - "description" => "Object data change tracking", + "name" => "data change", + "description" => "data change tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", @@ -357,5 +357,71 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute } } +/** + * Record the modification of a multiline string (text) + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "text change", + "description" => "text change tracking", + "key_type" => "", + "key_label" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_text", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeText("prevdata", array("label"=>"Previous data", "description"=>"previous contents of the attribute", "allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + + $sResult = ''; + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sTextView = '
    '.$this->GetAsHtml('prevdata').'
    '; + + //$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata'); + $sResult = "$sAttName changed, previous value: $sTextView"; + } + return $sResult; + } +} ?> diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 997f605d4d..5f490e9e5d 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -193,6 +193,26 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("prevdata", $original); $iId = $oMyChangeOp->DBInsertNoReload(); } + elseif ($oAttDef instanceOf AttributeText) + { + // Data blobs + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeText"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $original = $aOrigValues[$sAttCode]; + } + else + { + $original = null; + } + $oMyChangeOp->Set("prevdata", $original); + $iId = $oMyChangeOp->DBInsertNoReload(); + } else { // Scalars diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index b68474d3e4..20f3fed9e7 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -342,6 +342,16 @@ class CMDBSource return (strtolower($aFieldData["Null"]) == "yes"); } + public static function HasIndex($sTable, $sField) + { + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return false; + if (!array_key_exists($sField, $aTableInfo["Fields"])) return false; + $aFieldData = $aTableInfo["Fields"][$sField]; + // $aFieldData could be 'PRI' for the primary key, or 'MUL', or ? + return (strlen($aFieldData["Key"]) > 0); + } + // Returns an array of (fieldname => array of field info) public static function GetTableFieldsList($sTable) { diff --git a/core/dbobject.class.php b/core/dbobject.class.php index d309ca4db1..e20943a7ab 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -497,6 +497,17 @@ abstract class DBObject } } } + elseif ($oAtt->IsWritable() && $oAtt->IsScalar()) + { + $aValues = $oAtt->GetAllowedValues(); + if (count($aValues) > 0) + { + if (!array_key_exists($toCheck, $aValues)) + { + return false; + } + } + } return true; } @@ -690,9 +701,19 @@ abstract class DBObject } $this->DBWriteLinks(); - - // Reload to update the external attributes $this->m_bIsInDB = true; + + // Activate any existing trigger + $sClass = get_class($this); + $oSet = new DBObjectSet(new DBObjectSearch('TriggerOnObjectCreate')); + while ($oTrigger = $oSet->Fetch()) + { + if (MetaModel::IsParentClass($oTrigger->Get('target_class'), $sClass)) + { + $oTrigger->DoActivate($this->ToArgs('this')); + } + } + return $this->m_iKey; } @@ -849,6 +870,8 @@ abstract class DBObject $aScalarArgs = array(); $aScalarArgs[$sArgName] = $this->GetKey(); $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); + $aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink(); + $aScalarArgs[$sArgName.'->name()'] = $this->GetName(); $sClass = get_class($this); foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 833879e542..79b2efbe0c 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -32,15 +32,17 @@ class Event extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("message", array("label"=>"message", "description"=>"short description of the event", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"user info", "description"=>"identification of the user that was doing the action that triggered this event", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("message"); MetaModel::Init_AddFilterFromAttribute("date"); // Display lists - MetaModel::Init_SetZListItems('details', array('finalclass', 'date', 'userinfo')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('finalclass', 'date')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('message', 'date', 'userinfo')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'finalclass', 'message')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form diff --git a/core/metamodel.class.php b/core/metamodel.class.php index c54e828f98..6177cace23 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2092,18 +2092,30 @@ abstract class MetaModel { $aErrors[$sClass][] = "field '$sField' could not be found in table '$sTable'"; $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; - } - elseif ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) - { - if ($oAttDef->IsNullAllowed()) + if ($oAttDef->IsExternalKey()) { - $aErrors[$sClass][] = "field '$sField' in table '$sTable' could be NULL"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; } - else + } + else + { + if ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) { - $aErrors[$sClass][] = "field '$sField' in table '$sTable' could NOT be NULL"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + if ($oAttDef->IsNullAllowed()) + { + $aErrors[$sClass][] = "field '$sField' in table '$sTable' could be NULL"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + else + { + $aErrors[$sClass][] = "field '$sField' in table '$sTable' could NOT be NULL"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + } + if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) + { + $aErrors[$sClass][] = "Foreign key '$sField' in table '$sTable' should have an index"; + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; } } } diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index ffc043a9f9..6d9d1a637b 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -82,7 +82,7 @@ class TriggerOnStateChange extends Trigger ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("target_class", array("label"=>"Target class", "description"=>"label", "allowed_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("label"=>"Target class", "description"=>"label", "class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("state", array("label"=>"State", "description"=>"label", "allowed_values"=>null, "sql"=>"state", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); @@ -96,7 +96,6 @@ class TriggerOnStateChange extends Trigger // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } - } class TriggerOnStateEnter extends TriggerOnStateChange @@ -165,6 +164,41 @@ class TriggerOnStateLeave extends TriggerOnStateChange } } +class TriggerOnObjectCreate extends Trigger +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Trigger on object creation", + "description" => "Trigger on object creation of [a child class of] the given class", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_trigger_onobjcreate", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("label"=>"Target class", "description"=>"label", "class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("target_class"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} + class lnkTriggerAction extends cmdbAbstractObject { public static function Init() diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 393a06baac..e84b0c2b5e 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -128,16 +128,23 @@ class ValueSetObjects extends ValueSetDefinition */ class ValueSetEnum extends ValueSetDefinition { + protected $m_values; + public function __construct($Values) { - if (is_array($Values)) + $this->m_values = $Values; + } + + protected function LoadValues($aArgs) + { + if (is_array($this->m_values)) { - $aValues = $Values; + $aValues = $this->m_values; } else { $aValues = array(); - foreach (explode(",", $Values) as $sVal) + foreach (explode(",", $this->m_values) as $sVal) { $sVal = trim($sVal); $sKey = $sVal; @@ -145,10 +152,6 @@ class ValueSetEnum extends ValueSetDefinition } } $this->m_aValues = $aValues; - } - - protected function LoadValues($aArgs) - { return true; } } @@ -166,20 +169,25 @@ class ValueSetEnum extends ValueSetDefinition */ class ValueSetEnumClasses extends ValueSetEnum { - public function __construct($sCategory = '', $sAdditionalValues = '') - { - // First, build it from the series of additional values - parent::__construct($sAdditionalValues); + protected $m_sCategories; - // Second: add the list of classes - foreach (MetaModel::GetClasses($sCategory) as $sClass) - { - $this->m_aValues[$sClass] = MetaModel::GetName($sClass); - } + public function __construct($sCategories = '', $sAdditionalValues = '') + { + $this->m_sCategories = $sCategories; + parent::__construct($sAdditionalValues); } protected function LoadValues($aArgs) - { + { + // First, get the additional values + parent::LoadValues($aArgs); + + // Then, add the classes from the category definition + foreach (MetaModel::GetClasses($this->m_sCategories) as $sClass) + { + $this->m_aValues[$sClass] = MetaModel::GetName($sClass); + } + return true; } } diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 862e41206e..42d1375281 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -582,7 +582,6 @@ class TestMyBizModel extends TestBizModel $this->test_relations(); $this->test_linkedset(); $this->test_object_lifecycle(); - return true; } } @@ -929,7 +928,7 @@ class TestBulkChangeOnFarm extends TestBizModel $aRes = $oBulk->Process($oMyChange); print_r($aRes); - return true; + return; $oRawData = array( 'Mammal', @@ -978,7 +977,6 @@ class TestFullTextSearchOnFarm extends MyFarm $oSearch->AddCondition_FullText('manof'); //$oResultSet = new DBObjectSet($oSearch); $this->search_and_show_list($oSearch); - return true; } } @@ -1103,7 +1101,6 @@ class TestItopEfficiency extends TestBizModel $aData[] = $aValues; } echo MyHelpers::make_table_from_assoc_array($aData); - return true; } } @@ -1169,8 +1166,6 @@ class TestItopWebServices extends TestWebServices { $this->DoExecSingleLoad($aLoadSpec); } - - return true; } } @@ -1178,14 +1173,14 @@ class TestItopWebServices extends TestWebServices $aWebServices = array( array( 'verb' => 'GetVersion', - 'expected result' => true, + 'expected result' => '0.8', 'explain result' => 'n/a', 'args' => array(), ), array( 'verb' => 'CreateIncidentTicket', 'expected result' => true, - 'explain result' => 'ok, but link attribute unknown', + 'explain result' => 'link attribute unknown + a CI not found', 'args' => array( 'admin', /* sLogin */ 'admin', /* sPassword */ @@ -1195,7 +1190,7 @@ $aWebServices = array( 'very grave', /* sImpact */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aWorkgroupDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ array( new SOAPLinkCreationSpec( 'logInfra', @@ -1207,14 +1202,38 @@ $aWebServices = array( array(new SOAPSearchCondition('name', 'Router03')), array(new SOAPAttributeValue('impact', 'who cares')) ), + new SOAPLinkCreationSpec( + 'bizDevice', + array(new SOAPSearchCondition('name', 'thisone')), + array(new SOAPAttributeValue('impact', 'our lives')) + ), ), /* aImpact */ 'low' /* sSeverity */ ), ), array( 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'ok, but CI unknown', + 'expected result' => false, + 'explain result' => 'caller not specified', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Desktop', /* sType */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + 'The agent could not do his job', /* sImpact */ + null, /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong condition on CI to attach', 'args' => array( 'admin', /* sLogin */ 'admin', /* sPassword */ @@ -1224,11 +1243,11 @@ $aWebServices = array( 'The agent could not do his job', /* sImpact */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aWorkgroupDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ array( new SOAPLinkCreationSpec( 'logInfra', - array(new SOAPSearchCondition('id', 99999)), + array(new SOAPSearchCondition('dummyfiltercode', 2)), array(new SOAPAttributeValue('impact', 'very critical')) ), ), /* aImpact */ @@ -1237,8 +1256,8 @@ $aWebServices = array( ), array( 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'ok, no CI to attach', + 'expected result' => true, + 'explain result' => 'no CI to attach (empty array)', 'args' => array( 'admin', /* sLogin */ 'admin', /* sPassword */ @@ -1248,12 +1267,30 @@ $aWebServices = array( 'Could not talk to my wife', /* sImpact */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aWorkgroupDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ array( ), /* aImpact */ 'low' /* sSeverity */ ), ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (null)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Network', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + null, /* aImpact */ + 'low' /* sSeverity */ + ), + ), array( 'verb' => 'CreateIncidentTicket', 'expected result' => false, @@ -1267,12 +1304,31 @@ $aWebServices = array( 'Could not talk to my wife', /* sImpact */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1000))), /* aCallerDesc */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aWorkgroupDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ array( ), /* aImpact */ 'low' /* sSeverity */ ), ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong values for type and severity', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'my type', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'my severity' /* sSeverity */ + ), + ), array( 'verb' => 'CreateIncidentTicket', 'expected result' => false, @@ -1286,7 +1342,7 @@ $aWebServices = array( 'Could not talk to my wife', /* sImpact */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aWorkgroupDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ array( ), /* aImpact */ 'low' /* sSeverity */ @@ -1305,7 +1361,7 @@ $aWebServices = array( 'Could not talk to my wife', /* sImpact */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aWorkgroupDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ array( ), /* aImpact */ 'low' /* sSeverity */ @@ -1321,6 +1377,8 @@ class TestSoap extends TestSoapWebService protected function DoExecute() { + echo "

    Note: You may also want to try the sample SOAP client itopsoap.examples.php

    \n"; + global $aSOAPMapping; // this file is generated dynamically with location = here @@ -1331,7 +1389,6 @@ class TestSoap extends TestSoapWebService ( $sWsdlUri, array( - //'uri' => 'http://soap-itop/', 'classmap' => $aSOAPMapping, 'trace' => 1, ) @@ -1348,6 +1405,7 @@ class TestSoap extends TestSoapWebService foreach ($aWebServices as $iPos => $aWebService) { echo "

    SOAP call #$iPos ".$aWebService['explain result']."

    \n"; + try { $oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']); @@ -1359,7 +1417,7 @@ class TestSoap extends TestSoapWebService print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; print ""; print "Response in HTML:

    ".$this->m_SoapClient->__getLastResponse()."

    "; - return false; + throw $e; } echo "
    \n";
    @@ -1370,9 +1428,20 @@ class TestSoap extends TestSoapWebService
     			print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
     			print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
     			print "
    "; - } - return true; + if ($oRes instanceof SOAPResult) + { + $res = $oRes->status; + } + else + { + $res = $oRes; + } + if ($res != $aWebService['expected result']) + { + throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); + } + } } } diff --git a/webservices/itop.wsdl.tpl b/webservices/itop.wsdl.tpl index 32f6edf19e..c3e4289e36 100644 --- a/webservices/itop.wsdl.tpl +++ b/webservices/itop.wsdl.tpl @@ -1,6 +1,6 @@ - + @@ -8,9 +8,13 @@ + - + @@ -21,14 +25,23 @@ + + - + @@ -39,6 +52,9 @@ + @@ -53,6 +69,9 @@ + @@ -65,6 +84,9 @@ + @@ -72,7 +94,7 @@ - + @@ -83,6 +105,10 @@ + @@ -96,6 +122,12 @@ + @@ -129,17 +161,17 @@ - + Get the current version of Itop As this service is very simple, it is a test to get trained for more complex operations - + --> - + Create a ticket, return information about reconciliation on external keys and the created ticket - + --> @@ -166,6 +198,9 @@ + + ITop is the central solution for managing your IT infrastructure + diff --git a/webservices/itopsoap.examples.php b/webservices/itopsoap.examples.php new file mode 100644 index 0000000000..c93be4333d --- /dev/null +++ b/webservices/itopsoap.examples.php @@ -0,0 +1,69 @@ + 1, + 'classmap' => $aSOAPMapping, // defined in itopsoaptypes.class.inc.php + ) +); + +try +{ + // The most simple service, returning a string + // + $sServerVersion = $oSoapClient->GetVersion(); + echo "

    GetVersion() returned $sServerVersion

    "; + + // More complex ones, returning a SOAPResult structure + // (run the page to know more about the returned data) + // + $oRes = $oSoapClient->CreateIncidentTicket + ( + 'admin', /* login */ + 'admin', /* password */ + 'Server', /* type */ + 'Email server down', /* description */ + 'HW found shutdown', /* initial situation */ + 'Email not working', /* impact */ + null, /* caller */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* customer */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* workgroup */ + array( + new SOAPLinkCreationSpec( + 'bizDevice', + array(new SOAPSearchCondition('name', 'Router03')), + array(new SOAPAttributeValue('impact', 'root cause')) + ), + new SOAPLinkCreationSpec( + 'bizServer', + array(new SOAPSearchCondition('name', 'Server01')), + array(new SOAPAttributeValue('impact', '')) + ), + ), /* impact */ + 'high' /* severity */ + ); + + echo "

    CreateIncidentTicket() returned:\n"; + echo "

    \n";
    +	print_r($oRes);
    +	echo "
    \n"; + echo "

    \n"; +} +catch(SoapFault $e) +{ + echo "

    SoapFault Exception: {$e->getMessage()}

    \n"; + echo "

    Request

    \n"; + echo "
    \n"; 
    +	echo htmlspecialchars($oSoapClient->__getLastRequest())."\n"; 
    +	echo "
    "; + echo "

    Response

    "; + echo $oSoapClient->__getLastResponse()."\n"; +} +?> diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index e17163ed0d..5595f7113a 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -223,6 +223,14 @@ class WebServices protected function LogUsage($sVerb, $oRes) { $oLog = new EventWebService(); + if ($oRes->IsOk()) + { + $oLog->Set('message', $sVerb.' was successfully invoked'); + } + else + { + $oLog->Set('message', $sVerb.' returned errors'); + } $oLog->Set('userinfo', UserRights::GetUser()); $oLog->Set('verb', $sVerb); $oLog->Set('result', $oRes->IsOk()); @@ -233,6 +241,29 @@ class WebServices $oLog->DBInsertNoReload(); } + /** + * Helper to set a scalar attribute + * + * @param string sAttCode + * @param scalar value + * @param DBObject oTargetObj + * @param WebServiceResult oRes + * + */ + protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) + { + if ($oTargetObj->CheckValue($sAttCode, $value)) + { + $oTargetObj->Set($sAttCode, $value); + } + else + { + $aAllowedValues = MetaModel::GetAllowedValues_att(get_class($oTargetObj), $sAttCode); + $sValues = implode(', ', $aAllowedValues); + $oRes->LogError("Parameter $sParamName: found '$value' while expecting a value in {".$sValues."}"); + } + } + /** * Helper to set an external key * @@ -242,14 +273,28 @@ class WebServices * @param WebServiceResult oRes * */ - protected function SetExternalKey($sAttCode, $aExtKeyDesc, &$oTargetObj, &$oRes) + protected function MyObjectSetExternalKey($sAttCode, $sParamName, $aExtKeyDesc, &$oTargetObj, &$oRes) { $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); $bIsMandatory = !$oExtKey->IsNullAllowed(); + + if (is_null($aExtKeyDesc)) + { + if ($bIsMandatory) + { + $oRes->LogError("Parameter $sParamName: found null for a mandatory key"); + } + else + { + // skip silently + return; + } + } + if (count($aExtKeyDesc) == 0) { - $oRes->LogIssue("Ext key $sAttCode: no data was given to give a value to the key", $bIsMandatory); + $oRes->LogIssue("Parameter $sParamName: no search condition has been specified", $bIsMandatory); return; } @@ -259,7 +304,8 @@ class WebServices { if (!MetaModel::IsValidFilterCode($sKeyClass, $sForeignAttCode)) { - $sMsg = "Ext key $sAttCode: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass'"; + $aCodes = array_keys(MetaModel::GetClassFilterDefs($sKeyClass)); + $sMsg = "Parameter $sParamName: '$sForeignAttCode' is not a valid filter code for class '$sKeyClass', expecting a value in {".implode(', ', $aCodes)."}"; $oRes->LogIssue($sMsg, $bIsMandatory); } // The foreign attribute is one of our reconciliation key @@ -269,7 +315,7 @@ class WebServices switch($oExtObjects->Count()) { case 0: - $sMsg = "External key $sAttCode could not be found (searched: '".$oReconFilter->ToOQL()."')"; + $sMsg = "Parameter $sParamName: no match (searched: '".$oReconFilter->ToOQL()."')"; $oRes->LogIssue($sMsg, $bIsMandatory); break; case 1: @@ -280,11 +326,11 @@ class WebServices // Report it (no need to report if the object already had this value if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { - $oRes->LogInfo("$sAttCode has been set to ".$oForeignObj->GetKey()); + $oRes->LogInfo("Parameter $sParamName: found match ".get_class($oForeignObj)."::".$oForeignObj->GetKey()." '".$oForeignObj->GetName()."'"); } break; default: - $sMsg = "Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')"; + $sMsg = "Parameter $sParamName: Found ".$oExtObjects->Count()." matches (searched: '".$oReconFilter->ToOQL()."')"; $oRes->LogIssue($sMsg, $bIsMandatory); } } @@ -300,7 +346,7 @@ class WebServices * * @return array List of objects that could not be found */ - protected function AddLinkedObjects($sLinkAttCode, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) + protected function AddLinkedObjects($sLinkAttCode, $sParamName, $sLinkedClass, $aLinkList, &$oTargetObj, &$oRes) { $oLinkAtt = MetaModel::GetAttributeDef(get_class($oTargetObj), $sLinkAttCode); $sLinkClass = $oLinkAtt->GetLinkedClass(); @@ -308,22 +354,28 @@ class WebServices $aItemsFound = array(); $aItemsNotFound = array(); + + if (is_null($aLinkList)) + { + return $aItemsNotFound; + } + foreach ($aLinkList as $aItemData) { if (!array_key_exists('class', $aItemData)) { - $oRes->LogWarning("Linked object descriptor: missing 'class' specification"); + $oRes->LogWarning("Parameter $sParamName: missing 'class' specification"); continue; // skip } $sTargetClass = $aItemData['class']; if (!MetaModel::IsValidClass($sTargetClass)) { - $oRes->LogError("Invalid class $sTargetClass for impacted item"); + $oRes->LogError("Parameter $sParamName: invalid class '$sTargetClass'"); continue; // skip } if (!MetaModel::IsParentClass($sLinkedClass, $sTargetClass)) { - $oRes->LogError("$sTargetClass is not a child class of $sLinkedClass"); + $oRes->LogError("Parameter $sParamName: '$sTargetClass' is not a child class of '$sLinkedClass'"); continue; // skip } $oReconFilter = new CMDBSearchFilter($sTargetClass); @@ -332,8 +384,9 @@ class WebServices { if (!MetaModel::IsValidFilterCode($sTargetClass, $sAttCode)) { - $oRes->LogError("Invalid filter code $sAttCode for class $sTargetClass"); - continue; // skip + $aCodes = array_keys(MetaModel::GetClassFilterDefs($sTargetClass)); + $oRes->LogError("Parameter $sParamName: '$sAttCode' is not a valid filter code for class '$sTargetClass', expecting a value in {".implode(', ', $aCodes)."}"); + continue 2; // skip the entire item } $aCIStringDesc[] = "$sAttCode: $value"; @@ -355,7 +408,7 @@ class WebServices switch($oExtObjects->Count()) { case 0: - $oRes->LogWarning("Object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL()."')"); + $oRes->LogWarning("Parameter $sParamName: object to link $sLinkedClass / $sItemDesc could not be found (searched: '".$oReconFilter->ToOQL()."')"); $aItemsNotFound[] = $sItemDesc; break; case 1: @@ -366,7 +419,7 @@ class WebServices ); break; default: - $oRes->LogWarning("Found ".$oExtObjects->Count()." matches for external key $sAttCode (searched: '".$oReconFilter->ToOQL()."')"); + $oRes->LogWarning("Parameter $sParamName: Found ".$oExtObjects->Count()." matches for item '$sItemDesc' (searched: '".$oReconFilter->ToOQL()."')"); $aItemsNotFound[] = $sItemDesc; } } @@ -382,7 +435,7 @@ class WebServices { if(!MetaModel::IsValidAttCode($sLinkClass, $sKey)) { - $oRes->LogWarning("Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); + $oRes->LogWarning("Parameter $sParamName: Attaching item '".$aItemData['desc']."', the attribute code '$sKey' is not valid ; check the class '$sLinkClass'"); } else { @@ -398,9 +451,28 @@ class WebServices return $aItemsNotFound; } + protected function MyObjectInsert($oTargetObj, $sResultLabel, $oChange, &$oRes) + { + if ($oRes->IsOk()) + { + if ($oTargetObj->CheckToInsert()) + { + $iId = $oTargetObj->DBInsertTrackedNoReload($oChange); + $oRes->LogInfo("Created object ".get_class($$oTargetObj)."::$iId"); + $oRes->AddResultObject($sResultLabel, $oTargetObj); + } + else + { + $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); + } + } + } + static protected function SoapStructToExternalKeySearch(SoapExternalKeySearch $oExternalKeySearch) { + if (is_null($oExternalKeySearch)) return null; + $aRes = array(); foreach($oExternalKeySearch->conditions as $oSearchCondition) { @@ -504,36 +576,26 @@ class WebServices $iChangeId = $oMyChange->DBInsertNoReload(); $oNewTicket = MetaModel::NewObject('bizIncidentTicket'); - $oNewTicket->Set('type', $sType); - $oNewTicket->Set('title', $sDescription); - $oNewTicket->Set('initial_situation', $sInitialSituation); - $oNewTicket->Set('severity', $sSeverity); + $this->MyObjectSetScalar('type', 'type', $sType, $oNewTicket, $oRes); + $this->MyObjectSetScalar('title', 'title', $sDescription, $oNewTicket, $oRes); + $this->MyObjectSetScalar('initial_situation', 'initialsituation', $sInitialSituation, $oNewTicket, $oRes); + $this->MyObjectSetScalar('severity', 'severity', $sSeverity, $oNewTicket, $oRes); - $this->SetExternalKey('org_id', $aCustomerDesc, $oNewTicket, $oRes); - $this->SetExternalKey('caller_id', $aCallerDesc, $oNewTicket, $oRes); - $this->SetExternalKey('workgroup_id', $aWorkgroupDesc, $oNewTicket, $oRes); + $this->MyObjectSetExternalKey('org_id', 'customer', $aCustomerDesc, $oNewTicket, $oRes); + $this->MyObjectSetExternalKey('caller_id', 'caller', $aCallerDesc, $oNewTicket, $oRes); + $this->MyObjectSetExternalKey('workgroup_id', 'workgroup', $aWorkgroupDesc, $oNewTicket, $oRes); - $aDevicesNotFound = $this->AddLinkedObjects('impacted_infra_manual', 'logInfra', $aImpactedCIs, $oNewTicket, $oRes); + $aDevicesNotFound = $this->AddLinkedObjects('impacted_infra_manual', 'impacted_cis', 'logInfra', $aImpactedCIs, $oNewTicket, $oRes); if (count($aDevicesNotFound) > 0) { - $oNewTicket->Set('impact', $sImpact.' - Related CIs: '.implode(', ', $aDevicesNotFound)); + $this->MyObjectSetScalar('impact', 'n/a', $sImpact.' - Related CIs: '.implode(', ', $aDevicesNotFound), $oNewTicket, $oRes); } else { - $oNewTicket->Set('impact', $sImpact); - } - - if (!$oNewTicket->CheckToInsert()) - { - $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); - } - - if ($oRes->IsOk()) - { - $iId = $oNewTicket->DBInsertTrackedNoReload($oMyChange); - $oRes->LogInfo("Created ticket #$iId"); - $oRes->AddResultObject('created', $oNewTicket); + $this->MyObjectSetScalar('impact', 'n/a', $sImpact, $oNewTicket, $oRes); } + + $this->MyObjectInsert($oNewTicket, 'created', $oMyChange, $oRes); } catch (CoreException $e) { From cf92f5df2d8494cc4cca74549eb8054316c63796 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 13 Jan 2010 16:14:45 +0000 Subject: [PATCH 170/970] Profiles reviewed (+ secured the setup procedure against wrong class names) SVN:trunk[247] --- .../userrightsprofile.class.inc.php | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index d6c33e24ce..c3455ac6fb 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -557,7 +557,7 @@ class URP_ClassProjection extends UserRightsBaseClass MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"Target class", "allowed_values"=>null, "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("label"=>"Class", "description"=>"Target class", "class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant | | +attribute code", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -642,7 +642,7 @@ class URP_ActionGrant extends UserRightsBaseClass // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("label"=>"Class", "description"=>"Target class", "class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"Permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"Action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -691,7 +691,7 @@ class URP_StimulusGrant extends UserRightsBaseClass // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"Class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("label"=>"Class", "description"=>"Target class", "class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"Permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"Stimulus", "description"=>"stimulus code", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -1398,7 +1398,7 @@ class SetupITILProfiles 'bizOrganization', ), 'Documentation' => array( - 'bizDocVersion', + 'bizDocument', 'lnkDocumentRealObject', 'lnkDocumentContract', 'lnkDocumentError', @@ -1406,8 +1406,8 @@ class SetupITILProfiles 'Configuration' => array( 'logRealObject', 'lnkContactRealObject', - 'lnkInterfaces', - 'ClientServerLinks', +// 'lnkInterfaces', + 'lnkClientServer', 'lnkInfraGrouping', ), 'Incident' => array( @@ -1464,7 +1464,7 @@ class SetupITILProfiles */ 'Service Desk Agent' => array( 'description' => 'Person in charge of creating incident reports', - 'write_modules' => 'Documentation,Incident,Call', + 'write_modules' => 'Incident,Call', 'stimuli' => array( 'bizServer' => 'none', 'bizContract' => 'none', @@ -1475,7 +1475,7 @@ class SetupITILProfiles ), 'Support Agent' => array( 'description' => 'Person analyzing and solving the current incidents or problems', - 'write_modules' => 'Documentation,Incident,Problem', + 'write_modules' => 'Incident,Problem', 'stimuli' => array( 'bizIncidentTicket' => 'any', //'bizIncidentTicket' => 'ev_assign,ev_reassign,ev_start_working,ev_close', @@ -1483,7 +1483,7 @@ class SetupITILProfiles ), 'Change Implementor' => array( 'description' => 'Person executing the changes', - 'write_modules' => 'Documentation,Configuration,Change', + 'write_modules' => 'Change', 'stimuli' => array( 'bizServer' => 'none', 'bizContract' => 'none', @@ -1493,7 +1493,7 @@ class SetupITILProfiles ), 'Change Supervisor' => array( 'description' => 'Person responsible for the overall change execution', - 'write_modules' => 'Documentation,Change', + 'write_modules' => 'Change', 'stimuli' => array( 'bizServer' => 'none', 'bizContract' => 'none', @@ -1503,7 +1503,7 @@ class SetupITILProfiles ), 'Change Approver' => array( 'description' => 'Person who could be impacted by some changes', - 'write_modules' => 'Documentation,Change', + 'write_modules' => 'Change', 'stimuli' => array( 'bizServer' => 'none', 'bizContract' => 'none', @@ -1513,7 +1513,7 @@ class SetupITILProfiles ), 'Service Manager' => array( 'description' => 'Person responsible for the service delivered to the [internal] customer', - 'write_modules' => 'Documentation,Service', + 'write_modules' => 'Service', 'stimuli' => array( 'bizServer' => 'none', 'bizContract' => 'any', @@ -1522,6 +1522,12 @@ class SetupITILProfiles 'bizChangeTicket' => 'none', ), ), + 'Document author' => array( + 'description' => 'Any person who could contribute to documentation', + 'write_modules' => 'Documentation', + 'stimuli' => array( + ), + ), ); protected static function DoCreateClassProjection($iDimension, $sClass) @@ -1632,6 +1638,10 @@ class SetupITILProfiles } foreach ($aWriteableClasses as $sClass) { + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } self::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass); self::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass); self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass); @@ -1641,6 +1651,11 @@ class SetupITILProfiles // Grant stimuli for given classes foreach ($aStimuli as $sClass => $sAllowedStimuli) { + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } + if ($sAllowedStimuli == 'any') { $aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass)); @@ -1669,6 +1684,10 @@ class SetupITILProfiles foreach($aClass as $sClass) { + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } self::DoCreateClassProjection($iDimension, $sClass); } } From e621c65910d19d732f42082e29b6952765f7aadf Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 13 Jan 2010 17:36:35 +0000 Subject: [PATCH 171/970] Changes on the data model SVN:trunk[248] --- .../userrightsprofile.class.inc.php | 2 +- business/ChangeMgmt.business.php | 26 ++++--- business/ServiceMgmt.business.php | 2 +- business/itop.business.class.inc.php | 71 +++---------------- 4 files changed, 30 insertions(+), 71 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index c3455ac6fb..396766dba8 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -1498,7 +1498,7 @@ class SetupITILProfiles 'bizServer' => 'none', 'bizContract' => 'none', 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'ev_validate,ev_reject,ev_reopen,ev_finish', + 'bizChangeTicket' => 'ev_assign,ev_validate,ev_reject,ev_reopen,ev_finish', ), ), 'Change Approver' => array( diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index 415ce72a3a..e7ad10ffcd 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -32,13 +32,13 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason For Change", "description"=>"Reason for the Change", "allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("requestor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Requestor", "description"=>"who is requesting this change", "allowed_values"=>null, "sql"=>"requestor_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("requestor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Requestor", "description"=>"who is requesting this change", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"requestor_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("requestor_mail", array("label"=>"Requestor", "description"=>"mail of user requesting this change", "allowed_values"=>null, "extkey_attcode"=> 'requestor_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Validated,Rejected,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Validated,Rejected,Assigned,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("label"=>"Creation Date", "description"=>"Change creation date", "allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -50,17 +50,17 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Risk Assessment", "description"=>"Impact of the change", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"name of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisorgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Supervisor Group", "description"=>"which workgroup is supervising ticket", "allowed_values"=>null, "sql"=>"supervisorgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("supervisorgroup_name", array("label"=>"Supervisor Group", "description"=>"name of the group supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisorgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Supervisor", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"supervisor_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('supervisorgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Supervisor", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->supervisorgroup_id'), "sql"=>"supervisor_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('supervisorgroup_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("supervisor_mail", array("label"=>"Supervisor", "description"=>"name of agent supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisor_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("managergroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Manager Group", "description"=>"which workgroup is approving ticket", "allowed_values"=>null, "sql"=>"managergroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("managergroup_name", array("label"=>"Manager Group", "description"=>"name of workgroup approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'managergroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Manager", "description"=>"who is approving the ticket", "allowed_values"=>null, "sql"=>"manager_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('managergroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Manager", "description"=>"who is approving the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->managergroup_id'), "sql"=>"manager_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('managergroup_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("manager_mail", array("label"=>"Manager", "description"=>"name of agent approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'manager_id', "target_attcode"=>"email"))); MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("label"=>"Planned Outage", "description"=>"Flag to define if there is a planned outage", "allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -109,12 +109,16 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_MANDATORY, 'title' => OPT_ATT_MANDATORY, 'reason' => OPT_ATT_MANDATORY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_HIDDEN, 'supervisorgroup_id' => OPT_ATT_HIDDEN, 'managergroup_id' => OPT_ATT_HIDDEN, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN ))); - MetaModel::Init_DefineState("Validated", array("label"=>"Validated", "description"=>"Ticket is approved", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Validated", array("label"=>"Validated", "description"=>"Ticket is validated", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY, 'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, - 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY, 'managergroup_id' => OPT_ATT_MANDATORY, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN))); + 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_MUSTCHANGE, 'supervisorgroup_id' => OPT_ATT_MUSTCHANGE, 'managergroup_id' => OPT_ATT_MUSTCHANGE, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN))); MetaModel::Init_DefineState("Rejected", array("label"=>"Rejected", "description"=>"This ticket is not approved", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); - MetaModel::Init_DefineState("PlannedScheduled", array("label"=>"Planned&Scheduled", "description"=>"Evaluation is done for this change", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Ticket is assigned", "attribute_inherit"=>null, + "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY, 'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, + 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY, 'managergroup_id' => OPT_ATT_MANDATORY, 'supervisor_id' => OPT_ATT_MUSTCHANGE, 'manager_id' => OPT_ATT_MUSTCHANGE, 'agent_id' => OPT_ATT_MUSTCHANGE))); + + MetaModel::Init_DefineState("PlannedScheduled", array("label"=>"Planned&Scheduled", "description"=>"Evaluation is done for this change", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_MANDATORY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_MANDATORY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_MANDATORY, 'manager_id' => OPT_ATT_MANDATORY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); MetaModel::Init_DefineState("Approved", array("label"=>"Approved", "description"=>"Ticket is approved by CAB", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); @@ -128,6 +132,8 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_DefineStimulus("ev_validate", new StimulusUserAction(array("label"=>"Validate this change", "description"=>"Make sure it is a valid change request"))); MetaModel::Init_DefineStimulus("ev_reject", new StimulusUserAction(array("label"=>"Reject this change", "description"=>"This change request is rejected because it is a non valid one"))); + MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this change", "description"=>"This change request is assigned"))); + MetaModel::Init_DefineStimulus("ev_reopen", new StimulusUserAction(array("label"=>"Modify this change", "description"=>"Update change request to make it valid"))); MetaModel::Init_DefineStimulus("ev_plan", new StimulusUserAction(array("label"=>"Plan this change", "description"=>"Plan and Schedule this change for validation"))); MetaModel::Init_DefineStimulus("ev_approve", new StimulusUserAction(array("label"=>"Approve this change", "description"=>"This change is approved by CAB"))); @@ -140,7 +146,9 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_DefineTransition("New", "ev_validate", array("target_state"=>"Validated", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("New", "ev_reject", array("target_state"=>"Rejected", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Rejected", "ev_reopen", array("target_state"=>"New", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("Validated", "ev_plan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Validated", "ev_assign", array("target_state"=>"Assigned", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("Assigned", "ev_plan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("PlannedScheduled", "ev_approve", array("target_state"=>"Approved", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("PlannedScheduled", "ev_notapprove", array("target_state"=>"NotApproved", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("NotApproved", "ev_replan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index 68f6f91d0d..dd8865a055 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -22,7 +22,7 @@ class bizService extends cmdbAbstractObject "db_table" => "services", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "../business/templates/default.html", + "display_template" => "../business/templates/service.html", ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index e870ec4f4a..a13f76f31e 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -202,7 +202,7 @@ class bizContact extends logRealObject MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department of the contact", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeEmailAddress("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("phone", array("label"=>"Phone", "description"=>"Telephone", "allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"Id of the location where the contact is located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"Id of the location where the contact is located", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"Name of the location where the contact is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); MetaModel::Init_InheritFilters(); @@ -750,16 +750,16 @@ class bizCircuit extends logInfra MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"speed", "description"=>"speed of the circuit", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location1_id", array("targetclass"=>"bizLocation", "label"=>"Location 1", "description"=>"Id of the location 1", "allowed_values"=>null, "sql"=>"location1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location1_id", array("targetclass"=>"bizLocation", "label"=>"Location 1", "description"=>"Id of the location 1", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("location1_name", array("label"=>"Location 1", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location1_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location2_id", array("targetclass"=>"bizLocation", "label"=>"Location 2", "description"=>"Id of the location 2", "allowed_values"=>null, "sql"=>"location2_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL,"depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location2_id", array("targetclass"=>"bizLocation", "label"=>"Location 2", "description"=>"Id of the location 2", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location2_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL,"depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("location2_name", array("label"=>"Location 2", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location2_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface 1", "description"=>"id of the interface 1", "allowed_values"=>null, "sql"=>"interface1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface 1", "description"=>"id of the interface 1", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS p WHERE p.org_id = :this->org_id'), "sql"=>"interface1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("label"=>"Interface", "description"=>"Name of the interface 1", "allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalField("device1_name", array("label"=>"Device 1", "description"=>"Name of the device 1", "allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"device_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface 2", "description"=>"id of the interface 2", "allowed_values"=>null, "sql"=>"interface2_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface 2", "description"=>"id of the interface 2", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS p WHERE p.org_id = :this->org_id'), "sql"=>"interface2_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("label"=>"Interface", "description"=>"Name of the interface 2", "allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalField("device2_name", array("label"=>"Interface", "description"=>"Name of the device 2", "allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"device_name"))); @@ -901,56 +901,6 @@ class bizInterface extends logInfra } } -//////////////////////////////////////////////////////////////////////////////////// -/** -* n-n link between any Interfaces -*/ -//////////////////////////////////////////////////////////////////////////////////// -/* -class lnkInterfaces extends cmdbAbstractObject -{ - public static function Init() - { - $aParams = array - ( - "category" => "bizmodel,searchable", - "name" => "InterfacesLinks", - "description" => "A link between two interfaces", - "key_type" => "autoincrement", - "key_label" => "link_id", - "name_attcode" => "link_type", - "state_attcode" => "", - "reconc_keys" => array("interface1_id", "interface2_id"), - "db_table" => "interfaces_links", - "db_key_field" => "link_id", - "db_finalclass_field" => "", - "display_template" => "../business/templates/default.html", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface", "description"=>"The interface1", "sql"=>"interface1_id", "allowed_values"=> null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("label"=>"Interface1 Name", "description"=>"name of the interface1", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface", "description"=>"The interface2", "sql"=>"interface2_id", "allowed_values"=> null, "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("label"=>"Interface2 Name", "description"=>"name of the interface2", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_device_id", array("label"=>"Device", "description"=>"device", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"device_id"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_device_name", array("label"=>"Device Name", "description"=>"name of the device", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"device_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_device_id", array("label"=>"Device", "description"=>"device", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"device_id"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_device_name", array("label"=>"Device Name", "description"=>"name of the device", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"device_name"))); - MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"Link Type", "description"=>"More information", "sql"=>"link_type", "default_value"=>"", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array()))); - - MetaModel::Init_AddFilterFromAttribute("interface1_id"); - MetaModel::Init_AddFilterFromAttribute("interface1_name"); - MetaModel::Init_AddFilterFromAttribute("interface2_id"); - MetaModel::Init_AddFilterFromAttribute("interface2_name"); - MetaModel::Init_AddFilterFromAttribute("interface2_device_name"); - MetaModel::Init_AddFilterFromAttribute("link_type"); - - // Display lists - MetaModel::Init_SetZListItems('details', array('interface1_id', 'interface2_id', 'link_type')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('interface1_id', 'interface1_device_id', 'interface2_id', 'interface2_device_id', 'link_type')); // Attributes to be displayed for the complete details - } -} -*/ - //////////////////////////////////////////////////////////////////////////////////// /** * A subnet @@ -1060,13 +1010,13 @@ class bizDevice extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"where is the located object physically located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"where is the located object physically located", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalField("country", array("label"=>"Country", "description"=>"country where the device is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"country"))); MetaModel::Init_AddAttribute(new AttributeString("brand", array("label"=>"Brand", "description"=>"The manufacturer of the device", "allowed_values"=>null, "sql"=>"brand", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"The model number of the device", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("serial_number", array("label"=>"Serial Number", "description"=>"The serial number of the device", "allowed_values"=>null, "sql"=>"serial_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("location_id"); @@ -1083,7 +1033,8 @@ class bizDevice extends logInfra { case "impacts": $aRels = array( - "connected device" => array("sQuery"=>"bizDevice: PKEY IS device_id IN (bizInterface: PKEY IS interface2_id IN (lnkInterfaces: interface1_id IN (bizInterface: device_id = \$[this.pkey::])))", "bPropagate"=>true, "iDistance"=>3), +// commented out, because lnkInterfaces does not exist anymore +// "connected device" => array("sQuery"=>"bizDevice: PKEY IS device_id IN (bizInterface: PKEY IS interface2_id IN (lnkInterfaces: interface1_id IN (bizInterface: device_id = \$[this.pkey::])))", "bPropagate"=>true, "iDistance"=>3), "hosted app" => array("sQuery"=>"bizApplication: infra_id = \$[this.pkey::]", "bPropagate"=>true, "iDistance"=>3), ); return array_merge($aRels, parent::GetRelationQueries($sRelCode)); @@ -1548,7 +1499,7 @@ class bizApplication extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Hosting device", "description"=>"The device where application is installed", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Hosting device", "description"=>"The device where application is installed", "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Hosting device", "description"=>"Name of the device where application is installed", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installation Date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -1735,7 +1686,7 @@ class bizPatch extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Device", "description"=>"The Device where patch is installed", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Device", "description"=>"The Device where patch is installed", "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device Name", "description"=>"Name of the impacted device", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installation Date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); From 7e35b6c3718d43f87a985554d8c4b047207c1a8c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 14 Jan 2010 12:27:45 +0000 Subject: [PATCH 172/970] Setup page: added a scroll bar and grouped some warnings into one message that would fit on two lines SVN:trunk[249] --- setup/index.php | 6 ++---- setup/setuppage.class.inc.php | 13 +++++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/setup/index.php b/setup/index.php index 03e49d5d9e..2bc1f91186 100644 --- a/setup/index.php +++ b/setup/index.php @@ -153,15 +153,13 @@ function CheckServerConnection(setup_web_page $oP, $sDBServer, $sDBUser, $sDBPwd } else if($iMaxAllowedPacket < $iMaxUploadSize) { - $oP->warning("MySQL server's max_allowed_packet ($iMaxAllowedPacket) is not big enough."); - $oP->warning("Consider setting it to at least ".(500 + $iMaxUploadSize)."."); + $oP->warning("MySQL server's max_allowed_packet ($iMaxAllowedPacket) is not big enough. Please, consider setting it to at least ".(500 + $iMaxUploadSize)."."); } $oP->log("Info - MySQL max_allowed_packet: $iMaxAllowedPacket"); $iMaxConnections = $oDBSource->GetServerVariable('max_connections'); if ($iMaxConnections < 5) { - $oP->warning("MySQL server's max_connections ($iMaxConnections) is not enough."); - $oP->warning("Consider setting it to at least 5."); + $oP->warning("MySQL server's max_connections ($iMaxConnections) is not enough. Please, consider setting it to at least 5."); } $oP->log("Info - MySQL max_connections: ".($oDBSource->GetServerVariable('max_connections'))); } diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index 4ed7aade4b..51a12ed493 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -18,6 +18,7 @@ body { margin: 0; padding: 0; font-size: 10pt; + overflow-y: auto; } #setup { width: 600px; @@ -50,27 +51,23 @@ button { } p.info { padding-left: 50px; - background: url(../images/info-mid.png) no-repeat top left; + background: url(../images/info-mid.png) no-repeat left -5px; height: 48px; - line-height: 48px; } p.ok { padding-left: 50px; - background: url(../images/clean-mid.png) no-repeat top left; + background: url(../images/clean-mid.png) no-repeat left -8px; height: 48px; - line-height: 48px; } p.warning { padding-left: 50px; - background: url(../images/messagebox_warning-mid.png) no-repeat top left; + background: url(../images/messagebox_warning-mid.png) no-repeat left -5px; height: 48px; - line-height: 48px; } p.error { padding-left: 50px; - background: url(../images/stop-mid.png) no-repeat top left; + background: url(../images/stop-mid.png) no-repeat left -5px; height: 48px; - line-height: 48px; } td.label { text-align: left; From 0a413a32f6f2e4ff86cdf399de8dc9bee8f6c895 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 09:07:10 +0000 Subject: [PATCH 173/970] Renaming of the WebPage class (and its derived classes) to stick to the same naming convention as other classes. SVN:trunk[250] --- .../userrightsprofile.class.inc.php | 4 +-- application/ajaxwebpage.class.inc.php | 2 +- application/cmdbabstract.class.inc.php | 32 +++++++++---------- application/csvpage.class.inc.php | 2 +- application/displayblock.class.inc.php | 12 +++---- application/loginwebpage.class.inc.php | 6 ++-- application/menunode.class.inc.php | 4 +-- application/nicewebpage.class.inc.php | 2 +- application/template.class.inc.php | 2 +- application/ui.linkswidget.class.inc.php | 8 ++--- application/uilinkswizard.class.inc.php | 12 +++---- application/webpage.class.inc.php | 4 +-- application/xmlpage.class.inc.php | 2 +- business/itop.business.class.inc.php | 14 ++++---- pages/UI.php | 2 +- pages/advanced_search.php | 2 +- pages/ajax.php | 2 +- pages/ajax.render.php | 4 +-- pages/audit.php | 2 +- pages/csvimport.php | 2 +- pages/data_generator.php | 6 ++-- pages/db_importer.php | 2 +- pages/incident.php | 20 ++++++------ pages/index.php | 20 ++++++------ pages/run_query.php | 2 +- pages/schema.php | 2 +- pages/usermanagement_classproj.php | 2 +- pages/usermanagement_profileproj.php | 2 +- pages/usermanagement_userstatus.php | 2 +- setup/ajax.dataloader.php | 10 +++--- setup/index.php | 26 +++++++-------- setup/setuppage.class.inc.php | 2 +- setup/xmldataloader.class.inc.php | 2 +- webservices/export.php | 10 +++--- webservices/import.php | 4 +-- 35 files changed, 116 insertions(+), 116 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 396766dba8..f65b15145f 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -148,7 +148,7 @@ class URP_Users extends UserRightsBaseClass $oPage->table($aDisplayConfig, $aDisplayData); } - function DisplayBareRelations(web_page $oPage) + function DisplayBareRelations(WebPage $oPage) { parent::DisplayBareRelations($oPage); @@ -272,7 +272,7 @@ class URP_Profiles extends UserRightsBaseClass $oPage->table($aDisplayConfig, $aDisplayData); } - function DisplayBareRelations(web_page $oPage) + function DisplayBareRelations(WebPage $oPage) { parent::DisplayBareRelations($oPage); diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index fbacc00584..3c1ace5185 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -11,7 +11,7 @@ require_once("../application/webpage.class.inc.php"); * @license http://www.opensource.org/licenses/lgpl-license.php LGPL */ -class ajax_page extends web_page +class ajax_page extends WebPage { /** * Jquery style ready script diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 454cbd8e86..08617ed31a 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -116,7 +116,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sDisplayValue; } - function DisplayBareHeader(web_page $oPage) + function DisplayBareHeader(WebPage $oPage) { // Standard Header with name, actions menu and history block // @@ -140,12 +140,12 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("
    \n"); } - function DisplayBareDetails(web_page $oPage) + function DisplayBareDetails(WebPage $oPage) { $oPage->add($this->GetBareDetails($oPage)); } - function DisplayBareRelations(web_page $oPage) + function DisplayBareRelations(WebPage $oPage) { // Related objects $oPage->AddTabContainer('Related Objects'); @@ -197,7 +197,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sDisplayName; } - function GetBareDetails(web_page $oPage) + function GetBareDetails(WebPage $oPage) { $sHtml = ''; $oAppContext = new ApplicationContext(); @@ -244,7 +244,7 @@ abstract class cmdbAbstractObject extends CMDBObject } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { $sTemplate = Utils::ReadFromFile(MetaModel::GetDisplayTemplate(get_class($this))); if (!empty($sTemplate)) @@ -262,7 +262,7 @@ abstract class cmdbAbstractObject extends CMDBObject } } - function DisplayPreview(web_page $oPage) + function DisplayPreview(WebPage $oPage) { $aDetails = array(); $sClass = get_class($this); @@ -276,13 +276,13 @@ abstract class cmdbAbstractObject extends CMDBObject // Comment by Rom: this helper may be used to display objects of class DBObject // -> I am using this to display the changes history - public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { $oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams)); } - //public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false) - public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + //public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false) + public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { static $iListId = 0; $iListId++; @@ -418,7 +418,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sHtml; } - static function DisplaySetAsCSV(web_page $oPage, CMDBObjectSet $oSet, $aParams = array()) + static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $oPage->add(self::GetSetAsCSV($oSet, $aParams)); } @@ -455,7 +455,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sHtml; } - static function DisplaySetAsXML(web_page $oPage, CMDBObjectSet $oSet, $aParams = array()) + static function DisplaySetAsXML(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $oAppContext = new ApplicationContext(); $sClassName = $oSet->GetFilter()->GetClass(); @@ -480,7 +480,7 @@ abstract class cmdbAbstractObject extends CMDBObject } // By rom - function DisplayChangesLog(web_page $oPage) + function DisplayChangesLog(WebPage $oPage) { $oFltChangeOps = new CMDBSearchFilter('CMDBChangeOpSetAttribute'); $oFltChangeOps->AddCondition('objkey', $this->GetKey(), '='); @@ -498,13 +498,13 @@ abstract class cmdbAbstractObject extends CMDBObject } } - public static function DisplaySearchForm(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + public static function DisplaySearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { $oPage->add(self::GetSearchForm($oPage, $oSet, $aExtraParams)); } - public static function GetSearchForm(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { static $iSearchFormId = 0; $oAppContext = new ApplicationContext(); @@ -755,7 +755,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sHTMLValue; } - public function DisplayModifyForm(web_page $oPage) + public function DisplayModifyForm(WebPage $oPage) { static $iFormId = 0; $iFormId++; @@ -812,7 +812,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("\n"); } - public static function DisplayCreationForm(web_page $oPage, $sClass, $oObjectToClone = null, $aArgs = array()) + public static function DisplayCreationForm(WebPage $oPage, $sClass, $oObjectToClone = null, $aArgs = array()) { static $iCreationFormId = 0; diff --git a/application/csvpage.class.inc.php b/application/csvpage.class.inc.php index ed1fb724ba..2912f62a81 100644 --- a/application/csvpage.class.inc.php +++ b/application/csvpage.class.inc.php @@ -4,7 +4,7 @@ require_once("../application/webpage.class.inc.php"); * Simple web page with no includes or fancy formatting, useful to generateXML documents * The page adds the content-type text/XML and the encoding into the headers */ -class CSVPage extends web_page +class CSVPage extends WebPage { function __construct($s_title) { diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 42fea0d489..198bbebafc 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -149,7 +149,7 @@ class DisplayBlock return new $sBlockClass($oFilter, $sBlockType, $bAsynchronous, $aParams); } - public function Display(web_page $oPage, $sId, $aExtraParams = array()) + public function Display(WebPage $oPage, $sId, $aExtraParams = array()) { $oPage->add($this->GetDisplay($oPage, $sId, $aExtraParams)); /* @@ -184,7 +184,7 @@ class DisplayBlock */ } - public function GetDisplay(web_page $oPage, $sId, $aExtraParams = array()) + public function GetDisplay(WebPage $oPage, $sId, $aExtraParams = array()) { $sHtml = ''; $aExtraParams = array_merge($aExtraParams, $this->m_aParams); @@ -220,12 +220,12 @@ class DisplayBlock return $sHtml; } - public function RenderContent(web_page $oPage, $aExtraParams = array()) + public function RenderContent(WebPage $oPage, $aExtraParams = array()) { $oPage->add($this->GetRenderContent($oPage, $aExtraParams)); } - public function GetRenderContent(web_page $oPage, $aExtraParams = array()) + public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) { $sHtml = ''; // Add the extra params into the filter if they make sense for such a filter @@ -615,7 +615,7 @@ class DisplayBlock */ class HistoryBlock extends DisplayBlock { - public function GetRenderContent(web_page $oPage, $aExtraParams = array()) + public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) { $sHtml = ''; // Add the extra params into the filter if they make sense for such a filter @@ -689,7 +689,7 @@ class HistoryBlock extends DisplayBlock class MenuBlock extends DisplayBlock { - public function GetRenderContent(web_page $oPage, $aExtraParams = array()) + public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) { $sHtml = ''; $oAppContext = new ApplicationContext(); diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 32a330be04..ae8ae075d4 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -3,7 +3,7 @@ require_once("../application/nicewebpage.class.inc.php"); /** * Web page used for displaying the login form */ -class login_web_page extends nice_web_page +class LoginWebPage extends NiceWebPage { public function __construct() { @@ -96,7 +96,7 @@ h1 { } else { - $oPage = new login_web_page(); + $oPage = new LoginWebPage(); $oPage->DisplayLoginForm(); $oPage->output(); exit; @@ -110,7 +110,7 @@ h1 { if (!UserRights::Login($sAuthUser, $sAuthPwd)) { self::ResetSession(); - $oPage = new login_web_page(); + $oPage = new LoginWebPage(); $oPage->DisplayLoginForm( true /* failed attempt */); $oPage->output(); exit; diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 19b54b2452..943bd393cc 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -113,7 +113,7 @@ class menuNode extends DBObject return $oSet; } - public function RenderContent(web_page $oPage, $aExtraParams = array()) + public function RenderContent(WebPage $oPage, $aExtraParams = array()) { $sTemplate = $this->Get('template'); $oTemplate = new DisplayTemplate($sTemplate); @@ -136,7 +136,7 @@ class menuNode extends DBObject } $oP->AddToMenu("\n"); } - static public function DisplayCreationForm(web_page $oP, $sClass, $sFilter, $aExtraParams = array()) + static public function DisplayCreationForm(WebPage $oP, $sClass, $sFilter, $aExtraParams = array()) { $oFilter = DBObjectSearch::unserialize($sFilter); $oP->p('Create a new menu item for: '.$oFilter->__DescribeHTML()); diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index 63cfd4cc8d..e8e48e07ce 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -3,7 +3,7 @@ require_once("../application/webpage.class.inc.php"); /** * Web page with some associated CSS and scripts (jquery) for a fancier display */ -class nice_web_page extends web_page +class NiceWebPage extends WebPage { var $m_aReadyScripts; diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 4ebfdf4294..807ffdf301 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -15,7 +15,7 @@ class DisplayTemplate $this->m_sTemplate = $sTemplate; } - public function Render(web_page $oPage, $aParams = array()) + public function Render(WebPage $oPage, $aParams = array()) { $this->m_sTemplate = MetaModel::ApplyParams($this->m_sTemplate, $aParams); $iStart = 0; diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 8100f9ee8e..aa6b476842 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -17,7 +17,7 @@ class UILinksWidget $this->m_iInputId = $iInputId; } - public function Display(web_page $oPage, $oCurrentValuesSet = null) + public function Display(WebPage $oPage, $oCurrentValuesSet = null) { $sHTMLValue = ''; $sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode); @@ -122,7 +122,7 @@ class UILinksWidget } /** * This static function is called by the Ajax Page when there is a need to fill an autocomplete combo - * @param $oPage web_page The ajax page used for the put^put (sent back to the browser + * @param $oPage WebPage The ajax page used for the put^put (sent back to the browser * @param $oContext UserContext The context of the user (for limiting the search) * @param $sClass string The name of the class of the current object being edited * @param $sAttCode string The name of the attribute being edited @@ -130,7 +130,7 @@ class UILinksWidget * @param $iMaxCount integer The maximum number of items to return * @return void */ - static public function Autocomplete(web_page $oPage, UserContext $oContext, $sClass, $sAttCode, $sName, $iMaxCount) + static public function Autocomplete(WebPage $oPage, UserContext $oContext, $sClass, $sAttCode, $sName, $iMaxCount) { // #@# todo - add context information, otherwise any value will be authorized for external keys $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array() /* $aArgs */, $sName); @@ -166,7 +166,7 @@ class UILinksWidget /** * This static function is called by the Ajax Page display a set of objects being linked * to the object being created - * @param $oPage web_page The ajax page used for the put^put (sent back to the browser + * @param $oPage WebPage The ajax page used for the put^put (sent back to the browser * @param $sClass string The name of the 'linking class' which is the class of the objects to display * @param $sSet JSON serialized set of objects * @param $sExtKeyToMe Name of the attribute in sClass that is pointing to a given object diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index 3779efac12..ede866c453 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -55,7 +55,7 @@ class UILinksWizard } } - public function Display(web_page $oP, UserContext $oContext, $aExtraParams = array()) + public function Display(WebPage $oP, UserContext $oContext, $aExtraParams = array()) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); $sTargetClass = $oAttDef->GetTargetClass(); @@ -295,7 +295,7 @@ class UILinksWizard return $aRow; } - protected function DisplayFormTable(web_page $oP, $aConfig, $aData) + protected function DisplayFormTable(WebPage $oP, $aConfig, $aData) { $oP->add("\n"); // Header @@ -327,7 +327,7 @@ class UILinksWizard $oP->add("
    \n"); } - protected function DisplayFormRow(web_page $oP, $aConfig, $aRow, $iRowId) + protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId) { $oP->add("\n"); foreach($aConfig as $sName=>$void) @@ -337,7 +337,7 @@ class UILinksWizard $oP->add("\n"); } - public function DisplayAddForm(web_page $oP, UserContext $oContext) + public function DisplayAddForm(WebPage $oP, UserContext $oContext) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); $sTargetClass = $oAttDef->GetTargetClass(); @@ -361,7 +361,7 @@ class UILinksWizard $oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit', function() {var the_form = this; SearchObjectsToAdd(the_form.id); return false;});"); } - public function SearchObjectsToAdd(web_page $oP, UserContext $oContext) + public function SearchObjectsToAdd(WebPage $oP, UserContext $oContext) { //$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); @@ -371,7 +371,7 @@ class UILinksWizard $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results } - public function DoAddObjects(web_page $oP, UserContext $oContext, $aLinkedObjectIds = array()) + public function DoAddObjects(WebPage $oP, UserContext $oContext, $aLinkedObjectIds = array()) { //$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); //$sTargetClass = $oAttDef->GetTargetClass(); diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index b7cafa9406..42d7135cc1 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -6,11 +6,11 @@ * and renders the full web page by putting the elements in the proper place & order * when the output() method is called. * Usage: - * $oPage = new web_page("Title of my page"); + * $oPage = new WebPage("Title of my page"); * $oPage->p("Hello World !"); * $oPage->output(); */ -class web_page +class WebPage { protected $s_title; protected $s_content; diff --git a/application/xmlpage.class.inc.php b/application/xmlpage.class.inc.php index d3a62adfe3..24a81c5627 100644 --- a/application/xmlpage.class.inc.php +++ b/application/xmlpage.class.inc.php @@ -4,7 +4,7 @@ require_once("../application/webpage.class.inc.php"); * Simple web page with no includes or fancy formatting, useful to generateXML documents * The page adds the content-type text/XML and the encoding into the headers */ -class XMLPage extends web_page +class XMLPage extends WebPage { function __construct($s_title) { diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index a13f76f31e..17ae3d77c1 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -646,7 +646,7 @@ class bizLocation extends logInfra */ } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { parent::DisplayDetails($oPage); /* @@ -865,7 +865,7 @@ class bizInterface extends logInfra MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'device_id', 'org_id')); // Criteria of the advanced search form } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { parent::DisplayDetails($oPage); /* @@ -942,7 +942,7 @@ class bizSubnet extends logInfra MetaModel::Init_SetZListItems('advanced_search', array('name', 'ip','mask')); // Criteria of the advanced search form } - function DisplayBareRelations(web_page $oPage) + function DisplayBareRelations(WebPage $oPage) { parent::DisplayBareRelations($oPage); @@ -1109,7 +1109,7 @@ class bizPC extends bizDevice MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type', 'brand', 'model', 'cpu', 'memory_size', 'hdd_size')); // Criteria of the advanced search form } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { parent::DisplayDetails($oPage); /* @@ -1256,7 +1256,7 @@ class bizServer extends bizDevice MetaModel::Init_SetZListItems('advanced_search', array('name', 'status','brand', 'model', 'os_family', 'os_version', 'location_id', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Criteria of the advanced search form } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { parent::DisplayDetails($oPage); /* @@ -1436,7 +1436,7 @@ class bizInfraGroup extends logInfra MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type', 'description', 'org_id')); // Criteria of the advanced search form } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { parent::DisplayDetails($oPage); /* @@ -1537,7 +1537,7 @@ class bizApplication extends logInfra } } - function DisplayDetails(web_page $oPage) + function DisplayDetails(WebPage $oPage) { parent::DisplayDetails($oPage); /* diff --git a/pages/UI.php b/pages/UI.php index e3a34c6238..2bcb89fb7a 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -30,7 +30,7 @@ $currentOrganization = utils::ReadParam('org_id', ''); $operation = utils::ReadParam('operation', ''); require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oP = new iTopWebPage("Welcome to ITop", $currentOrganization); diff --git a/pages/advanced_search.php b/pages/advanced_search.php index 80ec356573..aa503c610d 100644 --- a/pages/advanced_search.php +++ b/pages/advanced_search.php @@ -9,7 +9,7 @@ require_once('../application/dialogstack.class.inc.php'); require_once('../application/startup.inc.php'); // #@# not used, but... require_once('../classes/usercontext.class.inc.php'); -$oPage = new nice_web_page("ITop finder"); +$oPage = new NiceWebPage("ITop finder"); $oPage->no_cache(); diff --git a/pages/ajax.php b/pages/ajax.php index 1173ddd212..76cef861a8 100644 --- a/pages/ajax.php +++ b/pages/ajax.php @@ -9,7 +9,7 @@ function ReadParam($sName, $defaultValue = "") return isset($_REQUEST[$sName]) ? $_REQUEST[$sName] : $defaultValue; } -$oPage = new nice_web_page("Asynchronous versus asynchronous DisplayBlocks"); +$oPage = new NiceWebPage("Asynchronous versus asynchronous DisplayBlocks"); $oPage->no_cache(); $oPage->add("

    Asynchronous versus asynchronous DisplayBlocks

    \n"); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 3ac20e6a97..477643d551 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -296,7 +296,7 @@ $oPage->output(); /** * Downloads a document to the browser, either as 'inline' or 'attachment' * - * @param web_page $oPage The web page for the output + * @param WebPage $oPage The web page for the output * @param UserContext $oContext The current User/security context to retreive the objects * @param string $sClass Class name of the object * @param mixed $id Identifier of the object @@ -304,7 +304,7 @@ $oPage->output(); * @param string $sContentDisposition Either 'inline' or 'attachment' * @return none */ -function DownloadDocument(web_page $oPage, UserContext $oContext, $sClass, $id, $sAttCode, $sContentDisposition = 'attachement') +function DownloadDocument(WebPage $oPage, UserContext $oContext, $sClass, $id, $sAttCode, $sContentDisposition = 'attachement') { try { diff --git a/pages/audit.php b/pages/audit.php index 88ce7c90ab..edfa99a3e8 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -8,7 +8,7 @@ $operation = utils::ReadParam('operation', ''); $oAppContext = new ApplicationContext(); require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oP = new iTopWebPage("iTop - CMDB Audit", $currentOrganization); diff --git a/pages/csvimport.php b/pages/csvimport.php index 377b2a7c34..8ba2e60f46 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -5,7 +5,7 @@ require_once('../application/itopwebpage.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oContext = new UserContext(); $oAppContext = new ApplicationContext(); diff --git a/pages/data_generator.php b/pages/data_generator.php index 351269d180..c804788121 100644 --- a/pages/data_generator.php +++ b/pages/data_generator.php @@ -62,11 +62,11 @@ $aClassesToGenerate = array('bizOrganization' /* This one is special and must go /** * Populate an organization with objects of each class listed in the (global) $aClassesToGenerate array * - * @param web_page $oPage The object used for the HTML output + * @param WebPage $oPage The object used for the HTML output * @param cmdbGenerator $oGenerator The object used for the generation of the objects * @param string $sSize An enum specifying (roughly) how many objects of each class to create: one of 'small', 'medium', 'big', 'huge' or 'max' */ -function PopulateOrganization(CMDBChange $oMyChange, web_page $oPage, cmdbDataGenerator $oGenerator, $sSize = 'small') +function PopulateOrganization(CMDBChange $oMyChange, WebPage $oPage, cmdbDataGenerator $oGenerator, $sSize = 'small') { global $aClassesToGenerate; @@ -111,7 +111,7 @@ function PopulateOrganization(CMDBChange $oMyChange, web_page $oPage, cmdbDataGe /** * Delete an organization and all the instances of 'Object' belonging to this organization * - * @param web_page $oPage The object used for the HTML output + * @param WebPage $oPage The object used for the HTML output * @param string $sOrganizationCode The code (pkey) of the organization to delete */ function DeleteOrganization($oMyChange, $oPage, $sOrganizationCode) diff --git a/pages/db_importer.php b/pages/db_importer.php index 7d7df98944..cb56bf4e78 100644 --- a/pages/db_importer.php +++ b/pages/db_importer.php @@ -5,7 +5,7 @@ require_once('../application/itopwebpage.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed $sOperation = utils::ReadParam('operation', 'menu'); $oContext = new UserContext(); diff --git a/pages/incident.php b/pages/incident.php index ff9fe3df97..8024cd77b3 100644 --- a/pages/incident.php +++ b/pages/incident.php @@ -24,7 +24,7 @@ abstract class DialogWizard return $this->m_aSteps[$sStep]; } - protected function AddContextToForm(web_page $oPage) + protected function AddContextToForm(WebPage $oPage) { // Store as hidden fields in the page all the variables from the previous steps foreach($this->m_aSteps as $sStep => $aFields) @@ -37,7 +37,7 @@ abstract class DialogWizard } } - function GetObjectPicker(web_page $oPage, $sTitle, $sFieldName, $sClass) + function GetObjectPicker(WebPage $oPage, $sTitle, $sFieldName, $sClass) { $sScript = <<AddCondition('pkey', array(0), 'IN'); @@ -258,7 +258,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add("m_sNextStep."\" />\n"); } - public function DisplayNewTicketForm(web_page $oPage) + public function DisplayNewTicketForm(WebPage $oPage) { assert($this->m_sCurrentStep == '1'); $this->m_sNextStep = '2'; @@ -280,7 +280,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add(''); } - public function DisplayImpactedInfraForm(web_page $oPage) + public function DisplayImpactedInfraForm(WebPage $oPage) { assert($this->m_sCurrentStep == '2'); $this->m_sNextStep = '3'; @@ -296,7 +296,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add(''); } - public function DisplayAdditionalImpactedObjectForm(web_page $oPage) + public function DisplayAdditionalImpactedObjectForm(WebPage $oPage) { assert($this->m_sCurrentStep == '3'); $this->m_sNextStep = '4'; @@ -344,7 +344,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add(''); } - public function DisplayRelatedTicketsForm(web_page $oPage) + public function DisplayRelatedTicketsForm(WebPage $oPage) { assert($this->m_sCurrentStep == '4'); $this->m_sNextStep = '5'; @@ -380,7 +380,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add(''); } - public function DisplayContactsToNotifyForm(web_page $oPage) + public function DisplayContactsToNotifyForm(WebPage $oPage) { assert($this->m_sCurrentStep == '5'); $this->m_sNextStep = '6'; @@ -395,7 +395,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add(''); } - function DisplayFinalForm(web_page $oPage) + function DisplayFinalForm(WebPage $oPage) { $oAppContext = new ApplicationContext(); assert($this->m_sCurrentStep == '6'); @@ -483,7 +483,7 @@ class IncidentCreationWizard extends DialogWizard $oPage->add(''); } - public function CreateIncident(web_page $oPage) + public function CreateIncident(WebPage $oPage) { $oAppContext = new ApplicationContext(); assert($this->m_sCurrentStep == '7'); diff --git a/pages/index.php b/pages/index.php index f8d93ec620..5d5b81581b 100644 --- a/pages/index.php +++ b/pages/index.php @@ -5,7 +5,7 @@ require_once('../application/dialogstack.class.inc.php'); require_once('../application/startup.inc.php'); -$oPage = new nice_web_page("The very first iTop page"); +$oPage = new NiceWebPage("The very first iTop page"); $oPage->no_cache(); @@ -82,7 +82,7 @@ function DisplaySelectOrg($oPage, $sCurrentOrganization, $iContext) } } -function DisplayDetails(web_page $oPage, $sClassName, $sKey) +function DisplayDetails(WebPage $oPage, $sClassName, $sKey) { global $oContext; //$oObj = MetaModel::GetObject($sClassName, $sKey); @@ -135,7 +135,7 @@ function DisplayDetails(web_page $oPage, $sClassName, $sKey) } // By Rom -function DisplayChangesLog(web_page $oPage, $sClassName, $sKey) +function DisplayChangesLog(WebPage $oPage, $sClassName, $sKey) { global $oContext; //$oObj = MetaModel::GetObject($sClassName, $sKey); @@ -149,7 +149,7 @@ function DisplayChangesLog(web_page $oPage, $sClassName, $sKey) $oPage->p("Delete this object (no confirmation!)"); } -function DumpObjectsAsCSV(web_page $oPage, $sClassName, $oSearchFilter = null, $sSeparator = ",") +function DumpObjectsAsCSV(WebPage $oPage, $sClassName, $oSearchFilter = null, $sSeparator = ",") { global $oContext; @@ -179,7 +179,7 @@ function DumpObjectsAsCSV(web_page $oPage, $sClassName, $oSearchFilter = null, } } -function DumpObjects(web_page $oPage, $sClassName, CMDBSearchFilter $oSearchFilter = null) +function DumpObjects(WebPage $oPage, $sClassName, CMDBSearchFilter $oSearchFilter = null) { global $oContext; @@ -209,7 +209,7 @@ function DumpObjects(web_page $oPage, $sClassName, CMDBSearchFilter $oSearchFilt $oPage->table($aAttribs, $aValues); } -function DisplayEditForm(web_page $oPage, $sClassName, $sKey) +function DisplayEditForm(WebPage $oPage, $sClassName, $sKey) { global $oContext; //$oObj = MetaModel::GetObject($sClassName, $sKey); @@ -264,7 +264,7 @@ function DisplayEditForm(web_page $oPage, $sClassName, $sKey) $oPage->add("
    \n"); } -function DisplayCreationForm(web_page $oPage, $sClassName) +function DisplayCreationForm(WebPage $oPage, $sClassName) { global $oContext; $oPage->p("New $sClassName\n"); @@ -303,7 +303,7 @@ function DisplayCreationForm(web_page $oPage, $sClassName) $oPage->add("\n"); } -function UpdateObject(web_page $oPage, $sClassName, $sKey, $aAttributes) +function UpdateObject(WebPage $oPage, $sClassName, $sKey, $aAttributes) { global $oContext; //$oObj = MetaModel::GetObject($sClassName, $sKey); @@ -346,7 +346,7 @@ function UpdateObject(web_page $oPage, $sClassName, $sKey, $aAttributes) $oPage->p("Return to main page"); } -function DeleteObject(web_page $oPage, $sClassName, $sKey) +function DeleteObject(WebPage $oPage, $sClassName, $sKey) { global $oContext; $sClassLabel = MetaModel::GetName($sClassName); @@ -380,7 +380,7 @@ function DeleteObject(web_page $oPage, $sClassName, $sKey) $oPage->p("Return to main page"); } -function CreateObject(web_page $oPage, $sClassName, $aAttributes) +function CreateObject(WebPage $oPage, $sClassName, $aAttributes) { $oObj = MetaModel::NewObject($sClassName); $sClassLabel = MetaModel::GetName(get_class($oObj)); diff --git a/pages/run_query.php b/pages/run_query.php index 2457121bfb..6c75ee4aed 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -5,7 +5,7 @@ require_once('../application/itopwebpage.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed function ShowExamples($oP, $sExpression) diff --git a/pages/schema.php b/pages/schema.php index 1162f1ecdf..d472424d42 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -457,7 +457,7 @@ function DisplayRelationDetails($oPage, $sRelCode) require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left $oContext = new UserContext(); diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php index 18ee898bb3..e5ca283929 100644 --- a/pages/usermanagement_classproj.php +++ b/pages/usermanagement_classproj.php @@ -79,7 +79,7 @@ function ComputeProjections($oPage, $sScope) require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left $oContext = new UserContext(); diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index c29f80849b..a5cfb2e94f 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -90,7 +90,7 @@ function ComputeProjections($oPage) require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left $oContext = new UserContext(); diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index 4468a0cca0..34cf804a89 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -242,7 +242,7 @@ function ComputeUserRights($oPage, $oUser, $oObject) require_once('../application/loginwebpage.class.inc.php'); -login_web_page::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left $oContext = new UserContext(); diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 17f899a5e6..66ab56195f 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -26,7 +26,7 @@ header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past $sFileName = Utils::ReadParam('file', ''); $sSessionStatus = Utils::ReadParam('session_status', ''); $iPercent = (integer)Utils::ReadParam('percent', 0); -setup_web_page::log("Info - Loading file: $sFileName"); +SetupWebPage::log("Info - Loading file: $sFileName"); try { @@ -42,26 +42,26 @@ try $oChange->Set("date", time()); $oChange->Set("userinfo", "Initialization"); $iChangeId = $oChange->DBInsert(); - setup_web_page::log("Info - starting data load session"); + SetupWebPage::log("Info - starting data load session"); $oDataLoader->StartSession($oChange); } $oDataLoader->LoadFile($sFileName); $sResult = sprintf("Info - loading of %s done. (Overall %d %% completed).", basename($sFileName), $iPercent); echo $sResult; - setup_web_page::log($sResult); + SetupWebPage::log($sResult); if ($sSessionStatus == 'end') { $oDataLoader->EndSession(); - setup_web_page::log("Info - ending data load session"); + SetupWebPage::log("Info - ending data load session"); } } catch(Exception $e) { echo "

    An error happened while loading the data

    \n"; echo '

    '.$e."

    \n"; - setup_web_page::log("Error - An error happened while loading the data. ".$e); + SetupWebPage::log("Error - An error happened while loading the data. ".$e); } ?> diff --git a/setup/index.php b/setup/index.php index 2bc1f91186..811f082876 100644 --- a/setup/index.php +++ b/setup/index.php @@ -14,14 +14,14 @@ define('PHP_MIN_VERSION', '5.2.0'); define('MYSQL_MIN_VERSION', '5.0.0'); $sOperation = Utils::ReadParam('operation', 'step1'); -$oP = new setup_web_page('iTop configuration wizard'); +$oP = new SetupWebPage('iTop configuration wizard'); /** * Helper function to check if the current version of PHP * is compatible with the application * @return boolean true if this is Ok, false otherwise */ -function CheckPHPVersion(setup_web_page $oP) +function CheckPHPVersion(SetupWebPage $oP) { $bResult = true; $oP->log('Info - CheckPHPVersion'); @@ -131,7 +131,7 @@ function CheckPHPVersion(setup_web_page $oP) * the existing databases * @return Array The list of databases found in the server */ -function CheckServerConnection(setup_web_page $oP, $sDBServer, $sDBUser, $sDBPwd) +function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd) { $aResult = array(); $oP->log('Info - CheckServerConnection'); @@ -194,7 +194,7 @@ function CheckServerConnection(setup_web_page $oP, $sDBServer, $sDBUser, $sDBPwd * @param $bAllowMissingDatabase boolean Whether or not to allow loading a data model with no corresponding DB * @return none */ -function InitDataModel(setup_web_page $oP, $sConfigFileName, $bAllowMissingDatabase = true) +function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bAllowMissingDatabase = true) { require_once('../core/coreexception.class.inc.php'); require_once('../core/attributedef.class.inc.php'); @@ -215,7 +215,7 @@ function InitDataModel(setup_web_page $oP, $sConfigFileName, $bAllowMissingDatab * Helper function to create the database structure * @return boolean true on success, false otherwise */ -function CreateDatabaseStructure(setup_web_page $oP, Config $oConfig, $sDBName, $sDBPrefix) +function CreateDatabaseStructure(SetupWebPage $oP, Config $oConfig, $sDBName, $sDBPrefix) { InitDataModel($oP, TMP_CONFIG_FILE, true); // Allow the DB to NOT exist since we're about to create it ! $oP->log('Info - CreateDatabaseStructure'); @@ -240,7 +240,7 @@ function CreateDatabaseStructure(setup_web_page $oP, Config $oConfig, $sDBName, * Helper function to create and administrator account for iTop * @return boolean true on success, false otherwise */ -function CreateAdminAccount(setup_web_page $oP, Config $oConfig, $sAdminUser, $sAdminPwd) +function CreateAdminAccount(SetupWebPage $oP, Config $oConfig, $sAdminUser, $sAdminPwd) { $oP->log('Info - CreateAdminAccount'); InitDataModel($oP, TMP_CONFIG_FILE, true); // allow missing DB @@ -258,7 +258,7 @@ function CreateAdminAccount(setup_web_page $oP, Config $oConfig, $sAdminUser, $s //aFilesToLoad[aFilesToLoad.length] = './menus.xml'; // First load the menus -function ListDataFiles($sDirectory, setup_web_page $oP) +function ListDataFiles($sDirectory, SetupWebPage $oP) { $aFilesToLoad = array(); if ($hDir = @opendir($sDirectory)) @@ -288,7 +288,7 @@ function ListDataFiles($sDirectory, setup_web_page $oP) /** * Scans the ./data directory for XML files and output them as a Javascript array */ -function PopulateDataFilesList(setup_web_page $oP) +function PopulateDataFilesList(SetupWebPage $oP) { $oP->add("'; // TO DO: add support for $aExtraParams in asynchronous/Ajax mode + '; + } + if ($bAutoReload) + { + $sHtml .= ' + '; } return $sHtml; } diff --git a/core/config.class.inc.php b/core/config.class.inc.php index aa7aa5289e..62107892e9 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -18,6 +18,8 @@ class ConfigException extends CoreException define ('DEFAULT_MIN_DISPLAY_LIMIT', 10); define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); +define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60); +define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60); class Config { @@ -43,6 +45,15 @@ class Config */ protected $m_iMaxDisplayLimit; + /** + * @var integer Number of seconds between two reloads of the display (standard) + */ + protected $m_iStandardReloadInterval; + /** + * @var integer Number of seconds between two reloads of the display (fast) + */ + protected $m_iFastReloadInterval; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; @@ -136,6 +147,8 @@ class Config $this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT; $this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT; + $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; + $this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL; } protected function Verify() @@ -204,6 +217,16 @@ class Config return $this->m_iMaxDisplayLimit; } + public function GetStandardReloadInterval() + { + return $this->m_iStandardReloadInterval; + } + + public function GetFastReloadInterval() + { + return $this->m_iFastReloadInterval; + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -239,6 +262,16 @@ class Config $this->m_iMaxDisplayLimit = $iMaxDisplayLimit; } + public function SetStandardReloadInterval($iStandardReloadInterval) + { + $this->m_iStandardReloadInterval = $iStandardReloadInterval; + } + + public function SetFastReloadInterval($iFastReloadInterval) + { + $this->m_iFastReloadInterval = $iFastReloadInterval; + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -278,6 +311,8 @@ class Config fwrite($hFile, "\n"); fwrite($hFile, "\t'min_display_limit' => {$this->m_iMinDisplayLimit},\n"); fwrite($hFile, "\t'max_display_limit' => {$this->m_iMaxDisplayLimit},\n"); + fwrite($hFile, "\t'standard_reload_interval' => {$this->m_iStandardReloadInterval},\n"); + fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n/**\n"); diff --git a/js/utils.js b/js/utils.js index 0f2e795ab9..c97e959924 100644 --- a/js/utils.js +++ b/js/utils.js @@ -18,6 +18,25 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) } ); } +/** + * Reload any block -- used for periodic auto-reload + */ +function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams) +{ + $('#'+divId).addClass('loading'); + //$('#'+divId).blockUI(); + $.get('ajax.render.php?filter='+sSerializedFilter+'&style='+sStyle, + { operation: 'ajax', extra_params: sExtraParams }, + function(data){ + $('#'+divId).empty(); + $('#'+divId).append(data); + $('#'+divId).removeClass('loading'); + $('#'+divId+' .listResults').tableHover(); // hover tables + $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + //$('#'+divId).unblockUI(); + } + ); +} /** * Update the display and value of a file input widget when the user picks a new file From 23c7d036816312d7975e725c750cbbe9bc88b797 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 16:37:04 +0000 Subject: [PATCH 180/970] Background image for the welcome screen SVN:trunk[257] --- images/welcome.jpg | Bin 0 -> 37902 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/welcome.jpg diff --git a/images/welcome.jpg b/images/welcome.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5eedefc4bd879690daf9d9bd098a0b8857aca533 GIT binary patch literal 37902 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 Lz-uY9U%meavS%+^ literal 0 HcmV?d00001 From 6b357322800e461c55570b2a111ee4423e28d4aa Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 16:58:19 +0000 Subject: [PATCH 181/970] - Removed the version number from the welcome page - Nicer welcome page - Removed old notification menus SVN:trunk[258] --- setup/data/structure/1.menus.xml | 128 +++++++++---------------------- 1 file changed, 38 insertions(+), 90 deletions(-) diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index 64820cb60c..d421cd77d7 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -849,15 +849,16 @@ text-align:center; ./UI.php - application 1 0 0 - -Customization - -UI.php - - -administrator -2 -1 -0 - - -Triggers - entering - -UI.php -../images/std_view.gif - -administrator -2 -500 -0 - - -Triggers - leaving - -UI.php -../images/std_view.gif - -administrator -2 -500 -0 - - -Actions - -UI.php -../images/std_view.gif - -administrator -2 -500 -0 - Application log From 07d2f5e763e60ebe396587b8fa17baf378ea9742 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 17:33:04 +0000 Subject: [PATCH 182/970] - New datas for the setup: default trigger & email action SVN:trunk[259] --- setup/data/structure/23.triggers.xml | 7 +++++++ setup/data/structure/24.actions.xml | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 setup/data/structure/23.triggers.xml create mode 100644 setup/data/structure/24.actions.xml diff --git a/setup/data/structure/23.triggers.xml b/setup/data/structure/23.triggers.xml new file mode 100644 index 0000000000..06b2858282 --- /dev/null +++ b/setup/data/structure/23.triggers.xml @@ -0,0 +1,7 @@ + + + +Incident Creation +bizIncidentTicket + + \ No newline at end of file diff --git a/setup/data/structure/24.actions.xml b/setup/data/structure/24.actions.xml new file mode 100644 index 0000000000..53563f5444 --- /dev/null +++ b/setup/data/structure/24.actions.xml @@ -0,0 +1,21 @@ + + + +Simple eMail +Default eMail action, tailor it to fit your needs + + +SELECT bizContact AS C WHERE C.id = :this->caller_id + + +New iTop ticket [$this->severity$]: $this->name$, $this->title$ +<h1>A new iTop incident has been created: $this->name$</h1> +<p>Description: $this->title$</p> +<p>Initial situation: <pre>$this->initial_situation$</pre></p> +<p>Ticket Type: $this->type$</p> +<p>Ticket assigned to: $this->workgroup_name$</p> +<hr/> +<p>More information: $this->hyperlink()$</p> +normal + + From c0545aa9ccf54fefe135fc5e84b106e0ddfde547 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 17:34:28 +0000 Subject: [PATCH 183/970] - Log the 'From' field for email notifications SVN:trunk[260] --- core/action.class.inc.php | 1 + core/event.class.inc.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 51cee9af5e..ccd747e959 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -231,6 +231,7 @@ class ActionEmail extends ActionNotification $oLog->Set('trigger_id', $oTrigger->GetKey()); $oLog->Set('action_id', $this->GetKey()); $oLog->Set('object_id', $aContextArgs['this->id']); + $oLog->Set('from', $sFrom); $oLog->Set('to', $sTo); $oLog->Set('cc', $sCC); $oLog->Set('bcc', $sBCC); diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 79b2efbe0c..77a1648923 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -112,13 +112,14 @@ class EventNotificationEmail extends EventNotification MetaModel::Init_AddAttribute(new AttributeText("to", array("label"=>"TO", "description"=>"TO", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("cc", array("label"=>"CC", "description"=>"CC", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("bcc", array("label"=>"BCC", "description"=>"BCC", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("from", array("label"=>"From", "description"=>"Sender of the message", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("subject", array("label"=>"Subject", "description"=>"Subject", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("body", array("label"=>"Body", "description"=>"Body", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); // Display lists - MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'trigger_id', 'action_id', 'object_id', 'to', 'cc', 'bcc', 'subject', 'body')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'trigger_id', 'action_id', 'object_id', 'from', 'to', 'cc', 'bcc', 'subject', 'body')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'subject')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form From cdb68e9159a4d13406c5b651fd9c42eb948fe612 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 17:55:57 +0000 Subject: [PATCH 184/970] Bug fix: initialize properly auto_reload timeouts SVN:trunk[261] --- core/config.class.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 62107892e9..7f32ff6158 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -68,6 +68,8 @@ class Config $this->m_sDBSubname = ''; $this->m_iMinDisplayLimit = DEFAULT_MIN_DISPLAY_LIMIT; $this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT; + $this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL; + $this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL; if ($bLoadConfig) { $this->Load($sConfigFile); From 9250cb3c130c2bdb6a0eb98309ab2450db399e80 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 17 Jan 2010 18:03:04 +0000 Subject: [PATCH 185/970] - Latest "Notifications" admin page. SVN:trunk[262] --- setup/data/structure/1.menus.xml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index d421cd77d7..6b5317bc72 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -954,7 +954,7 @@ display: list-item; <h1>Configuration of the <span class="hilite">Notifications</span></h1> </div> <itoptoggle name="Help" open="true"> -<div style="padding: 1em; font-size:1.25em;background: #E8F3CF;margin-top: 0.25em;"> +<div style="padding: 1em; font-size:1.25em;background:#E8F3CF;margin-top: 0.25em;"> <img src="../images/bell.png" style="margin-top: -60px; margin-right: 10px; float: right;"> <p>In iTop the notifications are fully customizable. They are based on two sets of objects: <i>triggers and actions</i>.</p> <p><i><b>Triggers</b></i> define when a notification will be executed. There are 3 types of triggers for covering 3 differents phases of an object life cycle: @@ -976,14 +976,18 @@ When associated with a trigger, each action is given an 'order' number <itoptabs> <itoptab name="Triggers"> <h3>Available Triggers</h3> - <table> + <table style="border:0;border-spacing:10px;width:100%;"> <tr> - <td style="vertical-align:top;width:50%;padding:1em;"> - <h4>When entering a state</h4> + <td style="vertical-align:top;width:33%;padding:10px;background:#f8f8f8;"> + <h4>When an object is created</h4> + <itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT TriggerOnObjectCreate</itopblock> + </td> + <td style="vertical-align:top;width:33%;padding:10px;background:#f8f8f8;"> + <h4>When an object enters a state</h4> <itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT TriggerOnStateEnter</itopblock> </td> - <td style="vertical-align:top;width:50%;padding:1em;"> - <h4>When leaving a state</h4> + <td style="vertical-align:top;width:33%;padding:10px;background:#f8f8f8;"> + <h4>When an object leaves a state</h4> <itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT TriggerOnStateLeave</itopblock> </td> </tr> @@ -993,8 +997,7 @@ When associated with a trigger, each action is given an 'order' number <h3>Available Actions</h3> <itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT ActionEmail</itopblock> </itoptab> -</itoptabs> - +</itoptabs> administrator 2 1 From 2e26062560f9b86552cfb9e482d688c5dc055a44 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 07:43:26 +0000 Subject: [PATCH 186/970] Finalized the display of Enumeration attributes (the label may now be different than the value) Finalized the verification of the DB structure, which could not detect wrong field types so far SVN:trunk[263] --- core/attributedef.class.inc.php | 31 +++++++++++++++++++++++++++---- core/cmdbsource.class.inc.php | 9 +++++++++ core/metamodel.class.php | 19 +++++++++++++++++-- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 145020461a..8f5937e046 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -406,7 +406,7 @@ class AttributeInteger extends AttributeDBField public function GetType() {return "Integer";} public function GetTypeDesc() {return "Numeric value (could be negative)";} public function GetEditClass() {return "String";} - protected function GetSQLCol() {return "INT";} + protected function GetSQLCol() {return "INT(11)";} public function GetBasicFilterOperators() { @@ -766,7 +766,7 @@ class AttributeEnum extends AttributeString $oValDef = $this->GetValuesDef(); if ($oValDef) { - $aValues = CMDBSource::Quote($oValDef->GetValues(array(), ""), true); + $aValues = CMDBSource::Quote(array_keys($oValDef->GetValues(array(), "")), true); } else { @@ -774,7 +774,11 @@ class AttributeEnum extends AttributeString } if (count($aValues) > 0) { - return "ENUM(".implode(", ", $aValues).")"; + // The syntax used here is matters + // In particular, I had to remove unnecessary spaces to stick to + // make sure that this string will match the field type returned by the DB + // (used to perform a comparison between the current DB format and the data model) + return "ENUM(".implode(",", $aValues).")"; } else { @@ -795,6 +799,25 @@ class AttributeEnum extends AttributeString { return parent::GetBasicFilterSQLExpr($sOpCode, $value); } + + public function GetAsHTML($sValue) + { + $oValDef = $this->GetValuesDef(); + if ($oValDef) + { + $aValues = $oValDef->GetValues(array(), ""); + } + if (!empty($aValues) && array_key_exists($sValue, $aValues)) + { + $sLabel = $aValues[$sValue]; + } + else + { + $sLabel = $sValue.' ERROR could not find'; + } + // later, we could imagine a detailed description in the title + return "".parent::GetAsHtml($sLabel).""; + } } /** @@ -985,7 +1008,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid public function GetType() {return "Extkey";} public function GetTypeDesc() {return "Link to another object";} public function GetEditClass() {return "ExtKey";} - protected function GetSQLCol() {return "INT";} + protected function GetSQLCol() {return "INT(11)";} public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;} public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");} diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 20f3fed9e7..3543349e69 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -342,6 +342,15 @@ class CMDBSource return (strtolower($aFieldData["Null"]) == "yes"); } + public static function GetFieldType($sTable, $sField) + { + $aTableInfo = self::GetTableInfo($sTable); + if (empty($aTableInfo)) return false; + if (!array_key_exists($sField, $aTableInfo["Fields"])) return false; + $aFieldData = $aTableInfo["Fields"][$sField]; + return ($aFieldData["Type"]); + } + public static function HasIndex($sTable, $sField) { $aTableInfo = self::GetTableInfo($sTable); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6177cace23..28a5fcce60 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2099,19 +2099,34 @@ abstract class MetaModel } else { + // The field already exists, does it have the relevant properties? + // + $bToBeChanged = false; if ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) { + $bToBeChanged = true; if ($oAttDef->IsNullAllowed()) { $aErrors[$sClass][] = "field '$sField' in table '$sTable' could be NULL"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; } else { $aErrors[$sClass][] = "field '$sField' in table '$sTable' could NOT be NULL"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; } } + $sActualFieldType = CMDBSource::GetFieldType($sTable, $sField); + if (strcasecmp($sDBFieldType, $sActualFieldType) != 0) + { + $bToBeChanged = true; + $aErrors[$sClass][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; + } + if ($bToBeChanged) + { + $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + + // Create indexes (external keys only... so far) + // if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) { $aErrors[$sClass][] = "Foreign key '$sField' in table '$sTable' should have an index"; From 7989d8dc7a8dd39dce08384bb35cf2c5f0fc51e1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 15:20:38 +0000 Subject: [PATCH 187/970] - Display the notifications sent for an Incident ticket SVN:trunk[264] --- business/templates/ticket.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/business/templates/ticket.html b/business/templates/ticket.html index fe8f2d5c74..10df943864 100644 --- a/business/templates/ticket.html +++ b/business/templates/ticket.html @@ -15,6 +15,9 @@ SELECT lnkContactTicket WHERE ticket_id = $pkey$ + + SELECT EventNotificationEmail AS Ev JOIN TriggerOnObject AS T ON Ev.trigger_id = T.id WHERE T.target_class = 'bizIncidentTicket' AND Ev.object_id = $pkey$ + From 871b511368cbe0f7747272927509e49b180c4ba6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 15:22:26 +0000 Subject: [PATCH 188/970] Disabled (commented out) the lifecycle declaration for bizServer, bizContract and bizService SVN:trunk[265] --- business/ServiceMgmt.business.php | 12 ++++++++---- business/itop.business.class.inc.php | 15 +++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index dd8865a055..0319fba53e 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -17,7 +17,8 @@ class bizService extends cmdbAbstractObject "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", - "state_attcode" => "status", + //"state_attcode" => "status", + "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "services", "db_key_field" => "id", @@ -42,6 +43,7 @@ class bizService extends cmdbAbstractObject MetaModel::Init_AddFilterFromAttribute("type"); MetaModel::Init_AddFilterFromAttribute("status"); +/* // Life cycle MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created service", "attribute_inherit"=>null, "attribute_list"=>array())); @@ -59,7 +61,7 @@ class bizService extends cmdbAbstractObject MetaModel::Init_DefineTransition("New", "ev_implement", array("target_state"=>"Implementation", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Implementation", "ev_move2prod", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Production", "ev_obsolete", array("target_state"=>"Obsolete", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); - +*/ MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id','service_category','type','status','description')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id','service_category','type')); // Attributes to be displayed for a list @@ -95,7 +97,8 @@ class bizContract extends cmdbAbstractObject "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", - "state_attcode" => "status", + //"state_attcode" => "status", + "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "contracts", "db_key_field" => "id", @@ -139,6 +142,7 @@ class bizContract extends cmdbAbstractObject MetaModel::Init_AddFilterFromAttribute("type"); +/* // Life cycle MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created contract", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_MANDATORY,'org_id' => OPT_ATT_MANDATORY, 'service_id' => OPT_ATT_MANDATORY,'type' => OPT_ATT_MANDATORY, 'description' => OPT_ATT_MANDATORY))); @@ -162,7 +166,7 @@ class bizContract extends cmdbAbstractObject MetaModel::Init_DefineTransition("Signed", "ev_begin", array("target_state"=>"Production", "actions"=>array('SetProdDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Signed", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Production", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null)); - +*/ MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'service_id','provider_name','type','description','team_id','service_level','cost','currency','cost_unit','cost_freq','move2prod_date','end_prod', 'version_number')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'service_id','provider_name','service_name','service_level','type')); // Attributes to be displayed for a list diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 17ae3d77c1..a9de9bbb70 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -249,7 +249,7 @@ class bizPerson extends bizContact "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("org_id", "first_name", "name"), // comment en dfinir plusieurs + "reconc_keys" => array("org_id", "first_name", "name"), // comment en définir plusieurs // "reconc_keys" => array("org_id", "employee_number"), "db_table" => "persons", // Can it use the same physical DB table as any contact ? "db_key_field" => "id", @@ -591,7 +591,7 @@ class lnkContactInfra extends cmdbAbstractObject //////////////////////////////////////////////////////////////////////////////////// /** * bizLocation (Region, Country, City, Site, Building, Floor, Room, Rack,...) -* pourrait tre mis en plusieurs sous objects, puisqu'une adresse sur region n'a pas trop de sens +* pourrait être mis en plusieurs sous objects, puisqu'une adresse sur region n'a pas trop de sens * */ //////////////////////////////////////////////////////////////////////////////////// @@ -1170,6 +1170,7 @@ class bizServer extends bizDevice "key_label" => "id", "name_attcode" => "name", "state_attcode" => "status", + "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "servers", "db_key_field" => "id", @@ -1190,6 +1191,7 @@ class bizServer extends bizDevice MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("status"); MetaModel::Init_AddFilterFromAttribute("memory_size"); MetaModel::Init_AddFilterFromAttribute("cpu"); MetaModel::Init_AddFilterFromAttribute("number_of_cpus"); @@ -1199,6 +1201,7 @@ class bizServer extends bizDevice MetaModel::Init_AddFilterFromAttribute("os_version"); +/* // Life cycle MetaModel::Init_DefineState("InStore", array("label"=>"InStore", "description"=>"Device in store", "attribute_inherit"=>null, "attribute_list"=>array())); @@ -1215,7 +1218,6 @@ class bizServer extends bizDevice MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The device is no more used", "attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineStimulus("ev_store", new StimulusUserAction(array("label"=>"Store this server", "description"=>"This server is move to storage"))); MetaModel::Init_DefineStimulus("ev_ship", new StimulusUserAction(array("label"=>"Ship this server", "description"=>"This server is shipped to futur location"))); MetaModel::Init_DefineStimulus("ev_plug", new StimulusUserAction(array("label"=>"Plug this server", "description"=>"The server is pluuged on the network"))); @@ -1242,14 +1244,11 @@ class bizServer extends bizDevice MetaModel::Init_DefineTransition("BeingDeconfigured", "ev_store", array("target_state"=>"InStore", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("BeingDeconfigured", "ev_obsolete", array("target_state"=>"Obsolete", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Obsolete", "ev_recycle", array("target_state"=>"BeingDeconfigured", "actions"=>array(), "user_restriction"=>null)); - - - - +*/ // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'mgmt_ip','default_gateway','status', 'severity','org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version','serial_number','shipment_number', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('name', 'status', 'mgmt_ip','default_gateway', 'severity','org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version','serial_number','shipment_number', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status','severity', 'org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('name', 'status','severity', 'brand', 'model', 'os_family', 'location_id')); // Criteria of the std search form From bda8f0b8149204c5039ad7a4b30f16d485784aaa Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 15:22:48 +0000 Subject: [PATCH 189/970] - Adapted the triggers hierarchy and the EventLog search capabilities to be able to display all the notifications sent for a given object (a ticket for instance) SVN:trunk[266] --- core/action.class.inc.php | 1 + core/event.class.inc.php | 1 + core/trigger.class.inc.php | 57 ++++++++++++++++++++++++++++++-------- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index ccd747e959..dbeaeeea85 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -34,6 +34,7 @@ abstract class Action extends cmdbAbstractObject //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_triggers", array("label"=>"Related Triggers", "description"=>"Triggers linked to this action", "linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 77a1648923..9955dddbdc 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -77,6 +77,7 @@ class EventNotification extends Event MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("trigger_id"); MetaModel::Init_AddFilterFromAttribute("action_id"); + MetaModel::Init_AddFilterFromAttribute("object_id"); // Display lists MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'trigger_id', 'action_id', 'object_id')); // Attributes to be displayed for the complete details diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 6d9d1a637b..e9de6eaa36 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -23,7 +23,7 @@ class Trigger extends cmdbAbstractObject "description" => "Custom event handler", "key_type" => "autoincrement", "key_label" => "", - "name_attcode" => "", + "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_trigger", @@ -61,7 +61,42 @@ class Trigger extends cmdbAbstractObject } } -class TriggerOnStateChange extends Trigger +class TriggerOnObject extends Trigger +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "name" => "Trigger on a class of objects", + "description" => "Trigger on a given class of objects", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_trigger_onobject", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("label"=>"Target class", "description"=>"label", "class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_InheritFilters(); + MetaModel::Init_AddFilterFromAttribute("target_class"); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'description')); // Attributes to be displayed for a list + // Search criteria +// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form +// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} + +class TriggerOnStateChange extends TriggerOnObject { public static function Init() { @@ -82,16 +117,14 @@ class TriggerOnStateChange extends Trigger ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("label"=>"Target class", "description"=>"label", "class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("state", array("label"=>"State", "description"=>"label", "allowed_values"=>null, "sql"=>"state", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); - MetaModel::Init_AddFilterFromAttribute("target_class"); MetaModel::Init_AddFilterFromAttribute("state"); // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'state')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'state')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'state', 'description')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form @@ -124,7 +157,7 @@ class TriggerOnStateEnter extends TriggerOnStateChange // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'state')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('target_class', 'state', 'description')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form @@ -157,14 +190,14 @@ class TriggerOnStateLeave extends TriggerOnStateChange // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'state')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('target_class', 'state')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('target_class', 'state', 'description')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('')); // Criteria of the advanced search form } } -class TriggerOnObjectCreate extends Trigger +class TriggerOnObjectCreate extends TriggerOnObject { public static function Init() { @@ -185,14 +218,12 @@ class TriggerOnObjectCreate extends Trigger ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("label"=>"Target class", "description"=>"label", "class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); - MetaModel::Init_AddFilterFromAttribute("target_class"); // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class', 'description')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form @@ -222,14 +253,16 @@ class lnkTriggerAction extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> '', "label"=>"Action", "description"=>"The action to be executed", "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("action_name", array("label"=>"Action Name", "description"=>"Name of the action", "allowed_values"=>null, "extkey_attcode"=> 'action_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> '', "label"=>"Trigger", "description"=>"Trigger", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("trigger_name", array("label"=>"Trigger Name", "description"=>"Name of the trigger", "allowed_values"=>null, "extkey_attcode"=> 'trigger_id', "target_attcode"=>"description"))); MetaModel::Init_AddAttribute(new AttributeInteger("order", array("label"=>"Order", "description"=>"Actions execution order", "allowed_values"=>null, "sql"=>"order", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("action_id"); MetaModel::Init_AddFilterFromAttribute("trigger_id"); + MetaModel::Init_AddFilterFromAttribute("order"); // Display lists MetaModel::Init_SetZListItems('details', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list - MetaModel::Init_SetZListItems('list', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('action_name', 'trigger_name', 'order')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('action_id', 'trigger_id', 'order')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form From 808c95531eee8e95060cc1154f9dcc3537249dd1 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 15:23:55 +0000 Subject: [PATCH 190/970] Setup: added missing link classes to the configuration manager profile SVN:trunk[267] --- addons/userrights/userrightsprofile.class.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index f65b15145f..2955289873 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -1409,6 +1409,8 @@ class SetupITILProfiles // 'lnkInterfaces', 'lnkClientServer', 'lnkInfraGrouping', + 'lnkContactInfra', + 'lnkContactTeam', ), 'Incident' => array( 'bizIncidentTicket', From 920a98a902cb35e726050f387c34e816ffbd5a14 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 15:50:18 +0000 Subject: [PATCH 191/970] Fixed bug in the data model: organization status was not an enum SVN:trunk[268] --- business/KEDB.business.php | 2 -- business/itop.business.class.inc.php | 8 +++----- core/attributedef.class.inc.php | 4 +++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 8d814eaf76..23ee63e735 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -1,7 +1,5 @@ "Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array() ))); MetaModel::Init_AddAttribute(new AttributeString("code", array("label"=>"Code", "description"=>"Organization code (Siret, DUNS,...)", "allowed_values"=>null, "sql"=>"code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array() ))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum(STANDARD_STATUSES), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"bizOrganization", "label"=>"Parent", "description"=>"Parent organization", "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Name", "description"=>"Name of the parent organization", "allowed_values"=>null, "extkey_attcode"=> 'parent_id', "target_attcode"=>"name"))); @@ -126,8 +126,6 @@ class logRealObject extends cmdbAbstractObject { public static function Init() { - global $oAllowedStatuses; - $aParams = array ( "category" => "bizmodel,searchable", @@ -145,7 +143,7 @@ class logRealObject extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum(STANDARD_STATUSES), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization", "description"=>"ID of the object owner organization", "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 8f5937e046..ea207937dc 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -813,7 +813,8 @@ class AttributeEnum extends AttributeString } else { - $sLabel = $sValue.' ERROR could not find'; + // #@# todo - record an error silently... + $sLabel = $sValue; } // later, we could imagine a detailed description in the title return "".parent::GetAsHtml($sLabel).""; @@ -843,6 +844,7 @@ class AttributeDate extends AttributeDBField self::$const_TIMEZONE = new DateTimeZone(self::MYDATETIMEZONE); // #@# Init default timezone -> do not get a notice... to be improved !!! + // duplicated in the email test page (the mail function does trigger a notice as well) date_default_timezone_set(self::MYDATETIMEZONE); } From 327696186617b8fab2499ef9eb4e360acc182e17 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 16:06:02 +0000 Subject: [PATCH 192/970] - MakeHyperlink now returns absolute URLs (including server name & port) - Fixed the handling of the "menu" option in the DisplayBlocks - removed unneeded GetUIPage functions SVN:trunk[269] --- application/cmdbabstract.class.inc.php | 7 ++-- application/displayblock.class.inc.php | 40 ++------------------- application/utils.inc.php | 48 ++++++++++++++++++++++++++ business/ServiceDesk.business.php | 5 --- business/incidentMgmt.business.php | 5 --- 5 files changed, 56 insertions(+), 49 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index dbe5665c21..83e54a317a 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -14,7 +14,7 @@ abstract class cmdbAbstractObject extends CMDBObject public static function GetUIPage() { - return './UI.php'; + return '../pages/UI.php'; } public static function ComputeUIPage($sClass) @@ -40,6 +40,9 @@ abstract class cmdbAbstractObject extends CMDBObject $oAppContext = new ApplicationContext(); $sExtClassNameAtt = MetaModel::GetNameAttributeCode($sObjClass); $sPage = self::ComputeUIPage($sObjClass); + $sAbsoluteUrl = utils::GetAbsoluteUrl(false); // False => Don't get the query string + $sAbsoluteUrl = substr($sAbsoluteUrl, 0, 1+strrpos($sAbsoluteUrl, '/')); // remove the current page, keep just the path, up to the last / + // Use the "name" of the target class as the label of the hyperlink // unless it's not available in the external attributes... if (isset($aAvailableFields[$sExtClassNameAtt])) @@ -71,7 +74,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sLabel = MetaModel::GetName($sObjClass)." #$sObjKey"; } $sHint = MetaModel::GetName($sObjClass)."::$sObjKey"; - return "GetForLink()."\" title=\"$sHint\">$sLabel"; + return "GetForLink()."\" title=\"$sHint\">$sLabel"; } public function GetDisplayValue($sAttCode) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 10438b921a..74d9f9e23b 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -330,7 +330,7 @@ class DisplayBlock { $sHtml .= $oPage->GetP("No object to display."); $sClass = $this->m_oFilter->GetClass(); - $bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true; + $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; if ($bDisplayMenu) { if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) @@ -779,7 +779,7 @@ class MenuBlock extends DisplayBlock } else { - $sUrl = self::GetAbsoluteUrl(); + $sUrl = utils::GetAbsoluteUrl(); $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); //$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); @@ -831,7 +831,7 @@ class MenuBlock extends DisplayBlock else { // many objects in the set, possible actions are: new / modify all / delete all - $sUrl = self::GetAbsoluteUrl(); + $sUrl = utils::GetAbsoluteUrl(); $aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); $aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); //$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); @@ -850,39 +850,5 @@ class MenuBlock extends DisplayBlock $oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n"); return $sHtml; } - - static public function GetAbsoluteUrl() - { - // Build an absolute URL to this page on this server/port - $sServerName = $_SERVER['SERVER_NAME']; - $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; - if ($sProtocol == 'http') - { - $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; - } - else - { - $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; - } - // $_SERVER['REQUEST_URI'] is empty when running on IIS - // Let's use Ivan Tcholakov's fix (found on www.dokeos.com) - if (!empty($_SERVER['REQUEST_URI'])) - { - $sPath = $_SERVER['REQUEST_URI']; - } - else - { - $sPath = $_SERVER['SCRIPT_NAME']; - if (!empty($_SERVER['QUERY_STRING'])) - { - $sPath .= '?'.$_SERVER['QUERY_STRING']; - } - $_SERVER['REQUEST_URI'] = $sPath; - } - $sPath = $_SERVER['REQUEST_URI']; - $sUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}"; - - return $sUrl; - } } ?> diff --git a/application/utils.inc.php b/application/utils.inc.php index 3330714fb9..6eb1722807 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -147,5 +147,53 @@ class utils } return $iReturn; } + + /** + * Returns an absolute URL to the current page + * @param $bQueryString bool True to also get the query string, false otherwise + * @return string The absolute URL to the current page + */ + static public function GetAbsoluteUrl($bQueryString = true) + { + // Build an absolute URL to this page on this server/port + $sServerName = $_SERVER['SERVER_NAME']; + $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + if ($sProtocol == 'http') + { + $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + else + { + $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + // $_SERVER['REQUEST_URI'] is empty when running on IIS + // Let's use Ivan Tcholakov's fix (found on www.dokeos.com) + if (!empty($_SERVER['REQUEST_URI'])) + { + $sPath = $_SERVER['REQUEST_URI']; + } + else + { + $sPath = $_SERVER['SCRIPT_NAME']; + if (!empty($_SERVER['QUERY_STRING'])) + { + $sPath .= '?'.$_SERVER['QUERY_STRING']; + } + $_SERVER['REQUEST_URI'] = $sPath; + } + $sPath = $_SERVER['REQUEST_URI']; + if (!$bQueryString) + { + // remove all the parameters from the query string + $iQuestionMarkPos = strpos($sPath, '?'); + if ($iQuestionMarkPos !== false) + { + $sPath = substr($sPath, 0, $iQuestionMarkPos); + } + } + $sUrl = "$sProtocol://{$sServerName}{$sPort}{$sPath}"; + + return $sUrl; + } } ?> diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index bdcd803479..ddb7244b6d 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -139,11 +139,6 @@ class bizServiceCall extends cmdbAbstractObject $this->Set('end_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); } - public static function GetUIPage() - { - return './UI.php'; - } - // State machine actions diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index 2a44a46727..b4b8c6d0a5 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -134,11 +134,6 @@ class bizIncidentTicket extends cmdbAbstractObject $this->Set('end_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)")); } - public static function GetUIPage() - { - return './UI.php'; - } - // State machine actions public function IncrementAssignmentCount($sStimulusCode) { From aa6d6029d952e7f48cec483d3075e1ca44dd47ad Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 16:42:00 +0000 Subject: [PATCH 193/970] Finalized the email error management: - errors in OQL - no recipient - smtp server response (windows only) - test option (status) - test page SVN:trunk[270] --- core/MyHelpers.class.inc.php | 9 -- core/action.class.inc.php | 215 +++++++++++++++++++------- core/dbobject.class.php | 21 ++- core/event.class.inc.php | 20 +-- core/test.class.inc.php | 47 +++++- core/trigger.class.inc.php | 5 +- pages/UniversalSearch.php | 3 +- pages/index.php | 18 ++- pages/testlist.inc.php | 152 ++++++++++++++++-- setup/email.test.php | 184 ++++++++++++++++++++++ setup/setup.js | 9 ++ toolkit.php | 1 + webservices/webservices.class.inc.php | 3 +- 13 files changed, 585 insertions(+), 102 deletions(-) create mode 100644 setup/email.test.php diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php index 16e149cd93..fded6913ad 100644 --- a/core/MyHelpers.class.inc.php +++ b/core/MyHelpers.class.inc.php @@ -331,15 +331,6 @@ class MyHelpers exit; } - /** - * utf8... converts non ASCII chars into '?' - * Decided after some complex investigations, to have the tools work fine (Oracle+Perl vs mySQL+PHP...) - */ - public static function utf8($strText) - { - return iconv("WINDOWS-1252", "ASCII//TRANSLIT", $strText); - } - /** * xmlentities() * ... same as htmlentities, but designed for xml ! diff --git a/core/action.class.inc.php b/core/action.class.inc.php index dbeaeeea85..6f8dfcb0ff 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -1,5 +1,7 @@ "Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"In production or ?", "allowed_values"=>new ValueSetEnum(array('test'=>'Being tested' ,'enabled'=>'In production', 'disabled'=>'Inactive')), "sql"=>"status", "default_value"=>"test", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_triggers", array("label"=>"Related Triggers", "description"=>"Triggers linked to this action", "linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); @@ -41,14 +44,39 @@ abstract class Action extends cmdbAbstractObject MetaModel::Init_AddFilterFromAttribute("description"); // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('name', 'description')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('name', 'description', 'status')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } abstract public function DoExecute($oTrigger, $aContextArgs); + + public function IsActive() + { + switch($this->Get('status')) + { + case 'enabled': + case 'test': + return true; + + default: + return false; + } + } + + public function IsBeingTested() + { + switch($this->Get('status')) + { + case 'test': + return true; + + default: + return false; + } + } } /** @@ -87,8 +115,8 @@ abstract class ActionNotification extends Action MetaModel::Init_InheritFilters(); // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('name', 'description', 'status')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form @@ -128,6 +156,8 @@ class ActionEmail extends ActionNotification MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("label"=>"Test recipient", "description"=>"Detination in case status is set to \"Test\"", "allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("from", array("label"=>"From", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("label"=>"Reply to", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeOQL("to", array("label"=>"To", "description"=>"Destination of the email", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); @@ -140,21 +170,36 @@ class ActionEmail extends ActionNotification MetaModel::Init_InheritFilters(); // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'description', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('name', 'to', 'subject')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } - // args: a search object - // returns an array of emails - protected function FindRecipients($sAttCode, $aArgs) + // count the recipients found + protected $m_iRecipients; + + // Errors management : not that simple because we need that function to be + // executed in the background, while making sure that any issue would be reported clearly + protected $m_aMailErrors; //array of strings explaining the issue + + // returns a the list of emails as a string, or a detailed error description + protected function FindRecipients($sRecipAttCode, $aArgs) { - $sOQL = $this->Get($sAttCode); + $sOQL = $this->Get($sRecipAttCode); if (strlen($sOQL) == '') return ''; - $oSearch = DBObjectSearch::FromOQL($sOQL); + try + { + $oSearch = DBObjectSearch::FromOQL($sOQL); + } + catch (OqlException $e) + { + $this->m_aMailErrors[] = "query syntax error for recipient '$sRecipAttCode'"; + return $e->getMessage(); + } + $sClass = $oSearch->GetClass(); // Determine the email attribute (the first one will be our choice) foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) @@ -166,78 +211,138 @@ class ActionEmail extends ActionNotification break; } } + if (!isset($sEmailAttCode)) + { + $this->m_aMailErrors[] = "wrong target for recipient '$sRecipAttCode'"; + return "The objects of the class '$sClass' do not have any email attribute"; + } $oSet = new DBObjectSet($oSearch, array() /* order */, $aArgs); $aRecipients = array(); while ($oObj = $oSet->Fetch()) { $aRecipients[] = $oObj->Get($sEmailAttCode); + $this->m_iRecipients++; } return implode(', ', $aRecipients); } + public function DoExecute($oTrigger, $aContextArgs) { - // Determine recicipients - // - $sTo = $this->FindRecipients('to', $aContextArgs); - $sCC = $this->FindRecipients('cc', $aContextArgs); - $sBCC = $this->FindRecipients('bcc', $aContextArgs); - - $sFrom = $this->Get('from'); - $sReplyTo = $this->Get('reply_to'); - - $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs); - $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs); - - // To send HTML mail, the Content-type header must be set - $sHeaders = 'MIME-Version: 1.0' . "\r\n"; - $sHeaders .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n"; - - // Additional headers - if (strlen($sFrom) > 0) + $this->m_iRecipients = 0; + $this->m_aMailErrors = array(); + $bRes = false; // until we do succeed in sending the email + try { - $sHeaders .= "From: $sFrom\r\n"; - // This is required on Windows because otherwise I would get the error - // "sendmail_from" not set in php.ini" even if it is correctly working - // (apparently, once it worked the SMTP server won't claim anymore for it) - ini_set("sendmail_from", $sFrom); + // Determine recicipients + // + $sTo = $this->FindRecipients('to', $aContextArgs); + $sCC = $this->FindRecipients('cc', $aContextArgs); + $sBCC = $this->FindRecipients('bcc', $aContextArgs); + + $sFrom = $this->Get('from'); + $sReplyTo = $this->Get('reply_to'); + + $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs); + $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs); + + $oEmail = new Email(); + + if ($this->IsBeingTested()) + { + $oEmail->SetSubject('TEST['.$sSubject.']'); + $sTestBody = $sBody; + $sTestBody .= "
    \n"; + $sTestBody .= "

    Testing email notification ".$this->GetHyperlink()."

    \n"; + $sTestBody .= "

    The email should be sent with the following properties\n"; + $sTestBody .= "

      \n"; + $sTestBody .= "
    • TO: $sTo
    • \n"; + $sTestBody .= "
    • CC: $sCC
    • \n"; + $sTestBody .= "
    • BCC: $sBCC
    • \n"; + $sTestBody .= "
    • From: $sFrom
    • \n"; + $sTestBody .= "
    • Reply-To: $sReplyTo
    • \n"; + $sTestBody .= "
    \n"; + $sTestBody .= "

    \n"; + $sTestBody .= "
    \n"; + $oEmail->SetBody($sTestBody); + $oEmail->SetRecipientTO($this->Get('test_recipient')); + $oEmail->SetRecipientFrom($this->Get('test_recipient')); + } + else + { + $oEmail->SetSubject($sSubject); + $oEmail->SetBody($sBody); + $oEmail->SetRecipientTO($sTo); + $oEmail->SetRecipientCC($sCC); + $oEmail->SetRecipientBCC($sBCC); + $oEmail->SetRecipientFrom($sFrom); + $oEmail->SetRecipientReplyTo($sReplyTo); + } + + if (empty($this->m_aMailErrors)) + { + if ($this->m_iRecipients == 0) + { + $this->m_aMailErrors[] = 'No recipient'; + } + else + { + $this->m_aMailErrors = array_merge($this->m_aMailErrors, $oEmail->Send()); + } + } } - if (strlen($sReplyTo) > 0) + catch (Exception $e) { - $sHeaders .= "Reply-To: $sReplyTo\r\n"; - } - if (strlen($sCC) > 0) - { - $sHeaders .= "Cc: $sCC\r\n"; - } - if (strlen($sBCC) > 0) - { - $sHeaders .= "Bcc: $sBCC\r\n"; + $this->m_aMailErrors[] = $e->getMessage(); } $oLog = new EventNotificationEmail(); - if (mail($sTo, $sSubject, $sBody, $sHeaders)) + if (empty($this->m_aMailErrors)) { - $oLog->Set('message', 'Notification sent'); + if ($this->IsBeingTested()) + { + $oLog->Set('message', 'TEST - Notification sent ('.$this->Get('test_recipient').')'); + } + else + { + $oLog->Set('message', 'Notification sent'); + } } else { - $aLastError = error_get_last(); - $oLog->Set('message', 'Mail could not be sent: '.$aLastError['message']); - //throw new CoreException('mail not sent', array('action'=>$this->GetKey(), 'to'=>$sTo, 'subject'=>$sSubject, 'headers'=>$sHeaders)); + if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0) + { + $sError = implode(', ', $this->m_aMailErrors); + } + else + { + $sError = 'Unknown reason'; + } + if ($this->IsBeingTested()) + { + $oLog->Set('message', 'TEST - Notification was not sent: '.$sError); + } + else + { + $oLog->Set('message', 'Notification was not sent: '.$sError); + } } $oLog->Set('userinfo', UserRights::GetUser()); $oLog->Set('trigger_id', $oTrigger->GetKey()); $oLog->Set('action_id', $this->GetKey()); $oLog->Set('object_id', $aContextArgs['this->id']); - $oLog->Set('from', $sFrom); - $oLog->Set('to', $sTo); - $oLog->Set('cc', $sCC); - $oLog->Set('bcc', $sBCC); - $oLog->Set('subject', $sSubject); - $oLog->Set('body', $sBody); + + // Note: we have to secure this because those values are calculated + // inside the try statement, and we would like to keep track of as + // many data as we could while some variables may still be undefined + if (isset($sTo)) $oLog->Set('to', $sTo); + if (isset($sCC)) $oLog->Set('cc', $sCC); + if (isset($sBCC)) $oLog->Set('bcc', $sBCC); + if (isset($sFrom)) $oLog->Set('from', $sFrom); + if (isset($sSubject)) $oLog->Set('subject', $sSubject); + if (isset($sBody)) $oLog->Set('body', $sBody); $oLog->DBInsertNoReload(); } } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index e20943a7ab..68479ca11a 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -522,14 +522,27 @@ abstract class DBObject // Note: checks the values and consistency public function CheckToInsert() { + $aIssues = array(); foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) { - if (!$this->CheckValue($sAttCode)) return false; + if (!$this->CheckValue($sAttCode)) + { + $aIssues[$sAttCode] = array( + 'issue' => 'unexpected value' + ); + } } - if (!$this->CheckConsistency()) return false; - return true; + if (count($aIssues) > 0) + { + return array(false, $aIssues); + } + if (!$this->CheckConsistency()) + { + return array(false, $aIssues); + } + return array(true, $aIssues); } - + // check if it is allowed to update the existing object into the database // a displayable error is returned // Note: checks the values and consistency diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 9955dddbdc..cbac9e792e 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -32,7 +32,7 @@ class Event extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("message", array("label"=>"message", "description"=>"short description of the event", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("message", array("label"=>"message", "description"=>"short description of the event", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"user info", "description"=>"identification of the user that was doing the action that triggered this event", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); @@ -110,18 +110,18 @@ class EventNotificationEmail extends EventNotification ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeText("to", array("label"=>"TO", "description"=>"TO", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("cc", array("label"=>"CC", "description"=>"CC", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("bcc", array("label"=>"BCC", "description"=>"BCC", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("from", array("label"=>"From", "description"=>"Sender of the message", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("subject", array("label"=>"Subject", "description"=>"Subject", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("body", array("label"=>"Body", "description"=>"Body", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - + MetaModel::Init_AddAttribute(new AttributeText("to", array("label"=>"TO", "description"=>"TO", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("cc", array("label"=>"CC", "description"=>"CC", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("bcc", array("label"=>"BCC", "description"=>"BCC", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("from", array("label"=>"From", "description"=>"Sender of the message", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("subject", array("label"=>"Subject", "description"=>"Subject", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("body", array("label"=>"Body", "description"=>"Body", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); // Display lists - MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'trigger_id', 'action_id', 'object_id', 'from', 'to', 'cc', 'bcc', 'subject', 'body')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'subject')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'message', 'subject')); // Attributes to be displayed for a list + // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 905e7ad063..87af7e1659 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -130,9 +130,8 @@ abstract class TestHandler $this->ReportWarning($errstr); break; default: - throw new ExceptionFromError("Fatal warning in line $errline of file $errfile: $errstr"); - $this->ReportWarning("Unknown error type: [$errno] $errstr"); - echo "Unknown error type: [$errno] $errstr
    \n"; + $this->ReportWarning("Unknown error type: [$errno] $errstr in $errfile at $errline"); + echo "Unknown error type: [$errno] $errstr in $errfile at $errline
    \n"; break; } return true; // do not call the default handler @@ -418,6 +417,48 @@ abstract class TestBizModel extends TestHandler // something here to create records... but that's another story } + protected $m_oChange; + protected function ObjectToDB($oNew, $bReload = false) + { + list($bRes, $aIssues) = $oNew->CheckToInsert(); + if (!$bRes) + { + throw new CoreException('Could not create object, unexpected values', array('attributes' => $aIssues)); + } + if ($oNew instanceof CMDBObject) + { + if (!isset($this->m_oChange)) + { + new CMDBChange(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Someone doing some tests"); + $iChangeId = $oMyChange->DBInsertNoReload(); + $this->m_oChange = $oMyChange; + } + if ($bReload) + { + $iId = $oNew->DBInsertTracked($this->m_oChange); + } + else + { + $iId = $oNew->DBInsertTrackedNoReload($this->m_oChange); + } + } + else + { + if ($bReload) + { + $iId = $oNew->DBInsert(); + } + else + { + $iId = $oNew->DBInsertNoReload(); + } + } + return $iId; + } + protected function ResetDB() { if (MetaModel::DBExists()) diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index e9de6eaa36..511bb42a32 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -56,7 +56,10 @@ class Trigger extends cmdbAbstractObject { $iActionId = $oLink->Get('action_id'); $oAction = MetaModel::GetObject('Action', $iActionId); - $oAction->DoExecute($this, $aContextArgs); + if ($oAction->IsActive()) + { + $oAction->DoExecute($this, $aContextArgs); + } } } } diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index b8b300ce66..4a9c9b0773 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -145,7 +145,8 @@ EOF; } } - if ($oMenuNode->CheckToInsert()) + list($bRes, $aIssues) = $oMenuNode->CheckToInsert(); + if ($bRes) { $oMenuNode->DBInsert(); $oP->add(""); diff --git a/pages/index.php b/pages/index.php index 5d5b81581b..4cbac19c2f 100644 --- a/pages/index.php +++ b/pages/index.php @@ -393,7 +393,8 @@ function CreateObject(WebPage $oPage, $sClassName, $aAttributes) $oObj->Set($sAttCode, $aAttributes[$sAttCode]); } } - if ($oObj->CheckToInsert()) + list($bRes, $aIssues) = $oObj->CheckToInsert(); + if ($bRes) { // By rom // $oObj->DBInsert(); @@ -413,6 +414,12 @@ function CreateObject(WebPage $oPage, $sClassName, $aAttributes) else { $oPage->p("Error: object can not be created!\n"); + $oPage->add("
      Issues:"); + foreach($aIssues as $sErrorMsg) + { + $oPage->add("
    • $sErrorMsg
    • "); + } + $oPage->add("
    "); } $oPage->p("Return to main page"); } @@ -442,7 +449,8 @@ function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKe $oNewLink = MetaModel::NewObject($sLinkClass); $oNewLink->Set($sExtKeyToMe, $sKey); $oNewLink->Set($sExtKeyToPartner, $oPartnerObj->GetKey()); - if ($oNewLink->CheckToInsert()) + list($bRes, $aIssues) = $oNewLink->CheckToInsert(); + if ($bRes) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); @@ -455,6 +463,12 @@ function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKe else { $oPage->p("Error: link can not be created!\n"); + $oPage->add("
      Issues:"); + foreach($aIssues as $sErrorMsg) + { + $oPage->add("
    • $sErrorMsg
    • "); + } + $oPage->add("
    "); } } } diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 42d1375281..800ac969f4 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -601,22 +601,6 @@ abstract class MyFarm extends TestBizModel MetaModel::DBCheckIntegrity(); } - protected $m_oChange; - protected function ObjectToDB(CMDBObject $oNew) - { - if (!isset($this->m_oChange)) - { - new CMDBChange(); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Administrator"); - $iChangeId = $oMyChange->DBInsertNoReload(); - $this->m_oChange = $oMyChange; - } - $iId = $oNew->DBInsertTrackedNoReload($this->m_oChange); - return $iId; -} - protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) { $oNew = MetaModel::NewObject('Mammal'); @@ -1467,4 +1451,140 @@ class TestWebServicesDirect extends TestBizModel return true; } } + +class TestTriggerAndEmail extends TestBizModel +{ + static public function GetName() {return 'Test trigger and email';} + static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail) + { + $oAction = MetaModel::NewObject("ActionEmail"); + $oAction->Set("status", $sStatus); + $oAction->Set("name", "New server"); + $oAction->Set("test_recipient", $sTesterEmail); + $oAction->Set("from", $sTesterEmail); + $oAction->Set("reply_to", $sTesterEmail); + $oAction->Set("to", $sTo); + $oAction->Set("cc", $sCC); + $oAction->Set("bcc", ""); + $oAction->Set("subject", "New server: '\$this->name()$'"); + $oAction->Set("body", "

    Dear customer,

    We have created the server \$this->hyperlink()$ in the IT infrastructure database.

    You will be further notified when it is in Production.

    The IT infrastructure management team.

    Here are some accentuated characters for french people: ''

    "); + $oAction->Set("importance", "low"); + $iActionId = $this->ObjectToDB($oAction, true); + + $oLink = MetaModel::NewObject("lnkTriggerAction"); + $oLink->Set("trigger_id", $oTrigger->GetKey()); + $oLink->Set("action_id", $iActionId); + $oLink->Set("order", "1"); + $iLink = $this->ObjectToDB($oLink, true); + } + + protected function DoExecute() + { + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail1"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "romain.quetiez@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail2"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "denis.flaven@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail3"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "erwan.taloc@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyServer = MetaModel::NewObject("bizServer"); + $oMyServer->Set("name", "wfr.terminator.com"); + $oMyServer->Set("severity", "low"); + $oMyServer->Set("status", "InStore"); + $oMyServer->Set("org_id", 2); + $oMyServer->Set("location_id", 2); + $iServerId = $this->ObjectToDB($oMyServer, true); + + $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); + $oMyTrigger->Set("description", "Testor"); + $oMyTrigger->Set("target_class", "bizServer"); + $oMyTrigger->Set("state", "Shipped"); + $iTriggerId = $this->ObjectToDB($oMyTrigger, true); + + // Error in OQL field(s) + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE naime = 'Dali'", + "SELECT bizServer", + 'romain.quetiez@hp.com' + ); + + // Error: no recipient + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "", + "", + 'romain.quetiez@hp.com' + ); + + // Test + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "SELECT bizPerson", + 'romain.quetiez@hp.com' + ); + + // Test failing because of a wrong test recipient address + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'toto@walibi.bg' + ); + + // Normal behavior + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'enabled', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + // Does nothing, because it is disabled + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'disabled', + "SELECT bizPerson WHERE name = 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); + + return true; + } +} ?> diff --git a/setup/email.test.php b/setup/email.test.php new file mode 100644 index 0000000000..76df1e5426 --- /dev/null +++ b/setup/email.test.php @@ -0,0 +1,184 @@ += 5.2.4 + { + $sPhpIniFile = php_ini_loaded_file(); + } + else + { + $sPhpIniFile = 'php.ini'; + } + + $bIsWindows = (array_key_exists('WINDIR', $_SERVER)); + if ($bIsWindows) + { + $sSmtpServer = ini_get('SMTP'); + if (empty($sSmtpServer)) + { + $oP->error("The SMTP server is not defined. Please add the 'SMTP' directive into $sPhpIniFile"); + $bRet = false; + } + else if (strcasecmp($sSmtpServer, 'localhost') == 0) + { + $oP->warning("Your SMTP server is configured to 'localhost'. You might want to set or change the 'SMTP' directive into $sPhpIniFile"); + } + else + { + $oP->info("Your SMTP server: $sSmtpServer. To change this value, modify the 'SMTP' directive into $sPhpIniFile"); + } + + $iSmtpPort = (int) ini_get('smtp_port'); + if (empty($iSmtpPort)) + { + $oP->info("The SMTP port is not defined. Please add the 'smtp_port' directive into $sPhpIniFile"); + $bRet = false; + } + else if ($iSmtpPort = 25) + { + $oP->info("Your SMTP port is configured to the default value: 25. You might want to set or change the 'smtp_port' directive into $sPhpIniFile"); + } + else + { + $oP->info("Your SMTP port is configured to $iSmtpPort. You might want to set or change the 'smtp_port' directive into $sPhpIniFile"); + } + } + else + { + // Not a windows system + } + if ($bRet) + { + $oP->ok("PHP settings are ok to proceed with a test of the email"); + } + return $bRet; +} + + +/** + * Display the form for the first step of the test wizard + * which consists in a basic check of the configuration and display of a form for testing + */ +function DisplayStep1(SetupWebPage $oP) +{ + $sNextOperation = 'step2'; + $oP->add("

    iTop email test

    \n"); + $oP->add("

    Checking prerequisites

    \n"); + if (CheckEmailSetting($oP)) + { + $sRedStar = '*'; + $oP->add("

    Try to send an email

    \n"); + $oP->add("\n"); + // Form goes here + $oP->add("
    Test configuration\n"); + $aForm = array(); + $aForm[] = array( + 'label' => "To$sRedStar:", + 'input' => "", + 'help' => ' pure email address (john.foo@worldcompany.com)', + ); + $aForm[] = array( + 'label' => "From:", + 'input' => "", + 'help' => ' defaults to \'To\'', + ); + $oP->form($aForm); + $oP->add("
    \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + } +} + +/** + * Display the form for the second step of the configuration wizard + * which consists in sending an email, which maybe a problem under Windows + */ +function DisplayStep2(SetupWebPage $oP, $sFrom, $sTo) +{ + //$sNextOperation = 'step3'; + $oP->add("

    iTop configuration wizard

    \n"); + $oP->add("

    Step 2: send an email

    \n"); + $oP->add("

    Sending an email to '$sTo'... (From: '$sFrom')

    \n"); + $oP->add("
    \n"); + + $oEmail = new Email(); + $oEmail->SetRecipientTO($sTo); + $oEmail->SetRecipientFrom($sFrom); + $oEmail->SetSubject("Test iTop"); + $oEmail->SetBody("

    Hello,

    The email function is now working fine.

    You may now be able to use the notification function.

    iTop

    "); + $aIssues = $oEmail->send(); + if (count($aIssues) > 0) + { + foreach ($aIssues as $sError) + { + $oP->error($sError); + } + $oP->add("\n"); + } + else + { + $oP->ok("The email has been sent, you may now check that the email will arrive..."); + } +} + +/** + * Main program + */ + +// #@# Init default timezone -> do not get a notice... to be improved !!! +// duplicated from 'attributedef.class.inc.php', needed here because mail() does +// generate a notice +date_default_timezone_set('Europe/Paris'); + + +try +{ + switch($sOperation) + { + case 'step1': + DisplayStep1($oP); + break; + + case 'step2': + $oP->no_cache(); + $sTo = Utils::ReadParam('to'); + $sFrom = Utils::ReadParam('from'); + if (strlen($sFrom) == 0) + { + $sFrom = $sTo; + } + DisplayStep2($oP, $sFrom, $sTo); + break; + + default: + $oP->error("Error: unsupported operation '$sOperation'"); + + } +} +catch(Exception $e) +{ + $oP->error("Error: '".$e->getMessage()."'"); +} +catch(CoreException $e) +{ + $oP->error("Error: '".$e->getHtmlDesc()."'"); +} +$oP->output(); +?> diff --git a/setup/setup.js b/setup/setup.js index 8c9e460089..de99764edf 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -77,6 +77,15 @@ function DoSubmit(sMsg, iStep) case 4: bResult = DoLoadDataAsynchronous(); + break; + + // Email test page + case 10: + if ($('#to').val() == '') + { + alert('Please specify a destination address'); + bResult = false; + } } if (bResult) { diff --git a/toolkit.php b/toolkit.php index 707bdb5205..d816778e33 100644 --- a/toolkit.php +++ b/toolkit.php @@ -15,6 +15,7 @@ echo "

    Itop consultant

    \n"; echo "Check model, Create DB, Update DB (new class, new attribute)
    \n"; echo "Backup and restore (shortcut)
    \n"; echo "Objects schema (shortcut)
    \n"; +echo "Setup the email
    \n"; echo "

    Web services

    \n"; echo "Available functions
    \n"; echo "WSDL (dynamically generated)
    \n"; diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index 5595f7113a..b525a69486 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -455,7 +455,8 @@ class WebServices { if ($oRes->IsOk()) { - if ($oTargetObj->CheckToInsert()) + list($bRes, $aIssues) = $oTargetObj->CheckToInsert(); + if ($bRes) { $iId = $oTargetObj->DBInsertTrackedNoReload($oChange); $oRes->LogInfo("Created object ".get_class($$oTargetObj)."::$iId"); From 4267218e49730e1182c89c4c8e84c2ea871b74c6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 16:55:00 +0000 Subject: [PATCH 194/970] Email test: added a check for linux (sendmail_path) SVN:trunk[271] --- setup/email.test.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/setup/email.test.php b/setup/email.test.php index 76df1e5426..bcf7935d8e 100644 --- a/setup/email.test.php +++ b/setup/email.test.php @@ -26,7 +26,7 @@ function CheckEmailSetting($oP) $sPhpIniFile = 'php.ini'; } - $bIsWindows = (array_key_exists('WINDIR', $_SERVER)); + $bIsWindows = (array_key_exists('WINDIR', $_SERVER)); if ($bIsWindows) { $sSmtpServer = ini_get('SMTP'); @@ -62,6 +62,16 @@ function CheckEmailSetting($oP) else { // Not a windows system + $sSendMail = ini_get('sendmail_path'); + if (empty($sSendMail)) + { + $oP->error("The command to send mail is not defined. Please add the 'sendmail_path' directive into $sPhpIniFile. A recommended setting is sendmail_path=sendmail -t -i"); + $bRet = false; + } + else + { + $oP->info("The command to send mail: $sSendMail. To change this value, modify the 'sendmail_path' directive into $sPhpIniFile"); + } } if ($bRet) { From f12b6d5c8ab99762652d025d99867fbc25495bae Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 17:17:49 +0000 Subject: [PATCH 195/970] Sample data for triggers and actions SVN:trunk[272] --- setup/data/structure/23.triggers.xml | 10 +++ setup/data/structure/24.actions.xml | 80 ++++++++++++++++----- setup/data/structure/25.trigger-actions.xml | 23 ++++++ 3 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 setup/data/structure/25.trigger-actions.xml diff --git a/setup/data/structure/23.triggers.xml b/setup/data/structure/23.triggers.xml index 06b2858282..51ac5676dd 100644 --- a/setup/data/structure/23.triggers.xml +++ b/setup/data/structure/23.triggers.xml @@ -4,4 +4,14 @@ Incident Creation bizIncidentTicket + +Incident ticket assigned to agent +bizIncidentTicket +Assigned + + +Incident ticket resolved +bizIncidentTicket +Resolved + \ No newline at end of file diff --git a/setup/data/structure/24.actions.xml b/setup/data/structure/24.actions.xml index 53563f5444..3bd64856b7 100644 --- a/setup/data/structure/24.actions.xml +++ b/setup/data/structure/24.actions.xml @@ -1,21 +1,63 @@ - - - -Simple eMail -Default eMail action, tailor it to fit your needs - - -SELECT bizContact AS C WHERE C.id = :this->caller_id - - -New iTop ticket [$this->severity$]: $this->name$, $this->title$ -<h1>A new iTop incident has been created: $this->name$</h1> + + + +Incident Notification to a Workgroup +This action informs a team that a ticket has been assigned their workgroup + + +SELECT bizTeam AS t JOIN bizWorkgroup AS w ON w.team_id = t.id WHERE w.id=:this->workgroup_id + + +The ticket $this->name()$, severity $this->severity$ has been assigned to the workgroup $this->workgroup_name$ +<html> +<body> +<p>The incident ticket $this->name()$ has been assigned to the workgroup $this->workgroup_name$.</p> <p>Description: $this->title$</p> -<p>Initial situation: <pre>$this->initial_situation$</pre></p> -<p>Ticket Type: $this->type$</p> -<p>Ticket assigned to: $this->workgroup_name$</p> +<p>Initial Situation: $this->initial_situation$</p> <hr/> -<p>More information: $this->hyperlink()$</p> -normal - - +<p>for more information on this ticket, click here: $this->hyperlink()$</p> +</body> +</html> +normal + + +Incident notification to Agent +This action informs an agent that a ticket has been assigned to her/him + + +SELECT bizPerson WHERE id=:this->agent_id + + +The ticket $this->name()$, severity $this->severity$ has been assigned to you +<html> +<body> +<p>The incident ticket $this->name()$ has been assigned to you.</p> +<p>Description: $this->title$</p> +<p>Initial Situation: $this->initial_situation$</p> +<hr/> +<p>for more information on this ticket, click here: $this->hyperlink()$</p> +</body> +</html> +normal + + +Incident Notification to caller +This action is used to inform the caller + + +SELECT bizPerson WHERE id=:this->caller_id + + +Ticket $this->name()$, severity $this->severity$ - $this->ticket_status$ +<html> +<body> +<p>The incident ticket $this->name()$ has changed to status $this->ticket_status$</p> +<p>Current situation: $this->current_situation$</p> +<p>Last update: $this->last_update$</p> +<hr/> +<p>for more information on this ticket, click here: $this->hyperlink()$</p> +</body> +</html> +normal + + \ No newline at end of file diff --git a/setup/data/structure/25.trigger-actions.xml b/setup/data/structure/25.trigger-actions.xml new file mode 100644 index 0000000000..bd58a7d670 --- /dev/null +++ b/setup/data/structure/25.trigger-actions.xml @@ -0,0 +1,23 @@ + + + +2 +5 +1 + + +3 +5 +2 + + +3 +6 +1 + + +4 +2 +1 + + \ No newline at end of file From ab1318291053bbe0c240f301b12f76bc2c03c235 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 17:19:57 +0000 Subject: [PATCH 196/970] Export script for triggers, actions and their link SVN:trunk[273] --- setup/data/structure/export_triggers_actions.cmd | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 setup/data/structure/export_triggers_actions.cmd diff --git a/setup/data/structure/export_triggers_actions.cmd b/setup/data/structure/export_triggers_actions.cmd new file mode 100644 index 0000000000..44958ad069 --- /dev/null +++ b/setup/data/structure/export_triggers_actions.cmd @@ -0,0 +1,12 @@ +SET WEBROOT=http://localhost:81/ +SET EXPORT=%WEBROOT%/webservices/export.php + +SET USER=admin +SET PWD=test + +REM The order (numbering) of the files is important since +REM it dictates the order to import them back +wget --output-document=23.triggers.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT Trigger&format=xml" +wget --output-document=24.actions.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT ActionEmail&format=xml" +wget --output-document=25.trigger-actions.xml --post-data="auth_user=%USER%&auth_pwd=%PWD%&operation=login" "%EXPORT%?expression=SELECT lnkTriggerAction&format=xml" +pause From ac931fb7ec5150695c3af86c145376d9a78989ab Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 17:33:14 +0000 Subject: [PATCH 197/970] - Export in XML each object according to its actual class SVN:trunk[274] --- application/cmdbabstract.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 83e54a317a..3f3dbc2d87 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -482,6 +482,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oSet->Seek(0); while ($oObj = $oSet->Fetch()) { + $sClassName = get_class($oObj); $oPage->add("<$sClassName id=\"".$oObj->GetKey()."\">\n"); foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) { From cd0271c8c1c6433d4dcbe83fcf97e617f3a6f66a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 17:35:05 +0000 Subject: [PATCH 198/970] Fixed bug revealed by a recent fix: the various sets of values allowed for the status fields were not consistent regarding the object hierarchy SVN:trunk[275] --- business/incidentMgmt.business.php | 1 + business/itop.business.class.inc.php | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index b4b8c6d0a5..b1f4df026f 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -345,6 +345,7 @@ class bizWorkgroup extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team owning the workgroup", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team Name", "description"=>"name of the team", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 47a6cf566e..20fc380473 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -143,7 +143,7 @@ class logRealObject extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum(STANDARD_STATUSES), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete,off,left company,available'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization", "description"=>"ID of the object owner organization", "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); @@ -197,6 +197,7 @@ class bizContact extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('off,left company,available'), "sql"=>"status", "default_value"=>"available", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department of the contact", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeEmailAddress("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("phone", array("label"=>"Phone", "description"=>"Telephone", "allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -237,7 +238,6 @@ class bizPerson extends bizContact { public static function Init() { - $oAllowedEmployeeStatuses = new ValueSetEnum('off,left company,available'); $aParams = array ( "category" => "bizmodel,searchable", @@ -258,7 +258,6 @@ class bizPerson extends bizContact MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"First Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("employee_number", array("label"=>"Employee Number", "description"=>"employee number", "allowed_values"=>null, "sql"=>"employee_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedEmployeeStatuses, "sql"=>"status", "default_value"=>"available", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("first_name"); @@ -393,6 +392,7 @@ class bizDocument extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning the document", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"type", "description"=>"usage of the document", "allowed_values"=>new ValueSetEnum("documentation,contract,working instructions,network map,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -534,6 +534,7 @@ class logInfra extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Business Criticity", "description"=>"Severity for this infrastructure", "allowed_values"=>new ValueSetEnum("high,medium,low"), "sql"=>"severity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); @@ -1637,6 +1638,7 @@ class lnkClientServer extends logRealObject ); MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("client_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Client", "description"=>"The client part of the link", "allowed_values"=>null, "sql"=>"client_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("client_name", array("label"=>"Client", "description"=>"Name of the client", "allowed_values"=>null, "extkey_attcode"=> 'client_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalKey("server_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Server", "description"=>"the server part of the link", "allowed_values"=>null, "sql"=>"server_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); @@ -1683,6 +1685,7 @@ class bizPatch extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Device", "description"=>"The Device where patch is installed", "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device Name", "description"=>"Name of the impacted device", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installation Date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); From 5f17dcf2dbf9a62db912e748c3679ef943517e2c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 17:43:37 +0000 Subject: [PATCH 199/970] Forgot to commit a new include SVN:trunk[276] --- core/email.class.inc.php | 121 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 core/email.class.inc.php diff --git a/core/email.class.inc.php b/core/email.class.inc.php new file mode 100644 index 0000000000..3a8a23e886 --- /dev/null +++ b/core/email.class.inc.php @@ -0,0 +1,121 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +class EMail +{ + protected $m_sBody; + protected $m_sSubject; + protected $m_sTo; + protected $m_aHeaders; // array of key=>value + + public function __construct() + { + $this->m_sTo = ''; + $this->m_sSubject = ''; + $this->m_sBody = ''; + $this->m_aHeaders = array(); + } + + + // Errors management : not that simple because we need that function to be + // executed in the background, while making sure that any issue would be reported clearly + protected $m_aMailErrors; //array of strings explaining the issues + + public function mail_error_handler($errno, $errstr, $errfile, $errline) + { + $sCleanMessage= str_replace("mail() [function.mail]: ", "", $errstr); + $this->m_aMailErrors[] = $sCleanMessage; + } + + + // returns a list of issues if any + public function Send() + { + $sHeaders = 'MIME-Version: 1.0' . "\r\n"; + $sHeaders .= 'Content-type: text/html; charset=utf-8' . "\r\n"; + foreach ($this->m_aHeaders as $sKey => $sValue) + { + $sHeaders .= "$sKey: $sValue\r\n"; + } + + // Under Windows (not yet proven for Linux/PHP) mail may issue a warning + // that I could not mask (tried error_reporting(), etc.) + $this->m_aMailErrors = array(); + set_error_handler(array($this, 'mail_error_handler')); + $bRes = mail + ( + $this->m_sTo, + $this->m_sSubject, + $this->m_sBody, + $sHeaders + ); + restore_error_handler(); + if (!$bRes && empty($this->m_aMailErrors)) + { + $this->m_aMailErrors[] = 'Unknown reason'; + } + return $this->m_aMailErrors; + } + + protected function AddToHeader($sKey, $sValue) + { + if (strlen($sValue) > 0) + { + $this->m_aHeaders[$sKey] = $sValue; + } + } + + public function SetBody($sBody) + { + $this->m_sBody = $sBody; + } + + public function SetSubject($aSubject) + { + $this->m_sSubject = $aSubject; + } + + public function SetRecipientTO($sAddress) + { + $this->m_sTo = $sAddress; + } + + public function SetRecipientCC($sAddress) + { + $this->AddToHeader('Cc', $sAddress); + } + + public function SetRecipientBCC($sAddress) + { + $this->AddToHeader('Bcc', $sAddress); + } + + public function SetRecipientFrom($sAddress) + { + $this->AddToHeader('From', $sAddress); + + // This is required on Windows because otherwise I would get the error + // "sendmail_from" not set in php.ini" even if it is correctly working + // (apparently, once it worked the SMTP server won't claim anymore for it) + ini_set("sendmail_from", $sAddress); + } + + public function SetRecipientReplyTo($sAddress) + { + $this->AddToHeader('Reply-To', $sAddress); + } + +} + +?> From defe8fff82ac11b1a9a16c8e38b97696ba12e805 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 18 Jan 2010 17:54:53 +0000 Subject: [PATCH 200/970] Removed status declaration for the class bizServer SVN:trunk[277] --- business/itop.business.class.inc.php | 4 ++-- pages/testlist.inc.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 20fc380473..d4d589dc1f 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -1168,7 +1168,7 @@ class bizServer extends bizDevice "key_type" => "", "key_label" => "id", "name_attcode" => "name", - "state_attcode" => "status", + //"state_attcode" => "status", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes "db_table" => "servers", @@ -1178,7 +1178,7 @@ class bizServer extends bizDevice ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("InStore,Shipped,Plugged,ProductionCandidate,InProduction,BeingDeconfigured,Obsolete"), "sql"=>"status", "default_value"=>"InStore", "is_null_allowed"=>false, "depends_on"=>array()))); +// MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("InStore,Shipped,Plugged,ProductionCandidate,InProduction,BeingDeconfigured,Obsolete"), "sql"=>"status", "default_value"=>"InStore", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"CPU type", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("number_of_cpus", array("label"=>"Number of CPUs", "description"=>"Number of CPUs", "allowed_values"=>null, "sql"=>"number_of_cpus", "default_value"=>"1", "is_null_allowed"=>true, "depends_on"=>array()))); diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 800ac969f4..cd0a15de22 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -1505,7 +1505,7 @@ class TestTriggerAndEmail extends TestBizModel $oMyServer = MetaModel::NewObject("bizServer"); $oMyServer->Set("name", "wfr.terminator.com"); $oMyServer->Set("severity", "low"); - $oMyServer->Set("status", "InStore"); + $oMyServer->Set("status", "production"); $oMyServer->Set("org_id", 2); $oMyServer->Set("location_id", 2); $iServerId = $this->ObjectToDB($oMyServer, true); From a9838035c20a4b7213ca566bc94d78be0bffc306 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 18:04:32 +0000 Subject: [PATCH 201/970] Updated templates for a better consistency SVN:trunk[278] --- business/templates/network.device.html | 4 ++-- business/templates/pc.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/business/templates/network.device.html b/business/templates/network.device.html index b18fb73af7..182cf984d9 100644 --- a/business/templates/network.device.html +++ b/business/templates/network.device.html @@ -7,13 +7,13 @@ SELECT bizNetworkDevice WHERE id = $pkey$ - SELECT bizInterface WHERE device_id = $pkey$ + SELECT bizInterface WHERE device_id = $pkey$ SELECT lnkContactInfra WHERE infra_id = $pkey$ - bizIncidentTicket:PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + SELECT lnkInfraTicket WHERE infra_id = $pkey$ bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$) diff --git a/business/templates/pc.html b/business/templates/pc.html index 3b0112983e..077b9cba1c 100644 --- a/business/templates/pc.html +++ b/business/templates/pc.html @@ -19,7 +19,7 @@ SELECT lnkContactRealObject WHERE object_id = $pkey$ - bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$) + SELECT lnkInfraTicket WHERE infra_id = $pkey$ SELECT lnkInfraGrouping WHERE infra_id = $pkey$ From 8f12cd39c50d9da5ca54caae574797f25064ea7a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 18:06:40 +0000 Subject: [PATCH 202/970] - New link to the email test page SVN:trunk[279] --- setup/data/structure/1.menus.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index 6b5317bc72..67f55994a4 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -968,6 +968,7 @@ display: list-item; <i><b>Actions</b></i> define the actions to be performed when the triggers execute. For now there is only one kind of action consisting in sending an email message. Such actions also define the template to be used for sending the email as well as the other parameters of the message like the recipients, importance, etc. </p> +<p>A special page: <a href="../setup/email.test.php" target="_blank">email.test.php</a> is available for testing and troubleshooting your PHP mail configuration.</p> <p>To be executed, actions must be associated to triggers. When associated with a trigger, each action is given an 'order' number, specifying in which order the actions are to be executed.</p> </div> From d2f3f51daa3cd0e20a31a663b2427acdd4183c0e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 18 Jan 2010 18:40:45 +0000 Subject: [PATCH 203/970] New actions consistent wit the updated DB model SVN:trunk[280] --- setup/data/structure/24.actions.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup/data/structure/24.actions.xml b/setup/data/structure/24.actions.xml index 3bd64856b7..465e9b2d8e 100644 --- a/setup/data/structure/24.actions.xml +++ b/setup/data/structure/24.actions.xml @@ -3,6 +3,8 @@ Incident Notification to a Workgroup This action informs a team that a ticket has been assigned their workgroup +enabled + SELECT bizTeam AS t JOIN bizWorkgroup AS w ON w.team_id = t.id WHERE w.id=:this->workgroup_id @@ -23,6 +25,8 @@ Incident notification to Agent This action informs an agent that a ticket has been assigned to her/him +enabled + SELECT bizPerson WHERE id=:this->agent_id @@ -43,6 +47,8 @@ Incident Notification to caller This action is used to inform the caller +enabled + SELECT bizPerson WHERE id=:this->caller_id From 7fe876b98317ae83d7d9bb920ddab50724daf0c4 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 19 Jan 2010 07:50:52 +0000 Subject: [PATCH 204/970] - Fixed status enumerations SVN:trunk[281] --- setup/data/04.teams.xml | 4 +- setup/data/06.servers.xml | 408 +++++++++++++++++++------------------- 2 files changed, 206 insertions(+), 206 deletions(-) diff --git a/setup/data/04.teams.xml b/setup/data/04.teams.xml index f9f64f1c16..c4b005e438 100644 --- a/setup/data/04.teams.xml +++ b/setup/data/04.teams.xml @@ -2,7 +2,7 @@ Application support -implementation +available 2 application@mecanorama.com 33456456788 @@ -10,7 +10,7 @@ ITOP admin team -production +available 2 combodo@gmail.com 33123453612 diff --git a/setup/data/06.servers.xml b/setup/data/06.servers.xml index 1e082ac82c..3343dc83a7 100644 --- a/setup/data/06.servers.xml +++ b/setup/data/06.servers.xml @@ -2,7 +2,7 @@ domino.combodo.com -InProduction +production 2 high 3 @@ -22,7 +22,7 @@ server02 -ProductionCandidate +production 2 high 3 @@ -42,7 +42,7 @@ server03 -ProductionCandidate +production 2 high 3 @@ -62,7 +62,7 @@ server04 -ProductionCandidate +production 2 high 3 @@ -82,7 +82,7 @@ server05 -ProductionCandidate +production 2 high 3 @@ -102,7 +102,7 @@ server06 -ProductionCandidate +production 2 medium 3 @@ -122,7 +122,7 @@ server07 -ProductionCandidate +production 2 high 3 @@ -142,7 +142,7 @@ server08 -ProductionCandidate +production 2 high 3 @@ -162,7 +162,7 @@ server09 -ProductionCandidate +production 2 high 3 @@ -182,7 +182,7 @@ server10 -ProductionCandidate +production 2 high 3 @@ -202,7 +202,7 @@ server100 -ProductionCandidate +production 2 high 2 @@ -222,7 +222,7 @@ server101 -ProductionCandidate +production 2 high 2 @@ -242,7 +242,7 @@ server102 -ProductionCandidate +production 2 high 2 @@ -262,7 +262,7 @@ server103 -ProductionCandidate +production 2 high 2 @@ -282,7 +282,7 @@ server104 -ProductionCandidate +production 2 high 2 @@ -302,7 +302,7 @@ server105 -ProductionCandidate +production 2 high 2 @@ -322,7 +322,7 @@ server106 -ProductionCandidate +production 2 high 2 @@ -342,7 +342,7 @@ server107 -ProductionCandidate +production 2 high 2 @@ -362,7 +362,7 @@ server108 -ProductionCandidate +production 2 high 2 @@ -382,7 +382,7 @@ server109 -ProductionCandidate +production 2 high 2 @@ -402,7 +402,7 @@ server11 -ProductionCandidate +production 2 high 3 @@ -422,7 +422,7 @@ server110 -ProductionCandidate +production 2 high 2 @@ -442,7 +442,7 @@ server111 -ProductionCandidate +production 2 high 2 @@ -462,7 +462,7 @@ server112 -ProductionCandidate +production 2 high 2 @@ -482,7 +482,7 @@ server113 -ProductionCandidate +production 2 high 2 @@ -502,7 +502,7 @@ server114 -ProductionCandidate +production 2 high 2 @@ -522,7 +522,7 @@ server115 -ProductionCandidate +production 2 high 2 @@ -542,7 +542,7 @@ server116 -ProductionCandidate +production 2 high 2 @@ -562,7 +562,7 @@ server117 -ProductionCandidate +production 2 high 2 @@ -582,7 +582,7 @@ server118 -ProductionCandidate +production 2 high 2 @@ -602,7 +602,7 @@ server119 -ProductionCandidate +production 2 high 2 @@ -622,7 +622,7 @@ server12 -ProductionCandidate +production 2 high 3 @@ -642,7 +642,7 @@ server120 -ProductionCandidate +production 2 high 2 @@ -662,7 +662,7 @@ server121 -ProductionCandidate +production 2 high 2 @@ -682,7 +682,7 @@ server122 -ProductionCandidate +production 2 high 2 @@ -702,7 +702,7 @@ server123 -ProductionCandidate +production 2 high 2 @@ -722,7 +722,7 @@ server124 -ProductionCandidate +production 2 high 2 @@ -742,7 +742,7 @@ server125 -ProductionCandidate +production 2 high 2 @@ -762,7 +762,7 @@ server126 -ProductionCandidate +production 2 high 2 @@ -782,7 +782,7 @@ server127 -ProductionCandidate +production 2 high 2 @@ -802,7 +802,7 @@ server128 -ProductionCandidate +production 2 high 2 @@ -822,7 +822,7 @@ server129 -ProductionCandidate +production 2 high 2 @@ -842,7 +842,7 @@ server13 -ProductionCandidate +production 2 high 3 @@ -862,7 +862,7 @@ server130 -ProductionCandidate +production 2 high 2 @@ -882,7 +882,7 @@ server131 -ProductionCandidate +production 2 high 2 @@ -902,7 +902,7 @@ server132 -ProductionCandidate +production 2 high 2 @@ -922,7 +922,7 @@ server133 -ProductionCandidate +production 2 high 2 @@ -942,7 +942,7 @@ server134 -ProductionCandidate +production 2 high 2 @@ -962,7 +962,7 @@ server135 -ProductionCandidate +production 2 high 2 @@ -982,7 +982,7 @@ server136 -ProductionCandidate +production 2 high 2 @@ -1002,7 +1002,7 @@ server137 -ProductionCandidate +production 2 high 2 @@ -1022,7 +1022,7 @@ server138 -ProductionCandidate +production 2 high 2 @@ -1042,7 +1042,7 @@ server139 -ProductionCandidate +production 2 high 2 @@ -1062,7 +1062,7 @@ server14 -ProductionCandidate +production 2 high 3 @@ -1082,7 +1082,7 @@ server140 -ProductionCandidate +production 2 high 2 @@ -1102,7 +1102,7 @@ server141 -ProductionCandidate +production 2 high 2 @@ -1122,7 +1122,7 @@ server142 -ProductionCandidate +production 2 high 2 @@ -1142,7 +1142,7 @@ server143 -ProductionCandidate +production 2 high 2 @@ -1162,7 +1162,7 @@ server144 -ProductionCandidate +production 2 high 2 @@ -1182,7 +1182,7 @@ server145 -ProductionCandidate +production 2 high 2 @@ -1202,7 +1202,7 @@ server146 -ProductionCandidate +production 2 high 2 @@ -1222,7 +1222,7 @@ server147 -ProductionCandidate +production 2 high 2 @@ -1242,7 +1242,7 @@ server148 -ProductionCandidate +production 2 high 2 @@ -1262,7 +1262,7 @@ server149 -ProductionCandidate +production 2 high 2 @@ -1282,7 +1282,7 @@ server15 -ProductionCandidate +production 2 high 3 @@ -1302,7 +1302,7 @@ server150 -ProductionCandidate +production 2 high 2 @@ -1322,7 +1322,7 @@ server151 -ProductionCandidate +production 2 high 2 @@ -1342,7 +1342,7 @@ server152 -ProductionCandidate +production 2 high 2 @@ -1362,7 +1362,7 @@ server153 -ProductionCandidate +production 2 high 2 @@ -1382,7 +1382,7 @@ server154 -ProductionCandidate +production 2 high 2 @@ -1402,7 +1402,7 @@ server155 -ProductionCandidate +production 2 high 2 @@ -1422,7 +1422,7 @@ server156 -ProductionCandidate +production 2 high 2 @@ -1442,7 +1442,7 @@ server157 -ProductionCandidate +production 2 high 2 @@ -1462,7 +1462,7 @@ server158 -ProductionCandidate +production 2 high 2 @@ -1482,7 +1482,7 @@ server159 -ProductionCandidate +production 2 high 2 @@ -1502,7 +1502,7 @@ server16 -ProductionCandidate +production 2 high 3 @@ -1522,7 +1522,7 @@ server160 -ProductionCandidate +production 2 high 2 @@ -1542,7 +1542,7 @@ server161 -ProductionCandidate +production 2 high 2 @@ -1562,7 +1562,7 @@ server162 -ProductionCandidate +production 2 high 2 @@ -1582,7 +1582,7 @@ server163 -ProductionCandidate +production 2 high 2 @@ -1602,7 +1602,7 @@ server164 -ProductionCandidate +production 2 high 2 @@ -1622,7 +1622,7 @@ server165 -ProductionCandidate +production 2 high 2 @@ -1642,7 +1642,7 @@ server166 -ProductionCandidate +production 2 high 2 @@ -1662,7 +1662,7 @@ server167 -ProductionCandidate +production 2 high 2 @@ -1682,7 +1682,7 @@ server168 -ProductionCandidate +production 2 high 2 @@ -1702,7 +1702,7 @@ server169 -ProductionCandidate +production 2 high 2 @@ -1722,7 +1722,7 @@ server17 -ProductionCandidate +production 2 high 3 @@ -1742,7 +1742,7 @@ server170 -ProductionCandidate +production 2 high 2 @@ -1762,7 +1762,7 @@ server171 -ProductionCandidate +production 2 high 2 @@ -1782,7 +1782,7 @@ server172 -ProductionCandidate +production 2 high 2 @@ -1802,7 +1802,7 @@ server173 -ProductionCandidate +production 2 high 2 @@ -1822,7 +1822,7 @@ server174 -ProductionCandidate +production 2 high 2 @@ -1842,7 +1842,7 @@ server175 -ProductionCandidate +production 2 high 2 @@ -1862,7 +1862,7 @@ server176 -ProductionCandidate +production 2 high 2 @@ -1882,7 +1882,7 @@ server177 -ProductionCandidate +production 2 high 2 @@ -1902,7 +1902,7 @@ server178 -ProductionCandidate +production 2 high 2 @@ -1922,7 +1922,7 @@ server179 -ProductionCandidate +production 2 high 2 @@ -1942,7 +1942,7 @@ server18 -ProductionCandidate +production 2 high 3 @@ -1962,7 +1962,7 @@ server180 -ProductionCandidate +production 2 high 2 @@ -1982,7 +1982,7 @@ server181 -ProductionCandidate +production 2 high 2 @@ -2002,7 +2002,7 @@ server182 -ProductionCandidate +production 2 high 2 @@ -2022,7 +2022,7 @@ server183 -ProductionCandidate +production 2 high 2 @@ -2042,7 +2042,7 @@ server184 -ProductionCandidate +production 2 high 2 @@ -2062,7 +2062,7 @@ server185 -ProductionCandidate +production 2 high 2 @@ -2082,7 +2082,7 @@ server186 -ProductionCandidate +production 2 high 2 @@ -2102,7 +2102,7 @@ server187 -ProductionCandidate +production 2 high 2 @@ -2122,7 +2122,7 @@ server188 -ProductionCandidate +production 2 high 2 @@ -2142,7 +2142,7 @@ server189 -ProductionCandidate +production 2 high 2 @@ -2162,7 +2162,7 @@ server19 -ProductionCandidate +production 2 high 3 @@ -2182,7 +2182,7 @@ server190 -ProductionCandidate +production 2 high 2 @@ -2202,7 +2202,7 @@ server191 -ProductionCandidate +production 2 high 2 @@ -2222,7 +2222,7 @@ server192 -ProductionCandidate +production 2 high 2 @@ -2242,7 +2242,7 @@ server193 -ProductionCandidate +production 2 high 2 @@ -2262,7 +2262,7 @@ server194 -ProductionCandidate +production 2 high 2 @@ -2282,7 +2282,7 @@ server195 -ProductionCandidate +production 2 high 2 @@ -2302,7 +2302,7 @@ server196 -ProductionCandidate +production 2 high 2 @@ -2322,7 +2322,7 @@ server197 -ProductionCandidate +production 2 high 2 @@ -2342,7 +2342,7 @@ server198 -ProductionCandidate +production 2 high 2 @@ -2362,7 +2362,7 @@ server199 -ProductionCandidate +production 2 high 2 @@ -2382,7 +2382,7 @@ server20 -ProductionCandidate +production 2 high 3 @@ -2402,7 +2402,7 @@ server21 -ProductionCandidate +production 2 high 3 @@ -2422,7 +2422,7 @@ server22 -ProductionCandidate +production 2 high 3 @@ -2442,7 +2442,7 @@ server23 -ProductionCandidate +production 2 high 3 @@ -2462,7 +2462,7 @@ server24 -ProductionCandidate +production 2 high 3 @@ -2482,7 +2482,7 @@ server25 -ProductionCandidate +production 2 high 3 @@ -2502,7 +2502,7 @@ server26 -ProductionCandidate +production 2 high 3 @@ -2522,7 +2522,7 @@ server27 -ProductionCandidate +production 2 high 3 @@ -2542,7 +2542,7 @@ server28 -ProductionCandidate +production 2 high 3 @@ -2562,7 +2562,7 @@ server29 -ProductionCandidate +production 2 high 3 @@ -2582,7 +2582,7 @@ server30 -ProductionCandidate +production 2 high 3 @@ -2602,7 +2602,7 @@ server31 -ProductionCandidate +production 2 high 3 @@ -2622,7 +2622,7 @@ server32 -ProductionCandidate +production 2 high 3 @@ -2642,7 +2642,7 @@ server33 -ProductionCandidate +production 2 high 3 @@ -2662,7 +2662,7 @@ server34 -ProductionCandidate +production 2 high 3 @@ -2682,7 +2682,7 @@ server35 -ProductionCandidate +production 2 high 3 @@ -2702,7 +2702,7 @@ server36 -ProductionCandidate +production 2 high 3 @@ -2722,7 +2722,7 @@ server37 -ProductionCandidate +production 2 high 3 @@ -2742,7 +2742,7 @@ server38 -ProductionCandidate +production 2 high 3 @@ -2762,7 +2762,7 @@ server39 -ProductionCandidate +production 2 high 3 @@ -2782,7 +2782,7 @@ server40 -ProductionCandidate +production 2 high 3 @@ -2802,7 +2802,7 @@ server41 -ProductionCandidate +production 2 high 3 @@ -2822,7 +2822,7 @@ server42 -ProductionCandidate +production 2 high 3 @@ -2842,7 +2842,7 @@ server43 -ProductionCandidate +production 2 high 3 @@ -2862,7 +2862,7 @@ server44 -ProductionCandidate +production 2 high 3 @@ -2882,7 +2882,7 @@ server45 -ProductionCandidate +production 2 high 3 @@ -2902,7 +2902,7 @@ server46 -ProductionCandidate +production 2 high 3 @@ -2922,7 +2922,7 @@ server47 -ProductionCandidate +production 2 high 3 @@ -2942,7 +2942,7 @@ server48 -ProductionCandidate +production 2 high 3 @@ -2962,7 +2962,7 @@ server49 -ProductionCandidate +production 2 high 3 @@ -2982,7 +2982,7 @@ server50 -ProductionCandidate +production 2 high 3 @@ -3002,7 +3002,7 @@ server51 -ProductionCandidate +production 2 high 3 @@ -3022,7 +3022,7 @@ server52 -ProductionCandidate +production 2 high 3 @@ -3042,7 +3042,7 @@ server53 -ProductionCandidate +production 2 high 3 @@ -3062,7 +3062,7 @@ server54 -ProductionCandidate +production 2 high 3 @@ -3082,7 +3082,7 @@ server55 -ProductionCandidate +production 2 high 3 @@ -3102,7 +3102,7 @@ server56 -ProductionCandidate +production 2 high 3 @@ -3122,7 +3122,7 @@ server57 -ProductionCandidate +production 2 high 3 @@ -3142,7 +3142,7 @@ server58 -ProductionCandidate +production 2 high 3 @@ -3162,7 +3162,7 @@ server59 -ProductionCandidate +production 2 high 3 @@ -3182,7 +3182,7 @@ server60 -ProductionCandidate +production 2 high 3 @@ -3202,7 +3202,7 @@ server61 -ProductionCandidate +production 2 high 3 @@ -3222,7 +3222,7 @@ server62 -ProductionCandidate +production 2 high 3 @@ -3242,7 +3242,7 @@ server63 -ProductionCandidate +production 2 high 3 @@ -3262,7 +3262,7 @@ server64 -ProductionCandidate +production 2 high 3 @@ -3282,7 +3282,7 @@ server65 -ProductionCandidate +production 2 high 3 @@ -3302,7 +3302,7 @@ server66 -ProductionCandidate +production 2 high 3 @@ -3322,7 +3322,7 @@ server67 -ProductionCandidate +production 2 high 3 @@ -3342,7 +3342,7 @@ server68 -ProductionCandidate +production 2 high 3 @@ -3362,7 +3362,7 @@ server69 -ProductionCandidate +production 2 high 3 @@ -3382,7 +3382,7 @@ server70 -ProductionCandidate +production 2 high 3 @@ -3402,7 +3402,7 @@ server71 -ProductionCandidate +production 2 high 3 @@ -3422,7 +3422,7 @@ server72 -ProductionCandidate +production 2 high 3 @@ -3442,7 +3442,7 @@ server73 -ProductionCandidate +production 2 high 3 @@ -3462,7 +3462,7 @@ server74 -ProductionCandidate +production 2 high 3 @@ -3482,7 +3482,7 @@ server75 -ProductionCandidate +production 2 high 3 @@ -3502,7 +3502,7 @@ server76 -ProductionCandidate +production 2 high 3 @@ -3522,7 +3522,7 @@ server77 -ProductionCandidate +production 2 high 3 @@ -3542,7 +3542,7 @@ server78 -ProductionCandidate +production 2 high 3 @@ -3562,7 +3562,7 @@ server79 -ProductionCandidate +production 2 high 3 @@ -3582,7 +3582,7 @@ server80 -ProductionCandidate +production 2 high 3 @@ -3602,7 +3602,7 @@ server81 -ProductionCandidate +production 2 high 3 @@ -3622,7 +3622,7 @@ server82 -ProductionCandidate +production 2 high 3 @@ -3642,7 +3642,7 @@ server83 -ProductionCandidate +production 2 high 3 @@ -3662,7 +3662,7 @@ server84 -ProductionCandidate +production 2 high 3 @@ -3682,7 +3682,7 @@ server85 -ProductionCandidate +production 2 high 2 @@ -3702,7 +3702,7 @@ server86 -ProductionCandidate +production 2 high 2 @@ -3722,7 +3722,7 @@ server87 -ProductionCandidate +production 2 high 2 @@ -3742,7 +3742,7 @@ server88 -ProductionCandidate +production 2 high 2 @@ -3762,7 +3762,7 @@ server89 -ProductionCandidate +production 2 high 2 @@ -3782,7 +3782,7 @@ server90 -ProductionCandidate +production 2 high 2 @@ -3802,7 +3802,7 @@ server91 -ProductionCandidate +production 2 high 2 @@ -3822,7 +3822,7 @@ server92 -ProductionCandidate +production 2 high 2 @@ -3842,7 +3842,7 @@ server93 -ProductionCandidate +production 2 high 2 @@ -3862,7 +3862,7 @@ server94 -ProductionCandidate +production 2 high 2 @@ -3882,7 +3882,7 @@ server95 -ProductionCandidate +production 2 high 2 @@ -3902,7 +3902,7 @@ server96 -ProductionCandidate +production 2 high 2 @@ -3922,7 +3922,7 @@ server97 -ProductionCandidate +production 2 high 2 @@ -3942,7 +3942,7 @@ server98 -ProductionCandidate +production 2 high 2 @@ -3962,7 +3962,7 @@ server99 -ProductionCandidate +production 2 high 2 @@ -3982,7 +3982,7 @@ srv01 -Plugged +production 2 low 3 @@ -4002,7 +4002,7 @@ srv01.combodo.com -ProductionCandidate +production 2 high 3 @@ -4022,7 +4022,7 @@ srv02.combodo.com -ProductionCandidate +production 2 high 3 @@ -4042,7 +4042,7 @@ srv03.combodo.com -ProductionCandidate +production 2 high 3 @@ -4062,7 +4062,7 @@ srv04.combodo.com -ProductionCandidate +production 2 high 3 From 1fb8c78fe0f8543c7379eccdf2ecfe72b015ab6a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 19 Jan 2010 09:37:29 +0000 Subject: [PATCH 205/970] Installation: improved the reporting during data load SVN:trunk[282] --- setup/xmldataloader.class.inc.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 3cd29cab14..46ee3acec4 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -168,14 +168,18 @@ class XMLDataLoader $iExtKey = -$iDstObj; // Convention: Unresolved keys are stored as negative ! $oTargetObj->RegisterAsDirty(); } - // tested by Romain, little impact on perf (not significant on the intial setup) + // here we allow external keys to be invalid because we will resolve them later on... //$oTargetObj->CheckValue($sAttCode, $iExtKey); $oTargetObj->Set($sAttCode, $iExtKey); } else { // tested by Romain, little impact on perf (not significant on the intial setup) - //$oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode); + if (!$oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode)) + { + SetupWebPage::log("Error - Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."'"); + echo "Wrong value for attribute $sAttCode: '".$oXmlObj->$sAttCode."'"; + } $oTargetObj->Set($sAttCode, (string)$oXmlObj->$sAttCode); } } @@ -219,6 +223,7 @@ class XMLDataLoader } catch(Exception $e) { + SetupWebPage::log("Error - An object could not be loaded - $sClass/$iSrcId - ".$e->getMessage()); echo $e->GetHtmlDesc(); } $aParentClasses = MetaModel::EnumParentClasses($sClass); From 8078e2ce2613979658c12a97d0dcb0c881fd7584 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 19 Jan 2010 09:38:58 +0000 Subject: [PATCH 206/970] Installation: allow a service manager to write bizService objects SVN:trunk[283] --- addons/userrights/userrightsprofile.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 2955289873..b127f38316 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -1429,6 +1429,7 @@ class SetupITILProfiles 'lnkContactChange', ), 'Service' => array( + 'bizService', 'bizContract', 'lnkInfraContract', 'lnkContactContract', From 24713204afa42cc98088b5b9a2cd88b3024855b2 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 19 Jan 2010 09:50:51 +0000 Subject: [PATCH 207/970] Installation: disabled the notification templates (was attempting to send emails when loading sample data) SVN:trunk[284] --- setup/data/structure/24.actions.xml | 106 ++++++++++++++-------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/setup/data/structure/24.actions.xml b/setup/data/structure/24.actions.xml index 465e9b2d8e..1b7af12f57 100644 --- a/setup/data/structure/24.actions.xml +++ b/setup/data/structure/24.actions.xml @@ -1,16 +1,16 @@ - - - -Incident Notification to a Workgroup -This action informs a team that a ticket has been assigned their workgroup -enabled - - - -SELECT bizTeam AS t JOIN bizWorkgroup AS w ON w.team_id = t.id WHERE w.id=:this->workgroup_id - - -The ticket $this->name()$, severity $this->severity$ has been assigned to the workgroup $this->workgroup_name$ + + + +Incident Notification to a Workgroup +This action informs a team that a ticket has been assigned their workgroup +disabled + + + +SELECT bizTeam AS t JOIN bizWorkgroup AS w ON w.team_id = t.id WHERE w.id=:this->workgroup_id + + +The ticket $this->name()$, severity $this->severity$ has been assigned to the workgroup $this->workgroup_name$ <html> <body> <p>The incident ticket $this->name()$ has been assigned to the workgroup $this->workgroup_name$.</p> @@ -19,20 +19,20 @@ <hr/> <p>for more information on this ticket, click here: $this->hyperlink()$</p> </body> -</html> -normal - - -Incident notification to Agent -This action informs an agent that a ticket has been assigned to her/him -enabled - - - -SELECT bizPerson WHERE id=:this->agent_id - - -The ticket $this->name()$, severity $this->severity$ has been assigned to you +</html> +normal + + +Incident notification to Agent +This action informs an agent that a ticket has been assigned to her/him +disabled + + + +SELECT bizPerson WHERE id=:this->agent_id + + +The ticket $this->name()$, severity $this->severity$ has been assigned to you <html> <body> <p>The incident ticket $this->name()$ has been assigned to you.</p> @@ -41,29 +41,29 @@ <hr/> <p>for more information on this ticket, click here: $this->hyperlink()$</p> </body> -</html> -normal - - -Incident Notification to caller -This action is used to inform the caller -enabled - - - -SELECT bizPerson WHERE id=:this->caller_id - - -Ticket $this->name()$, severity $this->severity$ - $this->ticket_status$ -<html> -<body> -<p>The incident ticket $this->name()$ has changed to status $this->ticket_status$</p> -<p>Current situation: $this->current_situation$</p> -<p>Last update: $this->last_update$</p> -<hr/> -<p>for more information on this ticket, click here: $this->hyperlink()$</p> -</body> -</html> -normal - - \ No newline at end of file +</html> +normal + + +Incident Notification to caller +This action is used to inform the caller +disabled + + + +SELECT bizPerson WHERE id=:this->caller_id + + +Ticket $this->name()$, severity $this->severity$ - $this->ticket_status$ +<html> +<body> +<p>The incident ticket $this->name()$ has changed to status $this->ticket_status$</p> +<p>Current situation: $this->current_situation$</p> +<p>Last update: $this->last_update$</p> +<hr/> +<p>for more information on this ticket, click here: $this->hyperlink()$</p> +</body> +</html> +normal + + From 0e7297087b54d991b87b5a41cb2d45a6e8ede803 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 19 Jan 2010 12:39:28 +0000 Subject: [PATCH 208/970] - Don't loose the current context (current Org) - When switching Org, reload the current pages SVN:trunk[285] --- application/itopwebpage.class.inc.php | 2 +- application/template.class.inc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 07ffba9797..f89976d076 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -135,7 +135,7 @@ EOF { // Combo box to select the organization $this->AddToMenu("
    - \n"); // List of visible Organizations $oContext = new UserContext(); $oSearchFilter = $oContext->NewFilter("bizOrganization"); diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 807ffdf301..6d13ff67af 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -34,7 +34,7 @@ class DisplayTemplate $oBlock = DisplayBlock::FromTemplate($sOuterTag); if (is_object($oBlock)) { - $oBlock->Display($oPage, 'block_'.self::$iBlockCount); + $oBlock->Display($oPage, 'block_'.self::$iBlockCount, $aParams); } self::$iBlockCount++; } From 07ebe739db008efdba41bf7230581029d9e7d5c2 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 19 Jan 2010 12:40:44 +0000 Subject: [PATCH 209/970] - Add the org_id in the shortlist of the Network Device SVN:trunk[286] --- business/itop.business.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index d4d589dc1f..09887c586e 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -1364,7 +1364,7 @@ class bizNetworkDevice extends bizDevice // Display lists MetaModel::Init_SetZListItems('details', array('name', 'status','severity','org_id', 'location_id', 'brand','model','type','mgmt_ip','default_gateway','serial_number','ios_version','memory','snmp_read','snmp_write')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('name', 'status','brand','model','type','mgmt_ip')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('name', 'status','org_id','brand','model','type','mgmt_ip')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'location_id', 'brand','model','type','mgmt_ip')); // Criteria of the std search form From f6b572ebcdcb904af68ddd8ebf1862fe4bd0ce3b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 19 Jan 2010 15:32:09 +0000 Subject: [PATCH 210/970] - Fixed the display of audit errors SVN:trunk[287] --- pages/audit.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pages/audit.php b/pages/audit.php index edfa99a3e8..af388e3a80 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -75,7 +75,10 @@ switch($operation) $oAuditRule = $oContext->GetObject('AuditRule', $iRuleIndex); $oP->add(''); $oP->p('[Back to audit results]'); - cmdbAbstractObject::DisplaySet($oP, $oErrorObjectSet); + $sBlockId = 'audit_errors'; + $oP->p("
    \n"); + cmdbAbstractObject::DisplaySet($oP, $oErrorObjectSet, array('block_id' => $sBlockId)); + $oP->p("
    \n"); break; case 'audit': From 73f0ac4e1c9ad8a7f47d871388d2caf779b36c8f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 25 Jan 2010 16:12:18 +0000 Subject: [PATCH 211/970] Trac#4 Improved the display of finalclass field (search results and graphs) SVN:trunk[288] --- application/displayblock.class.inc.php | 4 ++-- core/attributedef.class.inc.php | 5 +++++ core/metamodel.class.php | 5 +++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 74d9f9e23b..2f07a53e9c 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -298,7 +298,7 @@ class DisplayBlock $aGroupBy = array(); while($oObj = $this->m_oSet->Fetch()) { - $sValue = $oObj->Get($sGroupByField); + $sValue = $oObj->GetAsHtml($sGroupByField); $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; } $sFilter = urlencode($this->m_oFilter->serialize()); @@ -552,7 +552,7 @@ class DisplayBlock $aGroupBy = array(); while($oObj = $this->m_oSet->Fetch()) { - $sValue = $oObj->Get($sGroupByField); + $sValue = $oObj->GetAsHtml($sGroupByField); $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; } $sFilter = urlencode($this->m_oFilter->serialize()); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index ea207937dc..076453ade8 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -623,6 +623,11 @@ class AttributeClass extends AttributeString $aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']); parent::__construct($sCode, $aParams); } + + public function GetAsHTML($sValue) + { + return MetaModel::GetName($sValue); + } } /** diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 28a5fcce60..1725daaab1 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -832,10 +832,11 @@ abstract class MetaModel $sClassAttCode = 'finalclass'; $sRootClass = self::GetRootClass($sClass); $sDbFinalClassField = self::DBGetClassField($sRootClass); - $oClassAtt = new AttributeString($sClassAttCode, array( + $oClassAtt = new AttributeClass($sClassAttCode, array( "label"=>"Class", "description"=>"Real (final) object class", - "allowed_values"=>null, + "class_category"=>null, + "more_values"=>'', "sql"=>$sDbFinalClassField, "default_value"=>$sClass, "is_null_allowed"=>false, From 24e4e40e59a669468262f7970b71e28ef2b4f5a1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 25 Jan 2010 17:30:52 +0000 Subject: [PATCH 212/970] - fixed detection of the upload_tmp_dir (Trac #76 ) - added the check of the soap extension (Trac #68 ) SVN:trunk[289] --- setup/index.php | 56 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/setup/index.php b/setup/index.php index 811f082876..722cd012fb 100644 --- a/setup/index.php +++ b/setup/index.php @@ -16,6 +16,50 @@ define('MYSQL_MIN_VERSION', '5.0.0'); $sOperation = Utils::ReadParam('operation', 'step1'); $oP = new SetupWebPage('iTop configuration wizard'); +/** + * Helper function to retrieve the system's temporary directory + * Emulates sys_get_temp_dir if neeed (PHP < 5.2.1) + * @return string Path to the system's temp directory + */ +function GetTmpDir() +{ + // try to figure out what is the temporary directory + // prior to PHP 5.2.1 the function sys_get_temp_dir + // did not exist + if ( !function_exists('sys_get_temp_dir')) + { + if( $temp=getenv('TMP') ) return realpath($temp); + if( $temp=getenv('TEMP') ) return realpath($temp); + if( $temp=getenv('TMPDIR') ) return realpath($temp); + $temp=tempnam(__FILE__,''); + if (file_exists($temp)) + { + unlink($temp); + return realpath(dirname($temp)); + } + return null; + } + else + { + return realpath(sys_get_temp_dir()); + } +} + + +/** + * Helper function to retrieve the directory where files are to be uploaded + * @return string Path to the temp directory used for uploading files + */ +function GetUploadTmpDir() +{ + $sPath = ini_get('upload_tmp_dir'); + if (empty($sPath)) + { + $sPath = GetTmpDir(); + } + return $sPath; +} + /** * Helper function to check if the current version of PHP * is compatible with the application @@ -34,7 +78,7 @@ function CheckPHPVersion(SetupWebPage $oP) $oP->error("Error: The current PHP Version (".phpversion().") is lower than the minimum required version (".PHP_MIN_VERSION.")"); return false; } - $aMandatoryExtensions = array('mysql', 'iconv', 'simplexml'); + $aMandatoryExtensions = array('mysql', 'iconv', 'simplexml', 'soap'); asort($aMandatoryExtensions); // Sort the list to look clean ! $aExtensionsOk = array(); $aMissingExtensions = array(); @@ -76,11 +120,11 @@ function CheckPHPVersion(SetupWebPage $oP) $bResult = false; } - $sUploadTmpDir = ini_get('upload_tmp_dir'); - if (empty($sUploadTmpDir)) - { - $oP->error("Temporary directory for files upload is not defined (upload_tmp_dir)"); - $bResult = false; + $sUploadTmpDir = GetUploadTmpDir(); + if (empty($sUploadTmpDir)) + { + $sUploadTmpDir = '/tmp'; + $oP->warning("Temporary directory for files upload is not defined (upload_tmp_dir), assuming that $sUploadTmpDir is used."); } // check that the upload directory is indeed writable from PHP if (!empty($sUploadTmpDir)) From 68b9446fdd0b44c0309dbc9f2b13b906126c2d9b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 27 Jan 2010 17:13:55 +0000 Subject: [PATCH 213/970] - Fixed IE6/CSS bugs: #77 (calendar icon) and #78 (torn paper, workaround only) SVN:trunk[290] --- css/light-grey.css | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/css/light-grey.css b/css/light-grey.css index a3d4ef1ca8..3a768a5d74 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -262,6 +262,7 @@ a.CollapsibleLabel, td a.CollapsibleLabel { background: url(../images/mini-arrow-green.gif) no-repeat left; } +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ a.CollapsibleLabel.open, td a.CollapsibleLabel.open { margin: 0; padding: 0px 0pt 0px 16px; @@ -337,6 +338,7 @@ div.iTopLogo span { height: 50px; background: url(../images/vsplitter-grey.gif) repeat top; } +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ #MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover { background-color: #fff; } @@ -344,6 +346,7 @@ div.iTopLogo span { height: 8px; background-color: #fff; } +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ #MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover { background-color: #fff; } @@ -566,7 +569,7 @@ th.orange { /* For Date Picker: Creates a little calendar icon * instead of a text link for "Choose date" */ -td a.dp-choose-date, a.dp-choose-date, td a.dp-choose-date:hover, a.dp-choose-date:hover { +td a.dp-choose-date, a.dp-choose-date, td a.dp-choose-date:hover, a.dp-choose-date:hover, td a.dp-choose-date:visited, a.dp-choose-date:visited { float: left; width: 16px; height: 16px; @@ -664,24 +667,24 @@ div.HRDrawer { .mandatory { border: 1px solid #f00; } -table.listResults tr td.truncated { - background: transparent; -} -table.listResults tr td.truncated { +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ +table.listResults tr.odd td.truncated { background: url(../images/truncated.png) bottom repeat-x; - margin-bottom: -3px; } +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ table.listResults tr.even td.truncated { background: #f9f9f1 url(../images/truncated.png) bottom repeat-x; } -table.listResults tr.even td.truncated.hover { +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ +table.listResults tr.even td.hover.truncated { background: #E8FFD3 url(../images/truncated.png) bottom repeat-x; } -table.listResults tr.odd td.truncated.hover { +/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ +table.listResults tr.odd td.hover.truncated { background: #E8FFD3 url(../images/truncated.png) bottom repeat-x; } From f61ac3f4a0155ddc3420445a5ff959c906c3a743 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 28 Jan 2010 17:10:39 +0000 Subject: [PATCH 214/970] Fixed two bugs: - Trac#80 Misleading messages when using the back button during the setup (added back button in any error case) - Trac#72 Revised the setup to consider any installation scenario regarding Database and user privileges (user rights tracked into the log file) SVN:trunk[291] --- application/application.inc.php | 1 - core/cmdbsource.class.inc.php | 32 ++++++++++++++++-- core/metamodel.class.php | 55 +++++++++++++++++++++++-------- core/test.class.inc.php | 4 +-- pages/ITopConsultant.php | 4 +-- setup/index.php | 57 ++++++++++++++++++++++----------- 6 files changed, 114 insertions(+), 39 deletions(-) diff --git a/application/application.inc.php b/application/application.inc.php index d15464c19e..f613ff1e68 100644 --- a/application/application.inc.php +++ b/application/application.inc.php @@ -4,7 +4,6 @@ require_once('../application/applicationcontext.class.inc.php'); require_once('../application/usercontext.class.inc.php'); require_once('../application/cmdbabstract.class.inc.php'); require_once('../application/displayblock.class.inc.php'); -require_once('../application/iotask.class.inc.php'); require_once('../application/audit.category.class.inc.php'); require_once('../application/audit.rule.class.inc.php'); //require_once('../application/menunode.class.inc.php'); diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 3543349e69..8c0e72409d 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -20,6 +20,7 @@ class MySQLException extends CoreException public function __construct($sIssue, $aContext) { $aContext['mysql_error'] = mysql_error(); + $aContext['mysql_errno'] = mysql_errno(); parent::__construct($sIssue, $aContext); } } @@ -41,13 +42,13 @@ class CMDBSource self::$m_sDBName = $sSource; if (!self::$m_resDBLink = @mysql_pconnect($sServer, $sUser, $sPwd)) { - throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer)); + throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer, 'user'=>$sUser)); } if (!empty($sSource)) { if (!mysql_select_db($sSource, self::$m_resDBLink)) { - throw new MySQLException('Could not select DB', array('db_name'=>$sSource)); + throw new MySQLException('Could not select DB', array('host'=>$sServer, 'user'=>$sUser, 'db_name'=>$sSource)); } } } @@ -464,6 +465,33 @@ class CMDBSource } return $result; } + + + /** + * Returns the privileges of the current user + * @return string privileges in a raw format + */ + public static function GetRawPrivileges() + { + try + { + $result = self::Query('SHOW GRANTS'); // [ FOR CURRENT_USER()] + } + catch(MySQLException $e) + { + return "Current user not allowed to see his own privileges (could not access to the database 'mysql' - $iCode)"; + } + + $aRes = array(); + while ($aRow = mysql_fetch_array($result, MYSQL_NUM)) + { + // so far, only one column... + $aRes[] = implode('/', $aRow); + } + mysql_free_result($result); + // so far, only one line... + return implode(', ', $aRes); + } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 1725daaab1..6e053ea561 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1941,29 +1941,56 @@ abstract class MetaModel echo "\n"; } - public static function DBExists() + public static function DBExists($bMustBeComplete = true) { - // returns true if at least one table exists (taking into account the DB sharing) - // then some tables might be missing, but that is made in DBCheckFormat + // returns true if at least one table exists // - if (empty(self::$m_sTablePrefix)) - { - return CMDBSource::IsDB(self::$m_sDBName); - } - // DB sharing - // Check if there is at least one table with the prefix - // if (!CMDBSource::IsDB(self::$m_sDBName)) { return false; } CMDBSource::SelectDB(self::$m_sDBName); - // If a table matches the prefix, then consider that the database already exists - $sSQL = "SHOW TABLES LIKE '".strtolower(self::$m_sTablePrefix)."%' "; - $result = CMDBSource::Query($sSQL); - return (CMDBSource::NbRows($result) > 0); + $aFound = array(); + $aMissing = array(); + foreach (self::DBEnumTables() as $sTable => $aClasses) + { + if (CMDBSource::IsTable($sTable)) + { + $aFound[] = $sTable; + } + else + { + $aMissing[] = $sTable; + } + } + + if (count($aFound) == 0) + { + // no expected table has been found + return false; + } + else + { + if (count($aMissing) == 0) + { + // the database is complete (still, could be some fields missing!) + return true; + } + else + { + // not all the tables, could be an older version + if ($bMustBeComplete) + { + return false; + } + else + { + return true; + } + } + } } public static function DBDrop() diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 87af7e1659..1b1a6cdf0f 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -461,7 +461,7 @@ abstract class TestBizModel extends TestHandler protected function ResetDB() { - if (MetaModel::DBExists()) + if (MetaModel::DBExists(false)) { MetaModel::DBDrop(); } @@ -527,7 +527,7 @@ abstract class TestBizModelGeneric extends TestBizModel { parent::DoPrepare(); - if (!MetaModel::DBExists()) + if (!MetaModel::DBExists(false)) { MetaModel::DBCreate(); } diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index 68dcb6dcad..022788c8c3 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -151,9 +151,9 @@ function ShowDatabaseInfo() function CreateDB() { $sRes = "

    Creating the DB...

    \n"; - if (MetaModel::DBExists()) + if (MetaModel::DBExists(false)) { - $sRes .= "

    It appears that the DB already exists.

    \n"; + $sRes .= "

    It appears that the DB already exists (at least one table).

    \n"; } else { diff --git a/setup/index.php b/setup/index.php index 722cd012fb..07abf9f49a 100644 --- a/setup/index.php +++ b/setup/index.php @@ -184,6 +184,9 @@ function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd) $oDBSource = new CMDBSource; $oDBSource->Init($sDBServer, $sDBUser, $sDBPwd); $oP->ok("Connection to '$sDBServer' as '$sDBUser' successful."); + + $oP->log("Info - User privileges: ".($oDBSource->GetRawPrivileges())); + $sDBVersion = $oDBSource->GetDBVersion(); if (version_compare($sDBVersion, MYSQL_MIN_VERSION, '>=')) { @@ -262,19 +265,33 @@ function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bAllowMissingDatabas function CreateDatabaseStructure(SetupWebPage $oP, Config $oConfig, $sDBName, $sDBPrefix) { InitDataModel($oP, TMP_CONFIG_FILE, true); // Allow the DB to NOT exist since we're about to create it ! + $oP->log('Info - CreateDatabaseStructure'); - $oP->info("Creating the structure in '$sDBName' (prefix = '$sDBPrefix')."); - //MetaModel::CheckDefinitions(); - if (!MetaModel::DBExists()) + if (strlen($sDBPrefix) > 0) { - MetaModel::DBCreate(); - $oP->ok("Database structure created in '$sDBName' (prefix = '$sDBPrefix')."); + $oP->info("Creating the structure in '$sDBName' (table names prefixed by '$sDBPrefix')."); } else { - $oP->error("Error: database '$sDBName' (prefix = '$sDBPrefix') already exists."); - $oP->p("Tables with conflicting names already exist in the database. - Try selecting another database instance or specifiy a prefix to prevent conflicting table names."); + $oP->info("Creating the structure in '$sDBName'."); + } + + //MetaModel::CheckDefinitions(); + if (!MetaModel::DBExists(/* bMustBeComplete */ false)) + { + MetaModel::DBCreate(); + $oP->ok("Database structure successfuly created."); + } + else + { + if (strlen($sDBPrefix) > 0) + { + $oP->error("Error: found iTop tables into the database '$sDBName' (prefix: '$sDBPrefix'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names."); + } + else + { + $oP->error("Error: found iTop tables into the database '$sDBName'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names."); + } return false; } return true; @@ -380,8 +397,9 @@ function DisplayStep1(SetupWebPage $oP) $oP->add("
    Database connection\n"); $aForm = array(); $aForm[] = array('label' => "Server name$sRedStar:", 'input' => "", - 'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23".'); - $aForm[] = array('label' => "User name$sRedStar:", 'input' => ""); + 'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23"'); + $aForm[] = array('label' => "User name$sRedStar:", 'input' => "", + 'help' => 'The account must have the following privileges: SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER'); $aForm[] = array('label' => 'Password:', 'input' => ""); $oP->form($aForm); $oP->add("
    \n"); @@ -407,7 +425,7 @@ function DisplayStep2(SetupWebPage $oP, Config $oConfig, $sDBServer, $sDBUser, $ if ($aDatabases === false) { // Connection failed, invalid credentials ? Go back - $oP->add("\n"); + $oP->add("\n"); } else { @@ -441,7 +459,7 @@ function DisplayStep2(SetupWebPage $oP, Config $oConfig, $sDBServer, $sDBUser, $ $oP->form($aForm); $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("    \n"); $oP->add("\n"); } @@ -477,13 +495,13 @@ function DisplayStep3(SetupWebPage $oP, Config $oConfig, $sDBName, $sDBPrefix) $oP->form($aForm); $oP->add("\n"); $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("    \n"); $oP->add("\n"); } else { - $oP->add("\n"); + $oP->add("\n"); } // Form goes here $oP->add("\n"); @@ -509,14 +527,14 @@ function DisplayStep4(SetupWebPage $oP, Config $oConfig, $sAdminUser, $sAdminPwd $oP->p(" Yes, for testing purposes, populate the database with sample data.\n"); $oP->p(" No, this is a production system, load only the data required by the application.\n"); $oP->p("\n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("    \n"); $oP->add("\n"); } else { // Creation failed - $oP->add("\n"); + $oP->add("\n"); } // End of visible form $oP->add("\n"); @@ -561,7 +579,7 @@ function DisplayStep5(SetupWebPage $oP, Config $oConfig, $sAuthUser, $sAuthPwd) $oP->add("
    \n"); $oP->ok("The initialization completed successfully."); // Form goes here - $oP->add("\n"); + $oP->add("\n"); $oP->add("    \n"); $oP->add("\n"); $oP->add("
    \n"); @@ -575,7 +593,7 @@ function DisplayStep5(SetupWebPage $oP, Config $oConfig, $sAuthUser, $sAuthPwd) $oP->error("Error: Failed to login for user: '$sAuthUser'\n"); $oP->add("
    \n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("    \n"); $oP->add("
    \n"); } @@ -585,6 +603,7 @@ function DisplayStep5(SetupWebPage $oP, Config $oConfig, $sAuthUser, $sAuthPwd) $oP->error("Error: unable to create the configuration file."); $oP->p($e->getHtmlDesc()); $oP->p("Did you forget to remove the previous (read-only) configuration file ?"); + $oP->add("\n"); } } /** @@ -686,10 +705,12 @@ try catch(Exception $e) { $oP->error("Error: '".$e->getMessage()."'"); + $oP->add("\n"); } catch(CoreException $e) { $oP->error("Error: '".$e->getHtmlDesc()."'"); + $oP->add("\n"); } $oP->output(); ?> From e55a11df4bb19b483d4cb52c4fd7ef51520da5f5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 29 Jan 2010 17:52:47 +0000 Subject: [PATCH 215/970] Fixed #69 Accentuated chars now in utf-8 for real in the unit test page! BE CARREFUL WITH PSPAD!!! SVN:trunk[292] --- pages/testlist.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index cd0a15de22..341d313e5b 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -1471,7 +1471,7 @@ class TestTriggerAndEmail extends TestBizModel $oAction->Set("cc", $sCC); $oAction->Set("bcc", ""); $oAction->Set("subject", "New server: '\$this->name()$'"); - $oAction->Set("body", "

    Dear customer,

    We have created the server \$this->hyperlink()$ in the IT infrastructure database.

    You will be further notified when it is in Production.

    The IT infrastructure management team.

    Here are some accentuated characters for french people: ''

    "); + $oAction->Set("body", "

    Dear customer,

    We have created the server \$this->hyperlink()$ in the IT infrastructure database.

    You will be further notified when it is in Production.

    The IT infrastructure management team.

    Here are some accentuated characters for french people: 'ééà'

    "); $oAction->Set("importance", "low"); $iActionId = $this->ObjectToDB($oAction, true); From f8d6ef24072202855769bdb94192fd72e5dc1eab Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 29 Jan 2010 18:18:18 +0000 Subject: [PATCH 216/970] Fixed #81, character set and collation set to utf-8 by default SVN:trunk[293] --- core/cmdbsource.class.inc.php | 2 +- core/metamodel.class.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 8c0e72409d..13ac98f8f4 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -98,7 +98,7 @@ class CMDBSource public static function CreateDB($sSource) { - self::Query("CREATE DATABASE `$sSource`"); + self::Query("CREATE DATABASE `$sSource` CHARACTER SET utf8 COLLATE utf8_unicode_ci"); self::SelectDB($sSource); } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6e053ea561..acd8003c48 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2079,7 +2079,7 @@ abstract class MetaModel if (!CMDBSource::IsTable($sTable)) { $aErrors[$sClass][] = "table '$sTable' could not be found into the DB"; - $aSugFix[$sClass][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb"; + $aSugFix[$sClass][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; } // Check that the key field exists // From 1237fb92d55b90285debecf0ab43faff34753bd1 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 29 Jan 2010 18:22:00 +0000 Subject: [PATCH 217/970] Fixed #82, could not use wget due to a conflict with page argument 'operation' SVN:trunk[294] --- application/loginwebpage.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index ae8ae075d4..16195c5131 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -58,7 +58,7 @@ h1 { $this->add("\n"); $this->add(" \n"); $this->add("\n"); - $this->add("\n"); + $this->add("\n"); $this->add("\n"); $this->add("
    \n"); } @@ -79,7 +79,7 @@ h1 { static function DoLogin() { - $operation = utils::ReadParam('operation', ''); + $operation = utils::ReadParam('loginop', ''); session_start(); if ($operation == 'logoff') From 46c4d2f419ae3b49f8e59f74f3a5f6a1e44c5751 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 29 Jan 2010 18:26:03 +0000 Subject: [PATCH 218/970] Make sure that HTTP responses and the emails are considered as being utf-8... not sure of the real benefit, but we've seen a number of things about this so Denis and I decided to more forward and take the chance. SVN:trunk[295] --- application/itopwebpage.class.inc.php | 1 + core/email.class.inc.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index f89976d076..97f720d158 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -220,6 +220,7 @@ EOF echo "\n"; echo "\n"; echo "{$this->s_title}\n"; + echo "\n"; echo $this->get_base_tag(); // Stylesheets MUST be loaded before any scripts otherwise // jQuery scripts may face some spurious problems (like failing on a 'reload') diff --git a/core/email.class.inc.php b/core/email.class.inc.php index 3a8a23e886..136c6e5851 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -43,7 +43,9 @@ class EMail public function Send() { $sHeaders = 'MIME-Version: 1.0' . "\r\n"; - $sHeaders .= 'Content-type: text/html; charset=utf-8' . "\r\n"; + // ! the case is important for MS-Outlook + $sHeaders .= 'Content-Type: text/html; charset=UTF-8' . "\r\n"; + $sHeaders .= 'Content-Transfer-Encoding: 8bit' . "\r\n"; foreach ($this->m_aHeaders as $sKey => $sValue) { $sHeaders .= "$sKey: $sValue\r\n"; From 9952b9608d39f13fe30a6bf2ee3de41ad5b08eec Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 17 Feb 2010 09:10:24 +0000 Subject: [PATCH 219/970] - Removed the limit to the number of elements displayed in an autocomplete input. The maximum number of elements in the autocomplete is now 150 (scrolling enabled). (Trac #83) SVN:trunk[296] --- application/cmdbabstract.class.inc.php | 2 +- application/ui.linkswidget.class.inc.php | 2 +- pages/ajax.render.php | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3f3dbc2d87..c14eaa9f7e 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -741,7 +741,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sHTMLValue = ""; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; - $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); + $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); $oPage->add_ready_script("\$('#label_$iInputId').result( function(event, data, formatted) { if (data) { $('#{$iInputId}').val(data[1]); } } );"); // Prepopulate with a default value -- but no display value... //if (!empty($value)) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index aa6b476842..c01ea9d220 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -86,7 +86,7 @@ class UILinksWidget // another hidden input to store & pass the object's Id $sHTMLValue .= "m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n"; $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n"; - $oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\noLinkWidget{$this->m_iInputId}.Init();\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});"); + $oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\noLinkWidget{$this->m_iInputId}.Init();\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});"); $oPage->add_ready_script("\$('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled');"); $oPage->add_ready_script("\$('#ac_{$this->m_iInputId}').result( function(event, data, formatted) { if (data) { $('#id_ac_{$this->m_iInputId}').val(data[1]); $('#ac_add_{$this->m_iInputId}').attr('disabled', ''); } else { $('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled'); } } );"); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index fcc2e0e907..6a44e7e1aa 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -233,7 +233,6 @@ switch($operation) foreach($aAllowedValues as $key => $value) { $oPage->add($value."|".$key."\n"); - if ($iCount++) break; } break; From 0fe146ecfd35820a3b3d9e0c07bd0ea03806a69b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 17 Feb 2010 09:52:23 +0000 Subject: [PATCH 220/970] Fixed bug #79: When creating a new object, all mandatory attributes must be filled before the "Finish" button can be used (even for stateless objects). SVN:trunk[297] --- application/uiwizard.class.inc.php | 110 ++++++++++++++++++----------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 9ee6c1d835..61933198a2 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -146,58 +146,85 @@ $sJSHandlerCode $aStates = MetaModel::EnumStates($this->m_sClass); - if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) ) + $aMandatoryAttributes = array(); + // Some attributes are always mandatory independently of the state machine (if any) + foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + if (!$oAttDef->IsExternalField() && !$oAttDef->IsNullAllowed()) + { + $aMandatoryAttributes[$sAttCode] = OPT_ATT_MANDATORY; + } + } + + // Now check the attributes that are mandatory in the specified state + if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) ) { // Check all the fields that *must* be included in the wizard for this // particular target state $aFields = array(); foreach($aStates[$this->m_sTargetState]['attribute_list'] as $sAttCode => $iOptions) { - $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); - $sAttLabel = $oAttDef->GetLabel(); - - if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) - { - $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); - $aFields[$sAttCode] = array(); - foreach($aPrerequisites as $sCode) - { - $aFields[$sAttCode][$sCode] = ''; - } - } - } - - // Now use the dependencies between the fields to order them - while(count($aFields) > 0) + if (isset($aMandatoryAttributes[$sAttCode])) + { + $aMandatoryAttributes[$sAttCode] |= $iOptions; + } + else + { + $aMandatoryAttributes[$sAttCode] = $iOptions; + } + } + } + + // Check all the fields that *must* be included in the wizard + $aFields = array(); + foreach($aMandatoryAttributes as $sAttCode => $iOptions) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + $sAttLabel = $oAttDef->GetLabel(); + + if ( ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ) { - $aCurrentStep = array(); - foreach($aFields as $sAttCode => $aDependencies) + $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); + $aFields[$sAttCode] = array(); + foreach($aPrerequisites as $sCode) { - // All fields with no remaining dependencies can be entered at this - // step of the wizard - if (count($aDependencies) == 0) - { - $aCurrentStep[] = $sAttCode; - $aFieldsDone[$sAttCode] = ''; - unset($aFields[$sAttCode]); - // Remove this field from the dependencies of the other fields - foreach($aFields as $sUpdatedCode => $aDummy) - { - // remove the dependency - unset($aFields[$sUpdatedCode][$sAttCode]); - } - } + $aFields[$sAttCode][$sCode] = ''; } - if (count($aCurrentStep) == 0) - { - // This step of the wizard would contain NO field ! - echo "Error: Circular reference in the dependencies between the fields."; - print_r($aFields); - break; - } - $aWizardSteps['mandatory'][] = $aCurrentStep; } } + + // Now use the dependencies between the fields to order them + while(count($aFields) > 0) + { + $aCurrentStep = array(); + foreach($aFields as $sAttCode => $aDependencies) + { + // All fields with no remaining dependencies can be entered at this + // step of the wizard + if (count($aDependencies) == 0) + { + $aCurrentStep[] = $sAttCode; + $aFieldsDone[$sAttCode] = ''; + unset($aFields[$sAttCode]); + // Remove this field from the dependencies of the other fields + foreach($aFields as $sUpdatedCode => $aDummy) + { + // remove the dependency + unset($aFields[$sUpdatedCode][$sAttCode]); + } + } + } + if (count($aCurrentStep) == 0) + { + // This step of the wizard would contain NO field ! + echo "Error: Circular reference in the dependencies between the fields."; + print_r($aFields); + break; + } + $aWizardSteps['mandatory'][] = $aCurrentStep; + } + // Now computes the steps to fill the optional fields $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); @@ -258,7 +285,6 @@ $sJSHandlerCode } $aWizardSteps['optional'][] = $aCurrentStep; } - return $aWizardSteps; } From 4928220098c8b38649269c225daeead1e8d9d135 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 17 Feb 2010 22:45:17 +0000 Subject: [PATCH 221/970] - Try to protect as much as possible from fatal errors occuring during the data load: 1) Make sure we have enough memory to perform the task 2) In case of fatal error in the Ajax call, catch it, display it to the end-user and stop the installation. See Trac #84 for more information. SVN:trunk[298] --- setup/ajax.dataloader.php | 53 +++++++++++++++++++++++++++++++++++---- setup/index.php | 1 + setup/setup.js | 3 ++- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 66ab56195f..fab829719a 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -5,11 +5,51 @@ * 'file' string Name of the file to load * 'session_status' string 'start', 'continue' or 'end' * 'percent' integer 0..100 the percentage of completion once the file has been loaded - */ + */ +define('SAFE_MINIMUM_MEMORY', 32*1024*1024); require_once('../application/utils.inc.php'); +require_once('./setuppage.class.inc.php'); + +$iMemoryLimit = utils::ConvertToBytes(ini_get('memory_limit')); +if ($iMemoryLimit < SAFE_MINIMUM_MEMORY) +{ + if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE) + { + SetupWebPage::error("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); + } + else + { + SetupWebPage::log("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); + } +} + +function FatalErrorCatcher($sOutput) +{ + if ( preg_match('|.*|s', $sOutput, &$aMatches) ) + { + header("HTTP/1.0 500 Internal server error."); + foreach ($aMatches as $sMatch) + { + $errors .= strip_tags($sMatch)."\n"; + } + $sOutput = "$errors\n"; + // Logging to a file does not work if the whole memory is exhausted... + //SetupWebPage::error("Fatal error - in $__FILE__ , $errors"); + } + return $sOutput; +} + +//Define some bogus, invalid HTML tags that no sane +//person would ever put in an actual document and tell +//PHP to delimit fatal error warnings with them. +ini_set('error_prepend_string', ''); +ini_set('error_append_string', ''); + +// Starts the capture of the ouput, and sets a filter to capture the fatal errors. +ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it through the fatal error catcher + require_once('../core/config.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); -require_once('./setuppage.class.inc.php'); require_once('./xmldataloader.class.inc.php'); define('TMP_CONFIG_FILE', '../tmp-config-itop.php'); @@ -18,8 +58,7 @@ define('TMP_CONFIG_FILE', '../tmp-config-itop.php'); // Never cache this page header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past - - + /** * Main program */ @@ -62,6 +101,10 @@ catch(Exception $e) echo "

    An error happened while loading the data

    \n"; echo '

    '.$e."

    \n"; SetupWebPage::log("Error - An error happened while loading the data. ".$e); +} + +if (function_exists('memory_get_peak_usage')) +{ + SetupWebPage::log("Info - loading file '$sFileName', peak memory usage. ".memory_get_peak_usage()); } - ?> diff --git a/setup/index.php b/setup/index.php index 07abf9f49a..7afc7f5e4a 100644 --- a/setup/index.php +++ b/setup/index.php @@ -544,6 +544,7 @@ function DisplayStep4(SetupWebPage $oP, Config $oConfig, $sAdminUser, $sAdminPwd $oP->add("\n"); // To be compatible with login page $oP->add("\n"); $oP->add("\n"); + $oP->add("
    \n"); $oP->add_linked_script('./jquery.progression.js'); PopulateDataFilesList($oP); diff --git a/setup/setup.js b/setup/setup.js index de99764edf..d9fc63c5f8 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -103,7 +103,8 @@ function DoLoadDataAsynchronous() $('#setup').block({message: '

    Loading data...

    0%

    '}); $('#progress').progression( {Current:0, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); - LoadNextDataFile('', ''); + $('#log').ajaxError(function(e, xhr, settings, exception) { alert('Fatal error detected: '+ xhr.responseText); $('#log').append(xhr.responseText); $('#setup').unblock(); } ); + LoadNextDataFile('', ''); return false; // Stop here for now } From 6556f893767e018ef54998b22b41aa0f1e5bcdbc Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 18 Feb 2010 08:47:46 +0000 Subject: [PATCH 222/970] - Release of version 0.9 SVN:trunk[299] --- application/startup.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/startup.inc.php b/application/startup.inc.php index 3b4f86b88e..d7a3854cb8 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -1,5 +1,5 @@ Date: Fri, 19 Feb 2010 10:10:10 +0000 Subject: [PATCH 223/970] - When creating a new object linked to the location (contact, server, PC), the location must be pre-filled automatically. SVN:trunk[300] --- business/templates/location.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/business/templates/location.html b/business/templates/location.html index 04d0609cf3..2e3e67591d 100644 --- a/business/templates/location.html +++ b/business/templates/location.html @@ -10,13 +10,13 @@ SELECT bizContact WHERE location_id = $pkey$
    - SELECT bizServer WHERE location_id = $pkey$ + SELECT bizServer WHERE location_id = $pkey$ - SELECT bizPC WHERE location_id = $pkey$ + SELECT bizPC WHERE location_id = $pkey$ - SELECT bizNetworkDevice WHERE location_id = $pkey$ + SELECT bizNetworkDevice WHERE location_id = $pkey$ From f1226ca65e40cf7154c8237a8f24e35f3b71d134 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 19 Feb 2010 10:11:41 +0000 Subject: [PATCH 224/970] - cleanup of the "bizCircuit" object and its associated template. SVN:trunk[301] --- business/itop.business.class.inc.php | 6 +++--- business/templates/Circuits.html | 9 --------- business/templates/circuit.html | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 12 deletions(-) delete mode 100644 business/templates/Circuits.html create mode 100644 business/templates/circuit.html diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 09887c586e..61ef31a3bc 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -743,7 +743,7 @@ class bizCircuit extends logInfra "db_table" => "circuits", "db_key_field" => "id", "db_finalclass_field" => "", - "display_template" => "../business/templates/Circuits.html", + "display_template" => "../business/templates/circuit.html", ); MetaModel::Init_Params($aParams); @@ -754,11 +754,11 @@ class bizCircuit extends logInfra MetaModel::Init_AddAttribute(new AttributeExternalKey("location2_id", array("targetclass"=>"bizLocation", "label"=>"Location 2", "description"=>"Id of the location 2", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location2_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL,"depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("location2_name", array("label"=>"Location 2", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location2_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface 1", "description"=>"id of the interface 1", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS p WHERE p.org_id = :this->org_id'), "sql"=>"interface1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface 1", "description"=>"id of the interface 1", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS Intf JOIN bizDevice AS Dev ON Intf.device_id = Dev.id WHERE Intf.org_id = :this->org_id AND Dev.location_id = :this->location1_id'), "sql"=>"interface1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id", "location1_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("label"=>"Interface", "description"=>"Name of the interface 1", "allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalField("device1_name", array("label"=>"Device 1", "description"=>"Name of the device 1", "allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"device_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface 2", "description"=>"id of the interface 2", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS p WHERE p.org_id = :this->org_id'), "sql"=>"interface2_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface 2", "description"=>"id of the interface 2", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS Intf JOIN bizDevice AS Dev ON Intf.device_id = Dev.id WHERE Intf.org_id = :this->org_id AND Dev.location_id = :this->location2_id'), "sql"=>"interface2_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id", "location2_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("label"=>"Interface", "description"=>"Name of the interface 2", "allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeExternalField("device2_name", array("label"=>"Interface", "description"=>"Name of the device 2", "allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"device_name"))); diff --git a/business/templates/Circuits.html b/business/templates/Circuits.html deleted file mode 100644 index e01fc9fec9..0000000000 --- a/business/templates/Circuits.html +++ /dev/null @@ -1,9 +0,0 @@ - - -$class$: pkey = $pkey$ - - diff --git a/business/templates/circuit.html b/business/templates/circuit.html new file mode 100644 index 0000000000..2c2102c442 --- /dev/null +++ b/business/templates/circuit.html @@ -0,0 +1,21 @@ + + +$class$: pkey = $pkey$ + + + SELECT lnkContactRealObject WHERE object_id = $pkey$ + + + SELECT lnkInfraTicket WHERE infra_id = $pkey$ + + + SELECT bizChangeTicket AS Change JOIN lnkInfraChangeTicket AS Link ON Link.ticket_id = Change.id WHERE Link.infra_id = $pkey$ + + + SELECT lnkDocumentRealObject WHERE object_id = $pkey$ + + From ee2c9d6c373c2d4814c0f4c4c14b764135a811df Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 19 Feb 2010 14:45:06 +0000 Subject: [PATCH 225/970] - pie chart labels do not support HTML, only plain text (at least in this version of OFC) SVN:trunk[302] --- application/displayblock.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 2f07a53e9c..90ddbbcad1 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -552,7 +552,7 @@ class DisplayBlock $aGroupBy = array(); while($oObj = $this->m_oSet->Fetch()) { - $sValue = $oObj->GetAsHtml($sGroupByField); + $sValue = $oObj->Get($sGroupByField); $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; } $sFilter = urlencode($this->m_oFilter->serialize()); From 00ca7bb885d2f3766126a4f25e9d4e8b4ed0eacd Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 19 Feb 2010 17:00:29 +0000 Subject: [PATCH 226/970] - Cosmetic cleanup of menus to align them to the same presentation SVN:trunk[303] --- setup/data/structure/1.menus.xml | 1921 +++++++++++++++--------------- 1 file changed, 967 insertions(+), 954 deletions(-) diff --git a/setup/data/structure/1.menus.xml b/setup/data/structure/1.menus.xml index 67f55994a4..c1e725471c 100644 --- a/setup/data/structure/1.menus.xml +++ b/setup/data/structure/1.menus.xml @@ -1,325 +1,306 @@ - - - -Admin tools - -UI.php - - -administrator -1000 -0 -0 - - -All Applications - -UI.php - - -application -999 -16 -0 - - -All Circuits - -UI.php - - -application -999 -16 -0 - - -All Contracts - -./UI.php - - -application -2 -38 -0 - - -All Interfaces - -UI.php - - -application -999 -16 -0 - - -All Network devices - -UI.php - - -application -999 -16 -0 - - -All Patches - -UI.php - - -application -999 -16 -0 - - -All PCs - -UI.php - - -application -999 -16 -0 - - -All Servers - -UI.php - - -application -999 -16 -0 - - -All Services - -UI.php - - -application -1 -38 -0 - - -All Subnets - -UI.php - - -application -999 -16 -0 - - -Audit - -./audit.php - - -application -4 -17 -0 - - -Change Management - -./UI.php - - -application -7 -0 -0 - - -Closed Changes - -UI.php - - -application -5 -13 -0 - - -Closed Incident - -./UI.php - - -application -5 -24 -0 - - -Configuration Items - -UI.php - - -application -2 -17 -0 - - -Configuration Management - -UI.php - + + + +Admin tools + +UI.php + + +administrator +1000 +0 +0 + + +All Applications + +UI.php + + +application +999 +16 +0 + + +All Circuits + +UI.php + + +application +999 +16 +0 + + +All Contracts + +./UI.php + + +application +2 +38 +0 + + +All Interfaces + +UI.php + + +application +999 +16 +0 + + +All Network devices + +UI.php + + +application +999 +16 +0 + + +All Patches + +UI.php + + +application +999 +16 +0 + + +All PCs + +UI.php + + +application +999 +16 +0 + + +All Servers + +UI.php + + +application +999 +16 +0 + + +All Services + +UI.php + + +application +1 +38 +0 + + +All Subnets + +UI.php + + +application +999 +16 +0 + + +Application log + +UI.php +../images/std_view.gif + +administrator +2 +1 +0 + + +Audit + +./audit.php + + +application +4 +17 +0 + + +Change Management + +./UI.php + + +application +7 +0 +0 + + +Closed Changes + +UI.php + + +application +5 +13 +0 + + +Closed Incident + +./UI.php + + +application +5 +24 +0 + + +Configuration Items + +UI.php + -application -2 -0 -0 - - -Contacts - -UI.php - - +application +2 +17 +0 + + +Configuration Management + +UI.php + + +application +2 +0 +0 + + +Contacts + +UI.php + + -application -1 -17 -0 - - -CSV import - -csvimport.php - - -application -999 -40 -0 - - -Data Model - -schema.php - - -administrator -1500 -1 -0 - - -Document - -UI.php - - -application -6 -17 -0 - - -Export - -../webservices/export.php - - -administrator -1001 -1 -0 - - -Grouping - -UI.php - - -application -3 -17 -0 - - -Incident Management - -./UI.php - +<td class="dashboard"> +<p style="text-align:left; font-family:Verdana, Arial, sans-serif; font-size:16px;">Contacts by Status</p> +<itopblock BlockClass="DisplayBlock" objectclass="bizContact" type="count" parameters="group_by:status" asynchronous="false" encoding="text/oql">SELECT bizContact</itopblock> +</td> +</tr> +</table> +application +1 +17 +0 + + +CSV import + +csvimport.php + + +application +999 +40 +0 + + +Data Model + +schema.php + + +administrator +1500 +1 +0 + + +Document + +UI.php + + +application +6 +17 +0 + + +Export + +../webservices/export.php + + +administrator +1001 +1 +0 + + +Grouping + +UI.php + + +application +3 +17 +0 + + +Incident Management + +./UI.php + + +application +5 +0 +0 + + +Known Errors + +./UI.php + + +application +999 +24 +0 + + +Locations + +UI.php + + +application +5 +17 +0 + + +My Changes + +UI.php + + +application +1 +13 +0 + + +My Incidents + +UI.php + + +application +1 +24 +0 + + +My Service calls + +UI.php + + +application +1 +37 +0 + + +Notifications + +UI.php + + +administrator +2 +1 +0 + + +Open Changes + +./UI.php + + +application +3 +13 +0 + + +Open Incidents + +UI.php + + +application +3 +24 +0 + + +Open Service calls + +UI.php + + +application +2 +37 +0 + + +Persons + +UI.php + + +application +7 +18 +0 + + +Profiles + +UI.php +../images/std_view.gif + +administrator +11 +43 +0 + + +Run queries + +./run_query.php + + +administrator +1002 +1 +0 + + +Scheduled Outages + +./UI.php + + +application +7 +13 +0 + + +Service Desk + +UI.php + + +application +3 +0 +0 + + +Service Management + +./UI.php + -application -5 -0 -0 - - -Known Errors - -./UI.php - - -application -999 -24 -0 - - -Locations - -UI.php - - -application -5 -17 -0 - - -My Changes - -UI.php - - -application -1 -13 -0 - - -My Incidents - -UI.php - - -application -1 -24 -0 - - -My Service calls - -UI.php - - -application -1 -37 -0 - - -Open Changes - -./UI.php - - -application -3 -13 -0 - - -Open Incidents - -UI.php - - -application -3 -24 -0 - - -Open Service calls - -UI.php - - -application -2 -37 -0 - - -Persons - -UI.php - - -application -7 -18 -0 - - -Profiles - -UI.php -../images/std_view.gif - -administrator -11 -43 -0 - - -Run queries - -./run_query.php - - -administrator -1002 -1 -0 - - -Scheduled Outages - -./UI.php - - -application -7 -13 -0 - - -Service Desk - -UI.php - - -application -3 -0 -0 - - -Service Management - -./UI.php - - -application -8 -0 -0 - - -Teams - -UI.php - - -application -8 -18 -0 - - -Tools - -UI.php - - -application -9 -0 -0 - - -Universal Search - -UniversalSearch.php - - -administrator -1600 -1 -0 - - -User logins - -UI.php -../images/std_view.gif - -administrator -10 -43 -0 - - -User management - -UI.php - - -administrator -1 -1 -0 - - -Welcome - -./UI.php - - -application -1 -0 -0 - - -Application log - -UI.php -../images/std_view.gif - -administrator -2 -1 -0 - - -Notifications - -UI.php - - -administrator -2 -1 -0 - - + +application +8 +0 +0 + + +Teams + +UI.php + + +application +8 +18 +0 + + +Tools + +UI.php + + +application +9 +0 +0 + + +Universal Search + +UniversalSearch.php + + +administrator +1600 +1 +0 + + +User logins + +UI.php +../images/std_view.gif + +administrator +10 +43 +0 + + +User management + +UI.php + + +administrator +1 +1 +0 + + +Welcome + +./UI.php + + +application +1 +0 +0 + + From b163e2a606654725c7b62a59bd87741074e59805 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 22 Feb 2010 17:06:25 +0000 Subject: [PATCH 227/970] - Fixed logout bug introduced by revision [294] SVN:trunk[306] --- application/itopwebpage.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 97f720d158..53b861809c 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -296,7 +296,7 @@ EOF echo "
    Logged in as '$sUserName' $sIsAdmin  "; echo "
    \n"; echo "\n"; - echo "\n"; + echo "\n"; echo "
    \n"; echo "
    \n"; From 5f91c8ccc0ffa5096b345a2f6bcd14a76d4631cb Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 25 Feb 2010 08:35:57 +0000 Subject: [PATCH 228/970] Fixed bug #89: some hidden or read-only fields were displayed in the object creation wizard. SVN:trunk[310] --- application/uiwizard.class.inc.php | 40 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 61933198a2..f2819ff479 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -58,7 +58,7 @@ class UIWizard $aOptions[] = 'Prerequisites: '.implode(', ', $aPrerequisites); } - $sFieldFlag = ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) ? ' *' : ''; + $sFieldFlag = (($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) || (!$oAttDef->IsNullAllowed()) )? ' *' : ''; $oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId", '', $iOptions, $aArgs); $aFieldsMap[$iMaxInputId] = $sAttCode; @@ -158,33 +158,37 @@ $sJSHandlerCode } // Now check the attributes that are mandatory in the specified state - if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) ) + if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) ) { // Check all the fields that *must* be included in the wizard for this // particular target state $aFields = array(); foreach($aStates[$this->m_sTargetState]['attribute_list'] as $sAttCode => $iOptions) { - if (isset($aMandatoryAttributes[$sAttCode])) - { - $aMandatoryAttributes[$sAttCode] |= $iOptions; - } - else - { - $aMandatoryAttributes[$sAttCode] = $iOptions; - } - } - } - - // Check all the fields that *must* be included in the wizard + if ( (isset($aMandatoryAttributes[$sAttCode])) && + ($aMandatoryAttributes[$sAttCode] & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ) + { + $aMandatoryAttributes[$sAttCode] |= $iOptions; + } + else + { + $aMandatoryAttributes[$sAttCode] = $iOptions; + } + } + } + + // Check all the fields that *must* be included in the wizard + // i.e. all mandatory, must-change or must-prompt fields that are + // not also read-only or hidden. + // Some fields may be required (null not allowed) from the database + // perspective, but hidden or read-only from the user interface perspective $aFields = array(); foreach($aMandatoryAttributes as $sAttCode => $iOptions) { - $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); - $sAttLabel = $oAttDef->GetLabel(); - - if ( ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ) + if ( ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) && + !($iOptions & (OPT_ATT_READONLY | OPT_ATT_HIDDEN)) ) { + $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); $aPrerequisites = $oAttDef->GetPrerequisiteAttributes(); $aFields[$sAttCode] = array(); foreach($aPrerequisites as $sCode) From 22a0030a627c5de8c1292df71ae993546cab50dd Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 25 Feb 2010 08:38:56 +0000 Subject: [PATCH 229/970] whitepaces (tabs, etc...) cleanup SVN:trunk[311] --- business/incidentMgmt.business.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index b1f4df026f..35edcd4561 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -42,26 +42,26 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Ticket Ref", "description"=>"Refence number ofr this incident", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Incident", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Resolved, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("label"=>"Initial Situation", "description"=>"Initial situation of the Incident", "allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("label"=>"Current Situation", "description"=>"Current situation of the Incident", "allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - // dfinir une date de dfaut maintenant, alias creation ou modification du ticket + // dfinir une date de dfaut maintenant, alias creation ou modification du ticket MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closure Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"mail of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); // Comment afficher le first + last name de l'agent ? Est-ce utile d'ajouter ce champ? MetaModel::Init_AddAttribute(new AttributeText("action_log", array("label"=>"Action Logs", "description"=>"List all action performed during the incident", "allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); From 10fa31807b1b1fe91a6909d2184aa05c6200784e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Feb 2010 15:29:30 +0000 Subject: [PATCH 230/970] - Fixed bug #87: Strings consisting only of digits are not managed as strings SVN:trunk[312] --- core/cmdbsource.class.inc.php | 2 +- core/dbobject.class.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 13ac98f8f4..e21368fe20 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -159,7 +159,7 @@ class CMDBSource $value = stripslashes($value); } // Quote if not a number or a numeric string - if ($bAlways || !is_numeric($value)) + if ($bAlways || is_string($value)) { $value = $cQuoteStyle . mysql_real_escape_string($value, self::$m_resDBLink) . $cQuoteStyle; } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 68479ca11a..7d18d74fae 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -229,7 +229,7 @@ abstract class DBObject } public function Set($sAttCode, $value) - { + { if ($sAttCode == 'finalclass') { // Ignore it - this attribute is set upon object creation and that's it @@ -568,7 +568,7 @@ abstract class DBObject $aDelta = array(); foreach ($aProposal as $sAtt => $proposedValue) { - if (!array_key_exists($sAtt, $this->m_aOrigValues) || ($this->m_aOrigValues[$sAtt] != $proposedValue)) + if (!array_key_exists($sAtt, $this->m_aOrigValues) || ($this->m_aOrigValues[$sAtt] !== $proposedValue)) { $aDelta[$sAtt] = $proposedValue; } From 5a09dc6e2b1f3891f142e4446acac9038f5d52b1 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 8 Mar 2010 09:10:16 +0000 Subject: [PATCH 231/970] Trac #86 - CSV format aligned with standard specifications Trac #93 - Fixed issue within the setup data load (related to memory_limit) Fixed issues with the consultant toolkit: upgrade an existing DB (add new class/attribute) Developed core services to allow for demonstrating impact computation capability Deprecated option operation=direct on page UI.php SVN:trunk[313] --- application/cmdbabstract.class.inc.php | 5 +- application/nicewebpage.class.inc.php | 2 +- core/attributedef.class.inc.php | 62 +- core/csvparser.class.inc.php | 258 +- core/dbobject.class.php | 37 +- core/metamodel.class.php | 60 +- core/valuesetdef.class.inc.php | 51 +- pages/ITopConsultant.php | 17 +- pages/advanced_search.php | 4 +- pages/csvimport.php | 71 +- pages/index.php | 56 - pages/testlist.inc.php | 3272 ++++++++++++------------ setup/ajax.dataloader.php | 101 +- setup/setuppage.class.inc.php | 28 +- setup/xmldataloader.class.inc.php | 6 +- webservices/import.php | 10 +- 16 files changed, 2131 insertions(+), 1909 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index c14eaa9f7e..87088fd414 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -455,8 +455,7 @@ abstract class cmdbAbstractObject extends CMDBObject { $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); } - $sHtml = '#'.$oSet->GetFilter()->ToOQL()."\n"; - $sHtml .= implode($sSeparator, $aHeader)."\n"; + $sHtml = implode($sSeparator, $aHeader)."\n"; $oSet->Seek(0); while ($oObj = $oSet->Fetch()) { @@ -464,7 +463,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aRow[] = $oObj->GetKey(); foreach($aList as $sAttCode) { - $aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, '\\'); + $aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, $sTextQualifier); } $sHtml .= implode($sSeparator, $aRow)."\n"; } diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index e8e48e07ce..c01ff2c64e 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -53,7 +53,7 @@ class NiceWebPage extends WebPage foreach($aChoices as $sKey => $sValue) { $sSelected = ($sKey == $sDefaultValue) ? " SELECTED" : ""; - $this->add(""); + $this->add(""); } $this->add(""); } diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 076453ade8..b9728acaef 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -61,6 +61,7 @@ abstract class AttributeDefinition private $m_aParams = array(); private $m_sHostClass = array(); protected function Get($sParamName) {return $this->m_aParams[$sParamName];} + protected function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} public function __construct($sCode, $aParams) { @@ -194,9 +195,9 @@ abstract class AttributeDefinition return Str::pure2xml((string)$sValue); } - public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') { - return str_replace($sSeparator, $sSepEscape, (string)$sValue); + return (string)$sValue; } public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') @@ -233,7 +234,34 @@ class AttributeLinkedSet extends AttributeDefinition public function GetValuesDef() {return $this->Get("allowed_values");} public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} - public function GetDefaultValue() {return DBObjectSet::FromScratch($this->Get('linked_class'));} + public function GetDefaultValue($aArgs = array()) + { + // Note: so far, this feature is a prototype, + // later, the argument 'this' should always be present in the arguments + // + if (($this->IsParam('default_value')) && array_key_exists('this', $aArgs)) + { + $oSet = $this->Get('default_value'); + return $oSet->GetValues($aArgs); + } + else + { + return DBObjectSet::FromScratch($this->Get('linked_class')); + } + } + + public function GetSupportedRelations() + { + if (array_key_exists('supported_relations', $this->m_aParams)) + { + $aSupportedRelations = $this->Get('supported_relations'); + return $aSupportedRelations; + } + else + { + return array(); + } + } public function GetLinkedClass() {return $this->Get('linked_class');} public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} @@ -252,7 +280,7 @@ class AttributeLinkedSet extends AttributeDefinition return "ERROR: LIST OF OBJECTS"; } - public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') { return "ERROR: LIST OF OBJECTS"; } @@ -598,6 +626,14 @@ class AttributeString extends AttributeDBField { return $value; } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return '"'.$sEscaped.'"'; + } } /** @@ -678,11 +714,6 @@ class AttributeText extends AttributeString { return Str::pure2xml($value); } - - public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',') - { - return str_replace("\n", "[newline]", parent::GetAsCSV($sValue, $sSeparator, $sSepEscape)); - } } /** @@ -982,9 +1013,12 @@ class AttributeDate extends AttributeDBField return Str::pure2xml($value); } - public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',') + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') { - return str_replace($sSeparator, $sSepEscape, $value); + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return '"'.$sEscaped.'"'; } } @@ -1231,10 +1265,10 @@ class AttributeExternalField extends AttributeDefinition $oExtAttDef = $this->GetExtAttDef(); return $oExtAttDef->GetAsXML($value); } - public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',') + public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"') { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetAsCSV($value); + return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier); } } @@ -1394,7 +1428,7 @@ class AttributeBlob extends AttributeDefinition } } - public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',') + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') { return ''; // Not exportable in CSV ! } diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php index b6c6334a36..20b952427a 100644 --- a/core/csvparser.class.inc.php +++ b/core/csvparser.class.inc.php @@ -1,8 +1,7 @@ @@ -19,6 +18,16 @@ class CSVParserException extends CoreException +define('stSTARTING', 1); //grey zone: the type is undetermined +define('stRAW', 2); //building a non-qualified string +define('stQUALIFIED', 3); //building qualified string +define('stESCAPED', 4); //just encountered an escape char + +define('evSEPARATOR', 1); +define('evNEWLINE', 2); +define('evTEXTQUAL', 3); // used for escaping as well +define('evOTHERCHAR', 4); + /** * CSVParser @@ -34,157 +43,148 @@ class CSVParser { private $m_sCSVData; private $m_sSep; - private $m_iSkip; + private $m_sTextQualifier; - public function __construct($sTxt) - { - $this->m_sCSVData = $sTxt; - } - - public function SetSeparator($sSep) + public function __construct($sTxt, $sSep = ',', $sTextQualifier = '"') { + $this->m_sCSVData = str_replace("\r\n", "\n", $sTxt); $this->m_sSep = $sSep; - } - public function GetSeparator() - { - return $this->m_sSep; + $this->m_sTextQualifier = $sTextQualifier; } - public function SetSkipLines($iSkip) - { - $this->m_iSkip = $iSkip; - } - public function GetSkipLines() - { - return $this->m_iSkip; - } + protected $m_sCurrCell = ''; + protected $m_aCurrRow = array(); + protected $m_iToSkip = 0; + protected $m_aDataSet = array(); - public function GuessSeparator() + protected function __AddChar($c) { - // Note: skip the first line anyway - - $aKnownSeps = array(';', ',', "\t"); // Use double quote for special chars!!! - $aStatsBySeparator = array(); - foreach ($aKnownSeps as $sSep) + $this->m_sCurrCell .= $c; + } + protected function __ClearCell() + { + $this->m_sCurrCell = ''; + } + protected function __AddCell($c = null, $aFieldMap = null) + { + if (!is_null($aFieldMap)) { - $aStatsBySeparator[$sSep] = array(); + $iNextCol = count($this->m_aCurrRow); + $iNextName = $aFieldMap[$iNextCol]; + $this->m_aCurrRow[$iNextName] = $this->m_sCurrCell; } - - foreach(explode("\n", $this->m_sCSVData) as $sLine) + else { - $sLine = trim($sLine); - if (substr($sLine, 0, 1) == '#') continue; - if (empty($sLine)) continue; - - $aLineCharsCount = count_chars($sLine, 0); - foreach ($aKnownSeps as $sSep) + $this->m_aCurrRow[] = $this->m_sCurrCell; + } + $this->m_sCurrCell = ''; + } + protected function __AddRow($c = null, $aFieldMap = null) + { + $this->__AddCell($c, $aFieldMap); + + if ($this->m_iToSkip > 0) + { + $this->m_iToSkip--; + } + elseif (count($this->m_aCurrRow) > 1) + { + $this->m_aDataSet[] = $this->m_aCurrRow; + } + elseif ((count($this->m_aCurrRow) == 1) && (strlen($this->m_aCurrRow[0]) > 0)) + { + $this->m_aDataSet[] = $this->m_aCurrRow; + } + else + { + // blank line, skip silently + } + $this->m_aCurrRow = array(); + } + + function ToArray($iToSkip = 1, $aFieldMap = null, $iMax = 0) + { + $aTransitions = array(); + + $aTransitions[stSTARTING][evSEPARATOR] = array('__AddCell', stSTARTING); + $aTransitions[stSTARTING][evNEWLINE] = array('__AddRow', stSTARTING); + $aTransitions[stSTARTING][evTEXTQUAL] = array('', stQUALIFIED); + $aTransitions[stSTARTING][evOTHERCHAR] = array('__AddChar', stRAW); + + $aTransitions[stRAW][evSEPARATOR] = array('__AddCell', stSTARTING); + $aTransitions[stRAW][evNEWLINE] = array('__AddRow', stSTARTING); + $aTransitions[stRAW][evTEXTQUAL] = array('__AddChar', stRAW); + $aTransitions[stRAW][evOTHERCHAR] = array('__AddChar', stRAW); + + $aTransitions[stQUALIFIED][evSEPARATOR] = array('__AddChar', stQUALIFIED); + $aTransitions[stQUALIFIED][evNEWLINE] = array('__AddChar', stQUALIFIED); + $aTransitions[stQUALIFIED][evTEXTQUAL] = array('', stESCAPED); + $aTransitions[stQUALIFIED][evOTHERCHAR] = array('__AddChar', stQUALIFIED); + + $aTransitions[stESCAPED][evSEPARATOR] = array('__AddCell', stSTARTING); + $aTransitions[stESCAPED][evNEWLINE] = array('__AddRow', stSTARTING); + $aTransitions[stESCAPED][evTEXTQUAL] = array('__AddChar', stQUALIFIED); + $aTransitions[stESCAPED][evOTHERCHAR] = array('__AddChar', stSTARTING); + + // Reset parser variables + $this->m_sCurrCell = ''; + $this->m_aCurrRow = array(); + $this->m_iToSkip = $iToSkip; + $this->m_aDataSet = array(); + + $iState = stSTARTING; + for($i = 0; $i < strlen($this->m_sCSVData) ; $i++) + { + $c = $this->m_sCSVData[$i]; + +// // Note: I did that because the unit test was not working fine (file edited with notepad: \n chars padded :-( +// if (ord($c) == 0) continue; + + if ($c == $this->m_sSep) { - $aStatsBySeparator[$sSep][] = $aLineCharsCount[ord($sSep)]; + $iEvent = evSEPARATOR; } - } - - // Default to ',' - $this->SetSeparator(","); - - foreach ($aKnownSeps as $sSep) - { - // Note: this function is NOT available :-( - // stats_variance($aStatsBySeparator[$sSep]); - $iMin = min($aStatsBySeparator[$sSep]); - $iMax = max($aStatsBySeparator[$sSep]); - if (($iMin == $iMax) && ($iMax > 0)) + elseif ($c == "\n") { - $this->SetSeparator($sSep); - break; + $iEvent = evNEWLINE; } - } - return $this->GetSeparator(); - } - - public function GuessSkipLines() - { - // Take the FIRST -valuable- LINE ONLY - // If there is a number, then for sure this is not a header line - // Otherwise, we may consider that there is one line to skip - foreach(explode("\n", $this->m_sCSVData) as $sLine) - { - $sLine = trim($sLine); - if (substr($sLine, 0, 1) == '#') continue; - if (empty($sLine)) continue; - - foreach (explode($this->m_sSep, $sLine) as $value) + elseif ($c == $this->m_sTextQualifier) { - if (is_numeric($value)) + $iEvent = evTEXTQUAL; + } + else + { + $iEvent = evOTHERCHAR; + } + + $sAction = $aTransitions[$iState][$iEvent][0]; + $iState = $aTransitions[$iState][$iEvent][1]; + + if (!empty($sAction)) + { + $aCallSpec = array($this, $sAction); + if (is_callable($aCallSpec)) { - $this->SetSkipLines(0); - return 0; + call_user_func($aCallSpec, $c, $aFieldMap); + } + else + { + throw new CSVParserException("CSVParser: unknown verb '$sAction'"); } } - $this->SetSkipLines(1); - return 1; - } - } - function ToArray($aFieldMap = null, $iMax = 0) - { - // $aFieldMap is an array of col_index=>col_name - // $iMax is to limit the count of rows computed - $aRes = array(); - - $iCount = 0; - $iSkipped = 0; - foreach(explode("\n", $this->m_sCSVData) as $sLine) - { - $sLine = trim($sLine); - if (substr($sLine, 0, 1) == '#') continue; - if (empty($sLine)) continue; - - if ($iSkipped < $this->m_iSkip) - { - $iSkipped++; - continue; - } - - foreach (explode($this->m_sSep, $sLine) as $iCol=>$sValue) - { - if (is_array($aFieldMap)) $sColRef = $aFieldMap[$iCol]; - else $sColRef = $iCol; - $aRes[$iCount][$sColRef] = $sValue; - } - - $iCount++; - if (($iMax > 0) && ($iCount >= $iMax)) break; + $iLineCount = count($this->m_aDataSet); + if (($iMax > 0) && ($iLineCount >= $iMax)) break; } - return $aRes; + // Close the final line + $this->__AddRow(null, $aFieldMap); + return $this->m_aDataSet; } public function ListFields() { - // Take the first valuable line - foreach(explode("\n", $this->m_sCSVData) as $sLine) - { - $sLine = trim($sLine); - if (substr($sLine, 0, 1) == '#') continue; - if (empty($sLine)) continue; - // We've got the first valuable line, that's it! - break; - } - - $aRet = array(); - foreach (explode($this->m_sSep, $sLine) as $iCol=>$value) - { - if ($this->m_iSkip == 0) - { - // No header to help us - $sLabel = "field $iCol"; - } - else - { - $sLabel = "$value"; - } - $aRet[] = $sLabel; - } - return $aRet; + $aHeader = $this->ToArray(0, null, 1); + return $aHeader[0]; } } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 7d18d74fae..f0ddffe8d3 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -381,10 +381,10 @@ abstract class DBObject return $oAtt->GetAsXML($this->Get($sAttCode)); } - public function GetAsCSV($sAttCode, $sSeparator = ';', $sSepEscape = ',') + public function GetAsCSV($sAttCode, $sSeparator = ',', $sTextQualifier = '"') { $oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sSepEscape); + return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sTextQualifier); } protected static function MakeHyperLink($sObjClass, $sObjKey, $aAvailableFields) @@ -883,6 +883,7 @@ abstract class DBObject $aScalarArgs = array(); $aScalarArgs[$sArgName] = $this->GetKey(); $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); + $aScalarArgs[$sArgName.'->object()'] = $this; $aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink(); $aScalarArgs[$sArgName.'->name()'] = $this->GetName(); @@ -903,6 +904,38 @@ abstract class DBObject public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array()) { + foreach (MetaModel::GetLinkedSets($sClass) as $sAttCode => $oAttDef) + { + $aSupportedRelations = $oAttDef->GetSupportedRelations(); + if (!array_key_exists($sRelCode, $aSupportedRelations)) continue; //skip + + $bPropagate = true; // #@# Todo: discuss that setting + $iDepth = $bPropagate ? $iMaxDepth - 1 : 0; + + $oNeighbors = $this->Get($sAttCode); + while ($oObj = $oObjSet->Fetch()) + { + $sRootClass = MetaModel::GetRootClass(get_class($oObj)); + $sObjKey = $oObj->GetKey(); + if (array_key_exists($sRootClass, $aResults)) + { + if (array_key_exists($sObjKey, $aResults[$sRootClass])) + { + continue; // already visited, skip + } + } + + $aResults[$sRootClass][$sObjKey] = $oObj; + if ($iDepth > 0) + { + $oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults); + } + } + } + + return; + + // #@# todo : Discuss the Relations and the way they are defined (do we deprecate the queries ? what are the properties -e.g. depth- and where do we set them ?) foreach (MetaModel::EnumRelationQueries(get_class($this), $sRelCode) as $sDummy => $aQueryInfo) { MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo["sQuery"]); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index acd8003c48..a1ec24097e 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -458,6 +458,19 @@ abstract class MetaModel return $aExtKeys; } + final static public function GetLinkedSets($sClass) + { + $aLinkedSets = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if (is_subclass_of($oAtt, 'AttributeLinkedSet')) + { + $aLinkedSets[$sAttCode] = $oAtt; + } + } + return $aLinkedSets; + } + final static public function GetExternalFields($sClass, $sKeyAttCode) { $aExtFields = array(); @@ -2039,13 +2052,16 @@ abstract class MetaModel list($aErrors, $aSugFix) = self::DBCheckFormat(); $aSQL = array(); - foreach ($aSugFix as $sClass => $aQueries) + foreach ($aSugFix as $sClass => $aTarget) { - foreach ($aQueries as $sQuery) + foreach ($aTarget as $aQueries) { - //$aSQL[] = $sQuery; - // forces a refresh of cached information - CMDBSource::CreateTable($sQuery); + foreach ($aQueries as $sQuery) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } } } // does not work -how to have multiple statements in a single query? @@ -2078,15 +2094,15 @@ abstract class MetaModel $sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : ""); if (!CMDBSource::IsTable($sTable)) { - $aErrors[$sClass][] = "table '$sTable' could not be found into the DB"; - $aSugFix[$sClass][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; + $aErrors[$sClass]['*'][] = "table '$sTable' could not be found into the DB"; + $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; } // Check that the key field exists // elseif (!CMDBSource::IsField($sTable, $sKeyField)) { - $aErrors[$sClass][] = "key '$sKeyField' (table $sTable) could not be found"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; + $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) could not be found"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; } else { @@ -2094,13 +2110,13 @@ abstract class MetaModel // if (!CMDBSource::IsKey($sTable, $sKeyField)) { - $aErrors[$sClass][] = "key '$sKeyField' is not a key for table '$sTable'"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; + $aErrors[$sClass]['id'][] = "key '$sKeyField' is not a key for table '$sTable'"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; } if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField)) { - $aErrors[$sClass][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; + $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; } } @@ -2118,11 +2134,11 @@ abstract class MetaModel $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; if (!CMDBSource::IsField($sTable, $sField)) { - $aErrors[$sClass][] = "field '$sField' could not be found in table '$sTable'"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; + $aErrors[$sClass][$sAttCode][] = "field '$sField' could not be found in table '$sTable'"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; if ($oAttDef->IsExternalKey()) { - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; } } else @@ -2135,30 +2151,30 @@ abstract class MetaModel $bToBeChanged = true; if ($oAttDef->IsNullAllowed()) { - $aErrors[$sClass][] = "field '$sField' in table '$sTable' could be NULL"; + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could be NULL"; } else { - $aErrors[$sClass][] = "field '$sField' in table '$sTable' could NOT be NULL"; + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could NOT be NULL"; } } $sActualFieldType = CMDBSource::GetFieldType($sTable, $sField); if (strcasecmp($sDBFieldType, $sActualFieldType) != 0) { $bToBeChanged = true; - $aErrors[$sClass][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; } if ($bToBeChanged) { - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; } // Create indexes (external keys only... so far) // if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) { - $aErrors[$sClass][] = "Foreign key '$sField' in table '$sTable' should have an index"; - $aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; + $aErrors[$sClass][$sAttCode][] = "Foreign key '$sField' in table '$sTable' should have an index"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; } } } diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index e84b0c2b5e..1d9e315c78 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -35,7 +35,7 @@ abstract class ValueSetDefinition } - public function GetValues($aArgs, $sBeginsWith) + public function GetValues($aArgs, $sBeginsWith = '') { if (!$this->m_bIsLoaded) { @@ -116,6 +116,55 @@ class ValueSetObjects extends ValueSetDefinition } +/** + * Set of existing values for a link set attribute, given a relation code + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class ValueSetRelatedObjects extends ValueSetDefinition +{ + protected $m_sRelationCode; + protected $m_iMaxDepth; +// protected $m_aOrderBy; + + public function __construct($sRelationCode, $iMaxDepth = 99) + { + $this->m_sRelationCode = $sRelationCode; + $this->m_iMaxDepth = $iMaxDepth; +// $this->m_aOrderBy = $aOrderBy; + } + + protected function LoadValues($aArgs) + { + $this->m_aValues = array(); + + if (!array_key_exists('this', $aArgs)) + { + throw new CoreException("Missing 'this' in arguments", array('args' => $aArgs)); + } + + $oTarget = $aArgs['this->object()']; + + $oTargetNeighbors = $oTarget->GetRelatedObjects($this->m_sRelationCode, $this->m_iMaxDepth); + while ($oObject = $oTargetNeighbors->Fetch()) + { + $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($oObject->GetName()); + } + return true; + } + + public function GetValuesDescription() + { + return 'Filter: '.$this->m_sFilterExpr; + } +} + + /** * Fixed set values (could be hardcoded in the business model) * diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index 022788c8c3..3efbd68900 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -254,7 +254,7 @@ function DumpDatabase() function printMenu($sConfigFile) { $sClassCount = count(MetaModel::GetClasses()); - $bHasDB = MetaModel::DBExists(); + $bHasDB = MetaModel::DBExists(false); // no need to be complete to consider that something already exists $sUrl = "?config=".urlencode($sConfigFile); echo "
    \n"; @@ -345,22 +345,25 @@ function DisplayDBFormatIssues($aErrors, $aSugFix, $sRepairUrl = "", $sSQLStatem echo "
    "; echo "

    Wrong Database format

    \n"; echo "

    The current database is not consistent with the given business model. Please investigate.

    \n"; - foreach ($aErrors as $sClass => $aMessages) + foreach ($aErrors as $sClass => $aTarget) { echo "

    Wrong declaration (or DB format ?) for class $sClass

    \n"; echo "
      \n"; $i = 0; - foreach ($aMessages as $sMsg) + foreach ($aTarget as $sTarget => $aMessages) { + echo "

      Wrong declaration for attribute $sTarget

      \n"; + $sMsg = implode(' AND ', $aMessages); if (!empty($sRepairUrl)) { - $aSQLFixes[] = $aSugFix[$sClass][$i]; - $sUrl = "$sRepairUrl&$sSQLStatementArgName=".urlencode($aSugFix[$sClass][$i]); - echo "
    • $sMsg (fix it now!)
    • \n"; + $aSQLFixes = array_merge($aSQLFixes, $aSugFix[$sClass][$sTarget]); + $sSQLFixes = implode('; ', $aSugFix[$sClass][$sTarget]); + $sUrl = "$sRepairUrl&$sSQLStatementArgName=".urlencode($sSQLFixes); + echo "
    • $sMsg (fix it now!)
    • \n"; } else { - echo "
    • $sMsg ({$aSugFix[$sClass][$i]})
    • \n"; + echo "
    • $sMsg (".htmlentities($sSQLFixes).")
    • \n"; } $i++; } diff --git a/pages/advanced_search.php b/pages/advanced_search.php index aa503c610d..5629973d82 100644 --- a/pages/advanced_search.php +++ b/pages/advanced_search.php @@ -226,8 +226,8 @@ function Page3_ViewResults($oPage, $oFilter) $oSet = new CMDBObjectSet($oFilter); $oPage->p("Found ".$oSet->Count()." items"); - $sFilterPhrase = $oFilter->serialize(); - $oPage->p("See detailed results"); + $sFilterPhrase = urlencode($oFilter->serialize()); + $oPage->p("See detailed results"); } } diff --git a/pages/csvimport.php b/pages/csvimport.php index 8ba2e60f46..7fb11789bb 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -62,7 +62,7 @@ function MakeExtFieldSelectValue($sAttCode, $sExtAttCode) function ShowTableForm($oPage, $oCSVParser, $sClass) { - $aData = $oCSVParser->ToArray(null, 3); + $aData = $oCSVParser->ToArray(1, null, 3); $aColToRow = array(); foreach($aData as $aRow) { @@ -193,6 +193,10 @@ function ShowTableForm($oPage, $oCSVParser, $sClass) { $aFields["field$iFieldIndex"]["value"] = $aColToRow[$iFieldIndex]; } + else + { + // Houston... + } } $oPage->details($aFields); } @@ -354,12 +358,24 @@ function Do_Welcome($oPage, $sClass) $sWiztep = "1_welcome"; $oPage->p("

      Bulk load from CSV data / step 1

      "); + // Reload values (in case we are reaching this page from the next one $sCSVData = utils::ReadPostedParam('csvdata'); + $sSep = utils::ReadPostedParam('separator', ','); + $sTQualif = utils::ReadPostedParam('textqualifier', '"'); + + $aSeparators = array(',' => ', (coma)', ';' => ';', ';' => ';', '|' => '|', '#' => '#', '@' => '@', ':' => ':'); + $aTextQualifiers = array('"' => '"', "'" => "'", '`' => '`', '/' => '/'); $oPage->add("
      "); $oPage->MakeClassesSelect("class", $sClass, 50, UR_ACTION_BULK_MODIFY); $oPage->add("
      "); - $oPage->add(""); + $oPage->add(""); + $oPage->add("
      "); + $oPage->add("Separator: "); + $oPage->add_select($aSeparators, 'separator', $sSep, 50); + $oPage->add("
      "); + $oPage->add("Text qualifier: "); + $oPage->add_select($aTextQualifiers, 'textqualifier', $sTQualif, 50); $oPage->add("
      "); $oPage->add(""); $oPage->add("
      \n"); @@ -415,12 +431,13 @@ function Do_Format($oPage, $sClass) $sWiztep = "2_format"; $sCSVData = utils::ReadPostedParam('csvdata'); - $oCSVParser = new CSVParser($sCSVData); - $sSep = $oCSVParser->GuessSeparator(); - $iSkip = $oCSVParser->GuessSkipLines(); + $sSep = utils::ReadPostedParam('separator'); + $sTQualif = utils::ReadPostedParam('textqualifier'); + $oCSVParser = new CSVParser($sCSVData, $sSep, $sTQualif); + $iSkip = 1; // No data ? - $aData = $oCSVParser->ToArray(null); + $aData = $oCSVParser->ToArray(); $iTarget = count($aData); if ($iTarget == 0) { @@ -429,29 +446,32 @@ function Do_Format($oPage, $sClass) return; } - // Guess the format : - $oPage->p("Guessed separator: '$sSep' (ASCII=".ord($sSep).")"); - $oPage->p("Guessed # of lines to skip: $iSkip"); + // Expected format - to be improved + $oPage->p("Separator: '$sSep'"); + $oPage->p("Text qualifier: '$sTQualif'"); + $oPage->p("The first line will be skipped (considered as being the list of fields)"); $oPage->p("Target: $iTarget rows"); - $oPage->Add(""); + $oPage->add(""); ShowTableForm($oPage, $oCSVParser, $sClass); - $oPage->Add(""); - $oPage->Add(""); - $oPage->Add(""); - $oPage->Add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); - $oPage->Add(""); - $oPage->add(""); - $oPage->Add(""); - $oPage->Add("
      "); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); } function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) { $sCSVData = utils::ReadPostedParam('csvdata'); $sSep = utils::ReadPostedParam('separator'); + $sTQualif = utils::ReadPostedParam('textqualifier'); $iSkip = utils::ReadPostedParam('skiplines'); $aFieldMap = utils::ReadPostedParam('fmap'); $aIsReconcKey = utils::ReadPostedParam('iskey'); @@ -465,16 +485,15 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) return; } - $oCSVParser = new CSVParser($sCSVData); - $oCSVParser->SetSeparator($sSep); - $oCSVParser->SetSkipLines($iSkip); - $aData = $oCSVParser->ToArray(null); + $oCSVParser = new CSVParser($sCSVData, $sSep, $sTQualif); + $aData = $oCSVParser->ToArray($iSkip, null); $iTarget = count($aData); $oPage->p("

      Goal summary

      "); $oPage->p("Target: $iTarget rows"); - $aSampleData = $oCSVParser->ToArray(array_keys($aFieldMap), 5); + $aSampleData = $oCSVParser->ToArray($iSkip, array_keys($aFieldMap), 5); + $aDisplayConfig = array(); $aExtKeys = array(); foreach ($aFieldMap as $sFieldId=>$sColDesc) @@ -519,6 +538,7 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $aDisplayConfig[$sFieldId] = array("label"=>"-?-?-$sColDesc-?-?-", "description"=>""); } } + $oPage->table($aDisplayConfig, $aSampleData); if ($oChange) @@ -558,8 +578,9 @@ function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) $oPage->add("
      "); $oPage->add(""); - $oPage->add(""); - $oPage->add(""); + $oPage->add(""); + $oPage->add(""); + $oPage->add(""); $oPage->add(""); $oPage->add_input_hidden("fmap", $aFieldMap); $oPage->add_input_hidden("iskey", $aIsReconcKey); diff --git a/pages/index.php b/pages/index.php index 4cbac19c2f..01297ed74f 100644 --- a/pages/index.php +++ b/pages/index.php @@ -149,36 +149,6 @@ function DisplayChangesLog(WebPage $oPage, $sClassName, $sKey) $oPage->p("Delete this object (no confirmation!)"); } -function DumpObjectsAsCSV(WebPage $oPage, $sClassName, $oSearchFilter = null, $sSeparator = ",") -{ - global $oContext; - - $aHeader = array(); - $aHeader[] = 'pkey'; - foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) - { - $aHeader[] = $oAttDef->GetLabel(); - } - $oPage->Add(join($sSeparator, $aHeader)."\n"); - - if ($oSearchFilter == null) - { - $oSearchFilter = $oContext->NewFilter($sClassName); - } - $oObjectSet = new CMDBObjectSet($oSearchFilter); - - while ($oObj = $oObjectSet->Fetch()) - { - $aRow = array(); - $aRow[] = $oObj->GetKey(); - foreach($oObj->GetAttributesList($sClassName) as $sAttCode) - { - $aRow[] = $oObj->GetAsCSV($sAttCode); - } - $oPage->Add(join($sSeparator, $aRow)."\n"); - } -} - function DumpObjects(WebPage $oPage, $sClassName, CMDBSearchFilter $oSearchFilter = null) { global $oContext; @@ -554,32 +524,6 @@ switch($operation) DeleteObject($oPage, $sClass, $sKey); break; - case 'direct': - $sFilter = ReadParam('filter'); - $sFormat = ReadParam('format', 'html'); - $oSearchFilter = CMDBSearchFilter::unserialize($sFilter); - switch($sFormat) - { - case 'csv': - $oPage->small_p($oSearchFilter->__DescribeHTML()); - $oPage->Add(""); - break; - - case 'xls': - $oPage->add_header('Content-disposition: attachment;filename=served.xls'); // Will fool Excel - $oPage->add_header('Content-Type: application/vnd.ms-excel'); // Will fool Excel - DumpObjects($oPage, $oSearchFilter->GetClass(), $oSearchFilter); - break; - - case 'html': - default: - $oSet = new CMDBObjectSet($oSearchFilter); - cmdbAbstractObject::DisplaySet($oPage, $oSet); - } - break; - case 'addlinks': $sClass = ReadParam('class'); $sKey = ReadParam('key'); diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 341d313e5b..137fad97db 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -1,1590 +1,1682 @@ -new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), - $oCondition = new BinaryExpression(new FieldExpression('column1', 'myTableAlias'), 'LIKE', new ScalarExpression('trash')), - $aFullTextNeedles = array('column1'), - $bToDelete = false, - $aValues = array() - ); - $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); - - $oSubQuery1 = new SQLQuery( - $sTable = 'myTable1', - $sTableAlias = 'myTable1Alias', - $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), - $oCondition = new TrueSQLExpression, - $aFullTextNeedles = array(), - $bToDelete = false, - $aValues = array() - ); - - $oSubQuery2 = new SQLQuery( - $sTable = 'myTable2', - $sTableAlias = 'myTable2Alias', - $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), - $oCondition = new TrueSQLExpression, - $aFullTextNeedles = array(), - $bToDelete = false, - $aValues = array() - ); - - $oQuery->AddInnerJoin($oSubQuery1, 'column1', 'column1_1'); - $oQuery->AddLeftJoin($oSubQuery2, 'column2', 'column2_2'); - - $oQuery->DisplayHtml(); - $oQuery->RenderDelete(); - $oQuery->RenderUpdate(); - echo '

      '.$oQuery->RenderSelect().'

      '; - $oQuery->RenderSelect(array('column1')); - $oQuery->RenderSelect(array('column1', 'column2')); - } -} - -class TestOQLParser extends TestFunction -{ - static public function GetName() {return 'Check OQL parsing';} - static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - $oOql = new OqlInterpreter($sQuery); - try - { - $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery - MyHelpers::var_dump_html($oTrash, true); - } - catch (OQLException $OqlException) - { - if ($bIsCorrectQuery) - { - echo "

      More info on this unexpected failure:
      ".$OqlException->getHtmlDesc()."

      \n"; - throw $OqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

      More info on this expected failure:
      ".$OqlException->getHtmlDesc()."

      \n"; - return true; - } - } - // The query was correctly parsed, was it expected to be correct ? - if ($bIsCorrectQuery) - { - return true; - } - else - { - throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); - return false; - } - } - - protected function TestQuery($sQuery, $bIsCorrectQuery) - { - if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) - { - return false; - } - return true; - } - - public function DoExecute() - { - $aQueries = array( - 'SELECT toto' => true, - 'SELECT toto WHERE toto.a = 1' => true, - 'SELECT toto WHERE toto.a=1' => true, - 'SELECT toto WHERE toto.a = "1"' => true, - 'SELECT toto WHHHERE toto.a = "1"' => false, - 'SELECT toto WHERE toto.a == "1"' => false, - 'SELECT toto WHERE toto.a % 1' => false, - //'SELECT toto WHERE toto.a LIKE 1' => false, - 'SELECT toto WHERE toto.a like \'arg\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, - 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, - 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, - - 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE ""' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, - "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, - "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, - - 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, - - //'SELECT toto WHERE toto.a > \'asd\'' => false, - 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, - 'SELECT Device JOIN Site ON Device.site = Site.id' => true, - 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, - - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, - - 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, - ); - - $iErrors = 0; - - foreach($aQueries as $sQuery => $bIsCorrectQuery) - { - $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; - echo "

      Testing query: $sQuery ($sIsOk)

      \n"; - $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); - if (!$bRet) $iErrors++; - } - - return ($iErrors == 0); - } -} - - -class TestGenericItoMyModel extends TestBizModelGeneric -{ - static public function GetName() - { - return 'Generic RO test on '.self::GetConfigFile(); - } - - static public function GetConfigFile() {return '../config-test-mymodel.php';} -} - -class TestGenericItopBigModel extends TestBizModelGeneric -{ - static public function GetName() - { - return 'Generic RO test on '.self::GetConfigFile(); - } - - static public function GetConfigFile() {return '../config-test-itopv06.php';} -} - -class TestUserRightsMatrixItop extends TestUserRights -{ - static public function GetName() - { - return 'User rights test on user rights matrix'; - } - - static public function GetDescription() - { - return 'blah blah blah'; - } - - public function DoPrepare() - { - parent::DoPrepare(); - MetaModel::Startup('../config-test-itopv06.php'); - } - - protected function DoExecute() - { - $sUser = 'Romain'; - echo "

      Totor: ".(UserRights::Login('Totor', 'toto') ? 'ok' : 'NO')."

      \n"; - echo "

      Romain: ".(UserRights::Login('Romain', 'toto') ? 'ok' : 'NO')."

      \n"; - echo "

      User: ".UserRights::GetUser()."

      \n"; - echo "

      On behalf of...".UserRights::GetRealUser()."

      \n"; - - echo "

      Denis (impersonate) : ".(UserRights::Impersonate('Denis', 'tutu') ? 'ok' : 'NO')."

      \n"; - echo "

      User: ".UserRights::GetUser()."

      \n"; - echo "

      On behalf of...".UserRights::GetRealUser()."

      \n"; - - UserRights::GetFilter('bizOrganization'); // returns a filter object - - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); - echo "

      IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

      \n"; - echo "

      IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

      \n"; - echo "

      IsActionAllowedOnAttribute...".(UserRights::IsActionAllowedOnAttribute('bizOrganization', 'myattribute', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

      \n"; - return true; - } -} - -/////////////////////////////////////////////////////////////////////////// -// Test a complex biz model on the fly -/////////////////////////////////////////////////////////////////////////// - -class TestMyBizModel extends TestBizModel -{ - static public function GetName() - { - return 'A series of tests on a weird business model'; - } - - static public function GetDescription() - { - return 'Attempts various operations and build complex queries'; - } - - static public function GetConfigFile() {return '../config-test-mymodel.php';} - - function test_linksinfo() - { - echo "

      Enum links

      "; - MyHelpers::var_dump_html(MetaModel::EnumReferencedClasses("cmdbTeam")); - MyHelpers::var_dump_html(MetaModel::EnumReferencingClasses("Organization")); - - MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses()); - MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses("cmdbContact")); - MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses("cmdWorkshop")); - MyHelpers::var_dump_html(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); - } - - function test_list_attributes() - { - echo "

      List attributes

      "; - foreach(MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode=>$oAttDef) - { - echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
      \n"; - } - } - - function test_search() - { - echo "

      Two searches

      "; - $oFilterAllDevs = new DBObjectSearch("cmdbTeam"); - $oAllDevs = new DBObjectSet($oFilterAllDevs); - - echo "Found ".$oAllDevs->Count()." items.
      \n"; - while ($oDev = $oAllDevs->Fetch()) - { - $aValues = array(); - foreach(MetaModel::GetAttributesList($oAllDevs->GetClass()) as $sAttCode) - { - $aValues[] = MetaModel::GetLabel(get_class($oDev), $sAttCode)." (".MetaModel::GetDescription(get_class($oDev), $sAttCode).") = ".$oDev->GetAsHTML($sAttCode); - } - echo $oDev->GetKey()." => ".implode(", ", $aValues)."
      \n"; - } - - // a second one - $oMyFilter = new DBObjectSearch("cmdbContact"); - //$oMyFilter->AddCondition("name", "aii", "Finishes with"); - $oMyFilter->AddCondition("name", "aii"); - $this->search_and_show_list($oMyFilter); - - } - - function test_reload() - { - echo "

      Reload

      "; - $team = MetaModel::GetObject("cmdbContact", "2"); - echo "Chargement de l'attribut headcount: {$team->Get("headcount")}
      \n"; - MyHelpers::var_dump_html($team); - } - - function test_setattribute() - { - echo "

      Set attribute and update

      "; - $team = MetaModel::GetObject("cmdbTeam", "2"); - $team->Set("headcount", rand(1,1000)); - $team->Set("email", "Luis ".rand(9,250)); - MyHelpers::var_dump_html($team->ListChanges()); - echo "New headcount = {$team->Get("headcount")}
      \n"; - echo "Computed name = {$team->Get("name")}
      \n"; - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - - //MetaModel::StartDebugQuery(); - $team->DBUpdateTracked($oMyChange); - //MetaModel::StopDebugQuery(); - - echo "

      Check the modified team

      "; - $oTeam = MetaModel::GetObject("cmdbTeam", "2"); - MyHelpers::var_dump_html($oTeam); - } - function test_newobject() - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_newobject / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - - echo "

      Create a new object (team)

      "; - $oNewTeam = MetaModel::NewObject("cmdbTeam"); - $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); - $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); - $oNewTeam->Set("email", null); - $oNewTeam->Set("owner", "ITOP"); - $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value - $iId = $oNewTeam->DBInsertTracked($oMyChange); - echo "Created new team: $iId
      "; - echo "

      Delete team #$iId

      "; - $oTeam = MetaModel::GetObject("cmdbTeam", $iId); - $oTeam->DBDeleteTracked($oMyChange); - echo "Deleted team: $iId
      "; - MyHelpers::var_dump_html($oTeam); - } - - - function test_updatecolumn() - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_updatecolumn / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - - $sNewEmail = "updatecol".rand(9,250)."@quedlaballe.com"; - echo "

      Update a the email: set to '$sNewEmail'

      "; - $oMyFilter = new DBObjectSearch("cmdbContact"); - $oMyFilter->AddCondition("name", "o", "Contains"); - - echo "Candidates before:
      "; - $this->search_and_show_list($oMyFilter); - - MetaModel::BulkUpdateTracked($oMyChange, $oMyFilter, array("email" => $sNewEmail)); - - echo "Candidates after:
      "; - $this->search_and_show_list($oMyFilter); - } - - function test_error() - { - trigger_error("Stop requested", E_USER_ERROR); - } - - function test_changetracking() - { - echo "

      Create a change

      "; - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - echo "Created new change: $iChangeId
      "; - MyHelpers::var_dump_html($oMyChange); - - echo "

      Create a new object (team)

      "; - $oNewTeam = MetaModel::NewObject("cmdbTeam"); - $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); - $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); - $oNewTeam->Set("email", null); - $oNewTeam->Set("owner", "ITOP"); - $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value - $iId = $oNewTeam->DBInsertTracked($oMyChange); - echo "Created new team: $iId
      "; - echo "

      Delete team #$iId

      "; - $oTeam = MetaModel::GetObject("cmdbTeam", $iId); - $oTeam->DBDeleteTracked($oMyChange); - echo "Deleted team: $iId
      "; - MyHelpers::var_dump_html($oTeam); - } - - function test_zlist() - { - echo "

      Test ZLists

      "; - $aZLists = MetaModel::EnumZLists(); - foreach ($aZLists as $sListCode) - { - $aListInfos = MetaModel::GetZListInfo($sListCode); - echo "

      List '".$sListCode."' (".$aListInfos["description"].") of type '".$aListInfos["type"]."'

      \n"; - - foreach (MetaModel::GetSubclasses("cmdbObjectHomeMade") as $sKlass) - { - $aItems = MetaModel::GetZListItems($sKlass, $sListCode); - if (count($aItems) == 0) continue; - - echo "$sKlass - $sListCode : {".implode(", ", $aItems)."}
      \n"; - } - } - - echo "

      IsAttributeInZList()...

      "; - echo "Liens_entre_contacts_et_workshop::ws_info in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "ws_info") ? "yes" : "no")."
      \n"; - echo "Liens_entre_contacts_et_workshop::toworkshop in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "toworkshop") ? "yes" : "no")."
      \n"; - - } - - function test_SibuSQL() - { - echo "

      Simple But Structured Query Language

      "; - - $oMyFilter = new DBObjectSearch("cmdbContact"); - echo "Tous les contacts: ".$oMyFilter->ToSibuSQL()."
      \n"; - $oNewFilter = DBObjectSearch::FromSibuSQL($oMyFilter->ToSibuSQL()); - echo "En passant par un filtre, ca revient en : ".$oNewFilter->ToSibuSQL()."
      \n"; - $this->search_and_show_list($oNewFilter); - - $sFilterDesc = "cmdbContact: name Begins with '$[debutnom:as:debut du nom]' AND ownername NotLike $[ddd::]"; - echo "Construction d'un filtre a partir de sa description en SibuSQL: $sFilterDesc
      \n"; - - MyHelpers::var_dump_html(DBObjectSearch::ListSibusQLParams($sFilterDesc)); - $oNewFilter = DBObjectSearch::FromSibuSQL($sFilterDesc, array('ddd'=>123)); - echo "Ca revient en: ".$oNewFilter->ToSibuSQL(); - } - - function test_pkey() - { - echo "

      Test search on pkey

      "; - $sExpr1 = "cmdbContact: pkey IN {40, 42}"; - $sExpr2 = "cmdbContact: pkey NOTIN {40, 42}"; - $this->search_and_show_list_from_sibusql($sExpr1); - $this->search_and_show_list_from_sibusql($sExpr2); - - echo "Et maintenant, on fusionne....
      \n"; - $oSet1 = new CMDBObjectSet(DBObjectSearch::FromSibuSQL($sExpr1)); - $oSet2 = new CMDBObjectSet(DBObjectSearch::FromSibuSQL($sExpr2)); - $oIntersect = $oSet1->CreateIntersect($oSet2); - $oDelta = $oSet1->CreateDelta($oSet2); - - $oMerge = clone $oSet1; - $oMerge->Merge($oSet2); - $oMerge->Merge($oSet2); - - echo "Set1 - Found ".$oSet1->Count()." items.
      \n"; - echo "Set2 - Found ".$oSet2->Count()." items.
      \n"; - echo "Intersect - Found ".$oIntersect->Count()." items.
      \n"; - echo "Delta - Found ".$oDelta->Count()." items.
      \n"; - echo "Merge - Found ".$oMerge->Count()." items.
      \n"; - //$this->show_list($oObjSet); - } - - function test_relations() - { - echo "

      Test relations

      "; - - //MyHelpers::var_dump_html(MetaModel::EnumRelationQueries("cmdbObjectHomeMade", "Potes")); - MyHelpers::var_dump_html(MetaModel::EnumRelationQueries("cmdbContact", "Potes")); - - $iMaxDepth = 9; - echo "Max depth = $iMaxDepth
      \n"; - - $oObj = MetaModel::GetObject("cmdbContact", 18); - $aRels = $oObj->GetRelatedObjects("Potes", $iMaxDepth); - echo $oObj->Get('name')." has some 'Potes'...
      \n"; - foreach ($aRels as $sClass => $aObjs) - { - echo "$sClass, count = ".count($aObjs)." => ".implode(', ', array_keys($aObjs))."
      \n"; - $oObjectSet = CMDBObjectSet::FromArray($sClass, $aObjs); - $this->show_list($oObjectSet); - } - - echo "

      Test relations - same results, by the mean of a SibuSQL

      "; - $this->search_and_show_list_from_sibusql("cmdbContact: RELATED (Potes, $iMaxDepth) TO (cmdbContact: pkey = 18)"); - - } - - function test_linkedset() - { - echo "

      Linked set attributes

      \n"; - $oObj = MetaModel::GetObject("cmdbContact", 18); - - echo "
      Current workshops
      \n"; - $oSetWorkshopsCurr = $oObj->Get("myworkshops"); - $this->show_list($oSetWorkshopsCurr); - - echo "
      Setting workshops
      \n"; - $oNewLink = new cmdbLiens(); - $oNewLink->Set('toworkshop', 2); - $oNewLink->Set('function', 'mafonctioooon'); - $oNewLink->Set('a1', 'tralala1'); - $oNewLink->Set('a2', 'F7M'); - $oSetWorkshops = CMDBObjectSet::FromArray("cmdbLiens", array($oNewLink)); - $oObj->Set("myworkshops", $oSetWorkshops); - $this->show_list($oSetWorkshops); - - echo "
      New workshops
      \n"; - $oSetWorkshopsCurr = $oObj->Get("myworkshops"); - $this->show_list($oSetWorkshopsCurr); - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_linkedset / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); - $oObj = MetaModel::GetObject("cmdbContact", 18); - - echo "
      After the write
      \n"; - $oSetWorkshopsCurr = $oObj->Get("myworkshops"); - $this->show_list($oSetWorkshopsCurr); - } - - function test_object_lifecycle() - { - echo "

      Test object lifecycle

      "; - - - MyHelpers::var_dump_html(MetaModel::GetStateAttributeCode("cmdbContact")); - MyHelpers::var_dump_html(MetaModel::EnumStates("cmdbContact")); - MyHelpers::var_dump_html(MetaModel::EnumStimuli("cmdbContact")); - foreach(MetaModel::EnumStates("cmdbContact") as $sStateCode => $aStateDef) - { - echo "

      Transition from $sStateCode

      \n"; - MyHelpers::var_dump_html(MetaModel::EnumTransitions("cmdbContact", $sStateCode)); - } - - $oObj = MetaModel::GetObject("cmdbContact", 18); - echo "Current state: ".$oObj->GetState()."... let's go to school..."; - MyHelpers::var_dump_html($oObj->EnumTransitions()); - $oObj->ApplyStimulus("toschool"); - echo "New state: ".$oObj->GetState()."... let's get older..."; - MyHelpers::var_dump_html($oObj->EnumTransitions()); - $oObj->ApplyStimulus("raise"); - echo "New state: ".$oObj->GetState()."... let's try to go further... (should give an error)"; - MyHelpers::var_dump_html($oObj->EnumTransitions()); - $oObj->ApplyStimulus("raise"); // should give an error - } - - - protected function DoExecute() - { -// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); -// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - //$this->test_linksinfo(); - //$this->test_list_attributes(); - //$this->test_search(); - //$this->test_reload(); - //$this->test_newobject(); - $this->test_setattribute(); - //$this->test_updatecolumn(); - //$this->test_error(); - //$this->test_changetracking(); - $this->test_zlist(); - $this->test_SibuSQL(); - //$this->test_pkey(); - $this->test_relations(); - $this->test_linkedset(); - $this->test_object_lifecycle(); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test a complex biz model on the fly -/////////////////////////////////////////////////////////////////////////// - -abstract class MyFarm extends TestBizModel -{ - static public function GetConfigFile() {return '../config-test-farm.php';} - - protected function DoPrepare() - { - parent::DoPrepare(); - $this->ResetDB(); - MetaModel::DBCheckIntegrity(); - } - - protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) - { - $oNew = MetaModel::NewObject('Mammal'); - $oNew->Set('species', $sSpecies); - $oNew->Set('sex', $sSex); - $oNew->Set('speed', $iSpeed); - $oNew->Set('mother', $iMotherid); - $oNew->Set('father', $iFatherId); - $oNew->Set('name', $sName); - $oNew->Set('height', $iHeight); - $oNew->Set('birth', $sBirth); - return $this->ObjectToDB($oNew); - } - - protected function InsertBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId) - { - $oNew = MetaModel::NewObject('Bird'); - $oNew->Set('species', $sSpecies); - $oNew->Set('sex', $sSex); - $oNew->Set('speed', $iSpeed); - $oNew->Set('mother', $iMotherid); - $oNew->Set('father', $iFatherId); - return $this->ObjectToDB($oNew); - } - - protected function InsertFlyingBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $iFlyingSpeed) - { - $oNew = MetaModel::NewObject('FlyingBird'); - $oNew->Set('species', $sSpecies); - $oNew->Set('sex', $sSex); - $oNew->Set('speed', $iSpeed); - $oNew->Set('mother', $iMotherid); - $oNew->Set('father', $iFatherId); - $oNew->Set('flyingspeed', $iFlyingSpeed); - return $this->ObjectToDB($oNew); - } - - private function InsertGroup($sName, $iLeaderId) - { - $oNew = MetaModel::NewObject('Group'); - $oNew->Set('name', $sName); - $oNew->Set('leader', $iLeaderId); - $iId = $oNew->DBInsertNoReload(); - return $iId; - } -} - - -class TestQueriesOnFarm extends MyFarm -{ - static public function GetName() - { - return 'Farm test'; - } - - static public function GetDescription() - { - return 'A series of tests on the farm business model (SQL generation)'; - } - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - if ($bIsCorrectQuery) - { - echo "

      $sQuery

      \n"; - } - else - { - echo "

      $sQuery

      \n"; - } - try - { - //$oOql = new OqlInterpreter($sQuery); - //$oTrash = $oOql->ParseObjectQuery(); - //MyHelpers::var_dump_html($oTrash, true); - $oMyFilter = DBObjectSearch::FromOQL($sQuery); - } - catch (OQLException $oOqlException) - { - if ($bIsCorrectQuery) - { - echo "

      More info on this unexpected failure:
      ".$oOqlException->getHtmlDesc()."

      \n"; - throw $oOqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

      More info on this expected failure:\n"; - echo "

        \n"; - echo "
      • ".get_class($oOqlException)."
      • \n"; - echo "
      • ".$oOqlException->getMessage()."
      • \n"; - echo "
      • ".$oOqlException->getHtmlDesc()."
      • \n"; - echo "
      \n"; - echo "

      \n"; - return true; - } - } - // The query was correctly parsed, was it expected to be correct ? - if (!$bIsCorrectQuery) - { - throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); - return false; - } - echo "

      To OQL: ".$oMyFilter->ToOQL()."

      "; - - $this->search_and_show_list($oMyFilter); - - //echo "

      first pass

      \n"; - //MyHelpers::var_dump_html($oMyFilter, true); - $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); - //echo "

      second pass

      \n"; - //MyHelpers::var_dump_html($oMyFilter, true); - //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); - - $sSerialize = $oMyFilter->serialize(); - echo "

      Serialized:$sSerialize

      \n"; - $oFilter2 = DBObjectSearch::unserialize($sSerialize); - try - { - $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); - } - catch (Exception $e) - { - echo "

      Could not compute the query after unserialize

      \n"; - echo "

      Query 1: $sQuery1

      \n"; - MyHelpers::var_cmp_html($oMyFilter, $oFilter2); - throw $e; - } - //if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same! - if ($sQuery1 != $sQuery2) - { - echo "

      serialize/unserialize mismatch :-(

      \n"; - MyHelpers::var_cmp_html($sQuery1, $sQuery2); - MyHelpers::var_cmp_html($oMyFilter, $oFilter2); - return false; - } - return true; - } - - protected function DoExecute() - { -// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); -// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - echo "

      Create protagonists...

      "; - - $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); - $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); - $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); - $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); - $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); - - $this->InsertBird('rooster', 'male', 12, 0, 0); - $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); - - // Benchmarking - // - if (false) - { - define ('COUNT_BENCHMARK', 10); - echo "

      Parsing a long query, ".COUNT_BENCHMARK." times

      "; - $sQuery = "SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR) AND Dad.height * 2 <= ROUND(TO_DAYS(Dad.birth) / (3 + 1) * 5 - 3)"; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $oMyFilter = DBObjectSearch::FromOQL($sQuery); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fParsingDuration = $fDuration / COUNT_BENCHMARK; - echo "

      Mean time by op: $fParsingDuration

      "; - } - - echo "

      Test queries...

      "; - - $aQueries = array( - 'SELECT Animal' => true, - 'SELECT Animal WHERE Animal.pkey = 1' => false, - 'SELECT Animal WHERE Animal.id = 1' => true, - 'SELECT Aniiimal' => false, - 'SELECTe Animal' => false, - 'SELECT * FROM Animal' => false, - 'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true, - 'SELECT Animal AS zoo WHERE species = \'human\'' => true, - 'SELECT Animal AS zoo WHERE espece = \'human\'' => false, - 'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true, - 'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false, - 'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true, - 'SELECT Animal AS zoo WHERE zoo.species NOT IN (\'human\', "pig")' => true, - 'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false, - 'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true, - 'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true, - 'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true, - 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, - 'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true, - 'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true, - 'SELECT Mammal AS m WHERE m.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR)' => true, - 'SELECT Mammal AS m WHERE m.birth > DATE_SUB(NOW(), INTERVAL 2000 DAY)' => true, - 'SELECT Mammal AS m WHERE (TO_DAYS(NOW()) - TO_DAYS(m.birth)) > 2000' => true, - 'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true, - 'SELECT Mammal AS m WHERE (1 + 2' => false, - 'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true, - 'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true, - 'SELECT Mammal AS m WHERE 1/0' => true, - 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, - 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, - 'SELECT Group JOIN Animal ON Group.leader = Animal.id' => true, - 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, - 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.masterchief = A.id' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.pkey' => false, - 'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false, - 'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false, - 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true, - 'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false, - 'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false, - 'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false, - 'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true, - 'SELECT Group AS G WHERE G.leader_speed < 100000' => true, - 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, - 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true, - 'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false, - 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true, - 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, - 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, - 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, - ); - //$aQueries = array( - // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, - //); - foreach($aQueries as $sQuery => $bIsCorrect) - { - $this->CheckQuery($sQuery, $bIsCorrect); - } - return true; - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test data load -/////////////////////////////////////////////////////////////////////////// - -class TestBulkChangeOnFarm extends TestBizModel -{ - static public function GetName() - { - return 'Farm test - data load'; - } - - static public function GetDescription() - { - return 'Bulk load'; - } - - static public function GetConfigFile() {return '../config-test-farm.php';} - - protected function DoPrepare() - { - parent::DoPrepare(); - $this->ResetDB(); - MetaModel::DBCheckIntegrity(); - } - - protected function DoExecute() - { -// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); -// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - - $oParser = new CSVParser("#denomination,hauteur,age - suzy,123,2009-01-01 - chita,456, - "); - $oParser->SetSeparator(','); - $aData = $oParser->ToArray(array('_name', '_height', '_birth')); - MyHelpers::var_dump_html($aData); - - $oBulk = new BulkChange( - 'Mammal', - $aData, - array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), - array('name'), - array() - ); - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Testor"); - $iChangeId = $oMyChange->DBInsert(); -// echo "Created new change: $iChangeId
      "; - - echo "

      Planned for loading...

      "; - $aRes = $oBulk->Process(); - print_r($aRes); - echo "

      Go for loading...

      "; - $aRes = $oBulk->Process($oMyChange); - print_r($aRes); - - return; - - $oRawData = array( - 'Mammal', - array('species', 'sex', 'speed', 'mother', 'father', 'name', 'height', 'birth'), - "human,male,23,0,0,romulus,192,1971 - human,male,23,0,0,remus,154,-50 - human,male,23,0,0,julius,160,-49 - human,female,23,0,0,cleopatra,142,-50 - pig,female,23,0,0,confucius,50,2003" - ); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test data load -/////////////////////////////////////////////////////////////////////////// - -class TestFullTextSearchOnFarm extends MyFarm -{ - static public function GetName() - { - return 'Farm test - full text search'; - } - - static public function GetDescription() - { - return 'Focus on the full text search feature'; - } - - protected function DoExecute() - { - echo "

      Create protagonists...

      "; - - $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); - $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); - $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); - $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); - $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); - - $this->InsertBird('rooster', 'male', 12, 0, 0); - $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); - - echo "

      Search...

      "; - $oSearch = new DBObjectSearch('Mammal'); - $oSearch->AddCondition_FullText('manof'); - //$oResultSet = new DBObjectSet($oSearch); - $this->search_and_show_list($oSearch); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Benchmark queries -/////////////////////////////////////////////////////////////////////////// - -class TestItopEfficiency extends TestBizModel -{ - static public function GetName() - { - return 'Itop - benchmark'; - } - - static public function GetDescription() - { - return 'Measure time to perform the queries'; - } - - static public function GetConfigFile() {return '../config-itop.php';} - - protected function DoBenchmark($sOqlQuery) - { - echo "

      Testing query: $sOqlQuery

      "; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $oFilter = DBObjectSearch::FromOQL($sOqlQuery); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fParsingDuration = $fDuration / COUNT_BENCHMARK; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $sSQL = MetaModel::MakeSelectQuery($oFilter); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fBuildDuration = $fDuration / COUNT_BENCHMARK; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $res = CMDBSource::Query($sSQL); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fQueryDuration = $fDuration / COUNT_BENCHMARK; - - // The fetch could not be repeated with the same results - // But we've seen so far that is was very very quick to exec - // So it makes sense to benchmark it a single time - $fStart = MyHelpers::getmicrotime(); - $aRow = CMDBSource::FetchArray($res); - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fFetchDuration = $fDuration; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $sOql = $oFilter->ToOQL(); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fToOqlDuration = $fDuration / COUNT_BENCHMARK; - - echo "
        \n"; - echo "
      • Parsing: $fParsingDuration
      • \n"; - echo "
      • Build: $fBuildDuration
      • \n"; - echo "
      • Query: $fQueryDuration
      • \n"; - echo "
      • Fetch: $fFetchDuration
      • \n"; - echo "
      • ToOql: $fToOqlDuration
      • \n"; - echo "
      \n"; - - // Everything but the ToOQL (wich is interesting, anyhow) - $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; - - return array( - 'rows' => CMDBSource::NbRows($res), - 'duration (s)' => round($fTotal, 4), - 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), - 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), - 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), - 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), - 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), - 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), - ); - } - - protected function DoExecute() - { - define ('COUNT_BENCHMARK', 3); - echo "

      The test will be repeated ".COUNT_BENCHMARK." times

      "; - - $aQueries = array( - 'SELECT CMDBChangeOpSetAttribute', - 'SELECT CMDBChangeOpSetAttribute WHERE id=10', - 'SELECT CMDBChangeOpSetAttribute WHERE id=123456789', - 'SELECT CMDBChangeOpSetAttribute WHERE CMDBChangeOpSetAttribute.id=10', - 'SELECT bizIncidentTicket', - 'SELECT bizIncidentTicket WHERE id=1', - 'SELECT bizPerson', - 'SELECT bizPerson WHERE id=1', - 'SELECT bizIncidentTicket JOIN bizPerson ON bizIncidentTicket.agent_id = bizPerson.id WHERE bizPerson.id = 5', - ); - $aStats = array(); - foreach ($aQueries as $sOQL) - { - $aStats[$sOQL] = $this->DoBenchmark($sOQL); - } - - $aData = array(); - foreach ($aStats as $sOQL => $aResults) - { - $aValues = array(); - $aValues['OQL'] = htmlentities($sOQL); - - foreach($aResults as $sDesc => $sInfo) - { - $aValues[$sDesc] = htmlentities($sInfo); - } - $aData[] = $aValues; - } - echo MyHelpers::make_table_from_assoc_array($aData); - } -} - -/////////////////////////////////////////////////////////////////////////// -// Test data load -/////////////////////////////////////////////////////////////////////////// - -class TestItopWebServices extends TestWebServices -{ - static public function GetName() - { - return 'Itop - web services'; - } - - static public function GetDescription() - { - return 'Bulk load and ???'; - } - - protected function DoExecSingleLoad($aLoadSpec) - { - $sTitle = 'Load: '.$aLoadSpec['class']; - $sClass = $aLoadSpec['class']; - $sCsvData = $aLoadSpec['csvdata']; - - $aPostData = array('class' => $sClass, 'csvdata' => $sCsvData); - $sRes = self::DoPostRequestAuth('../webservices/import.php', $aPostData); - - echo "

      $sTitle

      $sCsvData
      $sRes
      "; - } - - protected function DoExecute() - { - - $aLoads = array( - array( - 'class' => 'bizOrganization', - 'csvdata' => "name;code\nWorldCompany;WCY" - ), - array( - 'class' => 'bizLocation', - 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca" - ), - array( - 'class' => 'bizPerson', - 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789" - ), - array( - 'class' => 'bizTeam', - 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris" - ), - array( - 'class' => 'bizWorkgroup', - 'csvdata' => "name;org_id;team_id\ntravailleurs alpins;1;6" - ), - array( - 'class' => 'bizIncidentTicket', - 'csvdata' => "name;title;type;org_id;initial_situation;start_date;next_update;caller_id;workgroup_id;agent_id\nOVSD-12345;server down;Network;1;server was found down;2009-04-10 12:00;2009-04-10 15:00;3;317;5" - ), - ); - - foreach ($aLoads as $aLoadSpec) - { - $this->DoExecSingleLoad($aLoadSpec); - } - } -} - - -$aWebServices = array( - array( - 'verb' => 'GetVersion', - 'expected result' => '0.8', - 'explain result' => 'n/a', - 'args' => array(), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'link attribute unknown + a CI not found', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Server', /* sType */ - 'desc of ticket', /* sDescription */ - 'initial situation blah blah blah', /* sInitialSituation */ - 'very grave', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - new SOAPLinkCreationSpec( - 'logInfra', - array(new SOAPSearchCondition('id', 108)), - array(new SOAPAttributeValue('impacting', 'very critical')) - ), - new SOAPLinkCreationSpec( - 'bizDevice', - array(new SOAPSearchCondition('name', 'Router03')), - array(new SOAPAttributeValue('impact', 'who cares')) - ), - new SOAPLinkCreationSpec( - 'bizDevice', - array(new SOAPSearchCondition('name', 'thisone')), - array(new SOAPAttributeValue('impact', 'our lives')) - ), - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'caller not specified', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Desktop', /* sType */ - 'PC burning', /* sDescription */ - 'The power supply suddenly started to warm up', /* sInitialSituation */ - 'The agent could not do his job', /* sImpact */ - null, /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong condition on CI to attach', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Desktop', /* sType */ - 'PC burning', /* sDescription */ - 'The power supply suddenly started to warm up', /* sInitialSituation */ - 'The agent could not do his job', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - new SOAPLinkCreationSpec( - 'logInfra', - array(new SOAPSearchCondition('dummyfiltercode', 2)), - array(new SOAPAttributeValue('impact', 'very critical')) - ), - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'no CI to attach (empty array)', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Network', /* sType */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - 'Could not talk to my wife', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'no CI to attach (null)', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Network', /* sType */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - 'Could not talk to my wife', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - null, /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'caller unknown', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Network', /* sType */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - 'Could not talk to my wife', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1000))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong values for type and severity', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'my type', /* sType */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - 'Could not talk to my wife', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - 'my severity' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong password', - 'args' => array( - 'admin', /* sLogin */ - 'xxxxx', /* sPassword */ - 'Network', /* sType */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - 'Could not talk to my wife', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong login', - 'args' => array( - 'xxxxx', /* sLogin */ - 'yyyyy', /* sPassword */ - 'Network', /* sType */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - 'Could not talk to my wife', /* sImpact */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - 'low' /* sSeverity */ - ), - ), -); - - -class TestSoap extends TestSoapWebService -{ - static public function GetName() {return 'Test SOAP';} - static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';} - - protected function DoExecute() - { - echo "

      Note: You may also want to try the sample SOAP client itopsoap.examples.php

      \n"; - - global $aSOAPMapping; - - // this file is generated dynamically with location = here - $sWsdlUri = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php'; - - ini_set("soap.wsdl_cache_enabled","0"); - $this->m_SoapClient = new SoapClient - ( - $sWsdlUri, - array( - 'classmap' => $aSOAPMapping, - 'trace' => 1, - ) - ); - - if (false) - { - print "
      \n"; 
      -			print_r($this->m_SoapClient->__getTypes());
      -			print "
      \n"; - } - - global $aWebServices; - foreach ($aWebServices as $iPos => $aWebService) - { - echo "

      SOAP call #$iPos ".$aWebService['explain result']."

      \n"; - - try - { - $oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']); - } - catch(SoapFault $e) - { - print "
      \n"; 
      -				print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
      -				print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
      -				print "
      "; - print "Response in HTML:

      ".$this->m_SoapClient->__getLastResponse()."

      "; - throw $e; - } - - echo "
      \n";
      -			print_r($oRes);
      -			echo "
      \n"; - - print "
      \n"; 
      -			print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
      -			print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
      -			print "
      "; - - if ($oRes instanceof SOAPResult) - { - $res = $oRes->status; - } - else - { - $res = $oRes; - } - if ($res != $aWebService['expected result']) - { - throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); - } - } - } -} - -class TestWebServicesDirect extends TestBizModel -{ - static public function GetName() {return 'Test web services locally';} - static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';} - - static public function GetConfigFile() {return '../config-itop.php';} - - protected function DoExecute() - { - $oWebServices = new WebServices(); - - global $aWebServices; - foreach ($aWebServices as $aWebService) - { - $oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']); - echo "
      \n";
      -			print_r($oRes);
      -			echo "
      \n"; - } - return true; - } -} - -class TestTriggerAndEmail extends TestBizModel -{ - static public function GetName() {return 'Test trigger and email';} - static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';} - - static public function GetConfigFile() {return '../config-itop.php';} - - protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail) - { - $oAction = MetaModel::NewObject("ActionEmail"); - $oAction->Set("status", $sStatus); - $oAction->Set("name", "New server"); - $oAction->Set("test_recipient", $sTesterEmail); - $oAction->Set("from", $sTesterEmail); - $oAction->Set("reply_to", $sTesterEmail); - $oAction->Set("to", $sTo); - $oAction->Set("cc", $sCC); - $oAction->Set("bcc", ""); - $oAction->Set("subject", "New server: '\$this->name()$'"); - $oAction->Set("body", "

      Dear customer,

      We have created the server \$this->hyperlink()$ in the IT infrastructure database.

      You will be further notified when it is in Production.

      The IT infrastructure management team.

      Here are some accentuated characters for french people: 'ééà'

      "); - $oAction->Set("importance", "low"); - $iActionId = $this->ObjectToDB($oAction, true); - - $oLink = MetaModel::NewObject("lnkTriggerAction"); - $oLink->Set("trigger_id", $oTrigger->GetKey()); - $oLink->Set("action_id", $iActionId); - $oLink->Set("order", "1"); - $iLink = $this->ObjectToDB($oLink, true); - } - - protected function DoExecute() - { - $oMyPerson = MetaModel::NewObject("bizPerson"); - $oMyPerson->Set("name", "testemail1"); - $oMyPerson->Set("org_id", "1"); - $oMyPerson->Set("email", "romain.quetiez@hp.com"); - $iPersonId = $this->ObjectToDB($oMyPerson, true); - - $oMyPerson = MetaModel::NewObject("bizPerson"); - $oMyPerson->Set("name", "testemail2"); - $oMyPerson->Set("org_id", "1"); - $oMyPerson->Set("email", "denis.flaven@hp.com"); - $iPersonId = $this->ObjectToDB($oMyPerson, true); - - $oMyPerson = MetaModel::NewObject("bizPerson"); - $oMyPerson->Set("name", "testemail3"); - $oMyPerson->Set("org_id", "1"); - $oMyPerson->Set("email", "erwan.taloc@hp.com"); - $iPersonId = $this->ObjectToDB($oMyPerson, true); - - $oMyServer = MetaModel::NewObject("bizServer"); - $oMyServer->Set("name", "wfr.terminator.com"); - $oMyServer->Set("severity", "low"); - $oMyServer->Set("status", "production"); - $oMyServer->Set("org_id", 2); - $oMyServer->Set("location_id", 2); - $iServerId = $this->ObjectToDB($oMyServer, true); - - $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); - $oMyTrigger->Set("description", "Testor"); - $oMyTrigger->Set("target_class", "bizServer"); - $oMyTrigger->Set("state", "Shipped"); - $iTriggerId = $this->ObjectToDB($oMyTrigger, true); - - // Error in OQL field(s) - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "SELECT bizPerson WHERE naime = 'Dali'", - "SELECT bizServer", - 'romain.quetiez@hp.com' - ); - - // Error: no recipient - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "", - "", - 'romain.quetiez@hp.com' - ); - - // Test - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "SELECT bizPerson WHERE name LIKE 'testemail%'", - "SELECT bizPerson", - 'romain.quetiez@hp.com' - ); - - // Test failing because of a wrong test recipient address - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "SELECT bizPerson WHERE name LIKE 'testemail%'", - "", - 'toto@walibi.bg' - ); - - // Normal behavior - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'enabled', - "SELECT bizPerson WHERE name LIKE 'testemail%'", - "", - 'romain.quetiez@hp.com' - ); - - // Does nothing, because it is disabled - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'disabled', - "SELECT bizPerson WHERE name = 'testemail%'", - "", - 'romain.quetiez@hp.com' - ); - - $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); - - return true; - } -} -?> +new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new BinaryExpression(new FieldExpression('column1', 'myTableAlias'), 'LIKE', new ScalarExpression('trash')), + $aFullTextNeedles = array('column1'), + $bToDelete = false, + $aValues = array() + ); + $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); + + $oSubQuery1 = new SQLQuery( + $sTable = 'myTable1', + $sTableAlias = 'myTable1Alias', + $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oSubQuery2 = new SQLQuery( + $sTable = 'myTable2', + $sTableAlias = 'myTable2Alias', + $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oQuery->AddInnerJoin($oSubQuery1, 'column1', 'column1_1'); + $oQuery->AddLeftJoin($oSubQuery2, 'column2', 'column2_2'); + + $oQuery->DisplayHtml(); + $oQuery->RenderDelete(); + $oQuery->RenderUpdate(); + echo '

      '.$oQuery->RenderSelect().'

      '; + $oQuery->RenderSelect(array('column1')); + $oQuery->RenderSelect(array('column1', 'column2')); + } +} + +class TestOQLParser extends TestFunction +{ + static public function GetName() {return 'Check OQL parsing';} + static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + $oOql = new OqlInterpreter($sQuery); + try + { + $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery + MyHelpers::var_dump_html($oTrash, true); + } + catch (OQLException $OqlException) + { + if ($bIsCorrectQuery) + { + echo "

      More info on this unexpected failure:
      ".$OqlException->getHtmlDesc()."

      \n"; + throw $OqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

      More info on this expected failure:
      ".$OqlException->getHtmlDesc()."

      \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if ($bIsCorrectQuery) + { + return true; + } + else + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + } + + protected function TestQuery($sQuery, $bIsCorrectQuery) + { + if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) + { + return false; + } + return true; + } + + public function DoExecute() + { + $aQueries = array( + 'SELECT toto' => true, + 'SELECT toto WHERE toto.a = 1' => true, + 'SELECT toto WHERE toto.a=1' => true, + 'SELECT toto WHERE toto.a = "1"' => true, + 'SELECT toto WHHHERE toto.a = "1"' => false, + 'SELECT toto WHERE toto.a == "1"' => false, + 'SELECT toto WHERE toto.a % 1' => false, + //'SELECT toto WHERE toto.a LIKE 1' => false, + 'SELECT toto WHERE toto.a like \'arg\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, + + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE ""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, + "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, + + 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, + + //'SELECT toto WHERE toto.a > \'asd\'' => false, + 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, + + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, + + 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, + ); + + $iErrors = 0; + + foreach($aQueries as $sQuery => $bIsCorrectQuery) + { + $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; + echo "

      Testing query: $sQuery ($sIsOk)

      \n"; + $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); + if (!$bRet) $iErrors++; + } + + return ($iErrors == 0); + } +} + + +class TestCSVParser extends TestFunction +{ + static public function GetName() {return 'Check CSV parsing';} + static public function GetDescription() {return 'Loads a set of CSV data';} + + public function DoExecute() + { + $sDataFile = '"field1","field2","field3" +"a","b","c" +a,b,c +"","","" +,, +"a""","b","c" +"a1 +a2","b","c" +"a1,a2","b","c" +"a","b","c1,"",c2 +,c3" +"a","b","ouf !" +'; + + $sDataFile = '?field1?;?field2?;?field3? +?a?;?b?;?c? +a;b;c +??;??;?? +;; +?a"?;?b?;?c? +?a1 +a2?;?b?;?c? +?a1,a2?;?b?;?c? +?a?;?b?;?c1,",c2 +,c3? +?a?;?b?;?ouf !? +'; + + echo "
      \n";
      +		print_r($sDataFile);
      +		echo "
      \n"; + + $aExpectedResult = array( + //array('field1', 'field2', 'field3'), + array('a', 'b', 'c'), + array('a', 'b', 'c'), + array('', '', ''), + array('', '', ''), + array('a"', 'b', 'c'), + array("a1\na2", 'b', 'c'), + array('a1,a2', 'b', 'c'), + array('a', 'b', "c1,\",c2\n,c3"), + array('a', 'b', 'ouf !'), + array('a', 'b', 'a'), + ); + + $oCSVParser = new CSVParser($sDataFile, ';', '?'); + $aData = $oCSVParser->ToArray(1, null, 0); + + $iIssues = 0; + + echo "\n"; + foreach ($aData as $iRow => $aRow) + { + echo "\n"; + foreach ($aRow as $iCol => $sCell) + { + if (empty($sCell)) + { + $sCellValue = ' '; + } + else + { + $sCellValue = htmlentities($sCell); + } + + if (!isset($aExpectedResult[$iRow][$iCol])) + { + $iIssues++; + $sCellValue = "$sCellValue"; + } + elseif ($aExpectedResult[$iRow][$iCol] != $sCell) + { + $iIssues++; + $sCellValue = "$sCellValue, expecting '".$aExpectedResult[$iRow][$iCol]."'"; + } + + echo ""; + } + echo "\n"; + } + echo "
      $sCellValue
      \n"; + return ($iIssues > 0); + } +} + +class TestGenericItoMyModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} +} + +class TestGenericItopBigModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-itopv06.php';} +} + +class TestUserRightsMatrixItop extends TestUserRights +{ + static public function GetName() + { + return 'User rights test on user rights matrix'; + } + + static public function GetDescription() + { + return 'blah blah blah'; + } + + public function DoPrepare() + { + parent::DoPrepare(); + MetaModel::Startup('../config-test-itopv06.php'); + } + + protected function DoExecute() + { + $sUser = 'Romain'; + echo "

      Totor: ".(UserRights::Login('Totor', 'toto') ? 'ok' : 'NO')."

      \n"; + echo "

      Romain: ".(UserRights::Login('Romain', 'toto') ? 'ok' : 'NO')."

      \n"; + echo "

      User: ".UserRights::GetUser()."

      \n"; + echo "

      On behalf of...".UserRights::GetRealUser()."

      \n"; + + echo "

      Denis (impersonate) : ".(UserRights::Impersonate('Denis', 'tutu') ? 'ok' : 'NO')."

      \n"; + echo "

      User: ".UserRights::GetUser()."

      \n"; + echo "

      On behalf of...".UserRights::GetRealUser()."

      \n"; + + UserRights::GetFilter('bizOrganization'); // returns a filter object + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); + echo "

      IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

      \n"; + echo "

      IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

      \n"; + echo "

      IsActionAllowedOnAttribute...".(UserRights::IsActionAllowedOnAttribute('bizOrganization', 'myattribute', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

      \n"; + return true; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +class TestMyBizModel extends TestBizModel +{ + static public function GetName() + { + return 'A series of tests on a weird business model'; + } + + static public function GetDescription() + { + return 'Attempts various operations and build complex queries'; + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} + + function test_linksinfo() + { + echo "

      Enum links

      "; + MyHelpers::var_dump_html(MetaModel::EnumReferencedClasses("cmdbTeam")); + MyHelpers::var_dump_html(MetaModel::EnumReferencingClasses("Organization")); + + MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses()); + MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses("cmdbContact")); + MyHelpers::var_dump_html(MetaModel::EnumLinkingClasses("cmdWorkshop")); + MyHelpers::var_dump_html(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); + } + + function test_list_attributes() + { + echo "

      List attributes

      "; + foreach(MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode=>$oAttDef) + { + echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
      \n"; + } + } + + function test_search() + { + echo "

      Two searches

      "; + $oFilterAllDevs = new DBObjectSearch("cmdbTeam"); + $oAllDevs = new DBObjectSet($oFilterAllDevs); + + echo "Found ".$oAllDevs->Count()." items.
      \n"; + while ($oDev = $oAllDevs->Fetch()) + { + $aValues = array(); + foreach(MetaModel::GetAttributesList($oAllDevs->GetClass()) as $sAttCode) + { + $aValues[] = MetaModel::GetLabel(get_class($oDev), $sAttCode)." (".MetaModel::GetDescription(get_class($oDev), $sAttCode).") = ".$oDev->GetAsHTML($sAttCode); + } + echo $oDev->GetKey()." => ".implode(", ", $aValues)."
      \n"; + } + + // a second one + $oMyFilter = new DBObjectSearch("cmdbContact"); + //$oMyFilter->AddCondition("name", "aii", "Finishes with"); + $oMyFilter->AddCondition("name", "aii"); + $this->search_and_show_list($oMyFilter); + + } + + function test_reload() + { + echo "

      Reload

      "; + $team = MetaModel::GetObject("cmdbContact", "2"); + echo "Chargement de l'attribut headcount: {$team->Get("headcount")}
      \n"; + MyHelpers::var_dump_html($team); + } + + function test_setattribute() + { + echo "

      Set attribute and update

      "; + $team = MetaModel::GetObject("cmdbTeam", "2"); + $team->Set("headcount", rand(1,1000)); + $team->Set("email", "Luis ".rand(9,250)); + MyHelpers::var_dump_html($team->ListChanges()); + echo "New headcount = {$team->Get("headcount")}
      \n"; + echo "Computed name = {$team->Get("name")}
      \n"; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + //MetaModel::StartDebugQuery(); + $team->DBUpdateTracked($oMyChange); + //MetaModel::StopDebugQuery(); + + echo "

      Check the modified team

      "; + $oTeam = MetaModel::GetObject("cmdbTeam", "2"); + MyHelpers::var_dump_html($oTeam); + } + function test_newobject() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_newobject / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + echo "

      Create a new object (team)

      "; + $oNewTeam = MetaModel::NewObject("cmdbTeam"); + $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); + $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); + $oNewTeam->Set("email", null); + $oNewTeam->Set("owner", "ITOP"); + $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
      "; + echo "

      Delete team #$iId

      "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
      "; + MyHelpers::var_dump_html($oTeam); + } + + + function test_updatecolumn() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_updatecolumn / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + $sNewEmail = "updatecol".rand(9,250)."@quedlaballe.com"; + echo "

      Update a the email: set to '$sNewEmail'

      "; + $oMyFilter = new DBObjectSearch("cmdbContact"); + $oMyFilter->AddCondition("name", "o", "Contains"); + + echo "Candidates before:
      "; + $this->search_and_show_list($oMyFilter); + + MetaModel::BulkUpdateTracked($oMyChange, $oMyFilter, array("email" => $sNewEmail)); + + echo "Candidates after:
      "; + $this->search_and_show_list($oMyFilter); + } + + function test_error() + { + trigger_error("Stop requested", E_USER_ERROR); + } + + function test_changetracking() + { + echo "

      Create a change

      "; + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + echo "Created new change: $iChangeId
      "; + MyHelpers::var_dump_html($oMyChange); + + echo "

      Create a new object (team)

      "; + $oNewTeam = MetaModel::NewObject("cmdbTeam"); + $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); + $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); + $oNewTeam->Set("email", null); + $oNewTeam->Set("owner", "ITOP"); + $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
      "; + echo "

      Delete team #$iId

      "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
      "; + MyHelpers::var_dump_html($oTeam); + } + + function test_zlist() + { + echo "

      Test ZLists

      "; + $aZLists = MetaModel::EnumZLists(); + foreach ($aZLists as $sListCode) + { + $aListInfos = MetaModel::GetZListInfo($sListCode); + echo "

      List '".$sListCode."' (".$aListInfos["description"].") of type '".$aListInfos["type"]."'

      \n"; + + foreach (MetaModel::GetSubclasses("cmdbObjectHomeMade") as $sKlass) + { + $aItems = MetaModel::GetZListItems($sKlass, $sListCode); + if (count($aItems) == 0) continue; + + echo "$sKlass - $sListCode : {".implode(", ", $aItems)."}
      \n"; + } + } + + echo "

      IsAttributeInZList()...

      "; + echo "Liens_entre_contacts_et_workshop::ws_info in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "ws_info") ? "yes" : "no")."
      \n"; + echo "Liens_entre_contacts_et_workshop::toworkshop in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "toworkshop") ? "yes" : "no")."
      \n"; + + } + + function test_SibuSQL() + { + echo "

      Simple But Structured Query Language

      "; + + $oMyFilter = new DBObjectSearch("cmdbContact"); + echo "Tous les contacts: ".$oMyFilter->ToSibuSQL()."
      \n"; + $oNewFilter = DBObjectSearch::FromSibuSQL($oMyFilter->ToSibuSQL()); + echo "En passant par un filtre, ca revient en : ".$oNewFilter->ToSibuSQL()."
      \n"; + $this->search_and_show_list($oNewFilter); + + $sFilterDesc = "cmdbContact: name Begins with '$[debutnom:as:debut du nom]' AND ownername NotLike $[ddd::]"; + echo "Construction d'un filtre a partir de sa description en SibuSQL: $sFilterDesc
      \n"; + + MyHelpers::var_dump_html(DBObjectSearch::ListSibusQLParams($sFilterDesc)); + $oNewFilter = DBObjectSearch::FromSibuSQL($sFilterDesc, array('ddd'=>123)); + echo "Ca revient en: ".$oNewFilter->ToSibuSQL(); + } + + function test_pkey() + { + echo "

      Test search on pkey

      "; + $sExpr1 = "cmdbContact: pkey IN {40, 42}"; + $sExpr2 = "cmdbContact: pkey NOTIN {40, 42}"; + $this->search_and_show_list_from_sibusql($sExpr1); + $this->search_and_show_list_from_sibusql($sExpr2); + + echo "Et maintenant, on fusionne....
      \n"; + $oSet1 = new CMDBObjectSet(DBObjectSearch::FromSibuSQL($sExpr1)); + $oSet2 = new CMDBObjectSet(DBObjectSearch::FromSibuSQL($sExpr2)); + $oIntersect = $oSet1->CreateIntersect($oSet2); + $oDelta = $oSet1->CreateDelta($oSet2); + + $oMerge = clone $oSet1; + $oMerge->Merge($oSet2); + $oMerge->Merge($oSet2); + + echo "Set1 - Found ".$oSet1->Count()." items.
      \n"; + echo "Set2 - Found ".$oSet2->Count()." items.
      \n"; + echo "Intersect - Found ".$oIntersect->Count()." items.
      \n"; + echo "Delta - Found ".$oDelta->Count()." items.
      \n"; + echo "Merge - Found ".$oMerge->Count()." items.
      \n"; + //$this->show_list($oObjSet); + } + + function test_relations() + { + echo "

      Test relations

      "; + + //MyHelpers::var_dump_html(MetaModel::EnumRelationQueries("cmdbObjectHomeMade", "Potes")); + MyHelpers::var_dump_html(MetaModel::EnumRelationQueries("cmdbContact", "Potes")); + + $iMaxDepth = 9; + echo "Max depth = $iMaxDepth
      \n"; + + $oObj = MetaModel::GetObject("cmdbContact", 18); + $aRels = $oObj->GetRelatedObjects("Potes", $iMaxDepth); + echo $oObj->Get('name')." has some 'Potes'...
      \n"; + foreach ($aRels as $sClass => $aObjs) + { + echo "$sClass, count = ".count($aObjs)." => ".implode(', ', array_keys($aObjs))."
      \n"; + $oObjectSet = CMDBObjectSet::FromArray($sClass, $aObjs); + $this->show_list($oObjectSet); + } + + echo "

      Test relations - same results, by the mean of a SibuSQL

      "; + $this->search_and_show_list_from_sibusql("cmdbContact: RELATED (Potes, $iMaxDepth) TO (cmdbContact: pkey = 18)"); + + } + + function test_linkedset() + { + echo "

      Linked set attributes

      \n"; + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
      Current workshops
      \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + echo "
      Setting workshops
      \n"; + $oNewLink = new cmdbLiens(); + $oNewLink->Set('toworkshop', 2); + $oNewLink->Set('function', 'mafonctioooon'); + $oNewLink->Set('a1', 'tralala1'); + $oNewLink->Set('a2', 'F7M'); + $oSetWorkshops = CMDBObjectSet::FromArray("cmdbLiens", array($oNewLink)); + $oObj->Set("myworkshops", $oSetWorkshops); + $this->show_list($oSetWorkshops); + + echo "
      New workshops
      \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_linkedset / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
      After the write
      \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + } + + function test_object_lifecycle() + { + echo "

      Test object lifecycle

      "; + + + MyHelpers::var_dump_html(MetaModel::GetStateAttributeCode("cmdbContact")); + MyHelpers::var_dump_html(MetaModel::EnumStates("cmdbContact")); + MyHelpers::var_dump_html(MetaModel::EnumStimuli("cmdbContact")); + foreach(MetaModel::EnumStates("cmdbContact") as $sStateCode => $aStateDef) + { + echo "

      Transition from $sStateCode

      \n"; + MyHelpers::var_dump_html(MetaModel::EnumTransitions("cmdbContact", $sStateCode)); + } + + $oObj = MetaModel::GetObject("cmdbContact", 18); + echo "Current state: ".$oObj->GetState()."... let's go to school..."; + MyHelpers::var_dump_html($oObj->EnumTransitions()); + $oObj->ApplyStimulus("toschool"); + echo "New state: ".$oObj->GetState()."... let's get older..."; + MyHelpers::var_dump_html($oObj->EnumTransitions()); + $oObj->ApplyStimulus("raise"); + echo "New state: ".$oObj->GetState()."... let's try to go further... (should give an error)"; + MyHelpers::var_dump_html($oObj->EnumTransitions()); + $oObj->ApplyStimulus("raise"); // should give an error + } + + + protected function DoExecute() + { +// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + //$this->test_linksinfo(); + //$this->test_list_attributes(); + //$this->test_search(); + //$this->test_reload(); + //$this->test_newobject(); + $this->test_setattribute(); + //$this->test_updatecolumn(); + //$this->test_error(); + //$this->test_changetracking(); + $this->test_zlist(); + $this->test_SibuSQL(); + //$this->test_pkey(); + $this->test_relations(); + $this->test_linkedset(); + $this->test_object_lifecycle(); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +abstract class MyFarm extends TestBizModel +{ + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) + { + $oNew = MetaModel::NewObject('Mammal'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('name', $sName); + $oNew->Set('height', $iHeight); + $oNew->Set('birth', $sBirth); + return $this->ObjectToDB($oNew); + } + + protected function InsertBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId) + { + $oNew = MetaModel::NewObject('Bird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + return $this->ObjectToDB($oNew); + } + + protected function InsertFlyingBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $iFlyingSpeed) + { + $oNew = MetaModel::NewObject('FlyingBird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('flyingspeed', $iFlyingSpeed); + return $this->ObjectToDB($oNew); + } + + private function InsertGroup($sName, $iLeaderId) + { + $oNew = MetaModel::NewObject('Group'); + $oNew->Set('name', $sName); + $oNew->Set('leader', $iLeaderId); + $iId = $oNew->DBInsertNoReload(); + return $iId; + } +} + + +class TestQueriesOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test'; + } + + static public function GetDescription() + { + return 'A series of tests on the farm business model (SQL generation)'; + } + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + if ($bIsCorrectQuery) + { + echo "

      $sQuery

      \n"; + } + else + { + echo "

      $sQuery

      \n"; + } + try + { + //$oOql = new OqlInterpreter($sQuery); + //$oTrash = $oOql->ParseObjectQuery(); + //MyHelpers::var_dump_html($oTrash, true); + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + catch (OQLException $oOqlException) + { + if ($bIsCorrectQuery) + { + echo "

      More info on this unexpected failure:
      ".$oOqlException->getHtmlDesc()."

      \n"; + throw $oOqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

      More info on this expected failure:\n"; + echo "

        \n"; + echo "
      • ".get_class($oOqlException)."
      • \n"; + echo "
      • ".$oOqlException->getMessage()."
      • \n"; + echo "
      • ".$oOqlException->getHtmlDesc()."
      • \n"; + echo "
      \n"; + echo "

      \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if (!$bIsCorrectQuery) + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + echo "

      To OQL: ".$oMyFilter->ToOQL()."

      "; + + $this->search_and_show_list($oMyFilter); + + //echo "

      first pass

      \n"; + //MyHelpers::var_dump_html($oMyFilter, true); + $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + //echo "

      second pass

      \n"; + //MyHelpers::var_dump_html($oMyFilter, true); + //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + + $sSerialize = $oMyFilter->serialize(); + echo "

      Serialized:$sSerialize

      \n"; + $oFilter2 = DBObjectSearch::unserialize($sSerialize); + try + { + $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); + } + catch (Exception $e) + { + echo "

      Could not compute the query after unserialize

      \n"; + echo "

      Query 1: $sQuery1

      \n"; + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + throw $e; + } + //if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same! + if ($sQuery1 != $sQuery2) + { + echo "

      serialize/unserialize mismatch :-(

      \n"; + MyHelpers::var_cmp_html($sQuery1, $sQuery2); + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + return false; + } + return true; + } + + protected function DoExecute() + { +// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + echo "

      Create protagonists...

      "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + // Benchmarking + // + if (false) + { + define ('COUNT_BENCHMARK', 10); + echo "

      Parsing a long query, ".COUNT_BENCHMARK." times

      "; + $sQuery = "SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR) AND Dad.height * 2 <= ROUND(TO_DAYS(Dad.birth) / (3 + 1) * 5 - 3)"; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + echo "

      Mean time by op: $fParsingDuration

      "; + } + + echo "

      Test queries...

      "; + + $aQueries = array( + 'SELECT Animal' => true, + 'SELECT Animal WHERE Animal.pkey = 1' => false, + 'SELECT Animal WHERE Animal.id = 1' => true, + 'SELECT Aniiimal' => false, + 'SELECTe Animal' => false, + 'SELECT * FROM Animal' => false, + 'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE espece = \'human\'' => false, + 'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false, + 'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true, + 'SELECT Animal AS zoo WHERE zoo.species NOT IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false, + 'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true, + 'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true, + 'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true, + 'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true, + 'SELECT Mammal AS m WHERE m.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR)' => true, + 'SELECT Mammal AS m WHERE m.birth > DATE_SUB(NOW(), INTERVAL 2000 DAY)' => true, + 'SELECT Mammal AS m WHERE (TO_DAYS(NOW()) - TO_DAYS(m.birth)) > 2000' => true, + 'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true, + 'SELECT Mammal AS m WHERE (1 + 2' => false, + 'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true, + 'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true, + 'SELECT Mammal AS m WHERE 1/0' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, + 'SELECT Group JOIN Animal ON Group.leader = Animal.id' => true, + 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, + 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.masterchief = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.pkey' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true, + 'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false, + 'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false, + 'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true, + 'SELECT Group AS G WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, + ); + //$aQueries = array( + // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + //); + foreach($aQueries as $sQuery => $bIsCorrect) + { + $this->CheckQuery($sQuery, $bIsCorrect); + } + return true; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestBulkChangeOnFarm extends TestBizModel +{ + static public function GetName() + { + return 'Farm test - data load'; + } + + static public function GetDescription() + { + return 'Bulk load'; + } + + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected function DoExecute() + { +// $this->ReportError("Found two different SibuSQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + + $oParser = new CSVParser("denomination,hauteur,age + suzy,123,2009-01-01 + chita,456, + "); + $aData = $oParser->ToArray(array('_name', '_height', '_birth'), ','); + MyHelpers::var_dump_html($aData); + + $oBulk = new BulkChange( + 'Mammal', + $aData, + array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), + array('name'), + array() + ); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Testor"); + $iChangeId = $oMyChange->DBInsert(); +// echo "Created new change: $iChangeId
      "; + + echo "

      Planned for loading...

      "; + $aRes = $oBulk->Process(); + print_r($aRes); + echo "

      Go for loading...

      "; + $aRes = $oBulk->Process($oMyChange); + print_r($aRes); + + return; + + $oRawData = array( + 'Mammal', + array('species', 'sex', 'speed', 'mother', 'father', 'name', 'height', 'birth'), + "human,male,23,0,0,romulus,192,1971 + human,male,23,0,0,remus,154,-50 + human,male,23,0,0,julius,160,-49 + human,female,23,0,0,cleopatra,142,-50 + pig,female,23,0,0,confucius,50,2003" + ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestFullTextSearchOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test - full text search'; + } + + static public function GetDescription() + { + return 'Focus on the full text search feature'; + } + + protected function DoExecute() + { + echo "

      Create protagonists...

      "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + echo "

      Search...

      "; + $oSearch = new DBObjectSearch('Mammal'); + $oSearch->AddCondition_FullText('manof'); + //$oResultSet = new DBObjectSet($oSearch); + $this->search_and_show_list($oSearch); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Benchmark queries +/////////////////////////////////////////////////////////////////////////// + +class TestItopEfficiency extends TestBizModel +{ + static public function GetName() + { + return 'Itop - benchmark'; + } + + static public function GetDescription() + { + return 'Measure time to perform the queries'; + } + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoBenchmark($sOqlQuery) + { + echo "

      Testing query: $sOqlQuery

      "; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oFilter = DBObjectSearch::FromOQL($sOqlQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $sSQL = MetaModel::MakeSelectQuery($oFilter); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fBuildDuration = $fDuration / COUNT_BENCHMARK; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $res = CMDBSource::Query($sSQL); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fQueryDuration = $fDuration / COUNT_BENCHMARK; + + // The fetch could not be repeated with the same results + // But we've seen so far that is was very very quick to exec + // So it makes sense to benchmark it a single time + $fStart = MyHelpers::getmicrotime(); + $aRow = CMDBSource::FetchArray($res); + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fFetchDuration = $fDuration; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $sOql = $oFilter->ToOQL(); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fToOqlDuration = $fDuration / COUNT_BENCHMARK; + + echo "
        \n"; + echo "
      • Parsing: $fParsingDuration
      • \n"; + echo "
      • Build: $fBuildDuration
      • \n"; + echo "
      • Query: $fQueryDuration
      • \n"; + echo "
      • Fetch: $fFetchDuration
      • \n"; + echo "
      • ToOql: $fToOqlDuration
      • \n"; + echo "
      \n"; + + // Everything but the ToOQL (wich is interesting, anyhow) + $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; + + return array( + 'rows' => CMDBSource::NbRows($res), + 'duration (s)' => round($fTotal, 4), + 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), + 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), + 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), + 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), + 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), + 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), + ); + } + + protected function DoExecute() + { + define ('COUNT_BENCHMARK', 3); + echo "

      The test will be repeated ".COUNT_BENCHMARK." times

      "; + + $aQueries = array( + 'SELECT CMDBChangeOpSetAttribute', + 'SELECT CMDBChangeOpSetAttribute WHERE id=10', + 'SELECT CMDBChangeOpSetAttribute WHERE id=123456789', + 'SELECT CMDBChangeOpSetAttribute WHERE CMDBChangeOpSetAttribute.id=10', + 'SELECT bizIncidentTicket', + 'SELECT bizIncidentTicket WHERE id=1', + 'SELECT bizPerson', + 'SELECT bizPerson WHERE id=1', + 'SELECT bizIncidentTicket JOIN bizPerson ON bizIncidentTicket.agent_id = bizPerson.id WHERE bizPerson.id = 5', + ); + $aStats = array(); + foreach ($aQueries as $sOQL) + { + $aStats[$sOQL] = $this->DoBenchmark($sOQL); + } + + $aData = array(); + foreach ($aStats as $sOQL => $aResults) + { + $aValues = array(); + $aValues['OQL'] = htmlentities($sOQL); + + foreach($aResults as $sDesc => $sInfo) + { + $aValues[$sDesc] = htmlentities($sInfo); + } + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestItopWebServices extends TestWebServices +{ + static public function GetName() + { + return 'Itop - web services'; + } + + static public function GetDescription() + { + return 'Bulk load and ???'; + } + + protected function DoExecSingleLoad($aLoadSpec) + { + $sTitle = 'Load: '.$aLoadSpec['class']; + $sClass = $aLoadSpec['class']; + $sCsvData = $aLoadSpec['csvdata']; + + $aPostData = array('class' => $sClass, 'csvdata' => $sCsvData); + $sRes = self::DoPostRequestAuth('../webservices/import.php', $aPostData); + + echo "

      $sTitle

      $sCsvData
      $sRes
      "; + } + + protected function DoExecute() + { + + $aLoads = array( + array( + 'class' => 'bizOrganization', + 'csvdata' => "name;code\nWorldCompany;WCY" + ), + array( + 'class' => 'bizLocation', + 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca" + ), + array( + 'class' => 'bizPerson', + 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789" + ), + array( + 'class' => 'bizTeam', + 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris" + ), + array( + 'class' => 'bizWorkgroup', + 'csvdata' => "name;org_id;team_id\ntravailleurs alpins;1;6" + ), + array( + 'class' => 'bizIncidentTicket', + 'csvdata' => "name;title;type;org_id;initial_situation;start_date;next_update;caller_id;workgroup_id;agent_id\nOVSD-12345;server down;Network;1;server was found down;2009-04-10 12:00;2009-04-10 15:00;3;317;5" + ), + ); + + foreach ($aLoads as $aLoadSpec) + { + $this->DoExecSingleLoad($aLoadSpec); + } + } +} + + +$aWebServices = array( + array( + 'verb' => 'GetVersion', + 'expected result' => '0.8', + 'explain result' => 'n/a', + 'args' => array(), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'link attribute unknown + a CI not found', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Server', /* sType */ + 'desc of ticket', /* sDescription */ + 'initial situation blah blah blah', /* sInitialSituation */ + 'very grave', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'logInfra', + array(new SOAPSearchCondition('id', 108)), + array(new SOAPAttributeValue('impacting', 'very critical')) + ), + new SOAPLinkCreationSpec( + 'bizDevice', + array(new SOAPSearchCondition('name', 'Router03')), + array(new SOAPAttributeValue('impact', 'who cares')) + ), + new SOAPLinkCreationSpec( + 'bizDevice', + array(new SOAPSearchCondition('name', 'thisone')), + array(new SOAPAttributeValue('impact', 'our lives')) + ), + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'caller not specified', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Desktop', /* sType */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + 'The agent could not do his job', /* sImpact */ + null, /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong condition on CI to attach', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Desktop', /* sType */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + 'The agent could not do his job', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'logInfra', + array(new SOAPSearchCondition('dummyfiltercode', 2)), + array(new SOAPAttributeValue('impact', 'very critical')) + ), + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (empty array)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Network', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (null)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Network', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + null, /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'caller unknown', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Network', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1000))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong values for type and severity', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'my type', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'my severity' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong password', + 'args' => array( + 'admin', /* sLogin */ + 'xxxxx', /* sPassword */ + 'Network', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong login', + 'args' => array( + 'xxxxx', /* sLogin */ + 'yyyyy', /* sPassword */ + 'Network', /* sType */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + 'Could not talk to my wife', /* sImpact */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 2))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'FLS Desktop'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + 'low' /* sSeverity */ + ), + ), +); + + +class TestSoap extends TestSoapWebService +{ + static public function GetName() {return 'Test SOAP';} + static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';} + + protected function DoExecute() + { + echo "

      Note: You may also want to try the sample SOAP client itopsoap.examples.php

      \n"; + + global $aSOAPMapping; + + // this file is generated dynamically with location = here + $sWsdlUri = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php'; + + ini_set("soap.wsdl_cache_enabled","0"); + $this->m_SoapClient = new SoapClient + ( + $sWsdlUri, + array( + 'classmap' => $aSOAPMapping, + 'trace' => 1, + ) + ); + + if (false) + { + print "
      \n"; 
      +			print_r($this->m_SoapClient->__getTypes());
      +			print "
      \n"; + } + + global $aWebServices; + foreach ($aWebServices as $iPos => $aWebService) + { + echo "

      SOAP call #$iPos ".$aWebService['explain result']."

      \n"; + + try + { + $oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']); + } + catch(SoapFault $e) + { + print "
      \n"; 
      +				print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
      +				print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
      +				print "
      "; + print "Response in HTML:

      ".$this->m_SoapClient->__getLastResponse()."

      "; + throw $e; + } + + echo "
      \n";
      +			print_r($oRes);
      +			echo "
      \n"; + + print "
      \n"; 
      +			print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
      +			print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
      +			print "
      "; + + if ($oRes instanceof SOAPResult) + { + $res = $oRes->status; + } + else + { + $res = $oRes; + } + if ($res != $aWebService['expected result']) + { + throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); + } + } + } +} + +class TestWebServicesDirect extends TestBizModel +{ + static public function GetName() {return 'Test web services locally';} + static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoExecute() + { + $oWebServices = new WebServices(); + + global $aWebServices; + foreach ($aWebServices as $aWebService) + { + $oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']); + echo "
      \n";
      +			print_r($oRes);
      +			echo "
      \n"; + } + return true; + } +} + +class TestTriggerAndEmail extends TestBizModel +{ + static public function GetName() {return 'Test trigger and email';} + static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail) + { + $oAction = MetaModel::NewObject("ActionEmail"); + $oAction->Set("status", $sStatus); + $oAction->Set("name", "New server"); + $oAction->Set("test_recipient", $sTesterEmail); + $oAction->Set("from", $sTesterEmail); + $oAction->Set("reply_to", $sTesterEmail); + $oAction->Set("to", $sTo); + $oAction->Set("cc", $sCC); + $oAction->Set("bcc", ""); + $oAction->Set("subject", "New server: '\$this->name()$'"); + $oAction->Set("body", "

      Dear customer,

      We have created the server \$this->hyperlink()$ in the IT infrastructure database.

      You will be further notified when it is in Production.

      The IT infrastructure management team.

      Here are some accentuated characters for french people: 'ééà'

      "); + $oAction->Set("importance", "low"); + $iActionId = $this->ObjectToDB($oAction, true); + + $oLink = MetaModel::NewObject("lnkTriggerAction"); + $oLink->Set("trigger_id", $oTrigger->GetKey()); + $oLink->Set("action_id", $iActionId); + $oLink->Set("order", "1"); + $iLink = $this->ObjectToDB($oLink, true); + } + + protected function DoExecute() + { + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail1"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "romain.quetiez@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail2"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "denis.flaven@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail3"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "erwan.taloc@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyServer = MetaModel::NewObject("bizServer"); + $oMyServer->Set("name", "wfr.terminator.com"); + $oMyServer->Set("severity", "low"); + $oMyServer->Set("status", "production"); + $oMyServer->Set("org_id", 2); + $oMyServer->Set("location_id", 2); + $iServerId = $this->ObjectToDB($oMyServer, true); + + $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); + $oMyTrigger->Set("description", "Testor"); + $oMyTrigger->Set("target_class", "bizServer"); + $oMyTrigger->Set("state", "Shipped"); + $iTriggerId = $this->ObjectToDB($oMyTrigger, true); + + // Error in OQL field(s) + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE naime = 'Dali'", + "SELECT bizServer", + 'romain.quetiez@hp.com' + ); + + // Error: no recipient + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "", + "", + 'romain.quetiez@hp.com' + ); + + // Test + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "SELECT bizPerson", + 'romain.quetiez@hp.com' + ); + + // Test failing because of a wrong test recipient address + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'toto@walibi.bg' + ); + + // Normal behavior + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'enabled', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + // Does nothing, because it is disabled + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'disabled', + "SELECT bizPerson WHERE name = 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); + + return true; + } +} +?> diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index fab829719a..4aa1b790e0 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -5,49 +5,64 @@ * 'file' string Name of the file to load * 'session_status' string 'start', 'continue' or 'end' * 'percent' integer 0..100 the percentage of completion once the file has been loaded - */ + */ define('SAFE_MINIMUM_MEMORY', 32*1024*1024); require_once('../application/utils.inc.php'); require_once('./setuppage.class.inc.php'); - -$iMemoryLimit = utils::ConvertToBytes(ini_get('memory_limit')); -if ($iMemoryLimit < SAFE_MINIMUM_MEMORY) -{ - if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE) - { - SetupWebPage::error("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); - } - else - { - SetupWebPage::log("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); - } -} - -function FatalErrorCatcher($sOutput) -{ - if ( preg_match('|.*|s', $sOutput, &$aMatches) ) - { + +$sMemoryLimit = trim(ini_get('memory_limit')); +if (empty($sMemoryLimit)) +{ + // On some PHP installations, memory_limit does not exist as a PHP setting! + // (encountered on a 5.2.0 under Windows) + // In that case, ini_set will not work, let's keep track of this and proceed with the data load + SetupWebPage::log_info("No memory limit has been defined in this instance of PHP"); +} +else +{ + // Check that the limit will allow us to load the data + // + $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); + if ($iMemoryLimit < SAFE_MINIMUM_MEMORY) + { + if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE) + { + SetupWebPage::log_error("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself."); + } + else + { + SetupWebPage::log_info("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY."."); + } + } + +} + + +function FatalErrorCatcher($sOutput) +{ + if ( preg_match('|.*|s', $sOutput, $aMatches) ) + { header("HTTP/1.0 500 Internal server error."); foreach ($aMatches as $sMatch) { $errors .= strip_tags($sMatch)."\n"; } - $sOutput = "$errors\n"; - // Logging to a file does not work if the whole memory is exhausted... - //SetupWebPage::error("Fatal error - in $__FILE__ , $errors"); - } + $sOutput = "$errors\n"; + // Logging to a file does not work if the whole memory is exhausted... + //SetupWebPage::log_error("Fatal error - in $__FILE__ , $errors"); + } return $sOutput; -} +} -//Define some bogus, invalid HTML tags that no sane -//person would ever put in an actual document and tell -//PHP to delimit fatal error warnings with them. +//Define some bogus, invalid HTML tags that no sane +//person would ever put in an actual document and tell +//PHP to delimit fatal error warnings with them. ini_set('error_prepend_string', ''); ini_set('error_append_string', ''); - -// Starts the capture of the ouput, and sets a filter to capture the fatal errors. -ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it through the fatal error catcher - + +// Starts the capture of the ouput, and sets a filter to capture the fatal errors. +ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it through the fatal error catcher + require_once('../core/config.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); require_once('./xmldataloader.class.inc.php'); @@ -58,14 +73,14 @@ define('TMP_CONFIG_FILE', '../tmp-config-itop.php'); // Never cache this page header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past - + /** * Main program */ $sFileName = Utils::ReadParam('file', ''); $sSessionStatus = Utils::ReadParam('session_status', ''); $iPercent = (integer)Utils::ReadParam('percent', 0); -SetupWebPage::log("Info - Loading file: $sFileName"); +SetupWebPage::log_info("Loading file: $sFileName"); try { @@ -81,30 +96,30 @@ try $oChange->Set("date", time()); $oChange->Set("userinfo", "Initialization"); $iChangeId = $oChange->DBInsert(); - SetupWebPage::log("Info - starting data load session"); + SetupWebPage::log_info("starting data load session"); $oDataLoader->StartSession($oChange); } $oDataLoader->LoadFile($sFileName); - $sResult = sprintf("Info - loading of %s done. (Overall %d %% completed).", basename($sFileName), $iPercent); + $sResult = sprintf("loading of %s done. (Overall %d %% completed).", basename($sFileName), $iPercent); echo $sResult; - SetupWebPage::log($sResult); + SetupWebPage::log_info($sResult); if ($sSessionStatus == 'end') { $oDataLoader->EndSession(); - SetupWebPage::log("Info - ending data load session"); + SetupWebPage::log_info("ending data load session"); } } catch(Exception $e) { echo "

      An error happened while loading the data

      \n"; echo '

      '.$e."

      \n"; - SetupWebPage::log("Error - An error happened while loading the data. ".$e); -} - -if (function_exists('memory_get_peak_usage')) -{ - SetupWebPage::log("Info - loading file '$sFileName', peak memory usage. ".memory_get_peak_usage()); + SetupWebPage::log_error("An error happened while loading the data. ".$e); +} + +if (function_exists('memory_get_peak_usage')) +{ + SetupWebPage::log_info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage()); } ?> diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index b375004a7d..37785327f9 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -101,25 +101,25 @@ table.formTable { public function info($sText) { $this->add("

      $sText

      \n"); - $this->log("Info - ".$sText); + $this->log_info($sText); } public function ok($sText) { $this->add("

      $sText

      \n"); - $this->log("Ok - ".$sText); + $this->log_ok($sText); } public function warning($sText) { $this->add("

      $sText

      \n"); - $this->log("Warning - ".$sText); + $this->log_warning($sText); } public function error($sText) { $this->add("

      $sText

      \n"); - $this->log("Error - ".$sText); + $this->log_error($sText); } public function form($aData) @@ -159,6 +159,26 @@ table.formTable { return parent::output(); } + public static function log_error($sText) + { + self::log("Error - ".$sText); + } + + public static function log_warning($sText) + { + self::log("Warning - ".$sText); + } + + public static function log_info($sText) + { + self::log("Info - ".$sText); + } + + public static function log_ok($sText) + { + self::log("Ok - ".$sText); + } + public static function log($sText) { $hLogFile = @fopen(INSTALL_LOG_FILE, 'a'); diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 46ee3acec4..aff8fa18ab 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -177,7 +177,7 @@ class XMLDataLoader // tested by Romain, little impact on perf (not significant on the intial setup) if (!$oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode)) { - SetupWebPage::log("Error - Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."'"); + SetupWebPage::log_error("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."'"); echo "Wrong value for attribute $sAttCode: '".$oXmlObj->$sAttCode."'"; } $oTargetObj->Set($sAttCode, (string)$oXmlObj->$sAttCode); @@ -223,7 +223,7 @@ class XMLDataLoader } catch(Exception $e) { - SetupWebPage::log("Error - An object could not be loaded - $sClass/$iSrcId - ".$e->getMessage()); + SetupWebPage::log_error("An object could not be loaded - $sClass/$iSrcId - ".$e->getMessage()); echo $e->GetHtmlDesc(); } $aParentClasses = MetaModel::EnumParentClasses($sClass); @@ -257,7 +257,7 @@ class XMLDataLoader if ($iExtKey == 0) { $sMsg = "unresolved extkey in $sClass::".$oTargetObj->GetKey()."(".$oTargetObj->GetName().")::$sAttCode=$sTargetClass::$iTempKey"; - SetupWebPage::log("Warning - $sMsg"); + SetupWebPage::log_warning($sMsg); echo "Warning: $sMsg
      \n"; echo "
      aKeys[".$sTargetClass."]:\n";
       							print_r($this->m_aKeys[$sTargetClass]);
      diff --git a/webservices/import.php b/webservices/import.php
      index 9de93e004e..eaeb1eb9f5 100644
      --- a/webservices/import.php
      +++ b/webservices/import.php
      @@ -18,6 +18,7 @@
       // - only external fields attributes could be used as reconciliation keys for external keys
       // - reconciliation is made on the first column
       // - no option to force 'always create' or 'never create'
      +// - text qualifier hardcoded to "
       //
       // Known issues
       // - ALMOST impossible to troubleshoot when an externl key has a wrong value
      @@ -58,11 +59,9 @@ try
       	$sSep = utils::ReadParam('separator', ';');
       	$sCSVData = utils::ReadPostedParam('csvdata');
       
      -	$oCSVParser = new CSVParser($sCSVData); 
      -	$oCSVParser->SetSeparator($sSep);
      -	$oCSVParser->SetSkipLines(1);
      +	$oCSVParser = new CSVParser($sCSVData, $sSep, $sDelimiter = '"'); 
       
      -	// Limitation: as the attribute list is in the first line, we can not match external key by an third-party attribute
      +	// Limitation: as the attribute list is in the first line, we can not match external key by a third-party attribute
       	$sRawFieldList = $oCSVParser->ListFields();
       	$aAttList = array();
       	$aExtKeys = array();
      @@ -93,9 +92,6 @@ try
       	// Limitation: the reconciliation key is the first attribute
       	$aReconcilKeys = array($sRawFieldList[0]);
       
      -//	print_r($oCSVParser->ListFields());
      -//	print_r($oCSVParser->ToArray($oCSVParser->ListFields()));
      -
       	$aData = $oCSVParser->ToArray();
       	$oBulk = new BulkChange(
       		$sClass,
      
      From e2524d28d700a6ffb9adaf789d4827b7547c40a9 Mon Sep 17 00:00:00 2001
      From: Romain Quetiez 
      Date: Tue, 9 Mar 2010 14:09:51 +0000
      Subject: [PATCH 232/970] Related objects: - moved to OQL - added capacity to
       set a default value based on the related objects (during the creation wizard)
      
      SVN:trunk[314]
      ---
       application/uilinkswizard.class.inc.php |  2 +-
       application/uiwizard.class.inc.php      | 16 ++++-----
       core/attributedef.class.inc.php         | 20 +++--------
       core/dbobject.class.php                 | 46 ++++++-------------------
       core/dbobjectsearch.class.php           |  2 +-
       core/dbobjectset.class.php              | 21 ++++++++++-
       core/valuesetdef.class.inc.php          | 37 +++++++++++++++++---
       7 files changed, 78 insertions(+), 66 deletions(-)
      
      diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php
      index ede866c453..70e89d226e 100644
      --- a/application/uilinkswizard.class.inc.php
      +++ b/application/uilinkswizard.class.inc.php
      @@ -285,7 +285,7 @@ class UILinksWizard
       			foreach($this->m_aEditableFields as $sFieldCode)
       			{
       				$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sFieldCode);
      -				$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue */, '' /* DisplayValue */, '' /* id */, $sNameSuffix);
      +				$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, '' /* id */, $sNameSuffix);
       			}
       		}
       		foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode)
      diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php
      index f2819ff479..c1b5c0e0a6 100644
      --- a/application/uiwizard.class.inc.php
      +++ b/application/uiwizard.class.inc.php
      @@ -59,7 +59,7 @@ class UIWizard
       				}
       				
       				$sFieldFlag = (($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) || (!$oAttDef->IsNullAllowed()) )? ' *' : '';
      -				$oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists
      +				$oDefaultValuesSet = $oAttDef->GetDefaultValue(/* $oObject->ToArgs() */); // @@@ TO DO: get the object's current value if the object exists
       				$sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId", '', $iOptions, $aArgs);
       				$aFieldsMap[$iMaxInputId] = $sAttCode;
       				$aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "
      $sHTMLValue
      "); @@ -165,7 +165,7 @@ $sJSHandlerCode $aFields = array(); foreach($aStates[$this->m_sTargetState]['attribute_list'] as $sAttCode => $iOptions) { - if ( (isset($aMandatoryAttributes[$sAttCode])) && + if ( (isset($aMandatoryAttributes[$sAttCode])) && ($aMandatoryAttributes[$sAttCode] & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ) { $aMandatoryAttributes[$sAttCode] |= $iOptions; @@ -176,16 +176,16 @@ $sJSHandlerCode } } } - - // Check all the fields that *must* be included in the wizard - // i.e. all mandatory, must-change or must-prompt fields that are - // not also read-only or hidden. - // Some fields may be required (null not allowed) from the database + + // Check all the fields that *must* be included in the wizard + // i.e. all mandatory, must-change or must-prompt fields that are + // not also read-only or hidden. + // Some fields may be required (null not allowed) from the database // perspective, but hidden or read-only from the user interface perspective $aFields = array(); foreach($aMandatoryAttributes as $sAttCode => $iOptions) { - if ( ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) && + if ( ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) && !($iOptions & (OPT_ATT_READONLY | OPT_ATT_HIDDEN)) ) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index b9728acaef..882bf531fe 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -241,28 +241,18 @@ class AttributeLinkedSet extends AttributeDefinition // if (($this->IsParam('default_value')) && array_key_exists('this', $aArgs)) { - $oSet = $this->Get('default_value'); - return $oSet->GetValues($aArgs); +echo "### this est la
      \n"; + $aValues = $this->Get('default_value')->GetValues($aArgs); + $oSet = DBObjectSet::FromArray($this->Get('linked_class'), $aValues); + return $oSet; } else { +echo "### this manque a l'appel
      \n"; return DBObjectSet::FromScratch($this->Get('linked_class')); } } - public function GetSupportedRelations() - { - if (array_key_exists('supported_relations', $this->m_aParams)) - { - $aSupportedRelations = $this->Get('supported_relations'); - return $aSupportedRelations; - } - else - { - return array(); - } - } - public function GetLinkedClass() {return $this->Get('linked_class');} public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} diff --git a/core/dbobject.class.php b/core/dbobject.class.php index f0ddffe8d3..07c498b448 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -229,7 +229,7 @@ abstract class DBObject } public function Set($sAttCode, $value) - { + { if ($sAttCode == 'finalclass') { // Ignore it - this attribute is set upon object creation and that's it @@ -665,6 +665,11 @@ abstract class DBObject } return $this->m_iKey; } + + // To be optionaly overloaded + public function OnInsert() + { + } // Insert of record for the new object into the database // Returns the key of the newly created object @@ -680,6 +685,7 @@ abstract class DBObject // Ensure the update of the values (we are accessing the data directly) $this->ComputeFields(); + $this->OnInsert(); if ($this->m_iKey < 0) { @@ -878,7 +884,7 @@ abstract class DBObject } // Make standard context arguments - public function ToArgs($sArgName) + public function ToArgs($sArgName = 'this') { $aScalarArgs = array(); $aScalarArgs[$sArgName] = $this->GetKey(); @@ -904,38 +910,6 @@ abstract class DBObject public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array()) { - foreach (MetaModel::GetLinkedSets($sClass) as $sAttCode => $oAttDef) - { - $aSupportedRelations = $oAttDef->GetSupportedRelations(); - if (!array_key_exists($sRelCode, $aSupportedRelations)) continue; //skip - - $bPropagate = true; // #@# Todo: discuss that setting - $iDepth = $bPropagate ? $iMaxDepth - 1 : 0; - - $oNeighbors = $this->Get($sAttCode); - while ($oObj = $oObjSet->Fetch()) - { - $sRootClass = MetaModel::GetRootClass(get_class($oObj)); - $sObjKey = $oObj->GetKey(); - if (array_key_exists($sRootClass, $aResults)) - { - if (array_key_exists($sObjKey, $aResults[$sRootClass])) - { - continue; // already visited, skip - } - } - - $aResults[$sRootClass][$sObjKey] = $oObj; - if ($iDepth > 0) - { - $oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults); - } - } - } - - return; - - // #@# todo : Discuss the Relations and the way they are defined (do we deprecate the queries ? what are the properties -e.g. depth- and where do we set them ?) foreach (MetaModel::EnumRelationQueries(get_class($this), $sRelCode) as $sDummy => $aQueryInfo) { MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo["sQuery"]); @@ -945,8 +919,8 @@ abstract class DBObject $iDepth = $bPropagate ? $iMaxDepth - 1 : 0; - $oFlt = DBObjectSearch::FromSibusQL($sQuery, array(), $this); - $oObjSet = new DBObjectSet($oFlt); + $oFlt = DBObjectSearch::FromOQL($sQuery); + $oObjSet = new DBObjectSet($oFlt, array(), $this->ToArgs()); while ($oObj = $oObjSet->Fetch()) { $sRootClass = MetaModel::GetRootClass(get_class($oObj)); diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 71dda95089..bec0d57ff4 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -933,7 +933,7 @@ class DBObjectSearch if (preg_match('@^\\s*SELECT@', $sQuery)) { - return self::FromOQL($sQuery, $aParams, $oObject); + return self::FromOQL($sQuery); } $iSepPos = strpos($sQuery, ":"); diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 6655b38bc7..1fcea5545c 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -81,6 +81,22 @@ class DBObjectSet return $oRetSet; } + static public function FromLinkSet($oObject, $sLinkSetAttCode, $sExtKeyToRemote) + { + $oLinkAttCode = MetaModel::GetAttributeDef(get_class($oObject), $sLinkSetAttCode); + $oExtKeyAttDef = MetaModel::GetAttributeDef($oLinkAttCode->GetLinkedClass(), $sExtKeyToRemote); + $sTargetClass = $oExtKeyAttDef->GetTargetClass(); + + $oLinkSet = $oObject->Get($sLinkSetAttCode); + $aTargets = array(); + while ($oLink = $oLinkSet->Fetch()) + { + $aTargets[] = MetaModel::GetObject($sTargetClass, $oLink->Get($sExtKeyToRemote)); + } + + return self::FromArray($sTargetClass, $aTargets); + } + public function ToArray($bWithId = true) { $aRet = array(); @@ -263,11 +279,14 @@ class DBObjectSet public function GetRelatedObjects($sRelCode, $iMaxDepth = 99) { + $aRelatedObjs = array(); + $aVisited = array(); // optimization for consecutive calls of MetaModel::GetRelatedObjects $this->Seek(0); while ($oObject = $this->Fetch()) { - $aRelatedObjs = $oObject->GetRelatedObjects($sRelCode, $iMaxDepth, $aVisited); + // #@# todo - actually merge ! + $aRelatedObjs = array_merge_recursive($aRelatedObjs, $oObject->GetRelatedObjects($sRelCode, $iMaxDepth, $aVisited)); } return $aRelatedObjs; } diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 1d9e315c78..78667e6fa1 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -126,16 +126,25 @@ class ValueSetObjects extends ValueSetDefinition * @since 1.0 * @version $itopversion$ */ -class ValueSetRelatedObjects extends ValueSetDefinition +class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition { + protected $m_sLinkSetAttCode; + protected $m_sExtKeyToRemote; protected $m_sRelationCode; protected $m_iMaxDepth; + protected $m_sTargetClass; + protected $m_sTargetExtKey; // protected $m_aOrderBy; - public function __construct($sRelationCode, $iMaxDepth = 99) + public function __construct($sLinkSetAttCode, $sExtKeyToRemote, $sRelationCode, $iMaxDepth, $sTargetClass, $sTargetLinkClass, $sTargetExtKey) { + $this->m_sLinkSetAttCode = $sLinkSetAttCode; + $this->m_sExtKeyToRemote = $sExtKeyToRemote; $this->m_sRelationCode = $sRelationCode; $this->m_iMaxDepth = $iMaxDepth; + $this->m_sTargetClass = $sTargetClass; + $this->m_sTargetLinkClass = $sTargetLinkClass; + $this->m_sTargetExtKey = $sTargetExtKey; // $this->m_aOrderBy = $aOrderBy; } @@ -150,11 +159,31 @@ class ValueSetRelatedObjects extends ValueSetDefinition $oTarget = $aArgs['this->object()']; - $oTargetNeighbors = $oTarget->GetRelatedObjects($this->m_sRelationCode, $this->m_iMaxDepth); - while ($oObject = $oTargetNeighbors->Fetch()) + // Nodes from which we will start the search for neighbourhood + $oNodes = DBObjectSet::FromLinkSet($oTarget, $this->m_sLinkSetAttCode, $this->m_sExtKeyToRemote); + + // Neighbours, whatever their class + $aRelated = $oNodes->GetRelatedObjects($this->m_sRelationCode, $this->m_iMaxDepth); + + $sRootClass = MetaModel::GetRootClass($this->m_sTargetClass); + if (array_key_exists($sRootClass, $aRelated)) { + $aLinksToCreate = array(); + foreach($aRelated[$sRootClass] as $iKey => $oObject) + { + if (MetaModel::IsParentClass($this->m_sTargetClass, get_class($oObject))) + { + $oNewLink = MetaModel::NewObject($this->m_sTargetLinkClass); + $oNewLink->Set($this->m_sTargetExtKey, $iKey); + //$oNewLink->Set('role', 'concerned by an impacted CI'); + + $aLinksToCreate[] = $oNewLink; + } + } + $oSetToCreate = DBObjectSet::FromArray($this->m_sTargetLinkClass, $aLinksToCreate); $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($oObject->GetName()); } + return true; } From 5b4057aebb0c57454bd820120c5284700cf02452 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 11 Mar 2010 08:15:19 +0000 Subject: [PATCH 233/970] Finalized the demo of impact computation (removed an ugly test message) and added few comments SVN:trunk[315] --- core/attributedef.class.inc.php | 2 -- core/dbobjectset.class.php | 1 + core/valuesetdef.class.inc.php | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 882bf531fe..328516a2bb 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -241,14 +241,12 @@ class AttributeLinkedSet extends AttributeDefinition // if (($this->IsParam('default_value')) && array_key_exists('this', $aArgs)) { -echo "### this est la
      \n"; $aValues = $this->Get('default_value')->GetValues($aArgs); $oSet = DBObjectSet::FromArray($this->Get('linked_class'), $aValues); return $oSet; } else { -echo "### this manque a l'appel
      \n"; return DBObjectSet::FromScratch($this->Get('linked_class')); } } diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 1fcea5545c..f4ab4aff1b 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -214,6 +214,7 @@ class DBObjectSet public function AddObjectArray($aObjects) { + // #@# todo - add a check on the object class ? foreach ($aObjects as $oObj) { $this->AddObject($oObj); diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 78667e6fa1..21c67c16e9 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -180,6 +180,7 @@ class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition $aLinksToCreate[] = $oNewLink; } } + // #@# or AddObjectArray($aObjects) ? $oSetToCreate = DBObjectSet::FromArray($this->m_sTargetLinkClass, $aLinksToCreate); $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($oObject->GetName()); } From f454ebf4b5965835b69142194d6710e844391363 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 12 Mar 2010 13:48:39 +0000 Subject: [PATCH 234/970] Fixed bugs on CSV import (was not working fine when exporting/importing incident tickets) SVN:trunk[316] --- core/attributedef.class.inc.php | 32 ++++++------------------ core/bulkchange.class.inc.php | 43 +++++++++++++++++++++------------ core/csvparser.class.inc.php | 10 ++++++-- core/dbobject.class.php | 24 +++++++++++++++++- 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 328516a2bb..94d4abfa75 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -130,6 +130,7 @@ abstract class AttributeDefinition public function IsExternalField() {return false;} public function IsWritable() {return false;} public function IsNullAllowed() {return true;} + public function GetNullValue() {return null;} public function GetCode() {return $this->m_sCode;} public function GetLabel() {return $this->Get("label");} public function GetDescription() {return $this->Get("description");} @@ -328,7 +329,6 @@ class AttributeDBFieldVoid extends AttributeDefinition public function IsNullAllowed() {return false;} protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) - protected function SQLToScalar($value) {return $value;} // take the result of a fetch... and make it a PHP variable public function GetSQLExpressions() { @@ -483,12 +483,6 @@ class AttributeInteger extends AttributeDBField assert(is_numeric($value)); return $value; // supposed to be an int } - public function SQLToScalar($value) - { - // Use cast (int) or intval() ? - return (int)$value; - - } } /** @@ -525,11 +519,6 @@ class AttributeBoolean extends AttributeInteger if ($value) return 1; return 0; } - public function SQLToScalar($value) - { - // Use cast (int) or intval() ? - return (int)$value; - } } /** @@ -610,10 +599,6 @@ class AttributeString extends AttributeDBField } return $value; } - public function SQLToScalar($value) - { - return $value; - } public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') { @@ -986,10 +971,6 @@ class AttributeDate extends AttributeDBField } return $value; } - public function SQLToScalar($value) - { - return $value; - } public function GetAsHTML($value) { @@ -1047,6 +1028,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid public function GetDefaultValue() {return 0;} public function IsNullAllowed() {return $this->Get("is_null_allowed");} + public function GetNullValue() {return 0;} public function GetBasicFilterOperators() { @@ -1093,6 +1075,11 @@ class AttributeExternalKey extends AttributeDBFieldVoid { return $this->Get("on_target_delete"); } + + public function MakeRealValue($proposedValue) + { + return (int)$proposedValue; + } } /** @@ -1226,11 +1213,6 @@ class AttributeExternalField extends AttributeDefinition $oExtAttDef = $this->GetExtAttDef(); return $oExtAttDef->ScalarToSQL($value); } - public function SQLToScalar($value) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->SQLToScalar($value); - } // Do not overload GetSQLExpression here because this is handled in the joins diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index f2641afeb8..8bafa10c2d 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -244,15 +244,16 @@ class BulkChange static protected function MakeSpecObject($sClass, $iId) { - $oObj = MetaModel::GetObject($sClass, $iId); - if (is_null($oObj)) + try { + $oObj = MetaModel::GetObject($sClass, $iId); + } + catch(CoreException $e) + { + // in case an ext key is 0 (which is currently acceptable) return $iId; } - else - { - return $oObj; - } + return $oObj; } protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors) @@ -276,18 +277,33 @@ class BulkChange switch($oExtObjects->Count()) { case 0: - $aErrors[$sAttCode] = "Object not found"; - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found - check the spelling (no space before/after)'); + if ($oExtKey->IsNullAllowed()) + { + $oTargetObj->Set($sAttCode, $oExtKey->GetNullValue()); + } + else + { + $aErrors[$sAttCode] = "Object not found"; + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found - check the spelling (no space before/after)'); + } break; case 1: // Do change the external key attribute $oForeignObj = $oExtObjects->Fetch(); $oTargetObj->Set($sAttCode, $oForeignObj->GetKey()); - - // Report it + break; + default: + $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; + $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); + $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $previousValue, "Found ".$oExtObjects->Count()." matches"); + } + + // Report + if (!array_key_exists($sAttCode, $aResults)) + { + $oForeignObj = $oTargetObj->Get($sAttCode); if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { - if ($oTargetObj->IsNew()) { $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj); @@ -302,11 +318,6 @@ class BulkChange { $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oForeignObj); } - break; - default: - $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; - $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $previousValue, "Found ".$oExtObjects->Count()." matches"); } } diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php index 20b952427a..fe0662b61a 100644 --- a/core/csvparser.class.inc.php +++ b/core/csvparser.class.inc.php @@ -91,9 +91,15 @@ class CSVParser { $this->m_aDataSet[] = $this->m_aCurrRow; } - elseif ((count($this->m_aCurrRow) == 1) && (strlen($this->m_aCurrRow[0]) > 0)) + elseif (count($this->m_aCurrRow) == 1) { - $this->m_aDataSet[] = $this->m_aCurrRow; + // Get the unique value + $aValues = array_values($this->m_aCurrRow); + $sValue = $aValues[0]; + if (strlen($sValue) > 0) + { + $this->m_aDataSet[] = $this->m_aCurrRow; + } } else { diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 07c498b448..7dbc94de96 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -568,10 +568,32 @@ abstract class DBObject $aDelta = array(); foreach ($aProposal as $sAtt => $proposedValue) { - if (!array_key_exists($sAtt, $this->m_aOrigValues) || ($this->m_aOrigValues[$sAtt] !== $proposedValue)) + if (!array_key_exists($sAtt, $this->m_aOrigValues)) { + // The value was not set $aDelta[$sAtt] = $proposedValue; } + elseif(is_object($proposedValue)) + { + // The value is an object, the comparison is not strict + // #@# todo - should be even less strict => add verb on AttributeDefinition: Compare($a, $b) + if ($this->m_aOrigValues[$sAtt] != $proposedValue) + { + $aDelta[$sAtt] = $proposedValue; + } + } + else + { + // The value is a scalar, the comparison must be 100% strict + if($this->m_aOrigValues[$sAtt] !== $proposedValue) + { + //echo "$sAtt:
      \n";
      +					//var_dump($this->m_aOrigValues[$sAtt]);
      +					//var_dump($proposedValue);
      +					//echo "
      \n"; + $aDelta[$sAtt] = $proposedValue; + } + } } return $aDelta; } From d8905c63b80a29d5b816380cb957a51e9fd78087 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 12 Mar 2010 16:58:50 +0000 Subject: [PATCH 235/970] Fixed bug in DisplayBlock (group by - visible on the page "contacts overview") SVN:trunk[317] --- application/displayblock.class.inc.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 90ddbbcad1..9a961d4a46 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -296,10 +296,12 @@ class DisplayBlock { $sGroupByField = $aExtraParams['group_by']; $aGroupBy = array(); + $sLabels = array(); while($oObj = $this->m_oSet->Fetch()) { - $sValue = $oObj->GetAsHtml($sGroupByField); + $sValue = $oObj->Get($sGroupByField); $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; + $sLabels[$sValue] = $oObj->GetAsHtml($sGroupByField); } $sFilter = urlencode($this->m_oFilter->serialize()); $aData = array(); @@ -307,7 +309,7 @@ class DisplayBlock $sParams = $oAppContext->GetForLink(); foreach($aGroupBy as $sValue => $iCount) { - $aData[] = array ( 'group' => $sValue, + $aData[] = array ( 'group' => $sLabels[$sValue], 'value' => "$iCount"); // TO DO: add the context information } $sHtml .= $oPage->GetTable(array('group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''), 'value' => array('label'=>'Count', 'description' => 'Number of elements')), $aData); From 071f268a594094722ad2bc75446ad55ae96c1154 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 23 Mar 2010 08:09:03 +0000 Subject: [PATCH 236/970] Bug fix: application crashes when a incorrect wizard config is detected SVN:trunk[318] --- application/uiwizard.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index c1b5c0e0a6..72ee34e6d5 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -283,7 +283,7 @@ $sJSHandlerCode if (count($aCurrentStep) == 0) { // This step of the wizard would contain NO field ! - $oPage->add("Error: Circular reference in the dependencies between the fields."); + $this->m_oPage->add("Error: Circular reference in the dependencies between the fields."); print_r($aFields); break; } From 12d0c865c229d04abe533ee7ff3b05f4d32d1c09 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 23 Mar 2010 08:17:50 +0000 Subject: [PATCH 237/970] - Fixed bug #85: MySQL password was displayed (along with the call stack) in case the connection to the database failed. - Additional fix to #87: strings consisting only of digits (like 001231) were treated as numbers (i.e 1231) SVN:trunk[319] --- pages/UI.php | 2012 +++++++++++++++++++++++++------------------------- 1 file changed, 1015 insertions(+), 997 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 2bcb89fb7a..db981b88b4 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1,96 +1,181 @@ Fetch()) + // No menu specified, let's get the default one: + // 1) It's a root menu item (parent_id == 0) + // 2) with the lowest rank + $oFilter = DBObjectSearch::FromOQL('SELECT menuNode AS M WHERE M.parent_id = 0'); + if ($oFilter) { - $aRanks[$oMenu->GetKey()] = $oMenu->Get('rank'); - } - asort($aRanks); // sort by ascending rank: menuId => rank - $aKeys = array_keys($aRanks); - $iActiveNodeId = array_shift($aKeys); // Takes the first key, i.e. the menuId with the lowest rank - } -} -$currentOrganization = utils::ReadParam('org_id', ''); -$operation = utils::ReadParam('operation', ''); - -require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - - -$oP = new iTopWebPage("Welcome to ITop", $currentOrganization); - -// From now on the context is limited to the the selected organization ?? -if ($iActiveNodeId != -1) -{ - $oActiveNode = $oContext->GetObject('menuNode', $iActiveNodeId); -} -else -{ - $oActiveNode = null; -} - -switch($operation) -{ - case 'details': - $sClass = utils::ReadParam('class', ''); - $sClassLabel = MetaModel::GetName($sClass); - $id = utils::ReadParam('id', ''); - $oSearch = new DBObjectSearch($sClass); - $oBlock = new DisplayBlock($oSearch, 'search', false); - $oBlock->Display($oP, 0); - if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! - { - $oP->add("

      'class' and 'id' parameters must be specifed for this operation.

      \n"); - } - else - { - $oObj = $oContext->GetObject($sClass, $id); - if ($oObj != null) + $oMenuSet = new CMDBObjectSet($oFilter); + while($oMenu = $oMenuSet->Fetch()) { - $oP->set_title("iTop - ".$oObj->GetDisplayName()." - $sClassLabel details"); - $oObj->DisplayDetails($oP); + $aRanks[$oMenu->GetKey()] = $oMenu->Get('rank'); + } + asort($aRanks); // sort by ascending rank: menuId => rank + $aKeys = array_keys($aRanks); + $iActiveNodeId = array_shift($aKeys); // Takes the first key, i.e. the menuId with the lowest rank + } + } + $currentOrganization = utils::ReadParam('org_id', ''); + $operation = utils::ReadParam('operation', ''); + + require_once('../application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed + + + $oP = new iTopWebPage("Welcome to ITop", $currentOrganization); + + // From now on the context is limited to the the selected organization ?? + if ($iActiveNodeId != -1) + { + $oActiveNode = $oContext->GetObject('menuNode', $iActiveNodeId); + } + else + { + $oActiveNode = null; + } + + switch($operation) + { + case 'details': + $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); + $id = utils::ReadParam('id', ''); + $oSearch = new DBObjectSearch($sClass); + $oBlock = new DisplayBlock($oSearch, 'search', false); + $oBlock->Display($oP, 0); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + $oP->add("

      'class' and 'id' parameters must be specifed for this operation.

      \n"); } else { - $oP->set_title("iTop - Error"); - $oP->add("

      Sorry this object does not exist (or you are not allowed to view it).

      \n"); + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $oP->set_title("iTop - ".$oObj->GetDisplayName()." - $sClassLabel details"); + $oObj->DisplayDetails($oP); + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

      Sorry this object does not exist (or you are not allowed to view it).

      \n"); + } } - } - break; + break; - case 'search_oql': - $sOQLClass = utils::ReadParam('oql_class', ''); - $sOQLClause = utils::ReadParam('oql_clause', ''); - $sFormat = utils::ReadParam('format', ''); - $bSearchForm = utils::ReadParam('search_form', true); - if (empty($sOQLClass)) - { - $oP->set_title("iTop - Error"); - $oP->add("

      'oql_class' must be specifed for this operation.

      \n"); - } - else - { - $oP->set_title("iTop - Search results"); - $sOQL = "SELECT $sOQLClass $sOQLClause"; - try + case 'search_oql': + $sOQLClass = utils::ReadParam('oql_class', ''); + $sOQLClause = utils::ReadParam('oql_clause', ''); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sOQLClass)) { - $oFilter = DBObjectSearch::FromOQL($sOQL); // To Do: Make sure we don't bypass security + $oP->set_title("iTop - Error"); + $oP->add("

      'oql_class' must be specifed for this operation.

      \n"); + } + else + { + $oP->set_title("iTop - Search results"); + $sOQL = "SELECT $sOQLClass $sOQLClause"; + try + { + $oFilter = DBObjectSearch::FromOQL($sOQL); // To Do: Make sure we don't bypass security + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); + } + if (strtolower($sFormat) == 'csv') + { + $oBlock = new DisplayBlock($oFilter, 'csv', false); + $oBlock->Display($oP, 'csv'); + $oPage->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block + } + else + { + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 1); + } + } + catch(CoreException $e) + { + $oFilter = new DBObjectSearch($sOQLClass); // To Do: Make sure we don't bypass security + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); + } + $oP->P("Error incorrect OQL query:"); + $oP->P($e->getHtmlDesc()); + } + catch(Exception $e) + { + $oP->p('An error occured while running the query:'); + $oP->p($e->getMessage()); + } + } + break; + case 'search_form': + $sClass = utils::ReadParam('class', ''); + $sFormat = utils::ReadParam('format', 'html'); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sClass)) + { + $oP->set_title("iTop - Error"); + $oP->add("

      'class' must be specifed for this operation.

      \n"); + } + else + { + $oP->set_title("iTop - Search results"); + $oFilter = $oContext->NewFilter($sClass); + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, array('open' => true)); + $oBlock->Display($oP, 0); + } + if (strtolower($sFormat) == 'csv') + { + $oBlock = new DisplayBlock($oFilter, 'csv', false); + $oBlock->Display($oP, 1); + $oP->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block + } + else + { + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 1); + } + } + break; + + case 'search': + $sFilter = utils::ReadParam('filter', ''); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sFilter)) + { + $oP->set_title("iTop - Error"); + $oP->add("

      'filter' must be specifed for this operation.

      \n"); + } + else + { + $oP->set_title("iTop - Search results"); + // TO DO: limit the search filter by the user context + $oFilter = CMDBSearchFilter::unserialize($sFilter); // TO DO : check that the filter is valid $oSet = new DBObjectSet($oFilter); if ($bSearchForm) { @@ -101,7 +186,7 @@ switch($operation) { $oBlock = new DisplayBlock($oFilter, 'csv', false); $oBlock->Display($oP, 'csv'); - $oPage->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block + $oP->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block } else { @@ -109,146 +194,102 @@ switch($operation) $oBlock->Display($oP, 1); } } - catch(CoreException $e) - { - $oFilter = new DBObjectSearch($sOQLClass); // To Do: Make sure we don't bypass security - $oSet = new DBObjectSet($oFilter); - if ($bSearchForm) - { - $oBlock = new DisplayBlock($oFilter, 'search', false); - $oBlock->Display($oP, 0); - } - $oP->P("Error incorrect OQL query:"); - $oP->P($e->getHtmlDesc()); - } - catch(Exception $e) - { - $oP->p('An error occured while running the query:'); - $oP->p($e->getMessage()); - } - } - break; - case 'search_form': - $sClass = utils::ReadParam('class', ''); - $sFormat = utils::ReadParam('format', 'html'); - $bSearchForm = utils::ReadParam('search_form', true); - if (empty($sClass)) - { - $oP->set_title("iTop - Error"); - $oP->add("

      'class' must be specifed for this operation.

      \n"); - } - else - { - $oP->set_title("iTop - Search results"); - $oFilter = $oContext->NewFilter($sClass); - $oSet = new DBObjectSet($oFilter); - if ($bSearchForm) - { - $oBlock = new DisplayBlock($oFilter, 'search', false /* Asynchronous */, array('open' => true)); - $oBlock->Display($oP, 0); - } - if (strtolower($sFormat) == 'csv') - { - $oBlock = new DisplayBlock($oFilter, 'csv', false); - $oBlock->Display($oP, 1); - $oP->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block - } - else - { - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oP, 1); - } - } - break; - - case 'search': - $sFilter = utils::ReadParam('filter', ''); - $sFormat = utils::ReadParam('format', ''); - $bSearchForm = utils::ReadParam('search_form', true); - if (empty($sFilter)) - { - $oP->set_title("iTop - Error"); - $oP->add("

      'filter' must be specifed for this operation.

      \n"); - } - else - { - $oP->set_title("iTop - Search results"); - // TO DO: limit the search filter by the user context - $oFilter = CMDBSearchFilter::unserialize($sFilter); // TO DO : check that the filter is valid - $oSet = new DBObjectSet($oFilter); - if ($bSearchForm) - { - $oBlock = new DisplayBlock($oFilter, 'search', false); - $oBlock->Display($oP, 0); - } - if (strtolower($sFormat) == 'csv') - { - $oBlock = new DisplayBlock($oFilter, 'csv', false); - $oBlock->Display($oP, 'csv'); - $oP->add_ready_script(" $('#csv').css('height', '95%');"); // adjust the size of the block - } - else - { - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oP, 1); - } - } - break; + break; - case 'full_text': - $sFullText = trim(utils::ReadParam('text', '')); - if (empty($sFullText)) - { - $oP->p('Nothing to search.'); - } - else - { - $oP->p("

      Results for '$sFullText':

      \n"); - $iCount = 0; - $iBlock = 0; - // Search in full text mode in all the classes - foreach(MetaModel::GetClasses('bizmodel') as $sClassName) + case 'full_text': + $sFullText = trim(utils::ReadParam('text', '')); + if (empty($sFullText)) { - $oFilter = new DBObjectSearch($sClassName); - $oFilter->AddCondition_FullText($sFullText); - $oSet = new DBObjectSet($oFilter); - if ($oSet->Count() > 0) + $oP->p('Nothing to search.'); + } + else + { + $oP->p("

      Results for '$sFullText':

      \n"); + $iCount = 0; + $iBlock = 0; + // Search in full text mode in all the classes + foreach(MetaModel::GetClasses('bizmodel') as $sClassName) { - $aLeafs = array(); - while($oObj = $oSet->Fetch()) + $oFilter = new DBObjectSearch($sClassName); + $oFilter->AddCondition_FullText($sFullText); + $oSet = new DBObjectSet($oFilter); + if ($oSet->Count() > 0) { - if (get_class($oObj) == $sClassName) + $aLeafs = array(); + while($oObj = $oSet->Fetch()) { - $aLeafs[] = $oObj->GetKey(); + if (get_class($oObj) == $sClassName) + { + $aLeafs[] = $oObj->GetKey(); + } + } + $oLeafsFilter = new DBObjectSearch($sClassName); + if (count($aLeafs) > 0) + { + $iCount += count($aLeafs); + $oP->add("
      \n"); + $oP->add("

      ".Metamodel::GetName($sClassName).": ".count($aLeafs)." object(s) found.

      \n"); + $oP->add("
      \n"); + $oLeafsFilter->AddCondition('pkey', $aLeafs, 'IN'); + $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); + $oBlock->Display($oP, $iBlock++); } } - $oLeafsFilter = new DBObjectSearch($sClassName); - if (count($aLeafs) > 0) - { - $iCount += count($aLeafs); - $oP->add("
      \n"); - $oP->add("

      ".Metamodel::GetName($sClassName).": ".count($aLeafs)." object(s) found.

      \n"); - $oP->add("
      \n"); - $oLeafsFilter->AddCondition('pkey', $aLeafs, 'IN'); - $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); - $oBlock->Display($oP, $iBlock++); - } + } + if ($iCount == 0) + { + $oP->p('No object found.'); + } + } + break; + + case 'modify': + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); + $id = utils::ReadParam('id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + $oP->add("

      'class' and 'id' parameters must be specifed for this operation.

      \n"); + } + else + { + // Check if the user can modify this object + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('pkey', $id, '='); + $oSet = new CMDBObjectSet($oSearch); + if ($oSet->Count() > 0) + { + $oObj = $oSet->Fetch(); + } + + $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES); + $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); + if( ($oObj != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) + { + $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel modification"); + $oP->add("
      \n"); + $oP->add("

      Modification of $sClassLabel: ".$oObj->GetName()."

      \n"); + $oP->add("
      \n"); + + $oP->add("
      \n"); + $oObj->DisplayModifyForm($oP); + $oP->add("
      \n"); + } + else + { + $oP->set_title("iTop - Error"); + $oP->add("

      Sorry this object does not exist (or you are not allowed to view it).

      \n"); } } - if ($iCount == 0) - { - $oP->p('No object found.'); - } - } - break; + break; - case 'modify': - $oP->add_linked_script("../js/json.js"); - $oP->add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/linkswidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); + case 'clone': $sClass = utils::ReadParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); @@ -264,20 +305,26 @@ switch($operation) $oSet = new CMDBObjectSet($oSearch); if ($oSet->Count() > 0) { - $oObj = $oSet->Fetch(); + $oObjToClone = $oSet->Fetch(); } - + $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES); $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); - if( ($oObj != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) + if( ($oObjToClone != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) { - $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel modification"); + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + $oP->set_title("iTop - ".$oObjToClone->GetName()." - $sClassLabel clone"); $oP->add("
      \n"); - $oP->add("

      Modification of $sClassLabel: ".$oObj->GetName()."

      \n"); + $oP->add("

      Clone of $sClassLabel: ".$oObjToClone->GetName()."

      \n"); $oP->add("
      \n"); $oP->add("
      \n"); - $oObj->DisplayModifyForm($oP); + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObjToClone); $oP->add("
      \n"); } else @@ -286,328 +333,722 @@ switch($operation) $oP->add("

      Sorry this object does not exist (or you are not allowed to view it).

      \n"); } } - break; + break; - case 'clone': - $sClass = utils::ReadParam('class', ''); - $sClassLabel = MetaModel::GetName($sClass); - $id = utils::ReadParam('id', ''); - if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! - { - $oP->add("

      'class' and 'id' parameters must be specifed for this operation.

      \n"); - } - else - { - // Check if the user can modify this object - $oSearch = new DBObjectSearch($sClass); - $oSearch->AddCondition('pkey', $id, '='); - $oSet = new CMDBObjectSet($oSearch); - if ($oSet->Count() > 0) - { - $oObjToClone = $oSet->Fetch(); - } + case 'new': + $sClass = utils::ReadParam('class', ''); + $sStateCode = utils::ReadParam('state', ''); + if ( empty($sClass) ) + { + $oP->p("The class must be specified for this operation!"); + } + else + { + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + $oWizard = new UIWizard($oP, $sClass, $sStateCode); + $oContext = new UserContext(); + $aArgs = array_merge($oAppContext->GetAsHash(), utils::ReadParam('default', array())); + $sStateCode = $oWizard->GetTargetState(); // Will computes the default state if none was supplied + $sClassLabel = MetaModel::GetName($sClass); + $oP->add("

      Creation of a new $sClassLabel

      "); + if (!empty($sStateCode)) + { + $aStates = MetaModel::EnumStates($sClass); + $sStateLabel = $aStates[$sStateCode]['label']; + } + $aWizardSteps = $oWizard->GetWizardStructure(); + + // Display the structure of the wizard + $iStepIndex = 1; + $iMaxInputId = 0; + $aFieldsMap = array(); + foreach($aWizardSteps['mandatory'] as $aSteps) + { + $oP->SetCurrentTab("Step $iStepIndex *"); + $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, false /* no finish button */, $aArgs); + //$oP->add("
    \n"); + $iStepIndex++; + } + foreach($aWizardSteps['optional'] as $aSteps) + { + $oP->SetCurrentTab("Step $iStepIndex *"); + $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, true, $aArgs); // true means enable the finish button + //$oP->add("
    \n"); + $iStepIndex++; + } + $oWizard->DisplayFinalStep($iStepIndex, $aFieldsMap); + + $oObj = null; + if (!empty($id)) + { + $oObj = $oContext->GetObject($sClass, $id); + } + if (!is_object($oObj)) + { + // new object or that can't be retrieved (corrupted id or object not allowed to this user) + $id = ''; + $oObj = MetaModel::NewObject($sClass); + } + $oP->add("\n"); + } + break; - case 'new': - $sClass = utils::ReadParam('class', ''); - $sStateCode = utils::ReadParam('state', ''); - if ( empty($sClass) ) - { - $oP->p("The class must be specified for this operation!"); - } - else - { - $oP->add_linked_script("../js/json.js"); - $oP->add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/linkswidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); - $oWizard = new UIWizard($oP, $sClass, $sStateCode); - $oContext = new UserContext(); - $aArgs = array_merge($oAppContext->GetAsHash(), utils::ReadParam('default', array())); - $sStateCode = $oWizard->GetTargetState(); // Will computes the default state if none was supplied + case 'apply_modify': + $sClass = utils::ReadPostedParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); - $oP->add("

    Creation of a new $sClassLabel

    "); - if (!empty($sStateCode)) + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! { - $aStates = MetaModel::EnumStates($sClass); - $sStateLabel = $aStates[$sStateCode]['label']; + $oP->add("

    'class' and 'id' parameters must be specifed for this operation.

    \n"); } - $aWizardSteps = $oWizard->GetWizardStructure(); - - // Display the structure of the wizard - $iStepIndex = 1; - $iMaxInputId = 0; - $aFieldsMap = array(); - foreach($aWizardSteps['mandatory'] as $aSteps) + else if (!utils::IsTransactionValid($sTransactionId)) { - $oP->SetCurrentTab("Step $iStepIndex *"); - $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, false /* no finish button */, $aArgs); - //$oP->add("
    \n"); - $iStepIndex++; - } - foreach($aWizardSteps['optional'] as $aSteps) - { - $oP->SetCurrentTab("Step $iStepIndex *"); - $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, true, $aArgs); // true means enable the finish button - //$oP->add("
    \n"); - $iStepIndex++; + $oP->p("Error: object has already be updated!\n"); } - $oWizard->DisplayFinalStep($iStepIndex, $aFieldsMap); - - $oObj = null; - if (!empty($id)) + else { $oObj = $oContext->GetObject($sClass, $id); - } - if (!is_object($oObj)) - { - // new object or that can't be retrieved (corrupted id or object not allowed to this user) - $id = ''; - $oObj = MetaModel::NewObject($sClass); - } - $oP->add("\n"); - } - break; + $oObj->DisplayDetails($oP); + break; - case 'apply_modify': + case 'delete': + case 'delete_confirmed': + $sClass = utils::ReadParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); + $id = utils::ReadParam('id', ''); + $oObj = $oContext->GetObject($sClass, $id); + $sName = $oObj->GetName(); + + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj))) + { + throw new SecurityException('You are not allowed to do delete objects of class '.$sClass); + } + + // Evaluate the impact on the DB integrity + // + list ($aDeletedObjs, $aResetedObjs) = $oObj->GetDeletionScheme(); + + // Evaluate feasibility (user access control) + // + $bFoundManual = false; + $bFoundStopper = false; + $iTotalDelete = 0; // count of object that must be deleted + $iTotalReset = 0; // count of object for which an ext key will be reset (to 0) + foreach ($aDeletedObjs as $sRemoteClass => $aDeletes) + { + $iTotalDelete += count($aDeletes); + foreach ($aDeletes as $iId => $aData) + { + $oToDelete = $aData['to_delete']; + $bDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oToDelete)); + if (!$bDeleteAllowed) + { + $aDeletedObjs[$sRemoteClass][$iId]['issue'] = 'not allowed to delete this object'; + $bFoundStopper = true; + } + + $bAutoDel = $aData['auto_delete']; + if (!$bAutoDel) + { + $bFoundManual = true; + } + } + } + foreach ($aResetedObjs as $sRemoteClass => $aToReset) + { + $iTotalReset += count($aToReset); + foreach ($aToReset as $iId => $aData) + { + $oToReset = $aData['to_reset']; + $aExtKeyLabels = array(); + $aForbiddenKeys = array(); // keys on which the current user is not allowed to write + foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) + { + $bUpdateAllowed = UserRights::IsActionAllowedOnAttribute($sClass, $sRemoteExtKey, UR_ACTION_MODIFY, DBObjectSet::FromObject($oToReset)); + if (!$bUpdateAllowed) + { + $bFoundStopper = true; + $aForbiddenKeys[] = $aRemoteAttDef->GetLabel(); + } + $aExtKeyLabels[] = $aRemoteAttDef->GetLabel(); + } + $aResetedObjs[$sRemoteClass][$iId]['attributes_list'] = implode(', ', $aExtKeyLabels); + if (count($aForbiddenKeys) > 0) + { + $aResetedObjs[$sRemoteClass][$iId]['issue'] = 'you are not allowed to update some fields: '.implode(', ', $aForbiddenKeys); + } + } + } + // Count of dependent objects (+ the current one) + $iTotalTargets = $iTotalDelete + $iTotalReset; + + if ($operation == 'delete_confirmed') + { + $oP->add("

    Deletion of ".$oObj->GetName()."

    \n"); + // Security - do not allow the user to force a forbidden delete by the mean of page arguments... + if ($bFoundStopper) + { + throw new SecurityException('This object could not be deleted because the current user do not have sufficient rights'); + } + if ($bFoundManual) + { + throw new SecurityException('This object could not be deleted because some manual operations must be performed prior to that'); + } + + // Prepare the change reporting + // + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $oMyChange->DBInsert(); + + // Delete dependencies + // + $aDisplayData = array(); + foreach ($aDeletedObjs as $sRemoteClass => $aDeletes) + { + foreach ($aDeletes as $iId => $aData) + { + $oToDelete = $aData['to_delete']; + + $aDisplayData[] = array( + 'class' => MetaModel::GetName(get_class($oToDelete)), + 'object' => $oToDelete->GetHyperLink(), + 'consequence' => 'automatically deleted', + ); + + $oToDelete->DBDeleteTracked($oMyChange); + } + } + + // Update dependencies + // + foreach ($aResetedObjs as $sRemoteClass => $aToReset) + { + foreach ($aToReset as $iId => $aData) + { + $oToReset = $aData['to_reset']; + $aDisplayData[] = array( + 'class' => MetaModel::GetName(get_class($oToReset)), + 'object' => $oToReset->GetHyperLink(), + 'consequence' => 'automatic reset of: '.$aData['attributes_list'], + ); + + foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) + { + $oToReset->Set($sRemoteExtKey, 0); + $oToReset->DBUpdateTracked($oMyChange); + } + } + } + + // Report automatic jobs + // + if ($iTotalTargets > 0) + { + $oP->p('Cleaning up any reference to '.$oObj->GetName().'...'); + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); + $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); + $aDisplayConfig['consequence'] = array('label' => 'Done', 'description' => 'What has been done'); + $oP->table($aDisplayConfig, $aDisplayData); + } + + $oObj->DBDeleteTracked($oMyChange); + $oP->add("

    ".$sName." - $sClassLabel deleted

    \n"); + } + else + { + $oP->add("

    Deletion of ".$oObj->GetHyperLink()."

    \n"); + // Explain what should be done + // + $aDisplayData = array(); + foreach ($aDeletedObjs as $sRemoteClass => $aDeletes) + { + foreach ($aDeletes as $iId => $aData) + { + $oToDelete = $aData['to_delete']; + $bAutoDel = $aData['auto_delete']; + if (array_key_exists('issue', $aData)) + { + if ($bAutoDel) + { + $sConsequence = 'Should be automaticaly deleted, but you are not allowed to do so'; + } + else + { + $sConsequence = 'Must be deleted manually - you are not allowed to delete this object, please contact your application admin'; + } + } + else + { + if ($bAutoDel) + { + $sConsequence = 'Will be automaticaly deleted'; + } + else + { + $sConsequence = 'Must be deleted manually'; + } + } + $aDisplayData[] = array( + 'class' => MetaModel::GetName(get_class($oToDelete)), + 'object' => $oToDelete->GetHyperLink(), + 'consequence' => $sConsequence, + ); + } + } + foreach ($aResetedObjs as $sRemoteClass => $aToReset) + { + foreach ($aToReset as $iId => $aData) + { + $oToReset = $aData['to_reset']; + if (array_key_exists('issue', $aData)) + { + $sConsequence = "Should be automatically updated, but: ".$aData['issue']; + } + else + { + $sConsequence = "will be automaticaly updated (reset: ".$aData['attributes_list'].")"; + } + $aDisplayData[] = array( + 'class' => MetaModel::GetName(get_class($oToReset)), + 'object' => $oToReset->GetHyperLink(), + 'consequence' => $sConsequence, + ); + } + } + + if ($iTotalTargets > 0) + { + $oP->p("$iTotalTargets objects/links are referencing ".$oObj->GetName()); + $oP->p('To ensure Database integrity, any reference should be further eliminated'); + + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); + $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); + $aDisplayConfig['consequence'] = array('label' => 'Consequence', 'description' => 'What will happen to this object'); + $oP->table($aDisplayConfig, $aDisplayData); + } + + if ($iTotalTargets > 0 && ($bFoundManual || $bFoundStopper)) + { + if ($bFoundStopper) + { + $oP->p("Sorry, you are not allowed to delete this object, please see detailed explanations above"); + } + elseif ($bFoundManual) + { + $oP->p("Please do the manual operations requested above prior to requesting the deletion of this object"); + } + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + } + else + { + $oP->p("Please confirm that you want to delete ".$oObj->GetHyperLink()); + $oP->add("
    \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
    \n"); + } + } + break; + + case 'apply_new': + $oP->p('Creation of the object'); + $oP->p('Obsolete, should now go through the wizard...'); + break; + + case 'apply_clone': $sClass = utils::ReadPostedParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); - $id = utils::ReadPostedParam('id', ''); + $iCloneId = utils::ReadPostedParam('clone_id', ''); $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + if (!utils::IsTransactionValid($sTransactionId)) { - $oP->add("

    'class' and 'id' parameters must be specifed for this operation.

    \n"); + $oP->p("Error: object has already be cloned!\n"); } - else if (!utils::IsTransactionValid($sTransactionId)) + else { - $oP->p("Error: object has already be updated!\n"); + $oObj = $oContext->GetObject($sClass, $iCloneId); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($oObj)); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ( ('finalclass' != $sAttCode) && // finalclass is a reserved word, hardcoded ! + ($sStateAttCode != $sAttCode) && + (!$oAttDef->IsExternalField()) ) + { + $value = utils::ReadPostedParam('attr_'.$sAttCode, ''); + $oObj->Set($sAttCode, $value); + } + } + $oObj->DBCloneTracked($oMyChange); + $oP->add("

    ".$oObj->GetName()." - $sClassLabel created

    \n"); + $oObj->DisplayDetails($oP); + } + + break; + + case 'wizard_apply_new': + $sJson = utils::ReadPostedParam('json_obj', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already be created!\n"); + } + else + { + $oObj = $oWizardHelper->GetTargetObject(true /* read uploaded files */); + if (is_object($oObj)) + { + $sClass = get_class($oObj); + $sClassLabel = MetaModel::GetName($sClass); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBInsertTracked($oMyChange); + $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel created"); + $oP->add("

    ".$oObj->GetName()." - $sClassLabel created

    \n"); + $oObj->DisplayDetails($oP); + } + } + break; + + case 'stimulus': + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', ''); + $sStimulus = utils::ReadParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + $oP->add("

    'class', 'id' and 'stimulus' parameters must be specifed for this operation.

    \n"); } else { $oObj = $oContext->GetObject($sClass, $id); if ($oObj != null) { - $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel modification"); - $oP->add("

    ".$oObj->GetName()." - $sClassLabel modification

    \n"); - $bObjectModified = false; - foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) { - $iFlags = $oObj->GetAttributeFlags($sAttCode); - if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) - { - // Non-visible, or read-only attribute, do nothing - } - else if ($sAttCode == 'finalclass') - { - // This very specific field is read-only - } - else if ($oAttDef->IsLinkSet()) - { - // Link set, the data is a set of link objects, encoded in JSON - $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); - if (!empty($aAttributes[$sAttCode])) - { - $oLinkSet = WizardHelper::ParseJsonSet($oObj, $oAttDef->GetLinkedClass(), $oAttDef->GetExtKeyToMe(), $aAttributes[$sAttCode]); - $oObj->Set($sAttCode, $oLinkSet); - // TO DO: detect a real modification, for now always update !! - $bObjectModified = true; - } - } - else if ($oAttDef->GetEditClass() == 'Document') - { - // There should be an uploaded file with the named attr_ - $oDocument = utils::ReadPostedDocument('file_'.$sAttCode); - if (!$oDocument->IsEmpty()) - { - // A new file has been uploaded - $oObj->Set($sAttCode, $oDocument); - $bObjectModified = true; - } - } - else if (!$oAttDef->IsExternalField()) - { - $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); - if (!is_null($rawValue)) - { - $aAttributes[$sAttCode] = trim($rawValue); - $previousValue = $oObj->Get($sAttCode); - if ($previousValue != $aAttributes[$sAttCode]) - { - $oObj->Set($sAttCode, $aAttributes[$sAttCode]); - $bObjectModified = true; - } - } - } - } - if (!$bObjectModified) - { - $oP->p("No modification detected. ".MetaModel::GetName(get_class($oObj))." has not been updated.\n"); - } - else if ($oObj->CheckToUpdate()) - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) - { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); - } - else - { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); - - $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); + $oP->add("

    Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

    \n"); } else { - $oP->p("Error: object can not be updated!\n"); - //$oObj->Reload(); // restore default values! + $sActionLabel = $aStimuli[$sStimulus]->Get('label'); + $sActionDetails = $aStimuli[$sStimulus]->Get('description'); + $aTransition = $aTransitions[$sStimulus]; + $sTargetState = $aTransition['target_state']; + $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add("
    \n"); + $oP->add("

    $sActionLabel - {$oObj->GetName()}

    \n"); + //$oP->add("

    Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

    \n"); + $oP->add("
    \n"); + $oObj->DisplayBareDetails($oP); + $aTargetState = $aTargetStates[$sTargetState]; + //print_r($aTransitions[$sStimulus]); + //print_r($aTargetState); + $aExpectedAttributes = $aTargetState['attribute_list']; + $oP->add("
    \n"); + $oP->add("

    $sActionDetails

    \n"); + $oP->add("
    \n"); + $oP->add("
    \n"); + $aDetails = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) + { + $aAttributesDef = MetaModel::ListAttributeDefs($sClass); + $oAttDef = $aAttributesDef[$sAttCode]; + $aArgs = array('this' => $oObj); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetDisplayValue($sAttCode), '', '', $iExpectCode, $aArgs); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + } + $oP->details($aDetails); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add($oAppContext->GetForForm()); + $oP->add("    \n"); + $oP->add("\n"); + $oP->add("
    \n"); + $oP->add("
    \n"); + $oP->add("
    \n"); } } else { $oP->set_title("iTop - Error"); $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); - } + } } - $oObj->DisplayDetails($oP); - break; - - case 'delete': - case 'delete_confirmed': - $sClass = utils::ReadParam('class', ''); - $sClassLabel = MetaModel::GetName($sClass); - $id = utils::ReadParam('id', ''); - $oObj = $oContext->GetObject($sClass, $id); - $sName = $oObj->GetName(); + break; - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj))) - { - throw new SecurityException('You are not allowed to do delete objects of class '.$sClass); - } - - // Evaluate the impact on the DB integrity - // - list ($aDeletedObjs, $aResetedObjs) = $oObj->GetDeletionScheme(); - - // Evaluate feasibility (user access control) - // - $bFoundManual = false; - $bFoundStopper = false; - $iTotalDelete = 0; // count of object that must be deleted - $iTotalReset = 0; // count of object for which an ext key will be reset (to 0) - foreach ($aDeletedObjs as $sRemoteClass => $aDeletes) - { - $iTotalDelete += count($aDeletes); - foreach ($aDeletes as $iId => $aData) + case 'apply_stimulus': + $sClass = utils::ReadPostedParam('class', ''); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + $sStimulus = utils::ReadPostedParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! { - $oToDelete = $aData['to_delete']; - $bDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oToDelete)); - if (!$bDeleteAllowed) - { - $aDeletedObjs[$sRemoteClass][$iId]['issue'] = 'not allowed to delete this object'; - $bFoundStopper = true; - } - - $bAutoDel = $aData['auto_delete']; - if (!$bAutoDel) - { - $bFoundManual = true; - } + $oP->add("

    'class', 'id' and 'stimulus' parameters must be specifed for this operation.

    \n"); } - } - foreach ($aResetedObjs as $sRemoteClass => $aToReset) - { - $iTotalReset += count($aToReset); - foreach ($aToReset as $iId => $aData) + else { - $oToReset = $aData['to_reset']; - $aExtKeyLabels = array(); - $aForbiddenKeys = array(); // keys on which the current user is not allowed to write - foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) { - $bUpdateAllowed = UserRights::IsActionAllowedOnAttribute($sClass, $sRemoteExtKey, UR_ACTION_MODIFY, DBObjectSet::FromObject($oToReset)); - if (!$bUpdateAllowed) + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) { - $bFoundStopper = true; - $aForbiddenKeys[] = $aRemoteAttDef->GetLabel(); + $oP->add("

    Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

    \n"); + } + else if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("Error: object has already been updated!\n"); + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->Get('label'); + $sActionDetails = $aStimuli[$sStimulus]->Get('description'); + $aTransition = $aTransitions[$sStimulus]; + $sTargetState = $aTransition['target_state']; + $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add("
    \n"); + $oP->add("

    $sActionLabel - {$oObj->GetName()}

    \n"); + $oP->add("

    $sActionDetails

    \n"); + $oP->add("

    Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

    \n"); + $oP->add("
    \n"); + $aTargetState = $aTargetStates[$sTargetState]; + //print_r($aTransitions[$sStimulus]); + //print_r($aTargetState); + $aExpectedAttributes = $aTargetState['attribute_list']; + $aDetails = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + if (($iExpectCode & OPT_ATT_MUSTCHANGE) || ($oObj->Get($sAttCode) == '') ) + { + $paramValue = utils::ReadPostedParam("attr_$sAttCode", ''); + $oObj->Set($sAttCode, $paramValue); + } + } + if ($oObj->ApplyStimulus($sStimulus) && $oObj->CheckToUpdate()) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + + $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); + } + $oObj->DisplayDetails($oP); } - $aExtKeyLabels[] = $aRemoteAttDef->GetLabel(); } - $aResetedObjs[$sRemoteClass][$iId]['attributes_list'] = implode(', ', $aExtKeyLabels); - if (count($aForbiddenKeys) > 0) + else { - $aResetedObjs[$sRemoteClass][$iId]['issue'] = 'you are not allowed to update some fields: '.implode(', ', $aForbiddenKeys); - } + $oP->set_title("iTop - Error"); + $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); + } } - } - // Count of dependent objects (+ the current one) - $iTotalTargets = $iTotalDelete + $iTotalReset; + break; - if ($operation == 'delete_confirmed') - { - $oP->add("

    Deletion of ".$oObj->GetName()."

    \n"); - // Security - do not allow the user to force a forbidden delete by the mean of page arguments... - if ($bFoundStopper) + case 'modify_links': + $sClass = utils::ReadParam('class', ''); + $sLinkAttr = utils::ReadParam('link_attr', ''); + $sTargetClass = utils::ReadParam('target_class', ''); + $id = utils::ReadParam('id', ''); + $bAddObjects = utils::ReadParam('addObjects', false); + if ( empty($sClass) || empty($id) || empty($sLinkAttr) || empty($sTargetClass)) // TO DO: check that the class name is valid ! { - throw new SecurityException('This object could not be deleted because the current user do not have sufficient rights'); + $oP->set_title("iTop - Error"); + $oP->add("

    4 parameters are mandatory for this operation: class, id, target_class and link_attr.

    \n"); } - if ($bFoundManual) + else { - throw new SecurityException('This object could not be deleted because some manual operations must be performed prior to that'); + require_once('../application/uilinkswizard.class.inc.php'); + $oWizard = new UILinksWizard($sClass, $sLinkAttr, $id, $sTargetClass); + $oWizard->Display($oP, $oContext, array('StartWithAdd' => $bAddObjects)); } - - // Prepare the change reporting - // + break; + + case 'do_modify_links': + $aLinks = utils::ReadParam('linkId', array(), 'post'); + $sLinksToRemove = trim(utils::ReadParam('linksToRemove', '', 'post')); + $aLinksToRemove = array(); + if (!empty($sLinksToRemove)) + { + $aLinksToRemove = explode(' ', trim($sLinksToRemove)); + } + $sClass = utils::ReadParam('class', '', 'post'); + $sLinkageAtt = utils::ReadParam('linkage', '', 'post'); + $iObjectId = utils::ReadParam('object_id', '', 'post'); + $sLinkingAttCode = utils::ReadParam('linking_attcode', '', 'post'); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); if (UserRights::GetUser() != UserRights::GetRealUser()) @@ -619,529 +1060,106 @@ switch($operation) $sUserString = UserRights::GetUser(); } $oMyChange->Set("userinfo", $sUserString); - $oMyChange->DBInsert(); - - // Delete dependencies - // - $aDisplayData = array(); - foreach ($aDeletedObjs as $sRemoteClass => $aDeletes) - { - foreach ($aDeletes as $iId => $aData) - { - $oToDelete = $aData['to_delete']; - - $aDisplayData[] = array( - 'class' => MetaModel::GetName(get_class($oToDelete)), - 'object' => $oToDelete->GetHyperLink(), - 'consequence' => 'automatically deleted', - ); - - $oToDelete->DBDeleteTracked($oMyChange); - } - } - - // Update dependencies - // - foreach ($aResetedObjs as $sRemoteClass => $aToReset) - { - foreach ($aToReset as $iId => $aData) - { - $oToReset = $aData['to_reset']; - $aDisplayData[] = array( - 'class' => MetaModel::GetName(get_class($oToReset)), - 'object' => $oToReset->GetHyperLink(), - 'consequence' => 'automatic reset of: '.$aData['attributes_list'], - ); - - foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) - { - $oToReset->Set($sRemoteExtKey, 0); - $oToReset->DBUpdateTracked($oMyChange); - } - } - } - - // Report automatic jobs - // - if ($iTotalTargets > 0) - { - $oP->p('Cleaning up any reference to '.$oObj->GetName().'...'); - $aDisplayConfig = array(); - $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); - $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); - $aDisplayConfig['consequence'] = array('label' => 'Done', 'description' => 'What has been done'); - $oP->table($aDisplayConfig, $aDisplayData); - } - - $oObj->DBDeleteTracked($oMyChange); - $oP->add("

    ".$sName." - $sClassLabel deleted

    \n"); - } - else - { - $oP->add("

    Deletion of ".$oObj->GetHyperLink()."

    \n"); - // Explain what should be done - // - $aDisplayData = array(); - foreach ($aDeletedObjs as $sRemoteClass => $aDeletes) - { - foreach ($aDeletes as $iId => $aData) - { - $oToDelete = $aData['to_delete']; - $bAutoDel = $aData['auto_delete']; - if (array_key_exists('issue', $aData)) - { - if ($bAutoDel) - { - $sConsequence = 'Should be automaticaly deleted, but you are not allowed to do so'; - } - else - { - $sConsequence = 'Must be deleted manually - you are not allowed to delete this object, please contact your application admin'; - } - } - else - { - if ($bAutoDel) - { - $sConsequence = 'Will be automaticaly deleted'; - } - else - { - $sConsequence = 'Must be deleted manually'; - } - } - $aDisplayData[] = array( - 'class' => MetaModel::GetName(get_class($oToDelete)), - 'object' => $oToDelete->GetHyperLink(), - 'consequence' => $sConsequence, - ); - } - } - foreach ($aResetedObjs as $sRemoteClass => $aToReset) - { - foreach ($aToReset as $iId => $aData) - { - $oToReset = $aData['to_reset']; - if (array_key_exists('issue', $aData)) - { - $sConsequence = "Should be automatically updated, but: ".$aData['issue']; - } - else - { - $sConsequence = "will be automaticaly updated (reset: ".$aData['attributes_list'].")"; - } - $aDisplayData[] = array( - 'class' => MetaModel::GetName(get_class($oToReset)), - 'object' => $oToReset->GetHyperLink(), - 'consequence' => $sConsequence, - ); - } - } - - if ($iTotalTargets > 0) - { - $oP->p("$iTotalTargets objects/links are referencing ".$oObj->GetName()); - $oP->p('To ensure Database integrity, any reference should be further eliminated'); - - $aDisplayConfig = array(); - $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); - $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); - $aDisplayConfig['consequence'] = array('label' => 'Consequence', 'description' => 'What will happen to this object'); - $oP->table($aDisplayConfig, $aDisplayData); - } - - if ($iTotalTargets > 0 && ($bFoundManual || $bFoundStopper)) - { - if ($bFoundStopper) - { - $oP->p("Sorry, you are not allowed to delete this object, please see detailed explanations above"); - } - elseif ($bFoundManual) - { - $oP->p("Please do the manual operations requested above prior to requesting the deletion of this object"); - } - $oP->add("
    \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
    \n"); - } - else - { - $oP->p("Please confirm that you want to delete ".$oObj->GetHyperLink()); - $oP->add("
    \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
    \n"); - } - } - break; + $iChangeId = $oMyChange->DBInsert(); - case 'apply_new': - $oP->p('Creation of the object'); - $oP->p('Obsolete, should now go through the wizard...'); - break; - - case 'apply_clone': - $sClass = utils::ReadPostedParam('class', ''); - $sClassLabel = MetaModel::GetName($sClass); - $iCloneId = utils::ReadPostedParam('clone_id', ''); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if (!utils::IsTransactionValid($sTransactionId)) - { - $oP->p("Error: object has already be cloned!\n"); - } - else - { - $oObj = $oContext->GetObject($sClass, $iCloneId); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) - { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); - } - else - { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($oObj)); - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - if ( ('finalclass' != $sAttCode) && // finalclass is a reserved word, hardcoded ! - ($sStateAttCode != $sAttCode) && - (!$oAttDef->IsExternalField()) ) - { - $value = utils::ReadPostedParam('attr_'.$sAttCode, ''); - $oObj->Set($sAttCode, $value); - } - } - $oObj->DBCloneTracked($oMyChange); - $oP->add("

    ".$oObj->GetName()." - $sClassLabel created

    \n"); - $oObj->DisplayDetails($oP); - } - - break; - - case 'wizard_apply_new': - $sJson = utils::ReadPostedParam('json_obj', ''); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - if (!utils::IsTransactionValid($sTransactionId)) - { - $oP->p("Error: object has already be created!\n"); - } - else - { - $oObj = $oWizardHelper->GetTargetObject(true /* read uploaded files */); - if (is_object($oObj)) + // Delete links that are to be deleted + foreach($aLinksToRemove as $iLinkId) { - $sClass = get_class($oObj); - $sClassLabel = MetaModel::GetName($sClass); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) + if ($iLinkId > 0) // Negative IDs are objects that were not even created { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); - } - else - { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBInsertTracked($oMyChange); - $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel created"); - $oP->add("

    ".$oObj->GetName()." - $sClassLabel created

    \n"); - $oObj->DisplayDetails($oP); - } - } - break; - - case 'stimulus': - $sClass = utils::ReadParam('class', ''); - $id = utils::ReadParam('id', ''); - $sStimulus = utils::ReadParam('stimulus', ''); - if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! - { - $oP->add("

    'class', 'id' and 'stimulus' parameters must be specifed for this operation.

    \n"); - } - else - { - $oObj = $oContext->GetObject($sClass, $id); - if ($oObj != null) - { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - if (!isset($aTransitions[$sStimulus])) - { - $oP->add("

    Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

    \n"); - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->Get('label'); - $sActionDetails = $aStimuli[$sStimulus]->Get('description'); - $aTransition = $aTransitions[$sStimulus]; - $sTargetState = $aTransition['target_state']; - $aTargetStates = MetaModel::EnumStates($sClass); - $oP->add("
    \n"); - $oP->add("

    $sActionLabel - {$oObj->GetName()}

    \n"); - //$oP->add("

    Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

    \n"); - $oP->add("
    \n"); - $oObj->DisplayBareDetails($oP); - $aTargetState = $aTargetStates[$sTargetState]; - //print_r($aTransitions[$sStimulus]); - //print_r($aTargetState); - $aExpectedAttributes = $aTargetState['attribute_list']; - $oP->add("
    \n"); - $oP->add("

    $sActionDetails

    \n"); - $oP->add("
    \n"); - $oP->add("
    \n"); - $aDetails = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - // Prompt for an attribute if - // - the attribute must be changed or must be displayed to the user for confirmation - // - or the field is mandatory and currently empty - if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || - (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) - { - $aAttributesDef = MetaModel::ListAttributeDefs($sClass); - $oAttDef = $aAttributesDef[$sAttCode]; - $aArgs = array('this' => $oObj); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetDisplayValue($sAttCode), '', '', $iExpectCode, $aArgs); - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - } - } - $oP->details($aDetails); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add($oAppContext->GetForForm()); - $oP->add("    \n"); - $oP->add("\n"); - $oP->add("
    \n"); - $oP->add("
    \n"); - $oP->add("
    \n"); + $oLink = $oContext->GetObject($sClass, $iLinkId); + $oLink->DBDeleteTracked($oMyChange); } } - else + + $aEditableFields = array(); + $aData = array(); + foreach(MetaModel::GetAttributesList($sClass) as $sAttCode) { - $oP->set_title("iTop - Error"); - $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); - } - } - break; - - case 'apply_stimulus': - $sClass = utils::ReadPostedParam('class', ''); - $id = utils::ReadPostedParam('id', ''); - $sTransactionId = utils::ReadPostedParam('transaction_id', ''); - $sStimulus = utils::ReadPostedParam('stimulus', ''); - if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! - { - $oP->add("

    'class', 'id' and 'stimulus' parameters must be specifed for this operation.

    \n"); - } - else - { - $oObj = $oContext->GetObject($sClass, $id); - if ($oObj != null) + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField())) + { + $aEditableFields[] = $sAttCode; + $aData[$sAttCode] = utils::ReadParam('attr_'.$sAttCode, array(), 'post'); + } + } + + // Update existing links or create new links + foreach($aLinks as $iLinkId) { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - if (!isset($aTransitions[$sStimulus])) + if ($iLinkId > 0) { - $oP->add("

    Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

    \n"); - } - else if (!utils::IsTransactionValid($sTransactionId)) - { - $oP->p("Error: object has already been updated!\n"); - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->Get('label'); - $sActionDetails = $aStimuli[$sStimulus]->Get('description'); - $aTransition = $aTransitions[$sStimulus]; - $sTargetState = $aTransition['target_state']; - $aTargetStates = MetaModel::EnumStates($sClass); - $oP->add("
    \n"); - $oP->add("

    $sActionLabel - {$oObj->GetName()}

    \n"); - $oP->add("

    $sActionDetails

    \n"); - $oP->add("

    Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

    \n"); - $oP->add("
    \n"); - $aTargetState = $aTargetStates[$sTargetState]; - //print_r($aTransitions[$sStimulus]); - //print_r($aTargetState); - $aExpectedAttributes = $aTargetState['attribute_list']; - $aDetails = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - if (($iExpectCode & OPT_ATT_MUSTCHANGE) || ($oObj->Get($sAttCode) == '') ) - { - $paramValue = utils::ReadPostedParam("attr_$sAttCode", ''); - $oObj->Set($sAttCode, $paramValue); - } - } - if ($oObj->ApplyStimulus($sStimulus) && $oObj->CheckToUpdate()) - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) - { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); - } - else - { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); + // This is an existing link to be modified + $oLink = $oContext->GetObject($sClass, $iLinkId); - $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); + // Update all the attributes of the link + foreach($aEditableFields as $sAttCode) + { + $value = $aData[$sAttCode][$iLinkId]; + $oLink->Set($sAttCode, $value); } - $oObj->DisplayDetails($oP); + if ($oLink->IsModified()) + { + $oLink->DBUpdateTracked($oMyChange); + } + //echo "Updated link:
    \n"; + //var_dump($oLink); } - } - else - { - $oP->set_title("iTop - Error"); - $oP->add("

    Sorry this object does not exist (or you are not allowed to edit it).

    \n"); - } - } - break; - - case 'modify_links': - $sClass = utils::ReadParam('class', ''); - $sLinkAttr = utils::ReadParam('link_attr', ''); - $sTargetClass = utils::ReadParam('target_class', ''); - $id = utils::ReadParam('id', ''); - $bAddObjects = utils::ReadParam('addObjects', false); - if ( empty($sClass) || empty($id) || empty($sLinkAttr) || empty($sTargetClass)) // TO DO: check that the class name is valid ! - { - $oP->set_title("iTop - Error"); - $oP->add("

    4 parameters are mandatory for this operation: class, id, target_class and link_attr.

    \n"); - } - else - { - require_once('../application/uilinkswizard.class.inc.php'); - $oWizard = new UILinksWizard($sClass, $sLinkAttr, $id, $sTargetClass); - $oWizard->Display($oP, $oContext, array('StartWithAdd' => $bAddObjects)); - } - break; - - case 'do_modify_links': - $aLinks = utils::ReadParam('linkId', array(), 'post'); - $sLinksToRemove = trim(utils::ReadParam('linksToRemove', '', 'post')); - $aLinksToRemove = array(); - if (!empty($sLinksToRemove)) - { - $aLinksToRemove = explode(' ', trim($sLinksToRemove)); - } - $sClass = utils::ReadParam('class', '', 'post'); - $sLinkageAtt = utils::ReadParam('linkage', '', 'post'); - $iObjectId = utils::ReadParam('object_id', '', 'post'); - $sLinkingAttCode = utils::ReadParam('linking_attcode', '', 'post'); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) - { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); - } - else - { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - - // Delete links that are to be deleted - foreach($aLinksToRemove as $iLinkId) - { - if ($iLinkId > 0) // Negative IDs are objects that were not even created - { - $oLink = $oContext->GetObject($sClass, $iLinkId); - $oLink->DBDeleteTracked($oMyChange); - } - } - - $aEditableFields = array(); - $aData = array(); - foreach(MetaModel::GetAttributesList($sClass) as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ( (!$oAttDef->IsExternalKey()) && (!$oAttDef->IsExternalField())) - { - $aEditableFields[] = $sAttCode; - $aData[$sAttCode] = utils::ReadParam('attr_'.$sAttCode, array(), 'post'); - } - } - - // Update existing links or create new links - foreach($aLinks as $iLinkId) - { - if ($iLinkId > 0) - { - // This is an existing link to be modified - $oLink = $oContext->GetObject($sClass, $iLinkId); - - // Update all the attributes of the link - foreach($aEditableFields as $sAttCode) + else { - $value = $aData[$sAttCode][$iLinkId]; - $oLink->Set($sAttCode, $value); + // A new link must be created + $oLink = MetaModel::NewObject($sClass); + $oLinkedObjectId = -$iLinkId; + // Set all the attributes of the link + foreach($aEditableFields as $sAttCode) + { + $value = $aData[$sAttCode][$iLinkId]; + $oLink->Set($sAttCode, $value); + } + // And the two external keys + $oLink->Set($sLinkageAtt, $iObjectId); + $oLink->Set($sLinkingAttCode, $oLinkedObjectId); + // then save it + //echo "Created link:
    \n"; + //var_dump($oLink); + $oLink->DBInsertTracked($oMyChange); } - if ($oLink->IsModified()) - { - $oLink->DBUpdateTracked($oMyChange); - } - //echo "Updated link:
    \n"; - //var_dump($oLink); } - else + // Display again the details of the linked object + $oAttDef = MetaModel::GetAttributeDef($sClass, $sLinkageAtt); + $sTargetClass = $oAttDef->GetTargetClass(); + $oObj = $oContext->GetObject($sTargetClass, $iObjectId); + + $oSearch = $oContext->NewFilter(get_class($oObj)); + $oBlock = new DisplayBlock($oSearch, 'search', false); + $oBlock->Display($oP, 0); + $oObj->DisplayDetails($oP); + break; + + default: + if (is_object($oActiveNode)) { - // A new link must be created - $oLink = MetaModel::NewObject($sClass); - $oLinkedObjectId = -$iLinkId; - // Set all the attributes of the link - foreach($aEditableFields as $sAttCode) - { - $value = $aData[$sAttCode][$iLinkId]; - $oLink->Set($sAttCode, $value); - } - // And the two external keys - $oLink->Set($sLinkageAtt, $iObjectId); - $oLink->Set($sLinkingAttCode, $oLinkedObjectId); - // then save it - //echo "Created link:
    \n"; - //var_dump($oLink); - $oLink->DBInsertTracked($oMyChange); + $oActiveNode->RenderContent($oP, $oAppContext->GetAsHash()); + $oP->set_title($oActiveNode->Get('label')); } } - // Display again the details of the linked object - $oAttDef = MetaModel::GetAttributeDef($sClass, $sLinkageAtt); - $sTargetClass = $oAttDef->GetTargetClass(); - $oObj = $oContext->GetObject($sTargetClass, $iObjectId); - - $oSearch = $oContext->NewFilter(get_class($oObj)); - $oBlock = new DisplayBlock($oSearch, 'search', false); - $oBlock->Display($oP, 0); - $oObj->DisplayDetails($oP); - break; - - default: - if (is_object($oActiveNode)) - { - $oActiveNode->RenderContent($oP, $oAppContext->GetAsHash()); - $oP->set_title($oActiveNode->Get('label')); - } + ////MetaModel::ShowQueryTrace(); + $oP->output(); +} +catch(Exception $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage('iTop - fatal error'); + $oP->add("

    Fatal Error, iTop cannot continue

    \n"); + $oP->error("Error: '".$e->getMessage()."'"); + $oP->output(); +} +catch(CoreException $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage('iTop - fatal error'); + $oP->add("

    Fatal Error, iTop cannot continue

    \n"); + $oP->error("Error: '".$e->getHtmlDesc()."'"); + $oP->output(); } -////MetaModel::ShowQueryTrace(); -$oP->output(); ?> From 735cd28dfd7db2b70f9cfdbc79924980b0a53e60 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Mar 2010 16:40:58 +0000 Subject: [PATCH 238/970] Regression when fixing bug #89: the "state" attribute was editable. This is now fixed. SVN:trunk[320] --- application/uiwizard.class.inc.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 72ee34e6d5..16f08163ef 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -145,13 +145,15 @@ $sJSHandlerCode $aFieldsDone = array(); // Store all the fields that are already covered by a previous step of the wizard $aStates = MetaModel::EnumStates($this->m_sClass); + $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); $aMandatoryAttributes = array(); // Some attributes are always mandatory independently of the state machine (if any) foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); - if (!$oAttDef->IsExternalField() && !$oAttDef->IsNullAllowed()) + if (!$oAttDef->IsExternalField() && !$oAttDef->IsNullAllowed() && + ($sAttCode != 'finalclass') && ($sAttCode != $sStateAttCode) ) { $aMandatoryAttributes[$sAttCode] = OPT_ATT_MANDATORY; } @@ -231,7 +233,6 @@ $sJSHandlerCode // Now computes the steps to fill the optional fields - $sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass); $aFields = array(); // reset foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAttDef) { From 84c2599e6b087f245297f258053fe3be525cfec6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 9 Apr 2010 11:41:57 +0000 Subject: [PATCH 239/970] Setup of user rights: an alternative to the ITIL model, just create two profiles (readers and writers) SVN:trunk[322] --- .../userrightsprofile.class.inc.php | 349 ++++++++++-------- 1 file changed, 185 insertions(+), 164 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index b127f38316..86e4858b1e 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -834,8 +834,12 @@ class UserRightsProfile extends UserRightsAddOnAPI public function Setup() { - SetupITILProfiles::DoCreateDimensions(); - SetupITILProfiles::DoCreateProfiles(); + SetupProfiles::ComputeITILProfiles(); + //SetupProfiles::ComputeBasicProfiles(); + + SetupProfiles::DoCreateDimensions(); + SetupProfiles::DoCreateDimensions(); + SetupProfiles::DoCreateProfiles(); return true; } @@ -1356,25 +1360,13 @@ exit; } // -// Here is the code that will create some profiles into our user management model, given an ITIL representation of the user profiles +// Create simple profiles into our user management model: +// - administrator +// - readers +// - contributors // -class SetupITILProfiles +class SetupProfiles { - /* - Later, maybe ? - - protected static $m_aDimensions = array( - 'organization' => array( - 'description' => '', - 'type' => 'bizOrganization', - ), - 'site' => array( - 'description' => '', - 'type' => '', - ), - ); - */ - protected static $m_aDimensions = array( 'organization' => array( 'description' => '', @@ -1391,147 +1383,10 @@ class SetupITILProfiles UR_ACTION_BULK_DELETE => 'Bulk Delete', ); - // It is possible to specify the same class in several modules - // - protected static $m_aModules = array( - 'General' => array( - 'bizOrganization', - ), - 'Documentation' => array( - 'bizDocument', - 'lnkDocumentRealObject', - 'lnkDocumentContract', - 'lnkDocumentError', - ), - 'Configuration' => array( - 'logRealObject', - 'lnkContactRealObject', -// 'lnkInterfaces', - 'lnkClientServer', - 'lnkInfraGrouping', - 'lnkContactInfra', - 'lnkContactTeam', - ), - 'Incident' => array( - 'bizIncidentTicket', - 'lnkRelatedTicket', - 'lnkInfraTicket', - 'lnkContactTicket', - ), - 'Problem' => array( - 'bizKnownError', - 'lnkInfraError', - 'lnkDocumentError', - ), - 'Change' => array( - 'bizChangeTicket', - 'lnkInfraChangeTicket', - 'lnkContactChange', - ), - 'Service' => array( - 'bizService', - 'bizContract', - 'lnkInfraContract', - 'lnkContactContract', - 'lnkDocumentContract', - ), - 'Call' => array( - 'bizServiceCall', - 'lnkCallTicket', - 'lnkInfraCall', - ), - ); - - protected static $m_aProfiles = array( - 'Configuration Manager' => array( - 'description' => 'Person in charge of the documentation of the managed CIs', - 'write_modules' => 'Documentation,Configuration', - 'stimuli' => array( - 'bizServer' => 'any', - //'bizServer' => 'ev_store,ev_ship,ev_plug,ev_configuration_finished,ev_val_failed,ev_mtp,ev_start_change,ev_end_change,ev_decomission,ev_obsolete,ev_recycle', - 'bizContract' => 'none', - 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'none', - ), - ), - /* 'Requestor pb granularite actions (create/delete)' => array( - 'description' => 'Person notifying an incident', - 'write_modules' => 'Incident', - 'stimuli' => array( - 'bizServer' => 'none', - 'bizContract' => 'none', - 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'none', - ), - ), - */ - 'Service Desk Agent' => array( - 'description' => 'Person in charge of creating incident reports', - 'write_modules' => 'Incident,Call', - 'stimuli' => array( - 'bizServer' => 'none', - 'bizContract' => 'none', - 'bizIncidentTicket' => 'ev_assign', - 'bizChangeTicket' => 'none', - 'bizServiceCall' => 'any', - ), - ), - 'Support Agent' => array( - 'description' => 'Person analyzing and solving the current incidents or problems', - 'write_modules' => 'Incident,Problem', - 'stimuli' => array( - 'bizIncidentTicket' => 'any', - //'bizIncidentTicket' => 'ev_assign,ev_reassign,ev_start_working,ev_close', - ), - ), - 'Change Implementor' => array( - 'description' => 'Person executing the changes', - 'write_modules' => 'Change', - 'stimuli' => array( - 'bizServer' => 'none', - 'bizContract' => 'none', - 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - ), - ), - 'Change Supervisor' => array( - 'description' => 'Person responsible for the overall change execution', - 'write_modules' => 'Change', - 'stimuli' => array( - 'bizServer' => 'none', - 'bizContract' => 'none', - 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'ev_assign,ev_validate,ev_reject,ev_reopen,ev_finish', - ), - ), - 'Change Approver' => array( - 'description' => 'Person who could be impacted by some changes', - 'write_modules' => 'Change', - 'stimuli' => array( - 'bizServer' => 'none', - 'bizContract' => 'none', - 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'ev_approve,ev_notapprove', - ), - ), - 'Service Manager' => array( - 'description' => 'Person responsible for the service delivered to the [internal] customer', - 'write_modules' => 'Service', - 'stimuli' => array( - 'bizServer' => 'none', - 'bizContract' => 'any', - //'bizContract' => 'ev_freeze_version,ev_sign,ev_begin,ev_notice,ev_terminate,ev_elapsed', - 'bizIncidentTicket' => 'none', - 'bizChangeTicket' => 'none', - ), - ), - 'Document author' => array( - 'description' => 'Any person who could contribute to documentation', - 'write_modules' => 'Documentation', - 'stimuli' => array( - ), - ), - ); + // Note: It is possible to specify the same class in several modules + // + protected static $m_aModules = array(); + protected static $m_aProfiles = array(); protected static function DoCreateClassProjection($iDimension, $sClass) { @@ -1603,7 +1458,14 @@ class SetupITILProfiles protected static function DoCreateOneProfile($sName, $aProfileData) { $sDescription = $aProfileData['description']; - $aWriteModules = explode(',', $aProfileData['write_modules']); + if (strlen(trim($aProfileData['write_modules'])) == 0) + { + $aWriteModules = array(); + } + else + { + $aWriteModules = explode(',', trim($aProfileData['write_modules'])); + } $aStimuli = $aProfileData['stimuli']; $oNewObj = MetaModel::NewObject("URP_Profiles"); @@ -1633,13 +1495,13 @@ class SetupITILProfiles $aWriteableClasses = array(); foreach ($aWriteModules as $sModule) { -// $oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); + //$oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); foreach (self::$m_aModules[$sModule] as $sClass) { - $aWriteableClasses[] = $sClass; + $aWriteableClasses[$sClass] = true; } } - foreach ($aWriteableClasses as $sClass) + foreach ($aWriteableClasses as $sClass => $foo) { if (!MetaModel::IsValidClass($sClass)) { @@ -1705,6 +1567,165 @@ class SetupITILProfiles self::DoCreateOneProfile($sName, $aProfileData); } } + + public static function ComputeBasicProfiles() + { + // In this profiling scheme, one single module represents all the classes + // + self::$m_aModules = array( + 'UserData' => MetaModel::GetClasses('bizmodel'), + ); + + self::$m_aProfiles = array( + 'Reader' => array( + 'description' => 'Person having a ready-only access to the data', + 'write_modules' => '', + 'stimuli' => array( + ), + ), + 'Writer' => array( + 'description' => 'Contributor to the contents (read + write access)', + 'write_modules' => 'UserData', + 'stimuli' => array( + // any class => 'any' + ), + ), + ); + } + + public static function ComputeITILProfiles() + { + // In this profiling scheme, modules are based on ITIL recommendations + // + self::$m_aModules = array( + 'General' => array( + 'bizOrganization', + ), + 'Documentation' => array( + 'bizDocument', + 'lnkDocumentRealObject', + 'lnkDocumentContract', + 'lnkDocumentError', + ), + 'Configuration' => array( + 'logRealObject', + 'lnkContactRealObject', + // 'lnkInterfaces', + 'lnkClientServer', + 'lnkInfraGrouping', + 'lnkContactInfra', + 'lnkContactTeam', + ), + 'Incident' => array( + 'bizIncidentTicket', + 'lnkRelatedTicket', + 'lnkInfraTicket', + 'lnkContactTicket', + ), + 'Problem' => array( + 'bizKnownError', + 'lnkInfraError', + 'lnkDocumentError', + ), + 'Change' => array( + 'bizChangeTicket', + 'lnkInfraChangeTicket', + 'lnkContactChange', + ), + 'Service' => array( + 'bizService', + 'bizContract', + 'lnkInfraContract', + 'lnkContactContract', + 'lnkDocumentContract', + ), + 'Call' => array( + 'bizServiceCall', + 'lnkCallTicket', + 'lnkInfraCall', + ), + ); + + self::$m_aProfiles = array( + 'Configuration Manager' => array( + 'description' => 'Person in charge of the documentation of the managed CIs', + 'write_modules' => 'Documentation,Configuration', + 'stimuli' => array( + 'bizServer' => 'any', + //'bizServer' => 'ev_store,ev_ship,ev_plug,ev_configuration_finished,ev_val_failed,ev_mtp,ev_start_change,ev_end_change,ev_decomission,ev_obsolete,ev_recycle', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'none', + ), + ), + 'Service Desk Agent' => array( + 'description' => 'Person in charge of creating incident reports', + 'write_modules' => 'Incident,Call', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'ev_assign', + 'bizChangeTicket' => 'none', + 'bizServiceCall' => 'any', + ), + ), + 'Support Agent' => array( + 'description' => 'Person analyzing and solving the current incidents or problems', + 'write_modules' => 'Incident,Problem', + 'stimuli' => array( + 'bizIncidentTicket' => 'any', + //'bizIncidentTicket' => 'ev_assign,ev_reassign,ev_start_working,ev_close', + ), + ), + 'Change Implementor' => array( + 'description' => 'Person executing the changes', + 'write_modules' => 'Change', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + ), + ), + 'Change Supervisor' => array( + 'description' => 'Person responsible for the overall change execution', + 'write_modules' => 'Change', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'ev_assign,ev_validate,ev_reject,ev_reopen,ev_finish', + ), + ), + 'Change Approver' => array( + 'description' => 'Person who could be impacted by some changes', + 'write_modules' => 'Change', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'none', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'ev_approve,ev_notapprove', + ), + ), + 'Service Manager' => array( + 'description' => 'Person responsible for the service delivered to the [internal] customer', + 'write_modules' => 'Service', + 'stimuli' => array( + 'bizServer' => 'none', + 'bizContract' => 'any', + //'bizContract' => 'ev_freeze_version,ev_sign,ev_begin,ev_notice,ev_terminate,ev_elapsed', + 'bizIncidentTicket' => 'none', + 'bizChangeTicket' => 'none', + ), + ), + 'Document author' => array( + 'description' => 'Any person who could contribute to documentation', + 'write_modules' => 'Documentation', + 'stimuli' => array( + ), + ), + ); + } } UserRights::SelectModule('UserRightsProfile'); From c4a51c31c98658aa1fffe26b38cb3eba5192a476 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 12 Apr 2010 12:13:14 +0000 Subject: [PATCH 240/970] Bug fix: the audit page was not working for object with no "org_id" filter criteria. SVN:trunk[323] --- pages/audit.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pages/audit.php b/pages/audit.php index af388e3a80..5219c4d3bc 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -97,8 +97,11 @@ switch($operation) $oDefinitionFilter = DBObjectSearch::FromSibusQL($oAuditCategory->Get('definition_set')); $aObjectsWithErrors = array(); if (!empty($currentOrganization)) - { - $oDefinitionFilter->AddCondition('org_id', $currentOrganization); + { + if (MetaModel::IsValidFilterCode($oDefinitionFilter->GetClass(), 'org_id')) + { + $oDefinitionFilter->AddCondition('org_id', $currentOrganization); + } } $aResults = array(); $oDefinitionSet = new CMDBObjectSet($oDefinitionFilter); From 37ccd94828728453abb08cc75fb443243c453a70 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 12 Apr 2010 12:19:12 +0000 Subject: [PATCH 241/970] Enhancement : new configuration option (secure_conenction_required) to force the use of HTTPS to connect to iTop. The redirection http -> https is still buggy but the usage of https is enforced anyway. SVN:trunk[324] --- application/loginwebpage.class.inc.php | 43 +++++++++++++++++++++----- application/utils.inc.php | 26 ++++++++++------ core/config.class.inc.php | 19 ++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 16195c5131..430b98ac81 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -75,21 +75,50 @@ h1 { } // Finally, destroy the session. session_destroy(); + } + + static function SecureConnectionRequired() + { + $oConfig = new Config(ITOP_CONFIG_FILE); + return $oConfig->GetSecureConnectionRequired(); + } + + static function IsConnectionSecure() + { + $bSecured = false; + + if ( !empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!= 'off') ) + { + $bSecured = true; + } + return $bSecured; } static function DoLogin() { - $operation = utils::ReadParam('loginop', ''); + if (self::SecureConnectionRequired() && !self::IsConnectionSecure()) + { + // Non secured URL... redirect to a secured one + $sUrl = Utils::GetAbsoluteUrl(true /* query string */, true /* force HTTPS */); + header("Location: $sUrl"); + exit; + } + $operation = utils::ReadParam('loginop', ''); session_start(); if ($operation == 'logoff') { self::ResetSession(); } - + if (!isset($_SESSION['auth_user']) || !isset($_SESSION['auth_pwd'])) { - if ($operation == 'login') + if ($operation == 'loginurl') + { + $sAuthUser = utils::ReadParam('auth_user', '', 'get'); + $sAuthPwd = utils::ReadParam('auth_pwd', '', 'get'); + } + else if ($operation == 'login') { $sAuthUser = utils::ReadParam('auth_user', '', 'post'); $sAuthPwd = utils::ReadParam('auth_pwd', '', 'post'); @@ -106,9 +135,9 @@ h1 { { $sAuthUser = $_SESSION['auth_user']; $sAuthPwd = $_SESSION['auth_pwd']; - } + } if (!UserRights::Login($sAuthUser, $sAuthPwd)) - { + { self::ResetSession(); $oPage = new LoginWebPage(); $oPage->DisplayLoginForm( true /* failed attempt */); @@ -119,8 +148,8 @@ h1 { { $_SESSION['auth_user'] = $sAuthUser ; $_SESSION['auth_pwd'] = $sAuthPwd; - - } + + } } } // End of class ?> diff --git a/application/utils.inc.php b/application/utils.inc.php index 6eb1722807..ecf2d11c00 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -153,18 +153,26 @@ class utils * @param $bQueryString bool True to also get the query string, false otherwise * @return string The absolute URL to the current page */ - static public function GetAbsoluteUrl($bQueryString = true) + static public function GetAbsoluteUrl($bQueryString = true, $bForceHTTPS = false) { // Build an absolute URL to this page on this server/port - $sServerName = $_SERVER['SERVER_NAME']; - $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; - if ($sProtocol == 'http') - { - $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; + $sServerName = $_SERVER['SERVER_NAME']; + if ($bForceHTTPS) + { + $sProtocol = 'https'; + $sPort = ''; } - else - { - $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; + else + { + $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + if ($sProtocol == 'http') + { + $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; + } + else + { + $sPort = ($_SERVER['SERVER_PORT'] == 443) ? '' : ':'.$_SERVER['SERVER_PORT']; + } } // $_SERVER['REQUEST_URI'] is empty when running on IIS // Let's use Ivan Tcholakov's fix (found on www.dokeos.com) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 7f32ff6158..cc57e0cb37 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -20,6 +20,7 @@ define ('DEFAULT_MIN_DISPLAY_LIMIT', 10); define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60); define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60); +define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); class Config { @@ -54,6 +55,11 @@ class Config */ protected $m_iFastReloadInterval; + /** + * @var boolean Whether or not a secure connection is required for using the application + */ + protected $m_bSecureConnectionRequired; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; @@ -70,6 +76,7 @@ class Config $this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT; $this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL; + $this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED; if ($bLoadConfig) { $this->Load($sConfigFile); @@ -151,6 +158,7 @@ class Config $this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT; $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL; + $this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED; } protected function Verify() @@ -229,6 +237,11 @@ class Config return $this->m_iFastReloadInterval; } + public function GetSecureConnectionRequired() + { + return $this->m_bSecureConnectionRequired; + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -274,6 +287,11 @@ class Config $this->m_iFastReloadInterval = $iFastReloadInterval; } + public function SetSecureConnectionRequired($bSecureConnectionRequired) + { + $this->m_bSecureConnectionRequired = $bSecureConnectionRequired; + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -315,6 +333,7 @@ class Config fwrite($hFile, "\t'max_display_limit' => {$this->m_iMaxDisplayLimit},\n"); fwrite($hFile, "\t'standard_reload_interval' => {$this->m_iStandardReloadInterval},\n"); fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n"); + fwrite($hFile, "\t'secure_connection_required' => ".($this->m_bSecureConnectionRequired ? 'true' : 'false').",\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n/**\n"); From 128f048800fca0ad4506fb54b03a12818159eead Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 12 Apr 2010 12:22:00 +0000 Subject: [PATCH 242/970] Enhancement / bug fix: allow the overload of the GetName method for building the short name of an object. This is also used when the object appears in a drop-down list. SVN:trunk[325] --- application/cmdbabstract.class.inc.php | 5 +++-- core/valuesetdef.class.inc.php | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 87088fd414..f4c2a88e0a 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -214,7 +214,7 @@ abstract class cmdbAbstractObject extends CMDBObject } function GetBareDetails(WebPage $oPage) - { + { $sHtml = ''; $oAppContext = new ApplicationContext(); $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); @@ -266,7 +266,8 @@ abstract class cmdbAbstractObject extends CMDBObject if (!empty($sTemplate)) { $oTemplate = new DisplayTemplate($sTemplate); - $oTemplate->Render($oPage, array('class_name'=> MetaModel::GetName(get_class($this)),'class'=> get_class($this),'pkey'=> $this->GetKey(), 'name' => $this->GetName())); + $sNameAttCode = MetaModel::GetNameAttributeCode(get_class($this)); + $oTemplate->Render($oPage, array('class_name'=> MetaModel::GetName(get_class($this)),'class'=> get_class($this),'pkey'=> $this->GetKey(), 'name' => $this->Get($sNameAttCode))); } else { diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 21c67c16e9..a96ffaeae2 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -96,15 +96,17 @@ class ValueSetObjects extends ValueSetDefinition $oFilter = DBObjectSearch::FromSibusQL($this->m_sFilterExpr, $aArgs); if (!$oFilter) return false; - if (empty($this->m_sValueAttCode)) - { - $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass()); - } - $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs); while ($oObject = $oObjects->Fetch()) { - $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($this->m_sValueAttCode); + if (empty($this->m_sValueAttCode)) + { + $this->m_aValues[$oObject->GetKey()] = $oObject->GetName(); + } + else + { + $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($this->m_sValueAttCode); + } } return true; } From 646ab068da8319ed48bb32d1c40ebe7baed1946b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 12 Apr 2010 12:29:48 +0000 Subject: [PATCH 243/970] - bug fix: it's now possible to have several search blocks (that can be toggled) in the same page. - new type of display block "Join" for grouping objects based on the attribute of a joined query. Will be enhanced when the full OQL support of associative select is implemented. SVN:trunk[326] --- application/displayblock.class.inc.php | 41 +++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 9a961d4a46..4df18c1ddb 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -323,6 +323,44 @@ class DisplayBlock break; + case 'join': + if (!isset($aExtraParams['oql2'])) + { + $sHtml .= $oPage->GetP("parameter oql2 is mandatory."); + } + else + { + $sOql2 = $aExtraParams['oql2']; + $sGroupByField = $aExtraParams['group_by']; + $aExtraParams['menu'] = false; + $oFilter1 = CMDBSearchFilter::FromOQL($sOql2); + $oSet1 = new CMDBObjectSet($oFilter1); + if (!isset($aExtraParams['group_by'])) + { + // No "group by" specified, use the name of the second class + $sGroupByField = MetaModel::GetNameAttributeCode($oFilter1->GetClass()); + } + $aResults = array(); + while($oObj = $oSet1->Fetch()) + { + $aResult[$oObj->Get($sGroupByField)] = array(); + $oSet2 = new CMDBObjectSet($this->m_oFilter, array(), array('oql2' => $oObj->GetKey())); + while($oObj2 = $oSet2->Fetch()) + { + $aResults[$oObj->Get($sGroupByField)][$oObj2->GetKey()] = $oObj2; + } + } + $sHtml .= "\n"; + foreach($aResults as $sCategory => $aObjects) + { + $sHtml .= "\n"; + $oSet = CMDBObjectSet::FromArray($this->m_oFilter->GetClass(), $aObjects); + $sHtml .= "\n"; + } + $sHtml .= "

    $sCategory

    ".cmdbAbstractObject::GetDisplaySet($oPage, $oSet, $aExtraParams)."
    \n"; + } + break; + case 'list': if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) { @@ -426,7 +464,7 @@ class DisplayBlock break; case 'search': - $iSearchSectionId = 1; + static $iSearchSectionId = 1; $sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; $sHtml .= "
    \n"; $oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); @@ -434,6 +472,7 @@ class DisplayBlock $sHtml .= "
    \n"; $sHtml .= "
    \n"; $sHtml .= "
    Search
    \n"; + $iSearchSectionId++; break; case 'pie_chart': From 5d46a94ddf9fd711d40965271b63bc5ed77a722d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 13 Apr 2010 09:42:22 +0000 Subject: [PATCH 244/970] Select multiple objects in OQL (beta, for integration within the UI) SVN:trunk[327] --- core/dbobject.class.php | 19 +- core/dbobjectsearch.class.php | 87 +- core/dbobjectset.class.php | 74 +- core/metamodel.class.php | 5897 +++++++++++++------------ core/oql/oql-parser.php | 1176 ++--- core/oql/oql-parser.y | 23 +- core/oql/oqlinterpreter.class.inc.php | 15 +- core/oql/oqlquery.class.inc.php | 24 +- pages/testlist.inc.php | 19 +- 9 files changed, 3734 insertions(+), 3600 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 7dbc94de96..f272aba729 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -40,11 +40,11 @@ abstract class DBObject private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode // Use the MetaModel::NewObject to build an object (do we have to force it?) - public function __construct($aRow = null) + public function __construct($aRow = null, $sClassAlias = '') { if (!empty($aRow)) { - $this->FromRow($aRow); + $this->FromRow($aRow, $sClassAlias); $this->m_bFullyLoaded = $this->IsFullyLoaded(); return; } @@ -170,8 +170,14 @@ abstract class DBObject $this->m_bFullyLoaded = true; } - protected function FromRow($aRow) + protected function FromRow($aRow, $sClassAlias = '') { + if (strlen($sClassAlias) == 0) + { + // Default to the current class + $sClassAlias = get_class($this); + } + $this->m_iKey = null; $this->m_bIsInDB = true; $this->m_aCurrValues = array(); @@ -180,7 +186,7 @@ abstract class DBObject // Get the key // - $sKeyField = "id"; + $sKeyField = $sClassAlias."id"; if (!array_key_exists($sKeyField, $aRow)) { // #@# Bug ? @@ -211,9 +217,10 @@ abstract class DBObject // then one column will be found with an empty suffix, the others have a suffix // Take care: the function isset will return false in case the value is null, // which is something that could happen on open joins - if (array_key_exists($sAttCode, $aRow)) + $sAttRef = $sClassAlias.$sAttCode; + if (array_key_exists($sAttRef, $aRow)) { - $value = $oAttDef->FromSQLToValue($aRow, $sAttCode); + $value = $oAttDef->FromSQLToValue($aRow, $sAttRef); $this->m_aCurrValues[$sAttCode] = $value; $this->m_aOrigValues[$sAttCode] = $value; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index bec0d57ff4..4c3c998a81 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -43,9 +43,8 @@ define('SIBUSQLTHISREGEXP', "/this\\.(.*)/U"); */ class DBObjectSearch { - private $m_sClass; - private $m_sClassAlias; private $m_aClasses; // queried classes (alias => class name) + private $m_aSelectedClasses; // selected for the output (alias => class name) private $m_oSearchCondition; private $m_aParams; private $m_aFullText; @@ -59,9 +58,10 @@ class DBObjectSearch assert('is_string($sClass)'); assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference // => idee d'un assert avec call stack (autre utilisation = echec sur query SQL) + if (empty($sClassAlias)) $sClassAlias = $sClass; - $this->m_sClass = $sClass; - $this->m_sClassAlias = $sClassAlias; + + $this->m_aSelectedClasses = array($sClassAlias => $sClass); $this->m_aClasses = array($sClassAlias => $sClass); $this->m_oSearchCondition = new TrueExpression; $this->m_aParams = array(); @@ -71,6 +71,38 @@ class DBObjectSearch $this->m_aRelatedTo = array(); } + public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];} + public function GetJoinedClasses() {return $this->m_aClasses;} + + public function GetClass() + { + return reset($this->m_aSelectedClasses); + } + public function GetClassAlias() + { + reset($this->m_aSelectedClasses); + return key($this->m_aSelectedClasses); + } + + public function SetSelectedClasses($aNewSet) + { + $this->m_aSelectedClasses = array(); + foreach ($aNewSet as $sAlias => $sClass) + { + if (!array_key_exists($sAlias, $this->m_aClasses)) + { + throw new CoreException('Unexpected class alias', array('alias'=>$sAlias, 'expected'=>$this->m_aClasses)); + } + $this->m_aSelectedClasses[$sAlias] = $sClass; + } + } + + public function GetSelectedClasses() + { + return $this->m_aSelectedClasses; + } + + public function IsAny() { // #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false; @@ -173,9 +205,9 @@ class DBObjectSearch } if (!empty($sConditionDesc)) { - return "Objects of class '$this->m_sClass', $sConditionDesc"; + return "Objects of class '".$this->GetClass()."', $sConditionDesc"; } - return "Any object of class '$this->m_sClass'"; + return "Any object of class '".$this->GetClass()."'"; } protected function TransferConditionExpression($oFilter, $aTranslation) @@ -190,7 +222,6 @@ class DBObjectSearch { $this->m_oSearchCondition = new TrueExpression(); // ? is that usefull/enough, do I need to rebuild the list after the subqueries ? - // $this->m_aClasses = array($this->m_sClassAlias => $this->m_sClass); } public function AddConditionExpression($oExpression) @@ -205,8 +236,8 @@ class DBObjectSearch // #@# todo - obsolete smoothly, first send exceptions // throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL())); - MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->m_sClass)); - $oFilterDef = MetaModel::GetClassFilterDef($this->m_sClass, $sFilterCode); + MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass())); + $oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode); if (empty($sOpCode)) { @@ -216,7 +247,7 @@ class DBObjectSearch // Preserve backward compatibility - quick n'dirty way to change that API semantic // - $oField = new FieldExpression($sFilterCode, $this->m_sClassAlias); + $oField = new FieldExpression($sFilterCode, $this->GetClassAlias()); switch($sOpCode) { case 'SameDay': @@ -286,14 +317,20 @@ class DBObjectSearch protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation) { - $sOrigAlias = $this->m_sClassAlias; + $sOrigAlias = $this->GetClassAlias(); if (array_key_exists($sOrigAlias, $aClassAliases)) { - $this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->m_sClass); + $sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetClass()); + $this->m_aSelectedClasses[$sNewAlias] = $this->GetClass(); + unset($sOrigAlias, $this->m_aSelectedClasses[$sNewAlias]); + // Translate the condition expression with the new alias - $aAliasTranslation[$sOrigAlias]['*'] = $this->m_sClassAlias; + $aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias; } + // add the alias into the filter aliases list + $aClassAliases[$this->GetClassAlias()] = $this->GetClass(); + foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter) { $oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation); @@ -429,12 +466,6 @@ class DBObjectSearch } } - - public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];} - public function GetClasses() {return $this->m_aClasses;} - - public function GetClass() {return $this->m_sClass;} - public function GetClassAlias() {return $this->m_sClassAlias;} public function GetCriteria() {return $this->m_oSearchCondition;} public function GetCriteria_FullText() {return $this->m_aFullText;} public function GetCriteria_PointingTo($sKeyAttCode = "") @@ -636,7 +667,10 @@ class DBObjectSearch $bRetrofitParams = true; } - $sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias(); + $sSelectedClasses = implode(', ', array_keys($this->m_aSelectedClasses)); + $sRes = 'SELECT '.$sSelectedClasses.' FROM'; + + $sRes .= ' '.$this->GetClass().' AS '.$this->GetClassAlias(); $sRes .= $this->ToOQL_Joins(); $sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams); @@ -912,6 +946,19 @@ class DBObjectSearch } } + // Check and prepare the select information + $aSelected = array(); + foreach ($oOqlQuery->GetSelectedClasses() as $oClassDetails) + { + $sClassToSelect = $oClassDetails->GetValue(); + if (!array_key_exists($sClassToSelect, $aAliases)) + { + throw new OqlNormalizeException('Unknown class [alias]', $sQuery, $oClassDetails, array_keys($aAliases)); + } + $aSelected[$sClassToSelect] = $aAliases[$sClassToSelect]; + } + $oResultFilter->SetSelectedClasses($aSelected); + $oConditionTree = $oOqlQuery->GetCondition(); if ($oConditionTree instanceof Expression) { diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index f4ab4aff1b..903663afdd 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -28,7 +28,7 @@ class DBObjectSet $this->m_aArgs = $aArgs; $this->m_bLoaded = false; - $this->m_aData = array(); + $this->m_aData = array(); // array of (row => array of (classalias) => object) $this->m_aId2Row = array(); $this->m_iCurrRow = 0; } @@ -143,6 +143,11 @@ class DBObjectSet return $this->m_oFilter->GetClass(); } + public function GetSelectedClasses() + { + return $this->m_oFilter->GetSelectedClasses(); + } + public function GetRootClass() { return MetaModel::GetRootClass($this->GetClass()); @@ -156,11 +161,16 @@ class DBObjectSet $resQuery = CMDBSource::Query($sSQL); if (!$resQuery) return; + $sClass = $this->m_oFilter->GetClass(); while ($aRow = CMDBSource::FetchArray($resQuery)) { - $sClass = $this->m_oFilter->GetClass(); - $oObject = MetaModel::GetObjectByRow($sClass, $aRow); - $this->AddObject($oObject); + $aObjects = array(); + foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass) + { + $oObject = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias); + $aObjects[$sClassAlias] = $oObject; + } + $this->AddObjectExtended($aObjects); } CMDBSource::FreeResult($resQuery); @@ -173,7 +183,7 @@ class DBObjectSet return count($this->m_aData); } - public function Fetch() + public function Fetch($sClassAlias = '') { if (!$this->m_bLoaded) $this->Load(); @@ -181,11 +191,32 @@ class DBObjectSet { return null; } - $oRetObj = $this->m_aData[$this->m_iCurrRow]; + + if (strlen($sClassAlias) == 0) + { + $sClassAlias = $this->m_oFilter->GetClassAlias(); + } + $oRetObj = $this->m_aData[$this->m_iCurrRow][$sClassAlias]; $this->m_iCurrRow++; return $oRetObj; } + // Return the whole line if several classes have been specified in the query + // + public function FetchAssoc() + { + if (!$this->m_bLoaded) $this->Load(); + + if ($this->m_iCurrRow >= count($this->m_aData)) + { + return null; + } + + $aRetObjects = $this->m_aData[$this->m_iCurrRow]; + $this->m_iCurrRow++; + return $aRetObjects; + } + public function Rewind() { $this->Seek(0); @@ -199,25 +230,36 @@ class DBObjectSet return $this->m_iCurrRow; } - public function AddObject($oObject) + public function AddObject($oObject, $sClassAlias = '') { - // ?usefull? if ($oObject->GetClass() != $this->GetClass()) return; + $sObjClass = get_class($oObject); + if (strlen($sClassAlias) == 0) + { + $sClassAlias = $sObjClass; + } - // it is mandatory to avoid duplicates - if (array_key_exists($oObject->GetKey(), $this->m_aId2Row)) return; - - // Do not load here, because the load uses that method too $iNextPos = count($this->m_aData); - $this->m_aData[$iNextPos] = $oObject; - $this->m_aId2Row[$oObject->GetKey()] = $iNextPos; + $this->m_aData[$iNextPos][$sClassAlias] = $oObject; + $this->m_aId2Row[$sObjClass][$oObject->GetKey()] = $iNextPos; } - public function AddObjectArray($aObjects) + protected function AddObjectExtended($aObjectArray) + { + $iNextPos = count($this->m_aData); + + foreach ($aObjectArray as $sClassAlias => $oObject) + { + $this->m_aData[$iNextPos][$sClassAlias] = $oObject; + $this->m_aId2Row[get_class($oObject)][$oObject->GetKey()] = $iNextPos; + } + } + + public function AddObjectArray($aObjects, $sClassAlias = '') { // #@# todo - add a check on the object class ? foreach ($aObjects as $oObj) { - $this->AddObject($oObj); + $this->AddObject($oObj, $sClassAlias); } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a1ec24097e..672662d540 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1,2945 +1,2952 @@ - - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -abstract class MetaModel -{ - /////////////////////////////////////////////////////////////////////////// - // - // STATIC Members - // - /////////////////////////////////////////////////////////////////////////// - - // Purpose: workaround the following limitation = PHP5 does not allow to know the class (derived from the current one) - // from which a static function is called (__CLASS__ and self are interpreted during parsing) - private static function GetCallersPHPClass($sExpectedFunctionName = null) - { - //var_dump(debug_backtrace()); - $aBacktrace = debug_backtrace(); - // $aBacktrace[0] is where we are - // $aBacktrace[1] is the caller of GetCallersPHPClass - // $aBacktrace[1] is the info we want - if (!empty($sExpectedFunctionName)) - { - assert('$aBacktrace[2]["function"] == $sExpectedFunctionName'); - } - return $aBacktrace[2]["class"]; - } - - // Static init -why and how it works - // - // We found the following limitations: - //- it is not possible to define non scalar constants - //- it is not possible to declare a static variable as '= new myclass()' - // Then we had do propose this model, in which a derived (non abstract) - // class should implement Init(), to call InheritAttributes or AddAttribute. - - private static function _check_subclass($sClass) - { - // See also IsValidClass()... ???? #@# - // class is mandatory - // (it is not possible to guess it when called as myderived::...) - if (!array_key_exists($sClass, self::$m_aClassParams)) - { - throw new CoreException("Unknown class '$sClass', expected a value in {".implode(', ', array_keys(self::$m_aClassParams))."}"); - } - } - - public static function static_var_dump() - { - var_dump(get_class_vars(__CLASS__)); - } - - private static $m_bDebugQuery = false; - private static $m_iStackDepthRef = 0; - - public static function StartDebugQuery() - { - $aBacktrace = debug_backtrace(); - self::$m_iStackDepthRef = count($aBacktrace); - self::$m_bDebugQuery = true; - } - public static function StopDebugQuery() - { - self::$m_bDebugQuery = false; - } - public static function DbgTrace($value) - { - if (!self::$m_bDebugQuery) return; - $aBacktrace = debug_backtrace(); - $iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery; - $sIndent = ""; - for ($i = 0 ; $i < $iCallStackPos ; $i++) - { - $sIndent .= " .-=^=-. "; - } - $aCallers = array(); - foreach($aBacktrace as $aStackInfo) - { - $aCallers[] = $aStackInfo["function"]; - } - $sCallers = "Callstack: ".implode(', ', $aCallers); - $sFunction = "".$aBacktrace[1]["function"].""; - - if (is_string($value)) - { - echo "$sIndent$sFunction: $value
    \n"; - } - else if (is_object($value)) - { - echo "$sIndent$sFunction:\n
    \n";
    -			print_r($value);
    -			echo "
    \n"; - } - else - { - echo "$sIndent$sFunction: $value
    \n"; - } - } - - private static $m_bTraceQueries = true; - private static $m_aQueriesLog = array(); - - - private static $m_sDBName = ""; - private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB) - private static $m_Category2Class = array(); - private static $m_aRootClasses = array(); // array of "classname" => "rootclass" - private static $m_aParentClasses = array(); // array of ("classname" => array of "parentclass") - private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass") - - private static $m_aClassParams = array(); // array of ("classname" => array of class information) - - static public function GetParentPersistentClass($sRefClass) - { - $sClass = get_parent_class($sRefClass); - if (!$sClass) return ''; - - if ($sClass == 'DBObject') return ''; // Warning: __CLASS__ is lower case in my version of PHP - - // Note: the UI/business model may implement pure PHP classes (intermediate layers) - if (array_key_exists($sClass, self::$m_aClassParams)) - { - return $sClass; - } - return self::GetParentPersistentClass($sClass); - } - - final static public function GetName($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["name"]; - } - final static public function GetCategory($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["category"]; - } - final static public function HasCategory($sClass, $sCategory) - { - self::_check_subclass($sClass); - return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); - } - final static public function GetClassDescription($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["description"]; - } - final static public function IsAutoIncrementKey($sClass) - { - self::_check_subclass($sClass); - return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement"); - } - final static public function GetKeyLabel($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["key_label"]; - } - final static public function GetNameAttributeCode($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["name_attcode"]; - } - final static public function GetStateAttributeCode($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["state_attcode"]; - } - final static public function GetDefaultState($sClass) - { - $sDefaultState = ''; - $sStateAttrCode = self::GetStateAttributeCode($sClass); - if (!empty($sStateAttrCode)) - { - $oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode); - $sDefaultState = $oStateAttrDef->GetDefaultValue(); - } - return $sDefaultState; - } - final static public function GetReconcKeys($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["reconc_keys"]; - } - final static public function GetDisplayTemplate($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["display_template"]; - } - final static public function GetAttributeOrigin($sClass, $sAttCode) - { - self::_check_subclass($sClass); - return self::$m_aAttribOrigins[$sClass][$sAttCode]; - } - final static public function GetPrequisiteAttributes($sClass, $sAttCode) - { - self::_check_subclass($sClass); - $oAtt = self::GetAttributeDef($sClass, $sAttCode); - // Temporary implementation: later, we might be able to compute - // the dependencies, based on the attributes definition - // (allowed values and default values) - if ($oAtt->IsWritable()) - { - return $oAtt->GetPrerequisiteAttributes(); - } - else - { - return array(); - } - } - // #@# restore to private ? - final static public function DBGetTable($sClass, $sAttCode = null) - { - self::_check_subclass($sClass); - if (empty($sAttCode) || ($sAttCode == "id")) - { - $sTableRaw = self::$m_aClassParams[$sClass]["db_table"]; - if (empty($sTableRaw)) - { - // return an empty string whenever the table is undefined, meaning that there is no table associated to this 'abstract' class - return ''; - } - else - { - return self::$m_sTablePrefix.$sTableRaw; - } - } - // This attribute has been inherited (compound objects) - return self::DBGetTable(self::$m_aAttribOrigins[$sClass][$sAttCode]); - } - - final static protected function DBEnumTables() - { - // This API do not rely on our capability to query the DB and retrieve - // the list of existing tables - // Rather, it uses the list of expected tables, corresponding to the data model - $aTables = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - $sTable = self::DBGetTable($sClass); - - // Could be completed later with all the classes that are using a given table - if (!array_key_exists($sTable, $aTables)) - { - $aTables[$sTable] = array(); - } - $aTables[$sTable][] = $sClass; - } - return $aTables; - } - - final static public function DBGetKey($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["db_key_field"]; - } - final static public function DBGetClassField($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["db_finalclass_field"]; - } - final static public function HasFinalClassField($sClass) - { - self::_check_subclass($sClass); - if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false; - return (self::$m_aClassParams[$sClass]["db_finalclass_field"]); - } - final static public function IsStandaloneClass($sClass) - { - self::_check_subclass($sClass); - - $sRootClass = self::GetRootClass($sClass); - return (!self::HasFinalClassField($sRootClass)); - } - final static public function IsParentClass($sParentClass, $sChildClass) - { - self::_check_subclass($sChildClass); - self::_check_subclass($sParentClass); - if (in_array($sParentClass, self::$m_aParentClasses[$sChildClass])) return true; - if ($sChildClass == $sParentClass) return true; - return false; - } - final static public function IsSameFamilyBranch($sClassA, $sClassB) - { - self::_check_subclass($sClassA); - self::_check_subclass($sClassB); - if (in_array($sClassA, self::$m_aParentClasses[$sClassB])) return true; - if (in_array($sClassB, self::$m_aParentClasses[$sClassA])) return true; - if ($sClassA == $sClassB) return true; - return false; - } - final static public function IsSameFamily($sClassA, $sClassB) - { - self::_check_subclass($sClassA); - self::_check_subclass($sClassB); - return (self::GetRootClass($sClassA) == self::GetRootClass($sClassB)); - } - - // Attributes of a given class may contain attributes defined in a parent class - // - Some attributes are a copy of the definition - // - Some attributes correspond to the upper class table definition (compound objects) - // (see also filters definition) - private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes) - private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) - private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field"))) - final static public function ListAttributeDefs($sClass) - { - self::_check_subclass($sClass); - return self::$m_aAttribDefs[$sClass]; - } - - final public static function GetAttributesList($sClass) - { - self::_check_subclass($sClass); - return array_keys(self::$m_aAttribDefs[$sClass]); - } - - final public static function GetFiltersList($sClass) - { - self::_check_subclass($sClass); - return array_keys(self::$m_aFilterDefs[$sClass]); - } - - final public static function GetKeysList($sClass) - { - self::_check_subclass($sClass); - $aExtKeys = array(); - foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) - { - if ($oAttDef->IsExternalKey()) - { - $aExtKeys[] = $sAttCode; - } - } - return $aExtKeys; - } - - final static public function IsValidKeyAttCode($sClass, $sAttCode) - { - if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; - if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return false; - return (self::$m_aAttribDefs[$sClass][$sAttCode]->IsExternalKey()); - } - final static public function IsValidAttCode($sClass, $sAttCode) - { - if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; - return (array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])); - } - final static public function IsAttributeOrigin($sClass, $sAttCode) - { - return (self::$m_aAttribOrigins[$sClass][$sAttCode] == $sClass); - } - - final static public function IsValidFilterCode($sClass, $sFilterCode) - { - if (!array_key_exists($sClass, self::$m_aFilterDefs)) return false; - return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])); - } - public static function IsValidClass($sClass) - { - return (array_key_exists($sClass, self::$m_aAttribDefs)); - } - - public static function IsValidObject($oObject) - { - if (!is_object($oObject)) return false; - return (self::IsValidClass(get_class($oObject))); - } - - public static function IsReconcKey($sClass, $sAttCode) - { - return (in_array($sAttCode, self::GetReconcKeys($sClass))); - } - - final static public function GetAttributeDef($sClass, $sAttCode) - { - self::_check_subclass($sClass); - return self::$m_aAttribDefs[$sClass][$sAttCode]; - } - - final static public function GetExternalKeys($sClass) - { - $aExtKeys = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) - { - if ($oAtt->IsExternalKey()) - { - $aExtKeys[$sAttCode] = $oAtt; - } - } - return $aExtKeys; - } - - final static public function GetLinkedSets($sClass) - { - $aLinkedSets = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) - { - if (is_subclass_of($oAtt, 'AttributeLinkedSet')) - { - $aLinkedSets[$sAttCode] = $oAtt; - } - } - return $aLinkedSets; - } - - final static public function GetExternalFields($sClass, $sKeyAttCode) - { - $aExtFields = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) - { - if ($oAtt->IsExternalField() && ($oAtt->GetKeyAttCode() == $sKeyAttCode)) - { - $aExtFields[] = $oAtt; - } - } - return $aExtFields; - } - - final static public function GetExtKeyFriends($sClass, $sExtKeyAttCode) - { - if (array_key_exists($sExtKeyAttCode, self::$m_aExtKeyFriends[$sClass])) - { - return self::$m_aExtKeyFriends[$sClass][$sExtKeyAttCode]; - } - else - { - return array(); - } - } - - public static function GetLabel($sClass, $sAttCode) - { - $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef) return $oAttDef->GetLabel(); - return ""; - } - - public static function GetDescription($sClass, $sAttCode) - { - $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef) return $oAttDef->GetDescription(); - return ""; - } - - // Filters of a given class may contain filters defined in a parent class - // - Some filters are a copy of the definition - // - Some filters correspond to the upper class table definition (compound objects) - // (see also attributes definition) - private static $m_aFilterDefs = array(); // array of ("classname" => array filterdef) - private static $m_aFilterOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) - - public static function GetClassFilterDefs($sClass) - { - self::_check_subclass($sClass); - return self::$m_aFilterDefs[$sClass]; - } - - final static public function GetClassFilterDef($sClass, $sFilterCode) - { - self::_check_subclass($sClass); - return self::$m_aFilterDefs[$sClass][$sFilterCode]; - } - - public static function GetFilterLabel($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetLabel(); - return ""; - } - - public static function GetFilterDescription($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetDescription(); - return ""; - } - - // returns an array of opcode=>oplabel (e.g. "differs from") - public static function GetFilterOperators($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetOperators(); - return array(); - } - - // returns an opcode - public static function GetFilterLooseOperator($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetLooseOperator(); - return array(); - } - - public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetOpDescription($sOpCode); - return ""; - } - - public static function GetFilterHTMLInput($sFilterCode) - { - return ""; - } - - // Lists of attributes/search filters - // - private static $m_aListInfos = array(); // array of ("listcode" => various info on the list, common to every classes) - private static $m_aListData = array(); // array of ("classname" => array of "listcode" => list) - // list may be an array of attcode / fltcode - // list may be an array of "groupname" => (array of attcode / fltcode) - - public static function EnumZLists() - { - return array_keys(self::$m_aListInfos); - } - - final static public function GetZListInfo($sListCode) - { - return self::$m_aListInfos[$sListCode]; - } - - public static function GetZListItems($sClass, $sListCode) - { - if (array_key_exists($sClass, self::$m_aListData)) - { - if (array_key_exists($sListCode, self::$m_aListData[$sClass])) - { - return self::$m_aListData[$sClass][$sListCode]; - } - } - $sParentClass = self::GetParentPersistentClass($sClass); - if (empty($sParentClass)) return array(); // nothing for the mother of all classes - // Dig recursively - return self::GetZListItems($sParentClass, $sListCode); - } - - public static function IsAttributeInZList($sClass, $sListCode, $sAttCodeOrFltCode, $sGroup = null) - { - $aZList = self::GetZListItems($sClass, $sListCode); - if (!$sGroup) - { - return (in_array($sAttCodeOrFltCode, $aZList)); - } - return (in_array($sAttCodeOrFltCode, $aZList[$sGroup])); - } - - // - // Relations - // - private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes) - - public static function EnumRelations() - { - return array_keys(self::$m_aRelationInfos); - } - - public static function EnumRelationProperties($sRelCode) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - return self::$m_aRelationInfos[$sRelCode]; - } - - final static public function GetRelationProperty($sRelCode, $sProperty) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]); - - return self::$m_aRelationInfos[$sRelCode][$sProperty]; - } - - public static function EnumRelationQueries($sClass, $sRelCode) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); - } - - // - // Object lifecycle model - // - private static $m_aStates = array(); // array of ("classname" => array of "statecode"=>array('label'=>..., 'description'=>..., attribute_inherit=> attribute_list=>...)) - private static $m_aStimuli = array(); // array of ("classname" => array of ("stimuluscode"=>array('label'=>..., 'description'=>...))) - private static $m_aTransitions = array(); // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD))) - - public static function EnumStates($sClass) - { - if (array_key_exists($sClass, self::$m_aStates)) - { - return self::$m_aStates[$sClass]; - } - else - { - return array(); - } - } - - public static function EnumStimuli($sClass) - { - if (array_key_exists($sClass, self::$m_aStimuli)) - { - return self::$m_aStimuli[$sClass]; - } - else - { - return array(); - } - } - - public static function EnumTransitions($sClass, $sStateCode) - { - if (array_key_exists($sClass, self::$m_aTransitions)) - { - if (array_key_exists($sStateCode, self::$m_aTransitions[$sClass])) - { - return self::$m_aTransitions[$sClass][$sStateCode]; - } - } - return array(); - } - public static function GetAttributeFlags($sClass, $sState, $sAttCode) - { - $iFlags = 0; // By default (if no life cycle) no flag at all - $sStateAttCode = self::GetStateAttributeCode($sClass); - if (!empty($sStateAttCode)) - { - $aStates = MetaModel::EnumStates($sClass); - if (!array_key_exists($sState, $aStates)) - { - throw new CoreException("Invalid state '$sState' for class '$sClass', expecting a value in {".implode(', ', array_keys($aStates))."}"); - } - $aCurrentState = $aStates[$sState]; - if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) ) - { - $iFlags = $aCurrentState['attribute_list'][$sAttCode]; - } - } - return $iFlags; - } - - // - // Allowed values - // - - public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') - { - $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); - } - - public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') - { - $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); - return $oFltDef->GetAllowedValues($aArgs, $sBeginsWith); - } - - // - // Businezz model declaration verbs (should be static) - // - - public static function RegisterZList($sListCode, $aListInfo) - { - // Check mandatory params - $aMandatParams = array( - "description" => "detailed (though one line) description of the list", - "type" => "attributes | filters", - ); - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aListInfo)) - { - throw new CoreException("Declaration of list $sListCode - missing parameter $sParamName"); - } - } - - self::$m_aListInfos[$sListCode] = $aListInfo; - } - - public static function RegisterRelation($sRelCode, $aRelationInfo) - { - // Check mandatory params - $aMandatParams = array( - "description" => "detailed (though one line) description of the list", - "verb_down" => "e.g.: 'impacts'", - "verb_up" => "e.g.: 'is impacted by'", - ); - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aRelationInfo)) - { - throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName"); - } - } - - self::$m_aRelationInfos[$sRelCode] = $aRelationInfo; - } - - // Must be called once and only once... - public static function InitClasses($sTablePrefix) - { - if (count(self::GetClasses()) > 0) - { - throw new CoreException("InitClasses should not be called more than once -skipped"); - return; - } - - self::$m_sTablePrefix = $sTablePrefix; - - foreach(get_declared_classes() as $sPHPClass) { - if (is_subclass_of($sPHPClass, 'DBObject')) - { - if (method_exists($sPHPClass, 'Init')) - { - call_user_func(array($sPHPClass, 'Init')); - } - } - } - foreach (self::GetClasses() as $sClass) - { - // Compute the fields that will be used to display a pointer to another object - // - self::$m_aExtKeyFriends[$sClass] = array(); - foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) - { - if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) - { - // oAttDef is either - // - an external KEY / FIELD (direct), - // - an external field pointing to an external KEY / FIELD - // - an external field pointing to an external field pointing to.... - - // Get the real external key attribute - // It will be our reference to determine the other ext fields related to the same ext key - $oFinalKeyAttDef = $oAttDef->GetKeyAttDef(EXTKEY_ABSOLUTE); - - self::$m_aExtKeyFriends[$sClass][$sAttCode] = array(); - foreach (self::GetExternalFields($sClass, $oAttDef->GetKeyAttCode($sAttCode)) as $oExtField) - { - // skip : those extfields will be processed as external keys - if ($oExtField->IsExternalKey(EXTKEY_ABSOLUTE)) continue; - - // Note: I could not compare the objects by the mean of '===' - // because they are copied for the inheritance, and the internal references are NOT updated - if ($oExtField->GetKeyAttDef(EXTKEY_ABSOLUTE) == $oFinalKeyAttDef) - { - self::$m_aExtKeyFriends[$sClass][$sAttCode][$oExtField->GetCode()] = $oExtField; - } - } - } - } - - // Add a 'id' filter - // - if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code"); - } - if (array_key_exists('id', self::$m_aFilterDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code"); - } - $oFilter = new FilterPrivateKey('id', array('id_field' => self::DBGetKey($sClass))); - self::$m_aFilterDefs[$sClass]['id'] = $oFilter; - self::$m_aFilterOrigins[$sClass]['id'] = $sClass; - - // Add a 'class' attribute/filter to the root classes and their children - // - if (!self::IsStandaloneClass($sClass)) - { - if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); - } - if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); - } - $sClassAttCode = 'finalclass'; - $sRootClass = self::GetRootClass($sClass); - $sDbFinalClassField = self::DBGetClassField($sRootClass); - $oClassAtt = new AttributeClass($sClassAttCode, array( - "label"=>"Class", - "description"=>"Real (final) object class", - "class_category"=>null, - "more_values"=>'', - "sql"=>$sDbFinalClassField, - "default_value"=>$sClass, - "is_null_allowed"=>false, - "depends_on"=>array() - )); - self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt; - self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass; - - $oClassFlt = new FilterFromAttribute($oClassAtt); - self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; - self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); - } - - // Define defaults values for the standard ZLists - // - //foreach (self::$m_aListInfos as $sListCode => $aListConfig) - //{ - // if (!isset(self::$m_aListData[$sClass][$sListCode])) - // { - // $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); - // self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; - // //echo "

    $sClass: $sListCode (".count($aAllAttributes)." attributes)

    \n"; - // } - //} - } - - } - - // To be overriden, must be called for any object class (optimization) - public static function Init() - { - // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) - } - // To be overloaded by biz model declarations - public static function GetRelationQueries($sRelCode) - { - // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) - return array(); - } - - public static function Init_Params($aParams) - { - // Check mandatory params - $aMandatParams = array( - "category" => "group classes by modules defining their visibility in the UI", - "name" => "internal class name, may be different than the PHP class name", - "description" => "detailed (though one line) description of the class", - "key_type" => "autoincrement | string", - "key_label" => "if set, then display the key as an attribute", - "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", - "state_attcode" => "define wich attribute is representing the state (object lifecycle)", - "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", - "db_table" => "database table", - "db_key_field" => "database field which is the key", - "db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class", - ); - - $sClass = self::GetCallersPHPClass("Init"); - if (!array_key_exists("name", $aParams)) - { - throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})"); - } - - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aParams)) - { - throw new CoreException("Declaration of class $sClass - missing parameter $sParamName"); - } - } - - $aCategories = explode(',', $aParams['category']); - foreach ($aCategories as $sCategory) - { - self::$m_Category2Class[$sCategory][] = $sClass; - } - self::$m_Category2Class[''][] = $sClass; // all categories, include this one - - - self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance - self::$m_aParentClasses[$sClass] = array(); - self::$m_aChildClasses[$sClass] = array(); - - self::$m_aClassParams[$sClass]= $aParams; - - self::$m_aAttribDefs[$sClass] = array(); - self::$m_aAttribOrigins[$sClass] = array(); - self::$m_aExtKeyFriends[$sClass] = array(); - self::$m_aFilterDefs[$sClass] = array(); - self::$m_aFilterOrigins[$sClass] = array(); - } - - protected static function object_array_mergeclone($aSource1, $aSource2) - { - $aRes = array(); - foreach ($aSource1 as $key=>$object) - { - $aRes[$key] = clone $object; - } - foreach ($aSource2 as $key=>$object) - { - $aRes[$key] = clone $object; - } - return $aRes; - } - - public static function Init_InheritAttributes($sSourceClass = null) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { - // Default: inherit from parent class - $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) return; // no attributes for the mother of all classes - } - if (isset(self::$m_aAttribDefs[$sSourceClass])) - { - if (!isset(self::$m_aAttribDefs[$sTargetClass])) - { - self::$m_aAttribDefs[$sTargetClass] = array(); - self::$m_aAttribOrigins[$sTargetClass] = array(); - } - self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); - self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); - } - // later on, we might consider inheritance in different ways !!! - //if (strlen(self::DBGetTable($sSourceClass)) != 0) - if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass])) - { - // Inherit the root class - self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; - } - else - { - // I am a root class, standalone as well ! - // ???? - //self::$m_aRootClasses[$sTargetClass] = $sTargetClass; - } - self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass]; - self::$m_aParentClasses[$sTargetClass][] = $sSourceClass; - // I am the child of each and every parent... - foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass) - { - self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass; - } - } - public static function Init_OverloadAttributeParams($sAttCode, $aParams) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - - if (!self::IsValidAttCode($sTargetClass, $sAttCode)) - { - throw new CoreException("Could not overload '$sAttCode', expecting a code from {".implode(", ", self::GetAttributesList($sTargetClass))."}"); - } - self::$m_aAttribDefs[$sTargetClass][$sAttCode]->OverloadParams($aParams); - } - public static function Init_AddAttribute(AttributeDefinition $oAtt) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; - self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; - // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used - - // Specific case of external fields: - // I wanted to simplify the syntax of the declaration of objects in the biz model - // Therefore, the reference to the host class is set there - $oAtt->SetHostClass($sTargetClass); - } - - public static function Init_InheritFilters($sSourceClass = null) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { - // Default: inherit from parent class - $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) return; // no filters for the mother of all classes - } - if (isset(self::$m_aFilterDefs[$sSourceClass])) - { - if (!isset(self::$m_aFilterDefs[$sTargetClass])) - { - self::$m_aFilterDefs[$sTargetClass] = array(); - self::$m_aFilterOrigins[$sTargetClass] = array(); - } - - foreach (self::$m_aFilterDefs[$sSourceClass] as $sFltCode=>$oFilter) - { - if ($oFilter instanceof FilterFromAttribute) - { - // In that case, cloning is not enough: - // we must ensure that we will point to the correct - // attribute definition (in case some properties are overloaded) - $oAttDef1 = $oFilter->__GetRefAttribute(); - $oAttDef2 = self::GetAttributeDef($sTargetClass, $oAttDef1->GetCode()); - $oNewFilter = new FilterFromAttribute($oAttDef2); - } - else - { - $oNewFilter = clone $oFilter; - } - self::$m_aFilterDefs[$sTargetClass][$sFltCode] = $oNewFilter; - } - - self::$m_aFilterOrigins[$sTargetClass] = array_merge(self::$m_aFilterOrigins[$sTargetClass], self::$m_aFilterOrigins[$sSourceClass]); - } - } - - public static function Init_OverloadFilterParams($sFltCode, $aParams) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - - if (!self::IsValidFilterCode($sTargetClass, $sFltCode)) - { - throw new CoreException("Could not overload '$sFltCode', expecting a code from {".implode(", ", self::GetFiltersList($sTargetClass))."}"); - } - self::$m_aFilterDefs[$sTargetClass][$sFltCode]->OverloadParams($aParams); - } - - public static function Init_AddFilter(FilterDefinition $oFilter) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aFilterDefs[$sTargetClass][$oFilter->GetCode()] = $oFilter; - self::$m_aFilterOrigins[$sTargetClass][$oFilter->GetCode()] = $sTargetClass; - // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used - } - public static function Init_AddFilterFromAttribute($sAttCode) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - - $oAttDef = self::GetAttributeDef($sTargetClass, $sAttCode); - - $sFilterCode = $sAttCode; - $oNewFilter = new FilterFromAttribute($oAttDef); - self::$m_aFilterDefs[$sTargetClass][$sFilterCode] = $oNewFilter; - - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $oKeyDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); - self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $oKeyDef->GetTargetClass(); - } - else - { - self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $sTargetClass; - } - // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used - } - - public static function Init_SetZListItems($sListCode, $aItems) - { - MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); - - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aListData[$sTargetClass][$sListCode] = $aItems; - } - - public static function Init_DefineState($sStateCode, $aStateDef) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); - - $sParentState = $aStateDef['attribute_inherit']; - if (!empty($sParentState)) - { - // Inherit from the given state (must be defined !) - $aToInherit = self::$m_aStates[$sTargetClass][$sParentState]; - // The inherited configuration could be overriden - $aStateDef['attribute_list'] = array_merge($aToInherit, $aStateDef['attribute_list']); - } - self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef; - - // by default, create an empty set of transitions associated to that state - self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); - } - - public static function Init_DefineStimulus($sStimulusCode, $oStimulus) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; - } - - public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); - self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef; - } - - public static function Init_InheritLifecycle($sSourceClass = '') - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { - // Default: inherit from parent class - $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) return; // no attributes for the mother of all classes - } - - self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"]; - self::$m_aStates[$sTargetClass] = clone self::$m_aStates[$sSourceClass]; - self::$m_aStimuli[$sTargetClass] = clone self::$m_aStimuli[$sSourceClass]; - self::$m_aTransitions[$sTargetClass] = clone self::$m_aTransitions[$sSourceClass]; - } - - // - // Static API - // - - public static function GetRootClass($sClass = null) - { - self::_check_subclass($sClass); - return self::$m_aRootClasses[$sClass]; - } - public static function IsRootClass($sClass) - { - self::_check_subclass($sClass); - return (self::GetRootClass($sClass) == $sClass); - } - public static function EnumParentClasses($sClass) - { - self::_check_subclass($sClass); - return self::$m_aParentClasses[$sClass]; - } - public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) - { - self::_check_subclass($sClass); - - $aRes = self::$m_aChildClasses[$sClass]; - if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP) - { - // Add it to the list - $aRes[] = $sClass; - } - return $aRes; - } - - public static function EnumCategories() - { - return array_keys(self::$m_Category2Class); - } - - // Note: use EnumChildClasses to take the compound objects into account - public static function GetSubclasses($sClass) - { - self::_check_subclass($sClass); - $aSubClasses = array(); - foreach(get_declared_classes() as $sSubClass) { - if (is_subclass_of($sSubClass, $sClass)) - { - $aSubClasses[] = $sSubClass; - } - } - return $aSubClasses; - } - public static function GetClasses($sCategory = '') - { - if (array_key_exists($sCategory, self::$m_Category2Class)) - { - return self::$m_Category2Class[$sCategory]; - } - - if (count(self::$m_Category2Class) > 0) - { - throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); - } - return array(); - } - - public static function IsAbstract($sClass) - { - if (strlen(self::DBGetTable($sClass)) == 0) return true; - return false; - } - - protected static $m_aQueryStructCache = array(); - - protected static function PrepareQueryArguments($aArgs) - { - // Translate any object into scalars - // - $aScalarArgs = array(); - foreach($aArgs as $sArgName => $value) - { - if (self::IsValidObject($value)) - { - $aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName)); - } - else - { - $aScalarArgs[$sArgName] = (string) $value; - } - } - // Add standard contextual arguments - // - $aScalarArgs['current_contact_id'] = UserRights::GetContactId(); - - return $aScalarArgs; - } - - public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) - { - // Query caching - // - $bQueryCacheEnabled = true; - $aParams = array(); - $sOqlQuery = $oFilter->ToOql($aParams); // Render with arguments in clear - if ($bQueryCacheEnabled) - { - if (array_key_exists($sOqlQuery, self::$m_aQueryStructCache)) - { - // hit! - $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; - } - } - - if (!isset($oSelect)) - { - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); - - self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; - } - - // Check the order by specification - // - foreach ($aOrderBy as $sFieldAlias => $bAscending) - { - MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); - if (!is_bool($bAscending)) - { - throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); - } - } - if (empty($aOrderBy)) - { - $sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass()); - if (!empty($sNameAttCode)) - { - // By default, simply order on the "name" attribute, ascending - $aOrderBy = array($sNameAttCode => true); - } - } - - // Go - // - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - - try - { - $sRes = $oSelect->RenderSelect($aOrderBy, $aScalarArgs); - } - catch (MissingQueryArgument $e) - { - // Add some information... - $e->addInfo('OQL', $sOqlQuery); - throw $e; - } - - if (self::$m_bTraceQueries) - { - $aParams = array(); - if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog)) - { - self::$m_aQueriesLog[$sOqlQuery] = array( - 'sql' => array(), - 'count' => 0, - ); - } - self::$m_aQueriesLog[$sOqlQuery]['count']++; - self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes; - } - - return $sRes; - } - - public static function ShowQueryTrace() - { - $iTotal = 0; - foreach (self::$m_aQueriesLog as $sOql => $aQueryData) - { - echo "

    $sOql

    \n"; - $iTotal += $aQueryData['count']; - echo '

    '.$aQueryData['count'].'

    '; - echo '

    Example: '.$aQueryData['sql'][0].'

    '; - } - echo "

    Total

    \n"; - echo "

    Count of executed queries: $iTotal

    "; - echo "

    Count of built queries: ".count(self::$m_aQueriesLog)."

    "; - } - - public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) - { - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - return $oSelect->RenderDelete($aScalarArgs); - } - - public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) - { - // $aValues is an array of $sAttCode => $value - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues); - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - return $oSelect->RenderUpdate($aScalarArgs); - } - - private static function MakeQuery($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array()) - { - // Note: query class might be different than the class of the filter - // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) - // $aExpectedAtts is an array of sAttCode=>sAlias - $sClass = $oFilter->GetClass(); - $sClassAlias = $oFilter->GetClassAlias(); - - $bIsOnQueriedClass = ($sClassAlias == $sGlobalTargetAlias); - if ($bIsOnQueriedClass) - { - $aClassAliases = array_merge($aClassAliases, $oFilter->GetClasses()); - } - - self::DbgTrace("Entering: ".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts)); - - $sRootClass = self::GetRootClass($sClass); - $sKeyField = self::DBGetKey($sClass); - - if (empty($aExpectedAtts) && $bIsOnQueriedClass) - { - // default to the whole list of attributes + the very std id/finalclass - $aExpectedAtts['id'] = 'id'; - foreach (self::GetAttributesList($sClass) as $sAttCode) - { - $aExpectedAtts[$sAttCode] = $sAttCode; // alias == attcode - } - } - - // Compute a clear view of external keys, and external attributes - // Build the list of external keys: - // -> ext keys required by a closed join ??? - // -> ext keys mentionned in a 'pointing to' condition - // -> ext keys required for an external field - // - $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) - // - // Optimization: could be computed once for all (cached) - // Could be done in MakeQuerySingleTable ??? - // - - if ($bIsOnQueriedClass) - { - // Get all Ext keys for the queried class (??) - foreach(self::GetKeysList($sClass) as $sKeyAttCode) - { - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - } - // Get all Ext keys used by the filter - foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $trash) - { - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - // Add the ext fields used in the select (eventually adds an external key) - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if (array_key_exists($sAttCode, $aExpectedAtts) || $oConditionTree->RequiresField($sClassAlias, $sAttCode)) - { - // Add the external attribute - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; - } - } - } - - // First query built upon on the leaf (ie current) class - // - self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); - $oSelectBase = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sClass, $aExpectedAtts, $aExtKeys, $aValues); - - // Then we join the queries of the eventual parent classes (compound model) - foreach(self::EnumParentClasses($sClass) as $sParentClass) - { - if (self::DBGetTable($sParentClass) == "") continue; - self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); - $oSelectParentTable = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sParentClass, $aExpectedAtts, $aExtKeys, $aValues); - $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass)); - } - - // Filter on objects referencing me - foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) - { - foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) - { - $oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); - - // We don't want any attribute from the foreign class, just filter on an inner join - $aExpAtts = array(); - - self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()"); - //self::DbgTrace($oForeignFilter); - //self::DbgTrace($oForeignFilter->ToSibuSQL()); - //self::DbgTrace($oSelectForeign); - //self::DbgTrace($oSelectForeign->RenderSelect(array())); - $oSelectForeign = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts); - - $sForeignClassAlias = $oForeignFilter->GetClassAlias(); - $sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0]; - $sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1]; - $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); - } - } - - // Filter on related objects - // - foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo) - { - $oSubFilter = $aCritInfo['flt']; - $sRelCode = $aCritInfo['relcode']; - $iMaxDepth = $aCritInfo['maxdepth']; - - // Get the starting point objects - $oStartSet = new CMDBObjectSet($oSubFilter); - - // Get the objects related to those objects... recursively... - $aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth); - $aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array(); - - // #@# todo - related objects and expressions... - // Create condition - if (count($aRestriction) > 0) - { - $oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')'); - } - else - { - // Quick N'dirty -> generate an empty set - $oSelectBase->AddCondition('false'); - } - } - - // Translate the conditions... and go - // - if ($bIsOnQueriedClass) - { - $oConditionTranslated = $oConditionTree->Translate($aTranslation); - $oSelectBase->SetCondition($oConditionTranslated); - } - - // That's all... cross fingers and we'll get some working query - - //MyHelpers::var_dump_html($oSelectBase, true); - //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); - if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); - return $oSelectBase; - } - - protected static function MakeQuerySingleTable($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, $oFilter, $sTableClass, $aExpectedAtts, $aExtKeys, $aValues) - { - // $aExpectedAtts is an array of sAttCode=>sAlias - // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) - - // Prepare the query for a single table (compound objects) - // Ignores the items (attributes/filters) that are not on the target table - // Perform an (inner or left) join for every external key (and specify the expected fields) - // - // Returns an SQLQuery - // - $sTargetClass = $oFilter->GetClass(); - $sTargetAlias = $oFilter->GetClassAlias(); - $sTable = self::DBGetTable($sTableClass); - $sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable); - - $bIsOnQueriedClass = ($sTargetAlias == $sGlobalTargetAlias); - - self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts)); - - // 1 - SELECT and UPDATE - // - // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) - // - $aSelect = array(); - $aUpdateValues = array(); - - // 1/a - Get the key - // - if ($bIsOnQueriedClass) - { - $aSelect[$aExpectedAtts['id']] = new FieldExpression(self::DBGetKey($sTableClass), $sTableAlias); - } - // We need one pkey to be the key, let's take the one corresponding to the leaf - if ($sTableClass == $sTargetClass) - { - $aTranslation[$sTargetAlias]['id'] = array($sTableAlias, self::DBGetKey($sTableClass)); - } - - // 1/b - Get the other attributes - // - foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; - - // Skip this attribute if not writable (means that it does not correspond - if (count($oAttDef->GetSQLExpressions()) == 0) continue; - - // Update... - // - if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) - { - assert ($oAttDef->IsDirectField()); - foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) - { - $aUpdateValues[$sColumn] = $sValue; - } - } - - // Select... - // - // Skip, if a list of fields has been specified and it is not there - if (!array_key_exists($sAttCode, $aExpectedAtts)) continue; - $sAttAlias = $aExpectedAtts[$sAttCode]; - - if ($oAttDef->IsExternalField()) - { - // skip, this will be handled in the joined tables - } - else - { - // standard field, or external key - // add it to the output - foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - $aSelect[$sAttAlias.$sColId] = new FieldExpression($sSQLExpr, $sTableAlias); - } - } - } - - // 2 - WHERE - // - foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt) - { - // Skip this filter if not defined in this table - if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue; - - // #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression" - foreach($oFltAtt->GetSQLExpressions() as $sColID => $sFltExpr) - { - // Note: I did not test it with filters relying on several expressions... - // as long as sColdID is empty, this is working, otherwise... ? - $aTranslation[$sTargetAlias][$sFltCode.$sColID] = array($sTableAlias, $sFltExpr); - } - } - - // #@# todo - See what a full text search condition should be - // 2' - WHERE / Full text search condition - // - if ($bIsOnQueriedClass) - { - $aFullText = $oFilter->GetCriteria_FullText(); - } - else - { - // Pourquoi ??? - $aFullText = array(); - } - - // 3 - The whole stuff, for this table only - // - $oSelectBase = new SQLQuery($sTable, $sTableAlias, $aSelect, null, $aFullText, $bIsOnQueriedClass, $aUpdateValues); - - // 4 - The external keys -> joins... - // - if (array_key_exists($sTableClass, $aExtKeys)) - { - foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) - { - $oKeyAttDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); - - $oExtFilter = $oFilter->GetCriteria_PointingTo($sKeyAttCode); - - // In case the join was not explicitely defined in the filter, - // we need to do it now - if (empty($oExtFilter)) - { - $sKeyClass = $oKeyAttDef->GetTargetClass(); - $sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass); - $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); - } - else - { - // The aliases should not conflict because normalization occured while building the filter - $sKeyClass = $oExtFilter->GetClass(); - $sKeyClassAlias = $oExtFilter->GetClassAlias(); - - // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree - } - - // Specify expected attributes for the target class query - // ... and use the current alias ! - $aExpAtts = array(); - $aIntermediateTranslation = array(); - foreach($aExtFields as $sAttCode => $oAtt) - { - - $sExtAttCode = $oAtt->GetExtAttCode(); - if (array_key_exists($sAttCode, $aExpectedAtts)) - { - // Request this attribute... transmit the alias ! - $aExpAtts[$sExtAttCode] = $aExpectedAtts[$sAttCode]; - } - // Translate mainclass.extfield => remoteclassalias.remotefieldcode - $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); - foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) - { - $aIntermediateTranslation[$sTargetAlias.$sColID][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); - } - //#@# debug - echo "

    $sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

    \n"; - } - $oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false); - - self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); - $oSelectExtKey = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); - - $sLocalKeyField = current($oKeyAttDef->GetSQLExpressions()); // get the first column for an external key - $sExternalKeyField = self::DBGetKey($sKeyClass); - self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); - if ($oKeyAttDef->IsNullAllowed()) - { - $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); - } - else - { - $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); - } - } - } - - //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); - return $oSelectBase; - } - - public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) - { - if (!array_key_exists($sNewName, $aAliases)) - { - $aAliases[$sNewName] = $sRealName; - return $sNewName; - } - - for ($i = 1 ; $i < 100 ; $i++) - { - $sAnAlias = $sNewName.$i; - if (!array_key_exists($sAnAlias, $aAliases)) - { - // Create that new alias - $aAliases[$sAnAlias] = $sRealName; - return $sAnAlias; - } - } - throw new CoreException('Failed to create an alias', array('aliases' => $aAliases, 'new'=>$sNewName)); - } - - public static function CheckDefinitions() - { - if (count(self::GetClasses()) == 0) - { - throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!"); - } - - $aErrors = array(); - $aSugFix = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - - $sNameAttCode = self::GetNameAttributeCode($sClass); - if (empty($sNameAttCode)) - { - // let's try this !!! - // $aErrors[$sClass][] = "Missing value for name definition: the framework will (should...) replace it by the id"; - // $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - else if(!self::IsValidAttCode($sClass, $sNameAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sNameAttCode."' for the name definition"; - $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - - foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode) - if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys"; - $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - // It makes no sense to check the attributes again and again in the subclasses - if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - - if ($oAttDef->IsExternalKey()) - { - if (!self::IsValidClass($oAttDef->GetTargetClass())) - { - $aErrors[$sClass][] = "Unkown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}"; - } - } - elseif ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode)) - { - $aErrors[$sClass][] = "Unkown key attribute code '".$sKeyAttCode."' for the external field $sAttCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}"; - } - else - { - $oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode); - $sTargetClass = $oKeyAttDef->GetTargetClass(); - $sExtAttCode = $oAttDef->GetExtAttCode(); - if (!self::IsValidAttCode($sTargetClass, $sExtAttCode)) - { - $aErrors[$sClass][] = "Unkown key attribute code '".$sExtAttCode."' for the external field $sAttCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}"; - } - } - } - else // standard attributes - { - // Check that the default values definition is a valid object! - $oValSetDef = $oAttDef->GetValuesDef(); - if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition) - { - $aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type"; - $aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object."; - } - else - { - // Default value must be listed in the allowed values (if defined) - $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); - if (!is_null($aAllowedValues)) - { - $sDefaultValue = $oAttDef->GetDefaultValue(); - if (!array_key_exists($sDefaultValue, $aAllowedValues)) - { - $aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value"; - $aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}"; - } - } - } - } - // Check dependencies - if ($oAttDef->IsWritable()) - { - foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode) - { - if (!self::IsValidAttCode($sClass, $sDependOnAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes"; - $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - } - } - } - foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef) - { - if (method_exists($oFilterDef, '__GetRefAttribute')) - { - $oAttDef = $oFilterDef->__GetRefAttribute(); - if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) - { - $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - } - } - - // Lifecycle - // - $sStateAttCode = self::GetStateAttributeCode($sClass); - if (strlen($sStateAttCode) > 0) - { - // Lifecycle - check that the state attribute does exist as an attribute - if (!self::IsValidAttCode($sClass, $sStateAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sStateAttCode."' for the state definition"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - else - { - // Lifecycle - check that there is a value set constraint on the state attribute - $aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode); - $aStates = array_keys(self::EnumStates($sClass)); - if (is_null($aAllowedValuesRaw)) - { - $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values"; - $aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]"; - } - else - { - $aAllowedValues = array_keys($aAllowedValuesRaw); - - // Lifecycle - check the the state attribute allowed values are defined states - foreach($aAllowedValues as $sValue) - { - if (!in_array($sValue, $aStates)) - { - $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state"; - $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; - } - } - - // Lifecycle - check that defined states are allowed values - foreach($aStates as $sStateValue) - { - if (!in_array($sStateValue, $aAllowedValues)) - { - $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value"; - $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; - } - } - } - - // Lifcycle - check that the action handlers are defined - foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef) - { - foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) - { - foreach ($aTransitionDef['actions'] as $sActionHandler) - { - if (!method_exists($sClass, $sActionHandler)) - { - $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; - $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; - } - } - } - } - } - } - - // ZList - // - foreach(self::EnumZLists() as $sListCode) - { - foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode) - { - if (!self::IsValidAttCode($sClass, $sMyAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sMyAttCode."' from ZList '$sListCode'"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - } - } - } - if (count($aErrors) > 0) - { - echo "
    "; - echo "

    Business model inconsistencies have been found

    \n"; - // #@# later -> this is the responsibility of the caller to format the output - foreach ($aErrors as $sClass => $aMessages) - { - echo "

    Wrong declaration for class $sClass

    \n"; - echo "
      \n"; - $i = 0; - foreach ($aMessages as $sMsg) - { - echo "
    • $sMsg ({$aSugFix[$sClass][$i]})
    • \n"; - $i++; - } - echo "
    \n"; - } - echo "

    Aborting...

    \n"; - echo "
    \n"; - exit; - } - } - - public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) - { - if (empty($sRepairUrl)) return; - if (count($aSQLFixes) == 0) return; - - echo "
    \n"; - echo " \n"; - echo " \n"; - echo "
    \n"; - } - - public static function DBExists($bMustBeComplete = true) - { - // returns true if at least one table exists - // - - if (!CMDBSource::IsDB(self::$m_sDBName)) - { - return false; - } - CMDBSource::SelectDB(self::$m_sDBName); - - $aFound = array(); - $aMissing = array(); - foreach (self::DBEnumTables() as $sTable => $aClasses) - { - if (CMDBSource::IsTable($sTable)) - { - $aFound[] = $sTable; - } - else - { - $aMissing[] = $sTable; - } - } - - if (count($aFound) == 0) - { - // no expected table has been found - return false; - } - else - { - if (count($aMissing) == 0) - { - // the database is complete (still, could be some fields missing!) - return true; - } - else - { - // not all the tables, could be an older version - if ($bMustBeComplete) - { - return false; - } - else - { - return true; - } - } - } - } - - public static function DBDrop() - { - $bDropEntireDB = true; - - if (!empty(self::$m_sTablePrefix)) - { - // Do drop only tables corresponding to the sub-database (table prefix) - // then possibly drop the DB itself (if no table remain) - foreach (CMDBSource::EnumTables() as $sTable) - { - // perform a case insensitive test because on Windows the table names become lowercase :-( - if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix)) - { - CMDBSource::DropTable($sTable); - } - else - { - // There is at least one table which is out of the scope of the current application - $bDropEntireDB = false; - } - } - } - - if ($bDropEntireDB) - { - CMDBSource::DropDB(self::$m_sDBName); - } - } - - - public static function DBCreate() - { - // Note: we have to check if the DB does exist, because we may share the DB - // with other applications (in which case the DB does exist, not the tables with the given prefix) - if (!CMDBSource::IsDB(self::$m_sDBName)) - { - CMDBSource::CreateDB(self::$m_sDBName); - } - self::DBCreateTables(); - } - - protected static function DBCreateTables() - { - list($aErrors, $aSugFix) = self::DBCheckFormat(); - - $aSQL = array(); - foreach ($aSugFix as $sClass => $aTarget) - { - foreach ($aTarget as $aQueries) - { - foreach ($aQueries as $sQuery) - { - //$aSQL[] = $sQuery; - // forces a refresh of cached information - CMDBSource::CreateTable($sQuery); - } - } - } - // does not work -how to have multiple statements in a single query? - // $sDoCreateAll = implode(" ; ", $aSQL); - } - - public static function DBDump() - { - $aDataDump = array(); - foreach (self::DBEnumTables() as $sTable => $aClasses) - { - $aRows = CMDBSource::DumpTable($sTable); - $aDataDump[$sTable] = $aRows; - } - return $aDataDump; - } - - public static function DBCheckFormat() - { - $aErrors = array(); - $aSugFix = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - - // Check that the table exists - // - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - $sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : ""); - if (!CMDBSource::IsTable($sTable)) - { - $aErrors[$sClass]['*'][] = "table '$sTable' could not be found into the DB"; - $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; - } - // Check that the key field exists - // - elseif (!CMDBSource::IsField($sTable, $sKeyField)) - { - $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) could not be found"; - $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; - } - else - { - // Check the key field properties - // - if (!CMDBSource::IsKey($sTable, $sKeyField)) - { - $aErrors[$sClass]['id'][] = "key '$sKeyField' is not a key for table '$sTable'"; - $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; - } - if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField)) - { - $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; - $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; - } - } - - // Check that any defined field exists - // - $aTableInfo = CMDBSource::GetTableInfo($sTable); - - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not originaly defined in this class - if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - - foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) - { - $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; - if (!CMDBSource::IsField($sTable, $sField)) - { - $aErrors[$sClass][$sAttCode][] = "field '$sField' could not be found in table '$sTable'"; - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; - if ($oAttDef->IsExternalKey()) - { - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; - } - } - else - { - // The field already exists, does it have the relevant properties? - // - $bToBeChanged = false; - if ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) - { - $bToBeChanged = true; - if ($oAttDef->IsNullAllowed()) - { - $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could be NULL"; - } - else - { - $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could NOT be NULL"; - } - } - $sActualFieldType = CMDBSource::GetFieldType($sTable, $sField); - if (strcasecmp($sDBFieldType, $sActualFieldType) != 0) - { - $bToBeChanged = true; - $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; - } - if ($bToBeChanged) - { - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; - } - - // Create indexes (external keys only... so far) - // - if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) - { - $aErrors[$sClass][$sAttCode][] = "Foreign key '$sField' in table '$sTable' should have an index"; - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; - } - } - } - } - } - return array($aErrors, $aSugFix); - } - - - private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false) - { - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) - { - $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; - } - $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); - if (count($aWrongRecords) == 0) return; - - if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); - if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); - - foreach ($aWrongRecords as $iRecordId) - { - if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) - { - switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) - { - case 'Delete': - // Already planned for a deletion - // Let's concatenate the errors description together - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; - break; - - case 'Update': - // Let's plan a deletion - break; - } - } - else - { - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; - } - - if (!$bProcessingFriends) - { - if (!array_key_exists($sTable, $aPlannedDel) || !in_array($iRecordId, $aPlannedDel[$sTable])) - { - // Something new to be deleted... - $iNewDelCount++; - } - } - - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Delete'; - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(); - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; - $aPlannedDel[$sTable][] = $iRecordId; - } - - // Now make sure that we would delete the records of the other tables for that class - // - if (!$bProcessingFriends) - { - $sDeleteKeys = "'".implode("', '", $aWrongRecords)."'"; - foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sFriendClass) - { - $sFriendTable = self::DBGetTable($sFriendClass); - $sFriendKey = self::DBGetKey($sFriendClass); - - // skip the current table - if ($sFriendTable == $sTable) continue; - - $sFindRelatedRec = "SELECT DISTINCT maintable.`$sFriendKey` AS id FROM `$sFriendTable` AS maintable WHERE maintable.`$sFriendKey` IN ($sDeleteKeys)"; - self::DBCheckIntegrity_Check2Delete($sFindRelatedRec, "Cascading deletion of record in friend table `$sTable`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true); - } - } - } - - private static function DBCheckIntegrity_Check2Update($sSelWrongRecs, $sErrorDesc, $sColumn, $sNewValue, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) - { - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) - { - $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; - } - $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); - if (count($aWrongRecords) == 0) return; - - if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); - if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); - - foreach ($aWrongRecords as $iRecordId) - { - if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) - { - switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) - { - case 'Delete': - // No need to update, the record will be deleted! - break; - - case 'Update': - // Already planned for an update - // Add this new update spec to the list - $bFoundSameSpec = false; - foreach ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] as $aUpdateSpec) - { - if (($sColumn == $aUpdateSpec['column']) && ($sNewValue == $aUpdateSpec['newvalue'])) - { - $bFoundSameSpec = true; - } - } - if (!$bFoundSameSpec) - { - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'][] = (array('column' => $sColumn, 'newvalue'=>$sNewValue)); - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; - } - break; - } - } - else - { - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Update'; - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(array('column' => $sColumn, 'newvalue'=>$sNewValue)); - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; - } - - } - } - - // returns the count of records found for deletion - public static function DBCheckIntegrity_SinglePass(&$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) - { - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - // Check that the final class field contains the name of a class which inherited from the current class - // - if (self::HasFinalClassField($sClass)) - { - $sFinalClassField = self::DBGetClassField($sClass); - - $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); - $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); - - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - // Compound objects model - node/leaf classes (not the root itself) - // - if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass)) - { - $sRootTable = self::DBGetTable($sRootClass); - $sRootKey = self::DBGetKey($sRootClass); - $sFinalClassField = self::DBGetClassField($sRootClass); - - $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); - $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); - - // Check that any record found here has its counterpart in the root table - // and which refers to a child class - // - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `$sTable`, but no counterpart in root table `$sRootTable` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - - // Check that any record found in the root table and referring to a child class - // has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy) - // - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - - if ($oAttDef->IsExternalKey()) - { - // Check that any external field is pointing to an existing object - // - $sRemoteClass = $oAttDef->GetTargetClass(); - $sRemoteTable = self::DBGetTable($sRemoteClass); - $sRemoteKey = self::DBGetKey($sRemoteClass); - - $sExtKeyField = current($oAttDef->GetSQLExpressions()); // get the first column for an external key - - // Note: a class/table may have an external key on itself - $sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`"; - - $sSelWrongRecs = $sSelBase." WHERE `$sRemoteTable`.`$sRemoteKey` IS NULL"; - if ($oAttDef->IsNullAllowed()) - { - // Exclude the records pointing to 0/null from the errors - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; - self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - else - { - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - // Do almost the same, taking into account the records planned for deletion - if (array_key_exists($sRemoteTable, $aPlannedDel) && count($aPlannedDel[$sRemoteTable]) > 0) - { - // This could be done by the mean of a 'OR ... IN (aIgnoreRecords) - // but in that case you won't be able to track the root cause (cascading) - $sSelWrongRecs = $sSelBase." WHERE maintable.`$sExtKeyField` IN ('".implode("', '", $aPlannedDel[$sRemoteTable])."')"; - if ($oAttDef->IsNullAllowed()) - { - // Exclude the records pointing to 0/null from the errors - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; - self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - else - { - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - } - } - else if ($oAttDef->IsDirectField()) - { - // Check that the values fit the allowed values - // - $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); - if (!is_null($aAllowedValues) && count($aAllowedValues) > 0) - { - $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); - - $sMyAttributeField = current($oAttDef->GetSQLExpressions()); // get the first column for the moment - $sDefaultValue = $oAttDef->GetDefaultValue(); - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)"; - self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - } - } - } - } - - public static function DBCheckIntegrity($sRepairUrl = "", $sSQLStatementArgName = "") - { - // Records in error, and action to be taken: delete or update - // by RootClass/Table/Record - $aErrorsAndFixes = array(); - - // Records to be ignored in the current/next pass - // by Table = array of RecordId - $aPlannedDel = array(); - - // Count of errors in the next pass: no error means that we can leave... - $iErrorCount = 0; - // Limit in case of a bug in the algorythm - $iLoopCount = 0; - - $iNewDelCount = 1; // startup... - while ($iNewDelCount > 0) - { - $iNewDelCount = 0; - self::DBCheckIntegrity_SinglePass($aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - $iErrorCount += $iNewDelCount; - - // Safety net #1 - limit the planned deletions - // - $iMaxDel = 1000; - $iPlannedDel = 0; - foreach ($aPlannedDel as $sTable => $aPlannedDelOnTable) - { - $iPlannedDel += count($aPlannedDelOnTable); - } - if ($iPlannedDel > $iMaxDel) - { - throw new CoreWarning("DB Integrity Check safety net - Exceeding the limit of $iMaxDel planned record deletion"); - break; - } - // Safety net #2 - limit the iterations - // - $iLoopCount++; - $iMaxLoops = 10; - if ($iLoopCount > $iMaxLoops) - { - throw new CoreWarning("DB Integrity Check safety net - Reached the limit of $iMaxLoops loops"); - break; - } - } - - // Display the results - // - $iIssueCount = 0; - $aFixesDelete = array(); - $aFixesUpdate = array(); - - foreach ($aErrorsAndFixes as $sRootClass => $aTables) - { - foreach ($aTables as $sTable => $aRecords) - { - foreach ($aRecords as $iRecord => $aError) - { - $sAction = $aError['Action']; - $sReason = $aError['Reason']; - $iPass = $aError['Pass']; - - switch ($sAction) - { - case 'Delete': - $sActionDetails = ""; - $aFixesDelete[$sTable][] = $iRecord; - break; - - case 'Update': - $aUpdateDesc = array(); - foreach($aError['Action_Details'] as $aUpdateSpec) - { - $aUpdateDesc[] = $aUpdateSpec['column']." -> ".$aUpdateSpec['newvalue']; - $aFixesUpdate[$sTable][$aUpdateSpec['column']][$aUpdateSpec['newvalue']][] = $iRecord; - } - $sActionDetails = "Set ".implode(", ", $aUpdateDesc); - - break; - - default: - $sActionDetails = "bug: unknown action '$sAction'"; - } - $aIssues[] = "$sRootClass / $sTable / $iRecord / $sReason / $sAction / $sActionDetails"; - $iIssueCount++; - } - } - } - - if ($iIssueCount > 0) - { - // Build the queries to fix in the database - // - // First step, be able to get class data out of the table name - // Could be optimized, because we've made the job earlier... but few benefits, so... - $aTable2ClassProp = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - $aErrorsAndFixes[$sRootClass][$sTable] = array(); - $aTable2ClassProp[$sTable] = array('rootclass'=>$sRootClass, 'class'=>$sClass, 'keyfield'=>$sKeyField); - } - // Second step, build a flat list of SQL queries - $aSQLFixes = array(); - $iPlannedUpdate = 0; - foreach ($aFixesUpdate as $sTable => $aColumns) - { - foreach ($aColumns as $sColumn => $aNewValues) - { - foreach ($aNewValues as $sNewValue => $aRecords) - { - $iPlannedUpdate += count($aRecords); - $sWrongRecords = "'".implode("', '", $aRecords)."'"; - $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; - - $aSQLFixes[] = "UPDATE `$sTable` SET `$sColumn` = $sNewValue WHERE `$sKeyField` IN ($sWrongRecords)"; - } - } - } - $iPlannedDel = 0; - foreach ($aFixesDelete as $sTable => $aRecords) - { - $iPlannedDel += count($aRecords); - $sWrongRecords = "'".implode("', '", $aRecords)."'"; - $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; - - $aSQLFixes[] = "DELETE FROM `$sTable` WHERE `$sKeyField` IN ($sWrongRecords)"; - } - - // Report the results - // - echo "
    "; - echo "

    Database corruption error(s): $iErrorCount issues have been encountered. $iPlannedDel records will be deleted, $iPlannedUpdate records will be updated:

    \n"; - // #@# later -> this is the responsibility of the caller to format the output - echo "
      \n"; - foreach ($aIssues as $sIssueDesc) - { - echo "
    • $sIssueDesc
    • \n"; - } - echo "
    \n"; - self::DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes); - echo "

    Aborting...

    \n"; - echo "
    \n"; - exit; - } - } - - public static function Startup($sConfigFile, $bAllowMissingDB = false) - { - self::LoadConfig($sConfigFile); - if (self::DBExists()) - { - CMDBSource::SelectDB(self::$m_sDBName); - - // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) - self::InitPlugins(); - } - else - { - if (!$bAllowMissingDB) - { - throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); - } - } - } - - public static function LoadConfig($sConfigFile) - { - $oConfig = new Config($sConfigFile); - - foreach ($oConfig->GetAppModules() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'application', $sToInclude); - } - foreach ($oConfig->GetDataModels() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'business', $sToInclude); - } - foreach ($oConfig->GetAddons() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'addons', $sToInclude); - } - - $sServer = $oConfig->GetDBHost(); - $sUser = $oConfig->GetDBUser(); - $sPwd = $oConfig->GetDBPwd(); - $sSource = $oConfig->GetDBName(); - $sTablePrefix = $oConfig->GetDBSubname(); - - // The include have been included, let's browse the existing classes and - // develop some data based on the proposed model - self::InitClasses($sTablePrefix); - - self::$m_sDBName = $sSource; - self::$m_sTablePrefix = $sTablePrefix; - - CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) - } - - protected static $m_aPlugins = array(); - public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) - { - self::$m_aPlugins[$sName] = array( - 'type' => $sType, - 'init' => $aInitCallSpec, - ); - } - - protected static function Plugin($sConfigFile, $sModuleType, $sToInclude) - { - if (!file_exists($sToInclude)) - { - throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sToInclude)); - } - require_once($sToInclude); - } - - protected static function InitPlugins() - { - foreach(self::$m_aPlugins as $sName => $aData) - { - $aCallSpec = @$aData['init']; - if (count($aCallSpec) == 2) - { - if (!is_callable($aCallSpec)) - { - throw new CoreException('Wrong declaration in plugin', array('plugin' => $aData['name'], 'type' => $aData['type'], 'class' => $aData['class'], 'init' => $aData['init'])); - } - call_user_func($aCallSpec); - } - } - } - - // Building an object - // - // - private static $aQueryCacheGetObject = array(); - private static $aQueryCacheGetObjectHits = array(); - public static function GetQueryCacheStatus() - { - $aRes = array(); - $iTotalHits = 0; - foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits) - { - $aRes[] = "$sClass: $iHits"; - $iTotalHits += $iHits; - } - return $iTotalHits.' ('.implode(', ', $aRes).')'; - } - - public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true) - { - if (!array_key_exists($sClass, self::$aQueryCacheGetObject)) - { - // NOTE: Quick and VERY dirty caching mechanism which relies on - // the fact that the string '987654321' will never appear in the - // standard query - // This will be replaced for sure with a prepared statement - // or a view... next optimization to come! - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', 987654321, '='); - - $sSQL = self::MakeSelectQuery($oFilter); - self::$aQueryCacheGetObject[$sClass] = $sSQL; - self::$aQueryCacheGetObjectHits[$sClass] = 0; - } - else - { - $sSQL = self::$aQueryCacheGetObject[$sClass]; - self::$aQueryCacheGetObjectHits[$sClass] += 1; -// echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."
    \n"; - } - $sSQL = str_replace('987654321', CMDBSource::Quote($iKey), $sSQL); - $res = CMDBSource::Query($sSQL); - - $aRow = CMDBSource::FetchArray($res); - CMDBSource::FreeResult($res); - if ($bMustBeFound && empty($aRow)) - { - throw new CoreException("No result for the single row query: '$sSQL'"); - } - return $aRow; - } - - public static function GetObjectByRow($sClass, $aRow) - { - self::_check_subclass($sClass); - - // Compound objects: if available, get the final object class - // - if (!array_key_exists("finalclass", $aRow)) - { - // Either this is a bug (forgot to specify a root class with a finalclass field - // Or this is the expected behavior, because the object is not made of several tables - } - elseif (empty($aRow["finalclass"])) - { - // The data is missing in the DB - // @#@ possible improvement: check that the class is valid ! - $sRootClass = self::GetRootClass($sClass); - $sFinalClassField = self::DBGetClassField($sRootClass); - throw new CoreException("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)"); - } - else - { - // do the job for the real target class - $sClass = $aRow["finalclass"]; - } - return new $sClass($aRow); - } - - public static function GetObject($sClass, $iKey, $bMustBeFound = true) - { - self::_check_subclass($sClass); - $aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound); - if (empty($aRow)) - { - return null; - } - return self::GetObjectByRow($sClass, $aRow); - } - - public static function GetHyperLink($sTargetClass, $iKey) - { - if ($iKey < 0) - { - return "$sTargetClass: $iKey (invalid value)"; - } - $oObj = self::GetObject($sTargetClass, $iKey, false); - if (is_null($oObj)) - { - return "$sTargetClass: $iKey (not found)"; - } - return $oObj->GetHyperLink(); - } - - public static function NewObject($sClass) - { - self::_check_subclass($sClass); - return new $sClass(); - } - - public static function GetNextKey($sClass) - { - $sRootClass = MetaModel::GetRootClass($sClass); - $sRootTable = MetaModel::DBGetTable($sRootClass); - $iNextKey = CMDBSource::GetNextInsertId($sRootTable); - return $iNextKey; - } - - public static function BulkDelete(DBObjectSearch $oFilter) - { - $sSQL = self::MakeDeleteQuery($oFilter); - CMDBSource::Query($sSQL); - } - - public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) - { - // $aValues is an array of $sAttCode => $value - $sSQL = self::MakeUpdateQuery($oFilter, $aValues); - CMDBSource::Query($sSQL); - } - - // Links - // - // - public static function EnumReferencedClasses($sClass) - { - self::_check_subclass($sClass); - - // 1-N links (referenced by my class), returns an array of sAttCode=>sClass - $aResult = array(); - foreach(self::$m_aAttribDefs[$sClass] as $sAttCode=>$oAttDef) - { - if ($oAttDef->IsExternalKey()) - { - $aResult[$sAttCode] = $oAttDef->GetTargetClass(); - } - } - return $aResult; - } - public static function EnumReferencingClasses($sClass, $bSkipLinkingClasses = false, $bInnerJoinsOnly = false) - { - self::_check_subclass($sClass); - - if ($bSkipLinkingClasses) - { - $aLinksClasses = self::EnumLinksClasses(); - } - - // 1-N links (referencing my class), array of sClass => array of sAttcode - $aResult = array(); - foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) - { - if ($bSkipLinkingClasses && in_array($sSomeClass, $aLinksClasses)) continue; - - $aExtKeys = array(); - foreach ($aClassAttributes as $sAttCode=>$oAttDef) - { - if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; - if ($oAttDef->IsExternalKey() && (self::IsParentClass($oAttDef->GetTargetClass(), $sClass))) - { - if ($bInnerJoinsOnly && $oAttDef->IsNullAllowed()) continue; - // Ok, I want this one - $aExtKeys[$sAttCode] = $oAttDef; - } - } - if (count($aExtKeys) != 0) - { - $aResult[$sSomeClass] = $aExtKeys; - } - } - return $aResult; - } - public static function EnumLinksClasses() - { - // Returns a flat array of classes having at least two external keys - $aResult = array(); - foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) - { - $iExtKeyCount = 0; - foreach ($aClassAttributes as $sAttCode=>$oAttDef) - { - if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; - if ($oAttDef->IsExternalKey()) - { - $iExtKeyCount++; - } - } - if ($iExtKeyCount >= 2) - { - $aResult[] = $sSomeClass; - } - } - return $aResult; - } - public static function EnumLinkingClasses($sClass = "") - { - // N-N links, array of sLinkClass => (array of sAttCode=>sClass) - $aResult = array(); - foreach (self::EnumLinksClasses() as $sSomeClass) - { - $aTargets = array(); - $bFoundClass = false; - foreach (self::ListAttributeDefs($sSomeClass) as $sAttCode=>$oAttDef) - { - if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; - if ($oAttDef->IsExternalKey()) - { - $sRemoteClass = $oAttDef->GetTargetClass(); - if (empty($sClass)) - { - $aTargets[$sAttCode] = $sRemoteClass; - } - elseif ($sClass == $sRemoteClass) - { - $bFoundClass = true; - } - else - { - $aTargets[$sAttCode] = $sRemoteClass; - } - } - } - if (empty($sClass) || $bFoundClass) - { - $aResult[$sSomeClass] = $aTargets; - } - } - return $aResult; - } - - public static function GetLinkLabel($sLinkClass, $sAttCode) - { - self::_check_subclass($sLinkClass); - - // e.g. "supported by" (later: $this->GetLinkLabel(), computed on link data!) - return self::GetLabel($sLinkClass, $sAttCode); - } - - /** - * Replaces all the parameters by the values passed in the hash array - */ - static public function ApplyParams($aInput, $aParams) - { - $aSearches = array(); - $aReplacements = array(); - foreach($aParams as $sSearch => $sReplace) - { - $aSearches[] = '$'.$sSearch.'$'; - $aReplacements[] = $sReplace; - } - return str_replace($aSearches, $aReplacements, $aInput); - } - -} // class MetaModel - - -// Standard attribute lists -MetaModel::RegisterZList("noneditable", array("description"=>"non editable fields", "type"=>"attributes")); - -MetaModel::RegisterZList("details", array("description"=>"All attributes to be displayed for the 'details' of an object", "type"=>"attributes")); -MetaModel::RegisterZList("list", array("description"=>"All attributes to be displayed for a list of objects", "type"=>"attributes")); -MetaModel::RegisterZList("preview", array("description"=>"All attributes visible in preview mode", "type"=>"attributes")); - -MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters")); -MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters")); - - -?> + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class MetaModel +{ + /////////////////////////////////////////////////////////////////////////// + // + // STATIC Members + // + /////////////////////////////////////////////////////////////////////////// + + // Purpose: workaround the following limitation = PHP5 does not allow to know the class (derived from the current one) + // from which a static function is called (__CLASS__ and self are interpreted during parsing) + private static function GetCallersPHPClass($sExpectedFunctionName = null) + { + //var_dump(debug_backtrace()); + $aBacktrace = debug_backtrace(); + // $aBacktrace[0] is where we are + // $aBacktrace[1] is the caller of GetCallersPHPClass + // $aBacktrace[1] is the info we want + if (!empty($sExpectedFunctionName)) + { + assert('$aBacktrace[2]["function"] == $sExpectedFunctionName'); + } + return $aBacktrace[2]["class"]; + } + + // Static init -why and how it works + // + // We found the following limitations: + //- it is not possible to define non scalar constants + //- it is not possible to declare a static variable as '= new myclass()' + // Then we had do propose this model, in which a derived (non abstract) + // class should implement Init(), to call InheritAttributes or AddAttribute. + + private static function _check_subclass($sClass) + { + // See also IsValidClass()... ???? #@# + // class is mandatory + // (it is not possible to guess it when called as myderived::...) + if (!array_key_exists($sClass, self::$m_aClassParams)) + { + throw new CoreException("Unknown class '$sClass', expected a value in {".implode(', ', array_keys(self::$m_aClassParams))."}"); + } + } + + public static function static_var_dump() + { + var_dump(get_class_vars(__CLASS__)); + } + + private static $m_bDebugQuery = false; + private static $m_iStackDepthRef = 0; + + public static function StartDebugQuery() + { + $aBacktrace = debug_backtrace(); + self::$m_iStackDepthRef = count($aBacktrace); + self::$m_bDebugQuery = true; + } + public static function StopDebugQuery() + { + self::$m_bDebugQuery = false; + } + public static function DbgTrace($value) + { + if (!self::$m_bDebugQuery) return; + $aBacktrace = debug_backtrace(); + $iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery; + $sIndent = ""; + for ($i = 0 ; $i < $iCallStackPos ; $i++) + { + $sIndent .= " .-=^=-. "; + } + $aCallers = array(); + foreach($aBacktrace as $aStackInfo) + { + $aCallers[] = $aStackInfo["function"]; + } + $sCallers = "Callstack: ".implode(', ', $aCallers); + $sFunction = "".$aBacktrace[1]["function"].""; + + if (is_string($value)) + { + echo "$sIndent$sFunction: $value
    \n"; + } + else if (is_object($value)) + { + echo "$sIndent$sFunction:\n
    \n";
    +			print_r($value);
    +			echo "
    \n"; + } + else + { + echo "$sIndent$sFunction: $value
    \n"; + } + } + + private static $m_bTraceQueries = true; + private static $m_aQueriesLog = array(); + + + private static $m_sDBName = ""; + private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB) + private static $m_Category2Class = array(); + private static $m_aRootClasses = array(); // array of "classname" => "rootclass" + private static $m_aParentClasses = array(); // array of ("classname" => array of "parentclass") + private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass") + + private static $m_aClassParams = array(); // array of ("classname" => array of class information) + + static public function GetParentPersistentClass($sRefClass) + { + $sClass = get_parent_class($sRefClass); + if (!$sClass) return ''; + + if ($sClass == 'DBObject') return ''; // Warning: __CLASS__ is lower case in my version of PHP + + // Note: the UI/business model may implement pure PHP classes (intermediate layers) + if (array_key_exists($sClass, self::$m_aClassParams)) + { + return $sClass; + } + return self::GetParentPersistentClass($sClass); + } + + final static public function GetName($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["name"]; + } + final static public function GetCategory($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["category"]; + } + final static public function HasCategory($sClass, $sCategory) + { + self::_check_subclass($sClass); + return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); + } + final static public function GetClassDescription($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["description"]; + } + final static public function IsAutoIncrementKey($sClass) + { + self::_check_subclass($sClass); + return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement"); + } + final static public function GetKeyLabel($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["key_label"]; + } + final static public function GetNameAttributeCode($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["name_attcode"]; + } + final static public function GetStateAttributeCode($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["state_attcode"]; + } + final static public function GetDefaultState($sClass) + { + $sDefaultState = ''; + $sStateAttrCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttrCode)) + { + $oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode); + $sDefaultState = $oStateAttrDef->GetDefaultValue(); + } + return $sDefaultState; + } + final static public function GetReconcKeys($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["reconc_keys"]; + } + final static public function GetDisplayTemplate($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["display_template"]; + } + final static public function GetAttributeOrigin($sClass, $sAttCode) + { + self::_check_subclass($sClass); + return self::$m_aAttribOrigins[$sClass][$sAttCode]; + } + final static public function GetPrequisiteAttributes($sClass, $sAttCode) + { + self::_check_subclass($sClass); + $oAtt = self::GetAttributeDef($sClass, $sAttCode); + // Temporary implementation: later, we might be able to compute + // the dependencies, based on the attributes definition + // (allowed values and default values) + if ($oAtt->IsWritable()) + { + return $oAtt->GetPrerequisiteAttributes(); + } + else + { + return array(); + } + } + // #@# restore to private ? + final static public function DBGetTable($sClass, $sAttCode = null) + { + self::_check_subclass($sClass); + if (empty($sAttCode) || ($sAttCode == "id")) + { + $sTableRaw = self::$m_aClassParams[$sClass]["db_table"]; + if (empty($sTableRaw)) + { + // return an empty string whenever the table is undefined, meaning that there is no table associated to this 'abstract' class + return ''; + } + else + { + return self::$m_sTablePrefix.$sTableRaw; + } + } + // This attribute has been inherited (compound objects) + return self::DBGetTable(self::$m_aAttribOrigins[$sClass][$sAttCode]); + } + + final static protected function DBEnumTables() + { + // This API do not rely on our capability to query the DB and retrieve + // the list of existing tables + // Rather, it uses the list of expected tables, corresponding to the data model + $aTables = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + $sTable = self::DBGetTable($sClass); + + // Could be completed later with all the classes that are using a given table + if (!array_key_exists($sTable, $aTables)) + { + $aTables[$sTable] = array(); + } + $aTables[$sTable][] = $sClass; + } + return $aTables; + } + + final static public function DBGetKey($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["db_key_field"]; + } + final static public function DBGetClassField($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["db_finalclass_field"]; + } + final static public function HasFinalClassField($sClass) + { + self::_check_subclass($sClass); + if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false; + return (self::$m_aClassParams[$sClass]["db_finalclass_field"]); + } + final static public function IsStandaloneClass($sClass) + { + self::_check_subclass($sClass); + + $sRootClass = self::GetRootClass($sClass); + return (!self::HasFinalClassField($sRootClass)); + } + final static public function IsParentClass($sParentClass, $sChildClass) + { + self::_check_subclass($sChildClass); + self::_check_subclass($sParentClass); + if (in_array($sParentClass, self::$m_aParentClasses[$sChildClass])) return true; + if ($sChildClass == $sParentClass) return true; + return false; + } + final static public function IsSameFamilyBranch($sClassA, $sClassB) + { + self::_check_subclass($sClassA); + self::_check_subclass($sClassB); + if (in_array($sClassA, self::$m_aParentClasses[$sClassB])) return true; + if (in_array($sClassB, self::$m_aParentClasses[$sClassA])) return true; + if ($sClassA == $sClassB) return true; + return false; + } + final static public function IsSameFamily($sClassA, $sClassB) + { + self::_check_subclass($sClassA); + self::_check_subclass($sClassB); + return (self::GetRootClass($sClassA) == self::GetRootClass($sClassB)); + } + + // Attributes of a given class may contain attributes defined in a parent class + // - Some attributes are a copy of the definition + // - Some attributes correspond to the upper class table definition (compound objects) + // (see also filters definition) + private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes) + private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) + private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field"))) + final static public function ListAttributeDefs($sClass) + { + self::_check_subclass($sClass); + return self::$m_aAttribDefs[$sClass]; + } + + final public static function GetAttributesList($sClass) + { + self::_check_subclass($sClass); + return array_keys(self::$m_aAttribDefs[$sClass]); + } + + final public static function GetFiltersList($sClass) + { + self::_check_subclass($sClass); + return array_keys(self::$m_aFilterDefs[$sClass]); + } + + final public static function GetKeysList($sClass) + { + self::_check_subclass($sClass); + $aExtKeys = array(); + foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aExtKeys[] = $sAttCode; + } + } + return $aExtKeys; + } + + final static public function IsValidKeyAttCode($sClass, $sAttCode) + { + if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; + if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return false; + return (self::$m_aAttribDefs[$sClass][$sAttCode]->IsExternalKey()); + } + final static public function IsValidAttCode($sClass, $sAttCode) + { + if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; + return (array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])); + } + final static public function IsAttributeOrigin($sClass, $sAttCode) + { + return (self::$m_aAttribOrigins[$sClass][$sAttCode] == $sClass); + } + + final static public function IsValidFilterCode($sClass, $sFilterCode) + { + if (!array_key_exists($sClass, self::$m_aFilterDefs)) return false; + return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])); + } + public static function IsValidClass($sClass) + { + return (array_key_exists($sClass, self::$m_aAttribDefs)); + } + + public static function IsValidObject($oObject) + { + if (!is_object($oObject)) return false; + return (self::IsValidClass(get_class($oObject))); + } + + public static function IsReconcKey($sClass, $sAttCode) + { + return (in_array($sAttCode, self::GetReconcKeys($sClass))); + } + + final static public function GetAttributeDef($sClass, $sAttCode) + { + self::_check_subclass($sClass); + return self::$m_aAttribDefs[$sClass][$sAttCode]; + } + + final static public function GetExternalKeys($sClass) + { + $aExtKeys = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if ($oAtt->IsExternalKey()) + { + $aExtKeys[$sAttCode] = $oAtt; + } + } + return $aExtKeys; + } + + final static public function GetLinkedSets($sClass) + { + $aLinkedSets = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if (is_subclass_of($oAtt, 'AttributeLinkedSet')) + { + $aLinkedSets[$sAttCode] = $oAtt; + } + } + return $aLinkedSets; + } + + final static public function GetExternalFields($sClass, $sKeyAttCode) + { + $aExtFields = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if ($oAtt->IsExternalField() && ($oAtt->GetKeyAttCode() == $sKeyAttCode)) + { + $aExtFields[] = $oAtt; + } + } + return $aExtFields; + } + + final static public function GetExtKeyFriends($sClass, $sExtKeyAttCode) + { + if (array_key_exists($sExtKeyAttCode, self::$m_aExtKeyFriends[$sClass])) + { + return self::$m_aExtKeyFriends[$sClass][$sExtKeyAttCode]; + } + else + { + return array(); + } + } + + public static function GetLabel($sClass, $sAttCode) + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef) return $oAttDef->GetLabel(); + return ""; + } + + public static function GetDescription($sClass, $sAttCode) + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef) return $oAttDef->GetDescription(); + return ""; + } + + // Filters of a given class may contain filters defined in a parent class + // - Some filters are a copy of the definition + // - Some filters correspond to the upper class table definition (compound objects) + // (see also attributes definition) + private static $m_aFilterDefs = array(); // array of ("classname" => array filterdef) + private static $m_aFilterOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) + + public static function GetClassFilterDefs($sClass) + { + self::_check_subclass($sClass); + return self::$m_aFilterDefs[$sClass]; + } + + final static public function GetClassFilterDef($sClass, $sFilterCode) + { + self::_check_subclass($sClass); + return self::$m_aFilterDefs[$sClass][$sFilterCode]; + } + + public static function GetFilterLabel($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetLabel(); + return ""; + } + + public static function GetFilterDescription($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetDescription(); + return ""; + } + + // returns an array of opcode=>oplabel (e.g. "differs from") + public static function GetFilterOperators($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetOperators(); + return array(); + } + + // returns an opcode + public static function GetFilterLooseOperator($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetLooseOperator(); + return array(); + } + + public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetOpDescription($sOpCode); + return ""; + } + + public static function GetFilterHTMLInput($sFilterCode) + { + return ""; + } + + // Lists of attributes/search filters + // + private static $m_aListInfos = array(); // array of ("listcode" => various info on the list, common to every classes) + private static $m_aListData = array(); // array of ("classname" => array of "listcode" => list) + // list may be an array of attcode / fltcode + // list may be an array of "groupname" => (array of attcode / fltcode) + + public static function EnumZLists() + { + return array_keys(self::$m_aListInfos); + } + + final static public function GetZListInfo($sListCode) + { + return self::$m_aListInfos[$sListCode]; + } + + public static function GetZListItems($sClass, $sListCode) + { + if (array_key_exists($sClass, self::$m_aListData)) + { + if (array_key_exists($sListCode, self::$m_aListData[$sClass])) + { + return self::$m_aListData[$sClass][$sListCode]; + } + } + $sParentClass = self::GetParentPersistentClass($sClass); + if (empty($sParentClass)) return array(); // nothing for the mother of all classes + // Dig recursively + return self::GetZListItems($sParentClass, $sListCode); + } + + public static function IsAttributeInZList($sClass, $sListCode, $sAttCodeOrFltCode, $sGroup = null) + { + $aZList = self::GetZListItems($sClass, $sListCode); + if (!$sGroup) + { + return (in_array($sAttCodeOrFltCode, $aZList)); + } + return (in_array($sAttCodeOrFltCode, $aZList[$sGroup])); + } + + // + // Relations + // + private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes) + + public static function EnumRelations() + { + return array_keys(self::$m_aRelationInfos); + } + + public static function EnumRelationProperties($sRelCode) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + return self::$m_aRelationInfos[$sRelCode]; + } + + final static public function GetRelationProperty($sRelCode, $sProperty) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]); + + return self::$m_aRelationInfos[$sRelCode][$sProperty]; + } + + public static function EnumRelationQueries($sClass, $sRelCode) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); + } + + // + // Object lifecycle model + // + private static $m_aStates = array(); // array of ("classname" => array of "statecode"=>array('label'=>..., 'description'=>..., attribute_inherit=> attribute_list=>...)) + private static $m_aStimuli = array(); // array of ("classname" => array of ("stimuluscode"=>array('label'=>..., 'description'=>...))) + private static $m_aTransitions = array(); // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD))) + + public static function EnumStates($sClass) + { + if (array_key_exists($sClass, self::$m_aStates)) + { + return self::$m_aStates[$sClass]; + } + else + { + return array(); + } + } + + public static function EnumStimuli($sClass) + { + if (array_key_exists($sClass, self::$m_aStimuli)) + { + return self::$m_aStimuli[$sClass]; + } + else + { + return array(); + } + } + + public static function EnumTransitions($sClass, $sStateCode) + { + if (array_key_exists($sClass, self::$m_aTransitions)) + { + if (array_key_exists($sStateCode, self::$m_aTransitions[$sClass])) + { + return self::$m_aTransitions[$sClass][$sStateCode]; + } + } + return array(); + } + public static function GetAttributeFlags($sClass, $sState, $sAttCode) + { + $iFlags = 0; // By default (if no life cycle) no flag at all + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttCode)) + { + $aStates = MetaModel::EnumStates($sClass); + if (!array_key_exists($sState, $aStates)) + { + throw new CoreException("Invalid state '$sState' for class '$sClass', expecting a value in {".implode(', ', array_keys($aStates))."}"); + } + $aCurrentState = $aStates[$sState]; + if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) ) + { + $iFlags = $aCurrentState['attribute_list'][$sAttCode]; + } + } + return $iFlags; + } + + // + // Allowed values + // + + public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); + } + + public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') + { + $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); + return $oFltDef->GetAllowedValues($aArgs, $sBeginsWith); + } + + // + // Businezz model declaration verbs (should be static) + // + + public static function RegisterZList($sListCode, $aListInfo) + { + // Check mandatory params + $aMandatParams = array( + "description" => "detailed (though one line) description of the list", + "type" => "attributes | filters", + ); + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aListInfo)) + { + throw new CoreException("Declaration of list $sListCode - missing parameter $sParamName"); + } + } + + self::$m_aListInfos[$sListCode] = $aListInfo; + } + + public static function RegisterRelation($sRelCode, $aRelationInfo) + { + // Check mandatory params + $aMandatParams = array( + "description" => "detailed (though one line) description of the list", + "verb_down" => "e.g.: 'impacts'", + "verb_up" => "e.g.: 'is impacted by'", + ); + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aRelationInfo)) + { + throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName"); + } + } + + self::$m_aRelationInfos[$sRelCode] = $aRelationInfo; + } + + // Must be called once and only once... + public static function InitClasses($sTablePrefix) + { + if (count(self::GetClasses()) > 0) + { + throw new CoreException("InitClasses should not be called more than once -skipped"); + return; + } + + self::$m_sTablePrefix = $sTablePrefix; + + foreach(get_declared_classes() as $sPHPClass) { + if (is_subclass_of($sPHPClass, 'DBObject')) + { + if (method_exists($sPHPClass, 'Init')) + { + call_user_func(array($sPHPClass, 'Init')); + } + } + } + foreach (self::GetClasses() as $sClass) + { + // Compute the fields that will be used to display a pointer to another object + // + self::$m_aExtKeyFriends[$sClass] = array(); + foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) + { + // oAttDef is either + // - an external KEY / FIELD (direct), + // - an external field pointing to an external KEY / FIELD + // - an external field pointing to an external field pointing to.... + + // Get the real external key attribute + // It will be our reference to determine the other ext fields related to the same ext key + $oFinalKeyAttDef = $oAttDef->GetKeyAttDef(EXTKEY_ABSOLUTE); + + self::$m_aExtKeyFriends[$sClass][$sAttCode] = array(); + foreach (self::GetExternalFields($sClass, $oAttDef->GetKeyAttCode($sAttCode)) as $oExtField) + { + // skip : those extfields will be processed as external keys + if ($oExtField->IsExternalKey(EXTKEY_ABSOLUTE)) continue; + + // Note: I could not compare the objects by the mean of '===' + // because they are copied for the inheritance, and the internal references are NOT updated + if ($oExtField->GetKeyAttDef(EXTKEY_ABSOLUTE) == $oFinalKeyAttDef) + { + self::$m_aExtKeyFriends[$sClass][$sAttCode][$oExtField->GetCode()] = $oExtField; + } + } + } + } + + // Add a 'id' filter + // + if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('id', self::$m_aFilterDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code"); + } + $oFilter = new FilterPrivateKey('id', array('id_field' => self::DBGetKey($sClass))); + self::$m_aFilterDefs[$sClass]['id'] = $oFilter; + self::$m_aFilterOrigins[$sClass]['id'] = $sClass; + + // Add a 'class' attribute/filter to the root classes and their children + // + if (!self::IsStandaloneClass($sClass)) + { + if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); + } + $sClassAttCode = 'finalclass'; + $sRootClass = self::GetRootClass($sClass); + $sDbFinalClassField = self::DBGetClassField($sRootClass); + $oClassAtt = new AttributeClass($sClassAttCode, array( + "label"=>"Class", + "description"=>"Real (final) object class", + "class_category"=>null, + "more_values"=>'', + "sql"=>$sDbFinalClassField, + "default_value"=>$sClass, + "is_null_allowed"=>false, + "depends_on"=>array() + )); + self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt; + self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass; + + $oClassFlt = new FilterFromAttribute($oClassAtt); + self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; + self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); + } + + // Define defaults values for the standard ZLists + // + //foreach (self::$m_aListInfos as $sListCode => $aListConfig) + //{ + // if (!isset(self::$m_aListData[$sClass][$sListCode])) + // { + // $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); + // self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; + // //echo "

    $sClass: $sListCode (".count($aAllAttributes)." attributes)

    \n"; + // } + //} + } + + } + + // To be overriden, must be called for any object class (optimization) + public static function Init() + { + // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) + } + // To be overloaded by biz model declarations + public static function GetRelationQueries($sRelCode) + { + // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) + return array(); + } + + public static function Init_Params($aParams) + { + // Check mandatory params + $aMandatParams = array( + "category" => "group classes by modules defining their visibility in the UI", + "name" => "internal class name, may be different than the PHP class name", + "description" => "detailed (though one line) description of the class", + "key_type" => "autoincrement | string", + "key_label" => "if set, then display the key as an attribute", + "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", + "state_attcode" => "define wich attribute is representing the state (object lifecycle)", + "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", + "db_table" => "database table", + "db_key_field" => "database field which is the key", + "db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class", + ); + + $sClass = self::GetCallersPHPClass("Init"); + if (!array_key_exists("name", $aParams)) + { + throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})"); + } + + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aParams)) + { + throw new CoreException("Declaration of class $sClass - missing parameter $sParamName"); + } + } + + $aCategories = explode(',', $aParams['category']); + foreach ($aCategories as $sCategory) + { + self::$m_Category2Class[$sCategory][] = $sClass; + } + self::$m_Category2Class[''][] = $sClass; // all categories, include this one + + + self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance + self::$m_aParentClasses[$sClass] = array(); + self::$m_aChildClasses[$sClass] = array(); + + self::$m_aClassParams[$sClass]= $aParams; + + self::$m_aAttribDefs[$sClass] = array(); + self::$m_aAttribOrigins[$sClass] = array(); + self::$m_aExtKeyFriends[$sClass] = array(); + self::$m_aFilterDefs[$sClass] = array(); + self::$m_aFilterOrigins[$sClass] = array(); + } + + protected static function object_array_mergeclone($aSource1, $aSource2) + { + $aRes = array(); + foreach ($aSource1 as $key=>$object) + { + $aRes[$key] = clone $object; + } + foreach ($aSource2 as $key=>$object) + { + $aRes[$key] = clone $object; + } + return $aRes; + } + + public static function Init_InheritAttributes($sSourceClass = null) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no attributes for the mother of all classes + } + if (isset(self::$m_aAttribDefs[$sSourceClass])) + { + if (!isset(self::$m_aAttribDefs[$sTargetClass])) + { + self::$m_aAttribDefs[$sTargetClass] = array(); + self::$m_aAttribOrigins[$sTargetClass] = array(); + } + self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); + self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); + } + // later on, we might consider inheritance in different ways !!! + //if (strlen(self::DBGetTable($sSourceClass)) != 0) + if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass])) + { + // Inherit the root class + self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; + } + else + { + // I am a root class, standalone as well ! + // ???? + //self::$m_aRootClasses[$sTargetClass] = $sTargetClass; + } + self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass]; + self::$m_aParentClasses[$sTargetClass][] = $sSourceClass; + // I am the child of each and every parent... + foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass) + { + self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass; + } + } + public static function Init_OverloadAttributeParams($sAttCode, $aParams) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + if (!self::IsValidAttCode($sTargetClass, $sAttCode)) + { + throw new CoreException("Could not overload '$sAttCode', expecting a code from {".implode(", ", self::GetAttributesList($sTargetClass))."}"); + } + self::$m_aAttribDefs[$sTargetClass][$sAttCode]->OverloadParams($aParams); + } + public static function Init_AddAttribute(AttributeDefinition $oAtt) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; + self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + + // Specific case of external fields: + // I wanted to simplify the syntax of the declaration of objects in the biz model + // Therefore, the reference to the host class is set there + $oAtt->SetHostClass($sTargetClass); + } + + public static function Init_InheritFilters($sSourceClass = null) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no filters for the mother of all classes + } + if (isset(self::$m_aFilterDefs[$sSourceClass])) + { + if (!isset(self::$m_aFilterDefs[$sTargetClass])) + { + self::$m_aFilterDefs[$sTargetClass] = array(); + self::$m_aFilterOrigins[$sTargetClass] = array(); + } + + foreach (self::$m_aFilterDefs[$sSourceClass] as $sFltCode=>$oFilter) + { + if ($oFilter instanceof FilterFromAttribute) + { + // In that case, cloning is not enough: + // we must ensure that we will point to the correct + // attribute definition (in case some properties are overloaded) + $oAttDef1 = $oFilter->__GetRefAttribute(); + $oAttDef2 = self::GetAttributeDef($sTargetClass, $oAttDef1->GetCode()); + $oNewFilter = new FilterFromAttribute($oAttDef2); + } + else + { + $oNewFilter = clone $oFilter; + } + self::$m_aFilterDefs[$sTargetClass][$sFltCode] = $oNewFilter; + } + + self::$m_aFilterOrigins[$sTargetClass] = array_merge(self::$m_aFilterOrigins[$sTargetClass], self::$m_aFilterOrigins[$sSourceClass]); + } + } + + public static function Init_OverloadFilterParams($sFltCode, $aParams) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + if (!self::IsValidFilterCode($sTargetClass, $sFltCode)) + { + throw new CoreException("Could not overload '$sFltCode', expecting a code from {".implode(", ", self::GetFiltersList($sTargetClass))."}"); + } + self::$m_aFilterDefs[$sTargetClass][$sFltCode]->OverloadParams($aParams); + } + + public static function Init_AddFilter(FilterDefinition $oFilter) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aFilterDefs[$sTargetClass][$oFilter->GetCode()] = $oFilter; + self::$m_aFilterOrigins[$sTargetClass][$oFilter->GetCode()] = $sTargetClass; + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + } + public static function Init_AddFilterFromAttribute($sAttCode) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + $oAttDef = self::GetAttributeDef($sTargetClass, $sAttCode); + + $sFilterCode = $sAttCode; + $oNewFilter = new FilterFromAttribute($oAttDef); + self::$m_aFilterDefs[$sTargetClass][$sFilterCode] = $oNewFilter; + + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $oKeyDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); + self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $oKeyDef->GetTargetClass(); + } + else + { + self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $sTargetClass; + } + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + } + + public static function Init_SetZListItems($sListCode, $aItems) + { + MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); + + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aListData[$sTargetClass][$sListCode] = $aItems; + } + + public static function Init_DefineState($sStateCode, $aStateDef) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); + + $sParentState = $aStateDef['attribute_inherit']; + if (!empty($sParentState)) + { + // Inherit from the given state (must be defined !) + $aToInherit = self::$m_aStates[$sTargetClass][$sParentState]; + // The inherited configuration could be overriden + $aStateDef['attribute_list'] = array_merge($aToInherit, $aStateDef['attribute_list']); + } + self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef; + + // by default, create an empty set of transitions associated to that state + self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); + } + + public static function Init_DefineStimulus($sStimulusCode, $oStimulus) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; + } + + public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); + self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef; + } + + public static function Init_InheritLifecycle($sSourceClass = '') + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no attributes for the mother of all classes + } + + self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"]; + self::$m_aStates[$sTargetClass] = clone self::$m_aStates[$sSourceClass]; + self::$m_aStimuli[$sTargetClass] = clone self::$m_aStimuli[$sSourceClass]; + self::$m_aTransitions[$sTargetClass] = clone self::$m_aTransitions[$sSourceClass]; + } + + // + // Static API + // + + public static function GetRootClass($sClass = null) + { + self::_check_subclass($sClass); + return self::$m_aRootClasses[$sClass]; + } + public static function IsRootClass($sClass) + { + self::_check_subclass($sClass); + return (self::GetRootClass($sClass) == $sClass); + } + public static function EnumParentClasses($sClass) + { + self::_check_subclass($sClass); + return self::$m_aParentClasses[$sClass]; + } + public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) + { + self::_check_subclass($sClass); + + $aRes = self::$m_aChildClasses[$sClass]; + if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP) + { + // Add it to the list + $aRes[] = $sClass; + } + return $aRes; + } + + public static function EnumCategories() + { + return array_keys(self::$m_Category2Class); + } + + // Note: use EnumChildClasses to take the compound objects into account + public static function GetSubclasses($sClass) + { + self::_check_subclass($sClass); + $aSubClasses = array(); + foreach(get_declared_classes() as $sSubClass) { + if (is_subclass_of($sSubClass, $sClass)) + { + $aSubClasses[] = $sSubClass; + } + } + return $aSubClasses; + } + public static function GetClasses($sCategory = '') + { + if (array_key_exists($sCategory, self::$m_Category2Class)) + { + return self::$m_Category2Class[$sCategory]; + } + + if (count(self::$m_Category2Class) > 0) + { + throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); + } + return array(); + } + + public static function IsAbstract($sClass) + { + if (strlen(self::DBGetTable($sClass)) == 0) return true; + return false; + } + + protected static $m_aQueryStructCache = array(); + + protected static function PrepareQueryArguments($aArgs) + { + // Translate any object into scalars + // + $aScalarArgs = array(); + foreach($aArgs as $sArgName => $value) + { + if (self::IsValidObject($value)) + { + $aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName)); + } + else + { + $aScalarArgs[$sArgName] = (string) $value; + } + } + // Add standard contextual arguments + // + $aScalarArgs['current_contact_id'] = UserRights::GetContactId(); + + return $aScalarArgs; + } + + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) + { + // Query caching + // + $bQueryCacheEnabled = true; + $aParams = array(); + $sOqlQuery = $oFilter->ToOql($aParams); // Render with arguments in clear + if ($bQueryCacheEnabled) + { + if (array_key_exists($sOqlQuery, self::$m_aQueryStructCache)) + { + // hit! + $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; + } + } + + if (!isset($oSelect)) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + + $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + + self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; + } + + // Check the order by specification, and prefix with the class alias + // + $aOrderSpec = array(); + foreach ($aOrderBy as $sFieldAlias => $bAscending) + { + MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); + if (!is_bool($bAscending)) + { + throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); + } + $aOrderSpec[$oFilter->GetClassAlias().$sFieldAlias] = $bAscending; + } + // By default, force the name attribute to be the ordering key + // + if (empty($aOrderSpec)) + { + $sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass()); + if (!empty($sNameAttCode)) + { + // By default, simply order on the "name" attribute, ascending + $aOrderSpec[$oFilter->GetClassAlias().$sNameAttCode] = true; + } + } + + // Go + // + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + + try + { + $sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs); + } + catch (MissingQueryArgument $e) + { + // Add some information... + $e->addInfo('OQL', $sOqlQuery); + throw $e; + } + + if (self::$m_bTraceQueries) + { + $aParams = array(); + if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog)) + { + self::$m_aQueriesLog[$sOqlQuery] = array( + 'sql' => array(), + 'count' => 0, + ); + } + self::$m_aQueriesLog[$sOqlQuery]['count']++; + self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes; + } + + return $sRes; + } + + public static function ShowQueryTrace() + { + $iTotal = 0; + foreach (self::$m_aQueriesLog as $sOql => $aQueryData) + { + echo "

    $sOql

    \n"; + $iTotal += $aQueryData['count']; + echo '

    '.$aQueryData['count'].'

    '; + echo '

    Example: '.$aQueryData['sql'][0].'

    '; + } + echo "

    Total

    \n"; + echo "

    Count of executed queries: $iTotal

    "; + echo "

    Count of built queries: ".count(self::$m_aQueriesLog)."

    "; + } + + public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + return $oSelect->RenderDelete($aScalarArgs); + } + + public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) + { + // $aValues is an array of $sAttCode => $value + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues); + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + return $oSelect->RenderUpdate($aScalarArgs); + } + + private static function MakeQuery($aSelectedClasses, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array()) + { + // Note: query class might be different than the class of the filter + // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) + // $aExpectedAtts is an array of sAttCode=>array of columns + $sClass = $oFilter->GetClass(); + $sClassAlias = $oFilter->GetClassAlias(); + + $bIsOnQueriedClass = array_key_exists($sClassAlias, $aSelectedClasses); + if ($bIsOnQueriedClass) + { + $aClassAliases = array_merge($aClassAliases, $oFilter->GetJoinedClasses()); + } + + self::DbgTrace("Entering: ".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", array_keys($aExpectedAtts))); + + $sRootClass = self::GetRootClass($sClass); + $sKeyField = self::DBGetKey($sClass); + + if ($bIsOnQueriedClass) + { + // default to the whole list of attributes + the very std id/finalclass + $aExpectedAtts['id'][] = $sClassAlias.'id'; + foreach (self::GetAttributesList($sClass) as $sAttCode) + { + $aExpectedAtts[$sAttCode][] = $sClassAlias.$sAttCode; // alias == class and attcode + } + } + + // Compute a clear view of external keys, and external attributes + // Build the list of external keys: + // -> ext keys required by a closed join ??? + // -> ext keys mentionned in a 'pointing to' condition + // -> ext keys required for an external field + // + $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) + // + // Optimization: could be computed once for all (cached) + // Could be done in MakeQuerySingleTable ??? + // + + if ($bIsOnQueriedClass) + { + // Get all Ext keys for the queried class (??) + foreach(self::GetKeysList($sClass) as $sKeyAttCode) + { + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + } + // Get all Ext keys used by the filter + foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $trash) + { + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + // Add the ext fields used in the select (eventually adds an external key) + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if (array_key_exists($sAttCode, $aExpectedAtts) || $oConditionTree->RequiresField($sClassAlias, $sAttCode)) + { + // Add the external attribute + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; + } + } + } + + // First query built upon on the leaf (ie current) class + // + self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); + $oSelectBase = self::MakeQuerySingleTable($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sClass, $aExpectedAtts, $aExtKeys, $aValues); + + // Then we join the queries of the eventual parent classes (compound model) + foreach(self::EnumParentClasses($sClass) as $sParentClass) + { + if (self::DBGetTable($sParentClass) == "") continue; + self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); + $oSelectParentTable = self::MakeQuerySingleTable($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sParentClass, $aExpectedAtts, $aExtKeys, $aValues); + $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass)); + } + + // Filter on objects referencing me + foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) + { + foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) + { + $oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); + + // We don't want any attribute from the foreign class, just filter on an inner join + $aExpAtts = array(); + + self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()"); + //self::DbgTrace($oForeignFilter); + //self::DbgTrace($oForeignFilter->ToSibuSQL()); + //self::DbgTrace($oSelectForeign); + //self::DbgTrace($oSelectForeign->RenderSelect(array())); + $oSelectForeign = self::MakeQuery($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts); + + $sForeignClassAlias = $oForeignFilter->GetClassAlias(); + $sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0]; + $sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1]; + $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); + } + } + + // Filter on related objects + // + foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo) + { + $oSubFilter = $aCritInfo['flt']; + $sRelCode = $aCritInfo['relcode']; + $iMaxDepth = $aCritInfo['maxdepth']; + + // Get the starting point objects + $oStartSet = new CMDBObjectSet($oSubFilter); + + // Get the objects related to those objects... recursively... + $aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth); + $aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array(); + + // #@# todo - related objects and expressions... + // Create condition + if (count($aRestriction) > 0) + { + $oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')'); + } + else + { + // Quick N'dirty -> generate an empty set + $oSelectBase->AddCondition('false'); + } + } + + // Translate the conditions... and go + // + if ($bIsOnQueriedClass) + { + $oConditionTranslated = $oConditionTree->Translate($aTranslation); + $oSelectBase->SetCondition($oConditionTranslated); + } + + // That's all... cross fingers and we'll get some working query + + //MyHelpers::var_dump_html($oSelectBase, true); + //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); + if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); + return $oSelectBase; + } + + protected static function MakeQuerySingleTable($aSelectedClasses, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, $oFilter, $sTableClass, $aExpectedAtts, $aExtKeys, $aValues) + { + // $aExpectedAtts is an array of sAttCode=>sAlias + // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) + + // Prepare the query for a single table (compound objects) + // Ignores the items (attributes/filters) that are not on the target table + // Perform an (inner or left) join for every external key (and specify the expected fields) + // + // Returns an SQLQuery + // + $sTargetClass = $oFilter->GetClass(); + $sTargetAlias = $oFilter->GetClassAlias(); + $sTable = self::DBGetTable($sTableClass); + $sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable); + + $bIsOnQueriedClass = array_key_exists($sTargetAlias, $aSelectedClasses); + + self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", array_keys($aExpectedAtts))); + + // 1 - SELECT and UPDATE + // + // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) + // + $aSelect = array(); + $aUpdateValues = array(); + + // 1/a - Get the key + // + if ($bIsOnQueriedClass) + { + $aSelect[$aExpectedAtts['id'][0]] = new FieldExpression(self::DBGetKey($sTableClass), $sTableAlias); + } + // We need one pkey to be the key, let's take the one corresponding to the leaf + if ($sTableClass == $sTargetClass) + { + $aTranslation[$sTargetAlias]['id'] = array($sTableAlias, self::DBGetKey($sTableClass)); + } + + // 1/b - Get the other attributes + // + foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; + + // Skip this attribute if not writable (means that it does not correspond + if (count($oAttDef->GetSQLExpressions()) == 0) continue; + + // Update... + // + if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) + { + assert ($oAttDef->IsDirectField()); + foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) + { + $aUpdateValues[$sColumn] = $sValue; + } + } + + // Select... + // + // Skip, if a list of fields has been specified and it is not there + if (!array_key_exists($sAttCode, $aExpectedAtts)) continue; + + if ($oAttDef->IsExternalField()) + { + // skip, this will be handled in the joined tables + } + else + { + // standard field, or external key + // add it to the output + foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) + { + foreach ($aExpectedAtts[$sAttCode] as $sAttAlias) + { + $aSelect[$sAttAlias.$sColId] = new FieldExpression($sSQLExpr, $sTableAlias); + } + } + } + } + + // 2 - WHERE + // + foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt) + { + // Skip this filter if not defined in this table + if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue; + + // #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression" + foreach($oFltAtt->GetSQLExpressions() as $sColID => $sFltExpr) + { + // Note: I did not test it with filters relying on several expressions... + // as long as sColdID is empty, this is working, otherwise... ? + $aTranslation[$sTargetAlias][$sFltCode.$sColID] = array($sTableAlias, $sFltExpr); + } + } + + // #@# todo - See what a full text search condition should be + // 2' - WHERE / Full text search condition + // + if ($bIsOnQueriedClass) + { + $aFullText = $oFilter->GetCriteria_FullText(); + } + else + { + // Pourquoi ??? + $aFullText = array(); + } + + // 3 - The whole stuff, for this table only + // + $oSelectBase = new SQLQuery($sTable, $sTableAlias, $aSelect, null, $aFullText, $bIsOnQueriedClass, $aUpdateValues); + + // 4 - The external keys -> joins... + // + if (array_key_exists($sTableClass, $aExtKeys)) + { + foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) + { + $oKeyAttDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); + + $oExtFilter = $oFilter->GetCriteria_PointingTo($sKeyAttCode); + + // In case the join was not explicitely defined in the filter, + // we need to do it now + if (empty($oExtFilter)) + { + $sKeyClass = $oKeyAttDef->GetTargetClass(); + $sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass); + $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); + } + else + { + // The aliases should not conflict because normalization occured while building the filter + $sKeyClass = $oExtFilter->GetClass(); + $sKeyClassAlias = $oExtFilter->GetClassAlias(); + + // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree + } + + // Specify expected attributes for the target class query + // ... and use the current alias ! + $aExpAtts = array(); + $aIntermediateTranslation = array(); + foreach($aExtFields as $sAttCode => $oAtt) + { + + $sExtAttCode = $oAtt->GetExtAttCode(); + if (array_key_exists($sAttCode, $aExpectedAtts)) + { + // Request this attribute... transmit the alias ! + $aExpAtts[$sExtAttCode] = $aExpectedAtts[$sAttCode]; + } + // Translate mainclass.extfield => remoteclassalias.remotefieldcode + $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); + foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) + { + $aIntermediateTranslation[$sTargetAlias.$sColID][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); + } + //#@# debug - echo "

    $sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

    \n"; + } + $oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false); + + self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); + $oSelectExtKey = self::MakeQuery($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); + + $sLocalKeyField = current($oKeyAttDef->GetSQLExpressions()); // get the first column for an external key + $sExternalKeyField = self::DBGetKey($sKeyClass); + self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); + if ($oKeyAttDef->IsNullAllowed()) + { + $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); + } + else + { + $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); + } + } + } + + //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); + return $oSelectBase; + } + + public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) + { + if (!array_key_exists($sNewName, $aAliases)) + { + $aAliases[$sNewName] = $sRealName; + return $sNewName; + } + + for ($i = 1 ; $i < 100 ; $i++) + { + $sAnAlias = $sNewName.$i; + if (!array_key_exists($sAnAlias, $aAliases)) + { + // Create that new alias + $aAliases[$sAnAlias] = $sRealName; + return $sAnAlias; + } + } + throw new CoreException('Failed to create an alias', array('aliases' => $aAliases, 'new'=>$sNewName)); + } + + public static function CheckDefinitions() + { + if (count(self::GetClasses()) == 0) + { + throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!"); + } + + $aErrors = array(); + $aSugFix = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sNameAttCode = self::GetNameAttributeCode($sClass); + if (empty($sNameAttCode)) + { + // let's try this !!! + // $aErrors[$sClass][] = "Missing value for name definition: the framework will (should...) replace it by the id"; + // $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + else if(!self::IsValidAttCode($sClass, $sNameAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sNameAttCode."' for the name definition"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + + foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode) + if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // It makes no sense to check the attributes again and again in the subclasses + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + if ($oAttDef->IsExternalKey()) + { + if (!self::IsValidClass($oAttDef->GetTargetClass())) + { + $aErrors[$sClass][] = "Unkown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}"; + } + } + elseif ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode)) + { + $aErrors[$sClass][] = "Unkown key attribute code '".$sKeyAttCode."' for the external field $sAttCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}"; + } + else + { + $oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode); + $sTargetClass = $oKeyAttDef->GetTargetClass(); + $sExtAttCode = $oAttDef->GetExtAttCode(); + if (!self::IsValidAttCode($sTargetClass, $sExtAttCode)) + { + $aErrors[$sClass][] = "Unkown key attribute code '".$sExtAttCode."' for the external field $sAttCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}"; + } + } + } + else // standard attributes + { + // Check that the default values definition is a valid object! + $oValSetDef = $oAttDef->GetValuesDef(); + if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition) + { + $aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type"; + $aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object."; + } + else + { + // Default value must be listed in the allowed values (if defined) + $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); + if (!is_null($aAllowedValues)) + { + $sDefaultValue = $oAttDef->GetDefaultValue(); + if (!array_key_exists($sDefaultValue, $aAllowedValues)) + { + $aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value"; + $aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}"; + } + } + } + } + // Check dependencies + if ($oAttDef->IsWritable()) + { + foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode) + { + if (!self::IsValidAttCode($sClass, $sDependOnAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + } + } + } + foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef) + { + if (method_exists($oFilterDef, '__GetRefAttribute')) + { + $oAttDef = $oFilterDef->__GetRefAttribute(); + if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) + { + $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + } + } + + // Lifecycle + // + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (strlen($sStateAttCode) > 0) + { + // Lifecycle - check that the state attribute does exist as an attribute + if (!self::IsValidAttCode($sClass, $sStateAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sStateAttCode."' for the state definition"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + else + { + // Lifecycle - check that there is a value set constraint on the state attribute + $aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode); + $aStates = array_keys(self::EnumStates($sClass)); + if (is_null($aAllowedValuesRaw)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values"; + $aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]"; + } + else + { + $aAllowedValues = array_keys($aAllowedValuesRaw); + + // Lifecycle - check the the state attribute allowed values are defined states + foreach($aAllowedValues as $sValue) + { + if (!in_array($sValue, $aStates)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state"; + $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; + } + } + + // Lifecycle - check that defined states are allowed values + foreach($aStates as $sStateValue) + { + if (!in_array($sStateValue, $aAllowedValues)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value"; + $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; + } + } + } + + // Lifcycle - check that the action handlers are defined + foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef) + { + foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) + { + foreach ($aTransitionDef['actions'] as $sActionHandler) + { + if (!method_exists($sClass, $sActionHandler)) + { + $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; + $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; + } + } + } + } + } + } + + // ZList + // + foreach(self::EnumZLists() as $sListCode) + { + foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode) + { + if (!self::IsValidAttCode($sClass, $sMyAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sMyAttCode."' from ZList '$sListCode'"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + } + } + } + if (count($aErrors) > 0) + { + echo "
    "; + echo "

    Business model inconsistencies have been found

    \n"; + // #@# later -> this is the responsibility of the caller to format the output + foreach ($aErrors as $sClass => $aMessages) + { + echo "

    Wrong declaration for class $sClass

    \n"; + echo "
      \n"; + $i = 0; + foreach ($aMessages as $sMsg) + { + echo "
    • $sMsg ({$aSugFix[$sClass][$i]})
    • \n"; + $i++; + } + echo "
    \n"; + } + echo "

    Aborting...

    \n"; + echo "
    \n"; + exit; + } + } + + public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) + { + if (empty($sRepairUrl)) return; + if (count($aSQLFixes) == 0) return; + + echo "
    \n"; + echo " \n"; + echo " \n"; + echo "
    \n"; + } + + public static function DBExists($bMustBeComplete = true) + { + // returns true if at least one table exists + // + + if (!CMDBSource::IsDB(self::$m_sDBName)) + { + return false; + } + CMDBSource::SelectDB(self::$m_sDBName); + + $aFound = array(); + $aMissing = array(); + foreach (self::DBEnumTables() as $sTable => $aClasses) + { + if (CMDBSource::IsTable($sTable)) + { + $aFound[] = $sTable; + } + else + { + $aMissing[] = $sTable; + } + } + + if (count($aFound) == 0) + { + // no expected table has been found + return false; + } + else + { + if (count($aMissing) == 0) + { + // the database is complete (still, could be some fields missing!) + return true; + } + else + { + // not all the tables, could be an older version + if ($bMustBeComplete) + { + return false; + } + else + { + return true; + } + } + } + } + + public static function DBDrop() + { + $bDropEntireDB = true; + + if (!empty(self::$m_sTablePrefix)) + { + // Do drop only tables corresponding to the sub-database (table prefix) + // then possibly drop the DB itself (if no table remain) + foreach (CMDBSource::EnumTables() as $sTable) + { + // perform a case insensitive test because on Windows the table names become lowercase :-( + if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix)) + { + CMDBSource::DropTable($sTable); + } + else + { + // There is at least one table which is out of the scope of the current application + $bDropEntireDB = false; + } + } + } + + if ($bDropEntireDB) + { + CMDBSource::DropDB(self::$m_sDBName); + } + } + + + public static function DBCreate() + { + // Note: we have to check if the DB does exist, because we may share the DB + // with other applications (in which case the DB does exist, not the tables with the given prefix) + if (!CMDBSource::IsDB(self::$m_sDBName)) + { + CMDBSource::CreateDB(self::$m_sDBName); + } + self::DBCreateTables(); + } + + protected static function DBCreateTables() + { + list($aErrors, $aSugFix) = self::DBCheckFormat(); + + $aSQL = array(); + foreach ($aSugFix as $sClass => $aTarget) + { + foreach ($aTarget as $aQueries) + { + foreach ($aQueries as $sQuery) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } + } + } + // does not work -how to have multiple statements in a single query? + // $sDoCreateAll = implode(" ; ", $aSQL); + } + + public static function DBDump() + { + $aDataDump = array(); + foreach (self::DBEnumTables() as $sTable => $aClasses) + { + $aRows = CMDBSource::DumpTable($sTable); + $aDataDump[$sTable] = $aRows; + } + return $aDataDump; + } + + public static function DBCheckFormat() + { + $aErrors = array(); + $aSugFix = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + // Check that the table exists + // + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + $sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : ""); + if (!CMDBSource::IsTable($sTable)) + { + $aErrors[$sClass]['*'][] = "table '$sTable' could not be found into the DB"; + $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; + } + // Check that the key field exists + // + elseif (!CMDBSource::IsField($sTable, $sKeyField)) + { + $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) could not be found"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; + } + else + { + // Check the key field properties + // + if (!CMDBSource::IsKey($sTable, $sKeyField)) + { + $aErrors[$sClass]['id'][] = "key '$sKeyField' is not a key for table '$sTable'"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; + } + if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField)) + { + $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; + } + } + + // Check that any defined field exists + // + $aTableInfo = CMDBSource::GetTableInfo($sTable); + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not originaly defined in this class + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) + { + $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; + if (!CMDBSource::IsField($sTable, $sField)) + { + $aErrors[$sClass][$sAttCode][] = "field '$sField' could not be found in table '$sTable'"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; + if ($oAttDef->IsExternalKey()) + { + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; + } + } + else + { + // The field already exists, does it have the relevant properties? + // + $bToBeChanged = false; + if ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) + { + $bToBeChanged = true; + if ($oAttDef->IsNullAllowed()) + { + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could be NULL"; + } + else + { + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could NOT be NULL"; + } + } + $sActualFieldType = CMDBSource::GetFieldType($sTable, $sField); + if (strcasecmp($sDBFieldType, $sActualFieldType) != 0) + { + $bToBeChanged = true; + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; + } + if ($bToBeChanged) + { + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + + // Create indexes (external keys only... so far) + // + if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) + { + $aErrors[$sClass][$sAttCode][] = "Foreign key '$sField' in table '$sTable' should have an index"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; + } + } + } + } + } + return array($aErrors, $aSugFix); + } + + + private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false) + { + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) + { + $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; + } + $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); + if (count($aWrongRecords) == 0) return; + + if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); + if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); + + foreach ($aWrongRecords as $iRecordId) + { + if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) + { + switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) + { + case 'Delete': + // Already planned for a deletion + // Let's concatenate the errors description together + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; + break; + + case 'Update': + // Let's plan a deletion + break; + } + } + else + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; + } + + if (!$bProcessingFriends) + { + if (!array_key_exists($sTable, $aPlannedDel) || !in_array($iRecordId, $aPlannedDel[$sTable])) + { + // Something new to be deleted... + $iNewDelCount++; + } + } + + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Delete'; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; + $aPlannedDel[$sTable][] = $iRecordId; + } + + // Now make sure that we would delete the records of the other tables for that class + // + if (!$bProcessingFriends) + { + $sDeleteKeys = "'".implode("', '", $aWrongRecords)."'"; + foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sFriendClass) + { + $sFriendTable = self::DBGetTable($sFriendClass); + $sFriendKey = self::DBGetKey($sFriendClass); + + // skip the current table + if ($sFriendTable == $sTable) continue; + + $sFindRelatedRec = "SELECT DISTINCT maintable.`$sFriendKey` AS id FROM `$sFriendTable` AS maintable WHERE maintable.`$sFriendKey` IN ($sDeleteKeys)"; + self::DBCheckIntegrity_Check2Delete($sFindRelatedRec, "Cascading deletion of record in friend table `$sTable`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true); + } + } + } + + private static function DBCheckIntegrity_Check2Update($sSelWrongRecs, $sErrorDesc, $sColumn, $sNewValue, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) + { + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) + { + $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; + } + $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); + if (count($aWrongRecords) == 0) return; + + if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); + if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); + + foreach ($aWrongRecords as $iRecordId) + { + if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) + { + switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) + { + case 'Delete': + // No need to update, the record will be deleted! + break; + + case 'Update': + // Already planned for an update + // Add this new update spec to the list + $bFoundSameSpec = false; + foreach ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] as $aUpdateSpec) + { + if (($sColumn == $aUpdateSpec['column']) && ($sNewValue == $aUpdateSpec['newvalue'])) + { + $bFoundSameSpec = true; + } + } + if (!$bFoundSameSpec) + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'][] = (array('column' => $sColumn, 'newvalue'=>$sNewValue)); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; + } + break; + } + } + else + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Update'; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(array('column' => $sColumn, 'newvalue'=>$sNewValue)); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; + } + + } + } + + // returns the count of records found for deletion + public static function DBCheckIntegrity_SinglePass(&$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) + { + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + // Check that the final class field contains the name of a class which inherited from the current class + // + if (self::HasFinalClassField($sClass)) + { + $sFinalClassField = self::DBGetClassField($sClass); + + $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); + + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + // Compound objects model - node/leaf classes (not the root itself) + // + if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass)) + { + $sRootTable = self::DBGetTable($sRootClass); + $sRootKey = self::DBGetKey($sRootClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + + $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); + + // Check that any record found here has its counterpart in the root table + // and which refers to a child class + // + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `$sTable`, but no counterpart in root table `$sRootTable` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + + // Check that any record found in the root table and referring to a child class + // has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy) + // + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + if ($oAttDef->IsExternalKey()) + { + // Check that any external field is pointing to an existing object + // + $sRemoteClass = $oAttDef->GetTargetClass(); + $sRemoteTable = self::DBGetTable($sRemoteClass); + $sRemoteKey = self::DBGetKey($sRemoteClass); + + $sExtKeyField = current($oAttDef->GetSQLExpressions()); // get the first column for an external key + + // Note: a class/table may have an external key on itself + $sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`"; + + $sSelWrongRecs = $sSelBase." WHERE `$sRemoteTable`.`$sRemoteKey` IS NULL"; + if ($oAttDef->IsNullAllowed()) + { + // Exclude the records pointing to 0/null from the errors + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + // Do almost the same, taking into account the records planned for deletion + if (array_key_exists($sRemoteTable, $aPlannedDel) && count($aPlannedDel[$sRemoteTable]) > 0) + { + // This could be done by the mean of a 'OR ... IN (aIgnoreRecords) + // but in that case you won't be able to track the root cause (cascading) + $sSelWrongRecs = $sSelBase." WHERE maintable.`$sExtKeyField` IN ('".implode("', '", $aPlannedDel[$sRemoteTable])."')"; + if ($oAttDef->IsNullAllowed()) + { + // Exclude the records pointing to 0/null from the errors + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + } + } + else if ($oAttDef->IsDirectField()) + { + // Check that the values fit the allowed values + // + $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); + if (!is_null($aAllowedValues) && count($aAllowedValues) > 0) + { + $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); + + $sMyAttributeField = current($oAttDef->GetSQLExpressions()); // get the first column for the moment + $sDefaultValue = $oAttDef->GetDefaultValue(); + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + } + } + } + } + + public static function DBCheckIntegrity($sRepairUrl = "", $sSQLStatementArgName = "") + { + // Records in error, and action to be taken: delete or update + // by RootClass/Table/Record + $aErrorsAndFixes = array(); + + // Records to be ignored in the current/next pass + // by Table = array of RecordId + $aPlannedDel = array(); + + // Count of errors in the next pass: no error means that we can leave... + $iErrorCount = 0; + // Limit in case of a bug in the algorythm + $iLoopCount = 0; + + $iNewDelCount = 1; // startup... + while ($iNewDelCount > 0) + { + $iNewDelCount = 0; + self::DBCheckIntegrity_SinglePass($aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + $iErrorCount += $iNewDelCount; + + // Safety net #1 - limit the planned deletions + // + $iMaxDel = 1000; + $iPlannedDel = 0; + foreach ($aPlannedDel as $sTable => $aPlannedDelOnTable) + { + $iPlannedDel += count($aPlannedDelOnTable); + } + if ($iPlannedDel > $iMaxDel) + { + throw new CoreWarning("DB Integrity Check safety net - Exceeding the limit of $iMaxDel planned record deletion"); + break; + } + // Safety net #2 - limit the iterations + // + $iLoopCount++; + $iMaxLoops = 10; + if ($iLoopCount > $iMaxLoops) + { + throw new CoreWarning("DB Integrity Check safety net - Reached the limit of $iMaxLoops loops"); + break; + } + } + + // Display the results + // + $iIssueCount = 0; + $aFixesDelete = array(); + $aFixesUpdate = array(); + + foreach ($aErrorsAndFixes as $sRootClass => $aTables) + { + foreach ($aTables as $sTable => $aRecords) + { + foreach ($aRecords as $iRecord => $aError) + { + $sAction = $aError['Action']; + $sReason = $aError['Reason']; + $iPass = $aError['Pass']; + + switch ($sAction) + { + case 'Delete': + $sActionDetails = ""; + $aFixesDelete[$sTable][] = $iRecord; + break; + + case 'Update': + $aUpdateDesc = array(); + foreach($aError['Action_Details'] as $aUpdateSpec) + { + $aUpdateDesc[] = $aUpdateSpec['column']." -> ".$aUpdateSpec['newvalue']; + $aFixesUpdate[$sTable][$aUpdateSpec['column']][$aUpdateSpec['newvalue']][] = $iRecord; + } + $sActionDetails = "Set ".implode(", ", $aUpdateDesc); + + break; + + default: + $sActionDetails = "bug: unknown action '$sAction'"; + } + $aIssues[] = "$sRootClass / $sTable / $iRecord / $sReason / $sAction / $sActionDetails"; + $iIssueCount++; + } + } + } + + if ($iIssueCount > 0) + { + // Build the queries to fix in the database + // + // First step, be able to get class data out of the table name + // Could be optimized, because we've made the job earlier... but few benefits, so... + $aTable2ClassProp = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + $aErrorsAndFixes[$sRootClass][$sTable] = array(); + $aTable2ClassProp[$sTable] = array('rootclass'=>$sRootClass, 'class'=>$sClass, 'keyfield'=>$sKeyField); + } + // Second step, build a flat list of SQL queries + $aSQLFixes = array(); + $iPlannedUpdate = 0; + foreach ($aFixesUpdate as $sTable => $aColumns) + { + foreach ($aColumns as $sColumn => $aNewValues) + { + foreach ($aNewValues as $sNewValue => $aRecords) + { + $iPlannedUpdate += count($aRecords); + $sWrongRecords = "'".implode("', '", $aRecords)."'"; + $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; + + $aSQLFixes[] = "UPDATE `$sTable` SET `$sColumn` = $sNewValue WHERE `$sKeyField` IN ($sWrongRecords)"; + } + } + } + $iPlannedDel = 0; + foreach ($aFixesDelete as $sTable => $aRecords) + { + $iPlannedDel += count($aRecords); + $sWrongRecords = "'".implode("', '", $aRecords)."'"; + $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; + + $aSQLFixes[] = "DELETE FROM `$sTable` WHERE `$sKeyField` IN ($sWrongRecords)"; + } + + // Report the results + // + echo "
    "; + echo "

    Database corruption error(s): $iErrorCount issues have been encountered. $iPlannedDel records will be deleted, $iPlannedUpdate records will be updated:

    \n"; + // #@# later -> this is the responsibility of the caller to format the output + echo "
      \n"; + foreach ($aIssues as $sIssueDesc) + { + echo "
    • $sIssueDesc
    • \n"; + } + echo "
    \n"; + self::DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes); + echo "

    Aborting...

    \n"; + echo "
    \n"; + exit; + } + } + + public static function Startup($sConfigFile, $bAllowMissingDB = false) + { + self::LoadConfig($sConfigFile); + if (self::DBExists()) + { + CMDBSource::SelectDB(self::$m_sDBName); + + // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) + self::InitPlugins(); + } + else + { + if (!$bAllowMissingDB) + { + throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); + } + } + } + + public static function LoadConfig($sConfigFile) + { + $oConfig = new Config($sConfigFile); + + foreach ($oConfig->GetAppModules() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'application', $sToInclude); + } + foreach ($oConfig->GetDataModels() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'business', $sToInclude); + } + foreach ($oConfig->GetAddons() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'addons', $sToInclude); + } + + $sServer = $oConfig->GetDBHost(); + $sUser = $oConfig->GetDBUser(); + $sPwd = $oConfig->GetDBPwd(); + $sSource = $oConfig->GetDBName(); + $sTablePrefix = $oConfig->GetDBSubname(); + + // The include have been included, let's browse the existing classes and + // develop some data based on the proposed model + self::InitClasses($sTablePrefix); + + self::$m_sDBName = $sSource; + self::$m_sTablePrefix = $sTablePrefix; + + CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) + } + + protected static $m_aPlugins = array(); + public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) + { + self::$m_aPlugins[$sName] = array( + 'type' => $sType, + 'init' => $aInitCallSpec, + ); + } + + protected static function Plugin($sConfigFile, $sModuleType, $sToInclude) + { + if (!file_exists($sToInclude)) + { + throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sToInclude)); + } + require_once($sToInclude); + } + + protected static function InitPlugins() + { + foreach(self::$m_aPlugins as $sName => $aData) + { + $aCallSpec = @$aData['init']; + if (count($aCallSpec) == 2) + { + if (!is_callable($aCallSpec)) + { + throw new CoreException('Wrong declaration in plugin', array('plugin' => $aData['name'], 'type' => $aData['type'], 'class' => $aData['class'], 'init' => $aData['init'])); + } + call_user_func($aCallSpec); + } + } + } + + // Building an object + // + // + private static $aQueryCacheGetObject = array(); + private static $aQueryCacheGetObjectHits = array(); + public static function GetQueryCacheStatus() + { + $aRes = array(); + $iTotalHits = 0; + foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits) + { + $aRes[] = "$sClass: $iHits"; + $iTotalHits += $iHits; + } + return $iTotalHits.' ('.implode(', ', $aRes).')'; + } + + public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true) + { + if (!array_key_exists($sClass, self::$aQueryCacheGetObject)) + { + // NOTE: Quick and VERY dirty caching mechanism which relies on + // the fact that the string '987654321' will never appear in the + // standard query + // This will be replaced for sure with a prepared statement + // or a view... next optimization to come! + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', 987654321, '='); + + $sSQL = self::MakeSelectQuery($oFilter); + self::$aQueryCacheGetObject[$sClass] = $sSQL; + self::$aQueryCacheGetObjectHits[$sClass] = 0; + } + else + { + $sSQL = self::$aQueryCacheGetObject[$sClass]; + self::$aQueryCacheGetObjectHits[$sClass] += 1; +// echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."
    \n"; + } + $sSQL = str_replace('987654321', CMDBSource::Quote($iKey), $sSQL); + $res = CMDBSource::Query($sSQL); + + $aRow = CMDBSource::FetchArray($res); + CMDBSource::FreeResult($res); + if ($bMustBeFound && empty($aRow)) + { + throw new CoreException("No result for the single row query: '$sSQL'"); + } + return $aRow; + } + + public static function GetObjectByRow($sClass, $aRow, $sClassAlias = '') + { + self::_check_subclass($sClass); + + // Compound objects: if available, get the final object class + // + if (!array_key_exists("finalclass", $aRow)) + { + // Either this is a bug (forgot to specify a root class with a finalclass field + // Or this is the expected behavior, because the object is not made of several tables + } + elseif (empty($aRow["finalclass"])) + { + // The data is missing in the DB + // @#@ possible improvement: check that the class is valid ! + $sRootClass = self::GetRootClass($sClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + throw new CoreException("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)"); + } + else + { + // do the job for the real target class + $sClass = $aRow["finalclass"]; + } + return new $sClass($aRow, $sClassAlias); + } + + public static function GetObject($sClass, $iKey, $bMustBeFound = true) + { + self::_check_subclass($sClass); + $aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound); + if (empty($aRow)) + { + return null; + } + return self::GetObjectByRow($sClass, $aRow); + } + + public static function GetHyperLink($sTargetClass, $iKey) + { + if ($iKey < 0) + { + return "$sTargetClass: $iKey (invalid value)"; + } + $oObj = self::GetObject($sTargetClass, $iKey, false); + if (is_null($oObj)) + { + return "$sTargetClass: $iKey (not found)"; + } + return $oObj->GetHyperLink(); + } + + public static function NewObject($sClass) + { + self::_check_subclass($sClass); + return new $sClass(); + } + + public static function GetNextKey($sClass) + { + $sRootClass = MetaModel::GetRootClass($sClass); + $sRootTable = MetaModel::DBGetTable($sRootClass); + $iNextKey = CMDBSource::GetNextInsertId($sRootTable); + return $iNextKey; + } + + public static function BulkDelete(DBObjectSearch $oFilter) + { + $sSQL = self::MakeDeleteQuery($oFilter); + CMDBSource::Query($sSQL); + } + + public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) + { + // $aValues is an array of $sAttCode => $value + $sSQL = self::MakeUpdateQuery($oFilter, $aValues); + CMDBSource::Query($sSQL); + } + + // Links + // + // + public static function EnumReferencedClasses($sClass) + { + self::_check_subclass($sClass); + + // 1-N links (referenced by my class), returns an array of sAttCode=>sClass + $aResult = array(); + foreach(self::$m_aAttribDefs[$sClass] as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aResult[$sAttCode] = $oAttDef->GetTargetClass(); + } + } + return $aResult; + } + public static function EnumReferencingClasses($sClass, $bSkipLinkingClasses = false, $bInnerJoinsOnly = false) + { + self::_check_subclass($sClass); + + if ($bSkipLinkingClasses) + { + $aLinksClasses = self::EnumLinksClasses(); + } + + // 1-N links (referencing my class), array of sClass => array of sAttcode + $aResult = array(); + foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) + { + if ($bSkipLinkingClasses && in_array($sSomeClass, $aLinksClasses)) continue; + + $aExtKeys = array(); + foreach ($aClassAttributes as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey() && (self::IsParentClass($oAttDef->GetTargetClass(), $sClass))) + { + if ($bInnerJoinsOnly && $oAttDef->IsNullAllowed()) continue; + // Ok, I want this one + $aExtKeys[$sAttCode] = $oAttDef; + } + } + if (count($aExtKeys) != 0) + { + $aResult[$sSomeClass] = $aExtKeys; + } + } + return $aResult; + } + public static function EnumLinksClasses() + { + // Returns a flat array of classes having at least two external keys + $aResult = array(); + foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) + { + $iExtKeyCount = 0; + foreach ($aClassAttributes as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey()) + { + $iExtKeyCount++; + } + } + if ($iExtKeyCount >= 2) + { + $aResult[] = $sSomeClass; + } + } + return $aResult; + } + public static function EnumLinkingClasses($sClass = "") + { + // N-N links, array of sLinkClass => (array of sAttCode=>sClass) + $aResult = array(); + foreach (self::EnumLinksClasses() as $sSomeClass) + { + $aTargets = array(); + $bFoundClass = false; + foreach (self::ListAttributeDefs($sSomeClass) as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey()) + { + $sRemoteClass = $oAttDef->GetTargetClass(); + if (empty($sClass)) + { + $aTargets[$sAttCode] = $sRemoteClass; + } + elseif ($sClass == $sRemoteClass) + { + $bFoundClass = true; + } + else + { + $aTargets[$sAttCode] = $sRemoteClass; + } + } + } + if (empty($sClass) || $bFoundClass) + { + $aResult[$sSomeClass] = $aTargets; + } + } + return $aResult; + } + + public static function GetLinkLabel($sLinkClass, $sAttCode) + { + self::_check_subclass($sLinkClass); + + // e.g. "supported by" (later: $this->GetLinkLabel(), computed on link data!) + return self::GetLabel($sLinkClass, $sAttCode); + } + + /** + * Replaces all the parameters by the values passed in the hash array + */ + static public function ApplyParams($aInput, $aParams) + { + $aSearches = array(); + $aReplacements = array(); + foreach($aParams as $sSearch => $sReplace) + { + $aSearches[] = '$'.$sSearch.'$'; + $aReplacements[] = $sReplace; + } + return str_replace($aSearches, $aReplacements, $aInput); + } + +} // class MetaModel + + +// Standard attribute lists +MetaModel::RegisterZList("noneditable", array("description"=>"non editable fields", "type"=>"attributes")); + +MetaModel::RegisterZList("details", array("description"=>"All attributes to be displayed for the 'details' of an object", "type"=>"attributes")); +MetaModel::RegisterZList("list", array("description"=>"All attributes to be displayed for a list of objects", "type"=>"attributes")); +MetaModel::RegisterZList("preview", array("description"=>"All attributes visible in preview mode", "type"=>"attributes")); + +MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters")); +MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters")); + + +?> diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index e8b52cc185..d97146a5d7 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -113,62 +113,63 @@ class OQLParserRaw#line 102 "oql-parser.php" */ const SELECT = 1; const AS_ALIAS = 2; - const WHERE = 3; - const JOIN = 4; - const ON = 5; - const EQ = 6; - const PAR_OPEN = 7; - const PAR_CLOSE = 8; - const COMA = 9; - const INTERVAL = 10; - const F_SECOND = 11; - const F_MINUTE = 12; - const F_HOUR = 13; - const F_DAY = 14; - const F_MONTH = 15; - const F_YEAR = 16; - const DOT = 17; - const VARNAME = 18; - const NAME = 19; - const NUMVAL = 20; - const STRVAL = 21; - const NOT_EQ = 22; - const LOG_AND = 23; - const LOG_OR = 24; - const MATH_DIV = 25; - const MATH_MULT = 26; - const MATH_PLUS = 27; - const MATH_MINUS = 28; - const GT = 29; - const LT = 30; - const GE = 31; - const LE = 32; - const LIKE = 33; - const NOT_LIKE = 34; - const IN = 35; - const NOT_IN = 36; - const F_IF = 37; - const F_ELT = 38; - const F_COALESCE = 39; - const F_CONCAT = 40; - const F_SUBSTR = 41; - const F_TRIM = 42; - const F_DATE = 43; - const F_DATE_FORMAT = 44; - const F_CURRENT_DATE = 45; - const F_NOW = 46; - const F_TIME = 47; - const F_TO_DAYS = 48; - const F_FROM_DAYS = 49; - const F_DATE_ADD = 50; - const F_DATE_SUB = 51; - const F_ROUND = 52; - const F_FLOOR = 53; - const F_INET_ATON = 54; - const F_INET_NTOA = 55; - const YY_NO_ACTION = 219; - const YY_ACCEPT_ACTION = 218; - const YY_ERROR_ACTION = 217; + const FROM = 3; + const COMA = 4; + const WHERE = 5; + const JOIN = 6; + const ON = 7; + const EQ = 8; + const PAR_OPEN = 9; + const PAR_CLOSE = 10; + const INTERVAL = 11; + const F_SECOND = 12; + const F_MINUTE = 13; + const F_HOUR = 14; + const F_DAY = 15; + const F_MONTH = 16; + const F_YEAR = 17; + const DOT = 18; + const VARNAME = 19; + const NAME = 20; + const NUMVAL = 21; + const STRVAL = 22; + const NOT_EQ = 23; + const LOG_AND = 24; + const LOG_OR = 25; + const MATH_DIV = 26; + const MATH_MULT = 27; + const MATH_PLUS = 28; + const MATH_MINUS = 29; + const GT = 30; + const LT = 31; + const GE = 32; + const LE = 33; + const LIKE = 34; + const NOT_LIKE = 35; + const IN = 36; + const NOT_IN = 37; + const F_IF = 38; + const F_ELT = 39; + const F_COALESCE = 40; + const F_CONCAT = 41; + const F_SUBSTR = 42; + const F_TRIM = 43; + const F_DATE = 44; + const F_DATE_FORMAT = 45; + const F_CURRENT_DATE = 46; + const F_NOW = 47; + const F_TIME = 48; + const F_TO_DAYS = 49; + const F_FROM_DAYS = 50; + const F_DATE_ADD = 51; + const F_DATE_SUB = 52; + const F_ROUND = 53; + const F_FLOOR = 54; + const F_INET_ATON = 55; + const F_INET_NTOA = 56; + const YY_NO_ACTION = 234; + const YY_ACCEPT_ACTION = 233; + const YY_ERROR_ACTION = 232; /* Next are that tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement @@ -220,171 +221,163 @@ class OQLParserRaw#line 102 "oql-parser.php" ** shifting non-terminals after a reduce. ** self::$yy_default Default action for each state. */ - const YY_SZ_ACTTAB = 432; + const YY_SZ_ACTTAB = 384; static public $yy_action = array( - /* 0 */ 4, 50, 81, 5, 97, 2, 70, 102, 103, 74, - /* 10 */ 10, 98, 82, 76, 80, 38, 90, 55, 79, 78, - /* 20 */ 77, 75, 57, 56, 58, 47, 48, 49, 46, 54, - /* 30 */ 99, 116, 115, 114, 113, 117, 118, 122, 121, 120, - /* 40 */ 119, 112, 111, 100, 101, 105, 106, 110, 109, 24, - /* 50 */ 6, 43, 43, 71, 86, 4, 92, 69, 29, 88, - /* 60 */ 93, 42, 102, 103, 74, 20, 98, 82, 76, 80, - /* 70 */ 79, 78, 77, 75, 68, 79, 78, 77, 75, 45, - /* 80 */ 45, 33, 64, 25, 25, 99, 116, 115, 114, 113, - /* 90 */ 117, 118, 122, 121, 120, 119, 112, 111, 100, 101, - /* 100 */ 105, 106, 110, 109, 4, 43, 8, 87, 11, 62, - /* 110 */ 41, 102, 103, 74, 9, 98, 82, 76, 80, 52, - /* 120 */ 51, 21, 85, 84, 12, 35, 7, 25, 108, 40, - /* 130 */ 18, 44, 3, 45, 99, 116, 115, 114, 113, 117, - /* 140 */ 118, 122, 121, 120, 119, 112, 111, 100, 101, 105, - /* 150 */ 106, 110, 109, 218, 104, 91, 43, 73, 73, 73, - /* 160 */ 96, 92, 37, 29, 88, 93, 42, 26, 23, 22, - /* 170 */ 19, 13, 15, 171, 31, 30, 95, 76, 80, 1, - /* 180 */ 79, 78, 77, 75, 45, 95, 72, 67, 66, 61, - /* 190 */ 60, 63, 107, 123, 6, 43, 73, 94, 16, 95, - /* 200 */ 92, 32, 29, 88, 93, 42, 39, 82, 22, 19, - /* 210 */ 36, 15, 187, 31, 83, 187, 187, 65, 187, 79, - /* 220 */ 78, 77, 75, 45, 43, 187, 187, 187, 187, 92, - /* 230 */ 32, 29, 88, 93, 42, 187, 187, 187, 19, 187, - /* 240 */ 15, 187, 31, 187, 187, 187, 59, 187, 79, 78, - /* 250 */ 77, 75, 45, 187, 187, 89, 43, 187, 187, 187, - /* 260 */ 187, 92, 37, 29, 88, 93, 42, 187, 187, 187, - /* 270 */ 19, 187, 15, 187, 31, 187, 187, 187, 187, 187, - /* 280 */ 79, 78, 77, 75, 45, 43, 187, 187, 187, 187, - /* 290 */ 92, 17, 29, 88, 93, 42, 187, 187, 187, 19, - /* 300 */ 187, 15, 187, 31, 187, 187, 187, 187, 187, 79, - /* 310 */ 78, 77, 75, 45, 187, 187, 43, 187, 187, 187, - /* 320 */ 187, 92, 28, 29, 88, 93, 42, 187, 187, 187, - /* 330 */ 19, 187, 15, 187, 31, 187, 187, 187, 187, 187, - /* 340 */ 79, 78, 77, 75, 45, 43, 187, 187, 187, 187, - /* 350 */ 92, 187, 29, 88, 93, 42, 187, 187, 187, 19, - /* 360 */ 187, 15, 187, 34, 187, 187, 187, 187, 187, 79, - /* 370 */ 78, 77, 75, 45, 43, 187, 187, 187, 187, 92, - /* 380 */ 187, 29, 88, 93, 42, 187, 43, 187, 19, 187, - /* 390 */ 14, 92, 187, 27, 88, 93, 42, 187, 79, 78, - /* 400 */ 77, 75, 45, 43, 187, 187, 187, 53, 41, 187, - /* 410 */ 79, 78, 77, 75, 45, 187, 187, 187, 187, 187, - /* 420 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 430 */ 187, 45, + /* 0 */ 4, 117, 5, 11, 8, 106, 121, 122, 130, 103, + /* 10 */ 89, 91, 82, 83, 26, 3, 134, 118, 116, 12, + /* 20 */ 105, 70, 54, 58, 60, 59, 63, 64, 57, 90, + /* 30 */ 107, 108, 127, 126, 125, 123, 124, 128, 129, 133, + /* 40 */ 132, 131, 113, 112, 81, 109, 110, 114, 16, 52, + /* 50 */ 69, 31, 30, 29, 95, 97, 4, 33, 96, 101, + /* 60 */ 49, 27, 121, 122, 130, 25, 89, 91, 82, 83, + /* 70 */ 94, 86, 85, 84, 94, 86, 85, 84, 50, 28, + /* 80 */ 141, 141, 74, 25, 53, 90, 107, 108, 127, 126, + /* 90 */ 125, 123, 124, 128, 129, 133, 132, 131, 113, 112, + /* 100 */ 81, 109, 110, 114, 4, 87, 42, 88, 93, 23, + /* 110 */ 121, 122, 130, 74, 89, 91, 82, 83, 46, 2, + /* 120 */ 7, 94, 86, 85, 84, 102, 82, 83, 19, 48, + /* 130 */ 62, 45, 105, 90, 107, 108, 127, 126, 125, 123, + /* 140 */ 124, 128, 129, 133, 132, 131, 113, 112, 81, 109, + /* 150 */ 110, 114, 233, 111, 100, 52, 56, 74, 74, 74, + /* 160 */ 6, 97, 37, 34, 96, 101, 49, 17, 38, 186, + /* 170 */ 22, 23, 14, 6, 41, 44, 76, 55, 23, 52, + /* 180 */ 94, 86, 85, 84, 50, 97, 40, 34, 96, 101, + /* 190 */ 49, 47, 20, 75, 22, 52, 14, 1, 41, 35, + /* 200 */ 61, 51, 67, 52, 94, 86, 85, 84, 50, 97, + /* 210 */ 40, 34, 96, 101, 49, 13, 104, 52, 22, 24, + /* 220 */ 14, 74, 41, 66, 50, 10, 80, 91, 94, 86, + /* 230 */ 85, 84, 50, 98, 52, 25, 36, 120, 119, 23, + /* 240 */ 97, 37, 34, 96, 101, 49, 50, 92, 74, 22, + /* 250 */ 52, 14, 43, 41, 71, 68, 51, 23, 52, 94, + /* 260 */ 86, 85, 84, 50, 97, 18, 34, 96, 101, 49, + /* 270 */ 193, 193, 99, 22, 193, 14, 193, 41, 193, 50, + /* 280 */ 9, 193, 52, 94, 86, 85, 84, 50, 97, 32, + /* 290 */ 34, 96, 101, 49, 115, 193, 193, 22, 193, 14, + /* 300 */ 193, 41, 193, 193, 193, 193, 52, 94, 86, 85, + /* 310 */ 84, 50, 97, 193, 34, 96, 101, 49, 193, 193, + /* 320 */ 193, 22, 193, 14, 193, 39, 193, 193, 193, 193, + /* 330 */ 52, 94, 86, 85, 84, 50, 97, 193, 34, 96, + /* 340 */ 101, 49, 193, 193, 193, 22, 193, 15, 65, 77, + /* 350 */ 79, 78, 73, 72, 52, 94, 86, 85, 84, 50, + /* 360 */ 97, 105, 34, 96, 101, 49, 193, 193, 193, 21, + /* 370 */ 193, 193, 193, 193, 193, 193, 193, 193, 193, 94, + /* 380 */ 86, 85, 84, 50, ); static public $yy_lookahead = array( - /* 0 */ 7, 6, 68, 10, 8, 9, 62, 14, 15, 16, - /* 10 */ 7, 18, 19, 20, 21, 81, 62, 22, 84, 85, - /* 20 */ 86, 87, 27, 28, 29, 30, 31, 32, 33, 34, - /* 30 */ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, - /* 40 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 1, - /* 50 */ 80, 60, 60, 83, 68, 7, 65, 65, 67, 68, - /* 60 */ 69, 70, 14, 15, 16, 74, 18, 19, 20, 21, - /* 70 */ 84, 85, 86, 87, 23, 84, 85, 86, 87, 88, - /* 80 */ 88, 61, 61, 63, 63, 37, 38, 39, 40, 41, - /* 90 */ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - /* 100 */ 52, 53, 54, 55, 7, 60, 77, 8, 9, 64, - /* 110 */ 65, 14, 15, 16, 75, 18, 19, 20, 21, 90, - /* 120 */ 91, 2, 35, 36, 5, 61, 79, 63, 89, 60, - /* 130 */ 60, 60, 3, 88, 37, 38, 39, 40, 41, 42, - /* 140 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 150 */ 53, 54, 55, 57, 58, 59, 60, 88, 88, 88, - /* 160 */ 8, 65, 66, 67, 68, 69, 70, 2, 60, 4, - /* 170 */ 74, 5, 76, 17, 78, 17, 24, 20, 21, 7, - /* 180 */ 84, 85, 86, 87, 88, 24, 11, 12, 13, 14, - /* 190 */ 15, 16, 25, 26, 80, 60, 88, 73, 6, 24, - /* 200 */ 65, 66, 67, 68, 69, 70, 71, 19, 4, 74, - /* 210 */ 72, 76, 92, 78, 88, 92, 92, 82, 92, 84, - /* 220 */ 85, 86, 87, 88, 60, 92, 92, 92, 92, 65, - /* 230 */ 66, 67, 68, 69, 70, 92, 92, 92, 74, 92, - /* 240 */ 76, 92, 78, 92, 92, 92, 82, 92, 84, 85, - /* 250 */ 86, 87, 88, 92, 92, 59, 60, 92, 92, 92, - /* 260 */ 92, 65, 66, 67, 68, 69, 70, 92, 92, 92, - /* 270 */ 74, 92, 76, 92, 78, 92, 92, 92, 92, 92, - /* 280 */ 84, 85, 86, 87, 88, 60, 92, 92, 92, 92, - /* 290 */ 65, 66, 67, 68, 69, 70, 92, 92, 92, 74, - /* 300 */ 92, 76, 92, 78, 92, 92, 92, 92, 92, 84, - /* 310 */ 85, 86, 87, 88, 92, 92, 60, 92, 92, 92, - /* 320 */ 92, 65, 66, 67, 68, 69, 70, 92, 92, 92, - /* 330 */ 74, 92, 76, 92, 78, 92, 92, 92, 92, 92, - /* 340 */ 84, 85, 86, 87, 88, 60, 92, 92, 92, 92, - /* 350 */ 65, 92, 67, 68, 69, 70, 92, 92, 92, 74, - /* 360 */ 92, 76, 92, 78, 92, 92, 92, 92, 92, 84, - /* 370 */ 85, 86, 87, 88, 60, 92, 92, 92, 92, 65, - /* 380 */ 92, 67, 68, 69, 70, 92, 60, 92, 74, 92, - /* 390 */ 76, 65, 92, 67, 68, 69, 70, 92, 84, 85, - /* 400 */ 86, 87, 88, 60, 92, 92, 92, 64, 65, 92, - /* 410 */ 84, 85, 86, 87, 88, 92, 92, 92, 92, 92, - /* 420 */ 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - /* 430 */ 92, 88, + /* 0 */ 9, 8, 11, 4, 79, 10, 15, 16, 17, 10, + /* 10 */ 19, 20, 21, 22, 2, 5, 23, 92, 93, 7, + /* 20 */ 25, 28, 29, 30, 31, 32, 33, 34, 35, 38, + /* 30 */ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + /* 40 */ 49, 50, 51, 52, 53, 54, 55, 56, 1, 61, + /* 50 */ 63, 3, 4, 61, 70, 67, 9, 69, 70, 71, + /* 60 */ 72, 2, 15, 16, 17, 6, 19, 20, 21, 22, + /* 70 */ 86, 87, 88, 89, 86, 87, 88, 89, 90, 2, + /* 80 */ 3, 4, 90, 6, 61, 38, 39, 40, 41, 42, + /* 90 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + /* 100 */ 53, 54, 55, 56, 9, 70, 62, 36, 37, 65, + /* 110 */ 15, 16, 17, 90, 19, 20, 21, 22, 83, 4, + /* 120 */ 81, 86, 87, 88, 89, 10, 21, 22, 61, 61, + /* 130 */ 61, 64, 25, 38, 39, 40, 41, 42, 43, 44, + /* 140 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + /* 150 */ 55, 56, 58, 59, 60, 61, 24, 90, 90, 90, + /* 160 */ 82, 67, 68, 69, 70, 71, 72, 8, 62, 18, + /* 170 */ 76, 65, 78, 82, 80, 62, 85, 63, 65, 61, + /* 180 */ 86, 87, 88, 89, 90, 67, 68, 69, 70, 71, + /* 190 */ 72, 73, 61, 63, 76, 61, 78, 9, 80, 18, + /* 200 */ 66, 67, 84, 61, 86, 87, 88, 89, 90, 67, + /* 210 */ 68, 69, 70, 71, 72, 7, 75, 61, 76, 61, + /* 220 */ 78, 90, 80, 67, 90, 9, 84, 20, 86, 87, + /* 230 */ 88, 89, 90, 60, 61, 6, 62, 26, 27, 65, + /* 240 */ 67, 68, 69, 70, 71, 72, 90, 90, 90, 76, + /* 250 */ 61, 78, 74, 80, 62, 66, 67, 65, 61, 86, + /* 260 */ 87, 88, 89, 90, 67, 68, 69, 70, 71, 72, + /* 270 */ 94, 94, 63, 76, 94, 78, 94, 80, 94, 90, + /* 280 */ 77, 94, 61, 86, 87, 88, 89, 90, 67, 68, + /* 290 */ 69, 70, 71, 72, 91, 94, 94, 76, 94, 78, + /* 300 */ 94, 80, 94, 94, 94, 94, 61, 86, 87, 88, + /* 310 */ 89, 90, 67, 94, 69, 70, 71, 72, 94, 94, + /* 320 */ 94, 76, 94, 78, 94, 80, 94, 94, 94, 94, + /* 330 */ 61, 86, 87, 88, 89, 90, 67, 94, 69, 70, + /* 340 */ 71, 72, 94, 94, 94, 76, 94, 78, 12, 13, + /* 350 */ 14, 15, 16, 17, 61, 86, 87, 88, 89, 90, + /* 360 */ 67, 25, 69, 70, 71, 72, 94, 94, 94, 76, + /* 370 */ 94, 94, 94, 94, 94, 94, 94, 94, 94, 86, + /* 380 */ 87, 88, 89, 90, ); - const YY_SHIFT_USE_DFLT = -8; - const YY_SHIFT_MAX = 45; + const YY_SHIFT_USE_DFLT = -10; + const YY_SHIFT_MAX = 53; static public $yy_shift_ofst = array( - /* 0 */ 48, -7, -7, 97, 97, 97, 97, 97, 97, 97, - /* 10 */ 157, 157, 188, 188, -5, -5, 188, 175, 165, 167, - /* 20 */ 167, 188, 188, 204, 188, 204, 188, 87, 152, 87, - /* 30 */ 188, 51, 161, 129, 51, 129, 3, 161, 99, -4, - /* 40 */ 119, 192, 172, 158, 166, 156, + /* 0 */ 47, -9, -9, 95, 95, 95, 95, 95, 95, 95, + /* 10 */ 105, 105, 207, 207, -7, -7, 207, 207, 336, 77, + /* 20 */ 59, 211, 211, 229, 229, 207, 207, 207, 207, 229, + /* 30 */ 207, 207, -5, 71, 71, 207, 10, 107, 10, 132, + /* 40 */ 107, 132, 10, 216, 10, 48, -1, 115, 12, 188, + /* 50 */ 151, 159, 181, 208, ); - const YY_REDUCE_USE_DFLT = -67; - const YY_REDUCE_MAX = 37; + const YY_REDUCE_USE_DFLT = -76; + const YY_REDUCE_MAX = 44; static public $yy_reduce_ofst = array( - /* 0 */ 96, 135, 164, 196, 256, 225, 285, 314, -9, 326, - /* 10 */ -66, -14, 343, 45, 29, 29, -8, -30, 64, 39, - /* 20 */ 39, 71, 69, 20, 70, 21, 108, 138, 114, 138, - /* 30 */ 126, 47, 114, -56, 47, -46, 124, 114, + /* 0 */ 94, 118, 142, 173, 221, 197, 245, 269, 293, -12, + /* 10 */ 35, -16, 134, 189, -75, -75, 67, 156, 91, 174, + /* 20 */ 113, 203, 203, 192, 44, 68, 23, -8, 158, 106, + /* 30 */ 69, 131, 78, 178, 178, 157, 209, 78, 114, 39, + /* 40 */ 78, 39, -13, 141, 130, ); static public $yyExpectedTokens = array( - /* 0 */ array(1, 7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 1 */ array(7, 10, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 2 */ array(7, 10, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 3 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 4 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 5 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 6 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 7 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 8 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 9 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 10 */ array(20, 21, ), - /* 11 */ array(20, 21, ), - /* 12 */ array(19, ), - /* 13 */ array(19, ), - /* 14 */ array(6, 22, 27, 28, 29, 30, 31, 32, 33, 34, ), - /* 15 */ array(6, 22, 27, 28, 29, 30, 31, 32, 33, 34, ), - /* 16 */ array(19, ), - /* 17 */ array(11, 12, 13, 14, 15, 16, 24, ), - /* 18 */ array(2, 4, ), - /* 19 */ array(25, 26, ), - /* 20 */ array(25, 26, ), - /* 21 */ array(19, ), - /* 22 */ array(19, ), - /* 23 */ array(4, ), - /* 24 */ array(19, ), - /* 25 */ array(4, ), - /* 26 */ array(19, ), - /* 27 */ array(35, 36, ), - /* 28 */ array(8, 24, ), - /* 29 */ array(35, 36, ), - /* 30 */ array(19, ), - /* 31 */ array(23, ), - /* 32 */ array(24, ), - /* 33 */ array(3, ), - /* 34 */ array(23, ), - /* 35 */ array(3, ), - /* 36 */ array(7, ), - /* 37 */ array(24, ), - /* 38 */ array(8, 9, ), - /* 39 */ array(8, 9, ), - /* 40 */ array(2, 5, ), - /* 41 */ array(6, ), - /* 42 */ array(7, ), - /* 43 */ array(17, ), + /* 0 */ array(1, 9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 1 */ array(9, 11, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 2 */ array(9, 11, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 3 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 4 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 5 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 6 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 7 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 8 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 9 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 10 */ array(21, 22, ), + /* 11 */ array(21, 22, ), + /* 12 */ array(20, ), + /* 13 */ array(20, ), + /* 14 */ array(8, 23, 28, 29, 30, 31, 32, 33, 34, 35, ), + /* 15 */ array(8, 23, 28, 29, 30, 31, 32, 33, 34, 35, ), + /* 16 */ array(20, ), + /* 17 */ array(20, ), + /* 18 */ array(12, 13, 14, 15, 16, 17, 25, ), + /* 19 */ array(2, 3, 4, 6, ), + /* 20 */ array(2, 6, ), + /* 21 */ array(26, 27, ), + /* 22 */ array(26, 27, ), + /* 23 */ array(6, ), + /* 24 */ array(6, ), + /* 25 */ array(20, ), + /* 26 */ array(20, ), + /* 27 */ array(20, ), + /* 28 */ array(20, ), + /* 29 */ array(6, ), + /* 30 */ array(20, ), + /* 31 */ array(20, ), + /* 32 */ array(10, 25, ), + /* 33 */ array(36, 37, ), + /* 34 */ array(36, 37, ), + /* 35 */ array(20, ), + /* 36 */ array(5, ), + /* 37 */ array(25, ), + /* 38 */ array(5, ), + /* 39 */ array(24, ), + /* 40 */ array(25, ), + /* 41 */ array(24, ), + /* 42 */ array(5, ), + /* 43 */ array(9, ), /* 44 */ array(5, ), - /* 45 */ array(17, ), - /* 46 */ array(), - /* 47 */ array(), - /* 48 */ array(), - /* 49 */ array(), - /* 50 */ array(), - /* 51 */ array(), - /* 52 */ array(), - /* 53 */ array(), + /* 45 */ array(3, 4, ), + /* 46 */ array(4, 10, ), + /* 47 */ array(4, 10, ), + /* 48 */ array(2, 7, ), + /* 49 */ array(9, ), + /* 50 */ array(18, ), + /* 51 */ array(8, ), + /* 52 */ array(18, ), + /* 53 */ array(7, ), /* 54 */ array(), /* 55 */ array(), /* 56 */ array(), @@ -455,21 +448,33 @@ static public $yy_action = array( /* 121 */ array(), /* 122 */ array(), /* 123 */ array(), + /* 124 */ array(), + /* 125 */ array(), + /* 126 */ array(), + /* 127 */ array(), + /* 128 */ array(), + /* 129 */ array(), + /* 130 */ array(), + /* 131 */ array(), + /* 132 */ array(), + /* 133 */ array(), + /* 134 */ array(), ); static public $yy_default = array( - /* 0 */ 217, 154, 217, 217, 217, 217, 217, 217, 217, 217, - /* 10 */ 217, 217, 217, 217, 148, 147, 217, 217, 132, 145, - /* 20 */ 146, 217, 217, 132, 217, 131, 217, 144, 217, 143, - /* 30 */ 217, 149, 157, 129, 150, 129, 217, 136, 217, 217, - /* 40 */ 217, 217, 217, 217, 217, 169, 191, 188, 189, 190, - /* 50 */ 179, 178, 177, 134, 192, 180, 186, 185, 187, 156, - /* 60 */ 163, 162, 133, 164, 130, 155, 161, 160, 181, 135, - /* 70 */ 127, 158, 159, 171, 208, 168, 174, 167, 166, 165, - /* 80 */ 175, 152, 173, 170, 194, 193, 153, 151, 137, 128, - /* 90 */ 126, 125, 138, 139, 142, 182, 141, 140, 172, 195, - /* 100 */ 211, 212, 210, 209, 124, 213, 214, 183, 176, 216, - /* 110 */ 215, 207, 206, 199, 198, 197, 196, 200, 201, 205, - /* 120 */ 204, 203, 202, 184, + /* 0 */ 232, 169, 232, 232, 232, 232, 232, 232, 232, 232, + /* 10 */ 232, 232, 232, 232, 162, 163, 232, 232, 232, 147, + /* 20 */ 147, 161, 160, 146, 147, 232, 232, 232, 232, 147, + /* 30 */ 232, 232, 232, 159, 158, 232, 144, 151, 144, 165, + /* 40 */ 172, 164, 144, 232, 144, 232, 232, 232, 232, 232, + /* 50 */ 184, 232, 232, 232, 201, 140, 196, 207, 202, 204, + /* 60 */ 203, 149, 142, 205, 206, 174, 150, 170, 148, 138, + /* 70 */ 200, 145, 179, 178, 186, 139, 173, 175, 177, 176, + /* 80 */ 171, 228, 189, 190, 183, 182, 181, 167, 208, 187, + /* 90 */ 210, 188, 185, 209, 180, 168, 152, 153, 143, 137, + /* 100 */ 136, 154, 155, 166, 157, 197, 156, 211, 212, 229, + /* 110 */ 230, 135, 227, 226, 231, 191, 193, 194, 192, 199, + /* 120 */ 198, 225, 224, 216, 217, 215, 214, 213, 218, 219, + /* 130 */ 223, 222, 221, 220, 195, ); /* The next thing included is series of defines which control ** various aspects of the generated parser. @@ -486,11 +491,11 @@ static public $yy_action = array( ** self::YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. */ - const YYNOCODE = 93; + const YYNOCODE = 95; const YYSTACKDEPTH = 100; - const YYNSTATE = 124; - const YYNRULE = 93; - const YYERRORSYMBOL = 56; + const YYNSTATE = 135; + const YYNRULE = 97; + const YYERRORSYMBOL = 57; const YYERRSYMDT = 'yy0'; const YYFALLBACK = 0; /** The next table maps tokens into fallback tokens. If a construct @@ -572,29 +577,30 @@ static public $yy_action = array( * @var array */ static public $yyTokenName = array( - '$', 'SELECT', 'AS_ALIAS', 'WHERE', - 'JOIN', 'ON', 'EQ', 'PAR_OPEN', - 'PAR_CLOSE', 'COMA', 'INTERVAL', 'F_SECOND', - 'F_MINUTE', 'F_HOUR', 'F_DAY', 'F_MONTH', - 'F_YEAR', 'DOT', 'VARNAME', 'NAME', - 'NUMVAL', 'STRVAL', 'NOT_EQ', 'LOG_AND', - 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', - 'MATH_MINUS', 'GT', 'LT', 'GE', - 'LE', 'LIKE', 'NOT_LIKE', 'IN', - 'NOT_IN', 'F_IF', 'F_ELT', 'F_COALESCE', - 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', 'F_DATE', - 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', 'F_TIME', - 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', 'F_DATE_SUB', - 'F_ROUND', 'F_FLOOR', 'F_INET_ATON', 'F_INET_NTOA', - 'error', 'result', 'query', 'condition', - 'class_name', 'join_statement', 'where_statement', 'join_item', - 'join_condition', 'field_id', 'expression_prio4', 'expression_basic', - 'scalar', 'var_name', 'func_name', 'arg_list', - 'list_operator', 'list', 'expression_prio1', 'operator1', - 'expression_prio2', 'operator2', 'expression_prio3', 'operator3', - 'operator4', 'scalar_list', 'argument', 'interval_unit', - 'num_scalar', 'str_scalar', 'num_value', 'str_value', - 'name', 'num_operator1', 'num_operator2', 'str_operator', + '$', 'SELECT', 'AS_ALIAS', 'FROM', + 'COMA', 'WHERE', 'JOIN', 'ON', + 'EQ', 'PAR_OPEN', 'PAR_CLOSE', 'INTERVAL', + 'F_SECOND', 'F_MINUTE', 'F_HOUR', 'F_DAY', + 'F_MONTH', 'F_YEAR', 'DOT', 'VARNAME', + 'NAME', 'NUMVAL', 'STRVAL', 'NOT_EQ', + 'LOG_AND', 'LOG_OR', 'MATH_DIV', 'MATH_MULT', + 'MATH_PLUS', 'MATH_MINUS', 'GT', 'LT', + 'GE', 'LE', 'LIKE', 'NOT_LIKE', + 'IN', 'NOT_IN', 'F_IF', 'F_ELT', + 'F_COALESCE', 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', + 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', + 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', + 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', 'F_INET_ATON', + 'F_INET_NTOA', 'error', 'result', 'query', + 'condition', 'class_name', 'join_statement', 'where_statement', + 'class_list', 'join_item', 'join_condition', 'field_id', + 'expression_prio4', 'expression_basic', 'scalar', 'var_name', + 'func_name', 'arg_list', 'list_operator', 'list', + 'expression_prio1', 'operator1', 'expression_prio2', 'operator2', + 'expression_prio3', 'operator3', 'operator4', 'scalar_list', + 'argument', 'interval_unit', 'num_scalar', 'str_scalar', + 'num_value', 'str_value', 'name', 'num_operator1', + 'num_operator2', 'str_operator', ); /** @@ -606,95 +612,99 @@ static public $yy_action = array( /* 1 */ "result ::= condition", /* 2 */ "query ::= SELECT class_name join_statement where_statement", /* 3 */ "query ::= SELECT class_name AS_ALIAS class_name join_statement where_statement", - /* 4 */ "where_statement ::= WHERE condition", - /* 5 */ "where_statement ::=", - /* 6 */ "join_statement ::= join_item join_statement", - /* 7 */ "join_statement ::= join_item", - /* 8 */ "join_statement ::=", - /* 9 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", - /* 10 */ "join_item ::= JOIN class_name ON join_condition", - /* 11 */ "join_condition ::= field_id EQ field_id", - /* 12 */ "condition ::= expression_prio4", - /* 13 */ "expression_basic ::= scalar", - /* 14 */ "expression_basic ::= field_id", - /* 15 */ "expression_basic ::= var_name", - /* 16 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", - /* 17 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", - /* 18 */ "expression_basic ::= expression_basic list_operator list", - /* 19 */ "expression_prio1 ::= expression_basic", - /* 20 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", - /* 21 */ "expression_prio2 ::= expression_prio1", - /* 22 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", - /* 23 */ "expression_prio3 ::= expression_prio2", - /* 24 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", - /* 25 */ "expression_prio4 ::= expression_prio3", - /* 26 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", - /* 27 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", - /* 28 */ "scalar_list ::= scalar", - /* 29 */ "scalar_list ::= scalar_list COMA scalar", - /* 30 */ "arg_list ::=", - /* 31 */ "arg_list ::= argument", - /* 32 */ "arg_list ::= arg_list COMA argument", - /* 33 */ "argument ::= expression_prio4", - /* 34 */ "argument ::= INTERVAL expression_prio4 interval_unit", - /* 35 */ "interval_unit ::= F_SECOND", - /* 36 */ "interval_unit ::= F_MINUTE", - /* 37 */ "interval_unit ::= F_HOUR", - /* 38 */ "interval_unit ::= F_DAY", - /* 39 */ "interval_unit ::= F_MONTH", - /* 40 */ "interval_unit ::= F_YEAR", - /* 41 */ "scalar ::= num_scalar", - /* 42 */ "scalar ::= str_scalar", - /* 43 */ "num_scalar ::= num_value", - /* 44 */ "str_scalar ::= str_value", - /* 45 */ "field_id ::= name", - /* 46 */ "field_id ::= class_name DOT name", - /* 47 */ "class_name ::= name", - /* 48 */ "var_name ::= VARNAME", - /* 49 */ "name ::= NAME", - /* 50 */ "num_value ::= NUMVAL", - /* 51 */ "str_value ::= STRVAL", - /* 52 */ "operator1 ::= num_operator1", - /* 53 */ "operator2 ::= num_operator2", - /* 54 */ "operator2 ::= str_operator", - /* 55 */ "operator2 ::= EQ", - /* 56 */ "operator2 ::= NOT_EQ", - /* 57 */ "operator3 ::= LOG_AND", - /* 58 */ "operator4 ::= LOG_OR", - /* 59 */ "num_operator1 ::= MATH_DIV", - /* 60 */ "num_operator1 ::= MATH_MULT", - /* 61 */ "num_operator2 ::= MATH_PLUS", - /* 62 */ "num_operator2 ::= MATH_MINUS", - /* 63 */ "num_operator2 ::= GT", - /* 64 */ "num_operator2 ::= LT", - /* 65 */ "num_operator2 ::= GE", - /* 66 */ "num_operator2 ::= LE", - /* 67 */ "str_operator ::= LIKE", - /* 68 */ "str_operator ::= NOT_LIKE", - /* 69 */ "list_operator ::= IN", - /* 70 */ "list_operator ::= NOT_IN", - /* 71 */ "func_name ::= F_IF", - /* 72 */ "func_name ::= F_ELT", - /* 73 */ "func_name ::= F_COALESCE", - /* 74 */ "func_name ::= F_CONCAT", - /* 75 */ "func_name ::= F_SUBSTR", - /* 76 */ "func_name ::= F_TRIM", - /* 77 */ "func_name ::= F_DATE", - /* 78 */ "func_name ::= F_DATE_FORMAT", - /* 79 */ "func_name ::= F_CURRENT_DATE", - /* 80 */ "func_name ::= F_NOW", - /* 81 */ "func_name ::= F_TIME", - /* 82 */ "func_name ::= F_TO_DAYS", - /* 83 */ "func_name ::= F_FROM_DAYS", - /* 84 */ "func_name ::= F_YEAR", - /* 85 */ "func_name ::= F_MONTH", - /* 86 */ "func_name ::= F_DAY", - /* 87 */ "func_name ::= F_DATE_ADD", - /* 88 */ "func_name ::= F_DATE_SUB", - /* 89 */ "func_name ::= F_ROUND", - /* 90 */ "func_name ::= F_FLOOR", - /* 91 */ "func_name ::= F_INET_ATON", - /* 92 */ "func_name ::= F_INET_NTOA", + /* 4 */ "query ::= SELECT class_list FROM class_name join_statement where_statement", + /* 5 */ "query ::= SELECT class_list FROM class_name AS_ALIAS class_name join_statement where_statement", + /* 6 */ "class_list ::= class_name", + /* 7 */ "class_list ::= class_list COMA class_name", + /* 8 */ "where_statement ::= WHERE condition", + /* 9 */ "where_statement ::=", + /* 10 */ "join_statement ::= join_item join_statement", + /* 11 */ "join_statement ::= join_item", + /* 12 */ "join_statement ::=", + /* 13 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", + /* 14 */ "join_item ::= JOIN class_name ON join_condition", + /* 15 */ "join_condition ::= field_id EQ field_id", + /* 16 */ "condition ::= expression_prio4", + /* 17 */ "expression_basic ::= scalar", + /* 18 */ "expression_basic ::= field_id", + /* 19 */ "expression_basic ::= var_name", + /* 20 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", + /* 21 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", + /* 22 */ "expression_basic ::= expression_basic list_operator list", + /* 23 */ "expression_prio1 ::= expression_basic", + /* 24 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", + /* 25 */ "expression_prio2 ::= expression_prio1", + /* 26 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", + /* 27 */ "expression_prio3 ::= expression_prio2", + /* 28 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", + /* 29 */ "expression_prio4 ::= expression_prio3", + /* 30 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", + /* 31 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", + /* 32 */ "scalar_list ::= scalar", + /* 33 */ "scalar_list ::= scalar_list COMA scalar", + /* 34 */ "arg_list ::=", + /* 35 */ "arg_list ::= argument", + /* 36 */ "arg_list ::= arg_list COMA argument", + /* 37 */ "argument ::= expression_prio4", + /* 38 */ "argument ::= INTERVAL expression_prio4 interval_unit", + /* 39 */ "interval_unit ::= F_SECOND", + /* 40 */ "interval_unit ::= F_MINUTE", + /* 41 */ "interval_unit ::= F_HOUR", + /* 42 */ "interval_unit ::= F_DAY", + /* 43 */ "interval_unit ::= F_MONTH", + /* 44 */ "interval_unit ::= F_YEAR", + /* 45 */ "scalar ::= num_scalar", + /* 46 */ "scalar ::= str_scalar", + /* 47 */ "num_scalar ::= num_value", + /* 48 */ "str_scalar ::= str_value", + /* 49 */ "field_id ::= name", + /* 50 */ "field_id ::= class_name DOT name", + /* 51 */ "class_name ::= name", + /* 52 */ "var_name ::= VARNAME", + /* 53 */ "name ::= NAME", + /* 54 */ "num_value ::= NUMVAL", + /* 55 */ "str_value ::= STRVAL", + /* 56 */ "operator1 ::= num_operator1", + /* 57 */ "operator2 ::= num_operator2", + /* 58 */ "operator2 ::= str_operator", + /* 59 */ "operator2 ::= EQ", + /* 60 */ "operator2 ::= NOT_EQ", + /* 61 */ "operator3 ::= LOG_AND", + /* 62 */ "operator4 ::= LOG_OR", + /* 63 */ "num_operator1 ::= MATH_DIV", + /* 64 */ "num_operator1 ::= MATH_MULT", + /* 65 */ "num_operator2 ::= MATH_PLUS", + /* 66 */ "num_operator2 ::= MATH_MINUS", + /* 67 */ "num_operator2 ::= GT", + /* 68 */ "num_operator2 ::= LT", + /* 69 */ "num_operator2 ::= GE", + /* 70 */ "num_operator2 ::= LE", + /* 71 */ "str_operator ::= LIKE", + /* 72 */ "str_operator ::= NOT_LIKE", + /* 73 */ "list_operator ::= IN", + /* 74 */ "list_operator ::= NOT_IN", + /* 75 */ "func_name ::= F_IF", + /* 76 */ "func_name ::= F_ELT", + /* 77 */ "func_name ::= F_COALESCE", + /* 78 */ "func_name ::= F_CONCAT", + /* 79 */ "func_name ::= F_SUBSTR", + /* 80 */ "func_name ::= F_TRIM", + /* 81 */ "func_name ::= F_DATE", + /* 82 */ "func_name ::= F_DATE_FORMAT", + /* 83 */ "func_name ::= F_CURRENT_DATE", + /* 84 */ "func_name ::= F_NOW", + /* 85 */ "func_name ::= F_TIME", + /* 86 */ "func_name ::= F_TO_DAYS", + /* 87 */ "func_name ::= F_FROM_DAYS", + /* 88 */ "func_name ::= F_YEAR", + /* 89 */ "func_name ::= F_MONTH", + /* 90 */ "func_name ::= F_DAY", + /* 91 */ "func_name ::= F_DATE_ADD", + /* 92 */ "func_name ::= F_DATE_SUB", + /* 93 */ "func_name ::= F_ROUND", + /* 94 */ "func_name ::= F_FLOOR", + /* 95 */ "func_name ::= F_INET_ATON", + /* 96 */ "func_name ::= F_INET_NTOA", ); /** @@ -1059,99 +1069,103 @@ static public $yy_action = array( * */ static public $yyRuleInfo = array( - array( 'lhs' => 57, 'rhs' => 1 ), - array( 'lhs' => 57, 'rhs' => 1 ), - array( 'lhs' => 58, 'rhs' => 4 ), - array( 'lhs' => 58, 'rhs' => 6 ), - array( 'lhs' => 62, 'rhs' => 2 ), - array( 'lhs' => 62, 'rhs' => 0 ), - array( 'lhs' => 61, 'rhs' => 2 ), - array( 'lhs' => 61, 'rhs' => 1 ), - array( 'lhs' => 61, 'rhs' => 0 ), - array( 'lhs' => 63, 'rhs' => 6 ), - array( 'lhs' => 63, 'rhs' => 4 ), + array( 'lhs' => 58, 'rhs' => 1 ), + array( 'lhs' => 58, 'rhs' => 1 ), + array( 'lhs' => 59, 'rhs' => 4 ), + array( 'lhs' => 59, 'rhs' => 6 ), + array( 'lhs' => 59, 'rhs' => 6 ), + array( 'lhs' => 59, 'rhs' => 8 ), + array( 'lhs' => 64, 'rhs' => 1 ), array( 'lhs' => 64, 'rhs' => 3 ), - array( 'lhs' => 59, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 4 ), - array( 'lhs' => 67, 'rhs' => 3 ), - array( 'lhs' => 67, 'rhs' => 3 ), - array( 'lhs' => 74, 'rhs' => 1 ), - array( 'lhs' => 74, 'rhs' => 3 ), + array( 'lhs' => 63, 'rhs' => 2 ), + array( 'lhs' => 63, 'rhs' => 0 ), + array( 'lhs' => 62, 'rhs' => 2 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 0 ), + array( 'lhs' => 65, 'rhs' => 6 ), + array( 'lhs' => 65, 'rhs' => 4 ), + array( 'lhs' => 66, 'rhs' => 3 ), + array( 'lhs' => 60, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 4 ), + array( 'lhs' => 69, 'rhs' => 3 ), + array( 'lhs' => 69, 'rhs' => 3 ), array( 'lhs' => 76, 'rhs' => 1 ), array( 'lhs' => 76, 'rhs' => 3 ), array( 'lhs' => 78, 'rhs' => 1 ), array( 'lhs' => 78, 'rhs' => 3 ), - array( 'lhs' => 66, 'rhs' => 1 ), - array( 'lhs' => 66, 'rhs' => 3 ), + array( 'lhs' => 80, 'rhs' => 1 ), + array( 'lhs' => 80, 'rhs' => 3 ), + array( 'lhs' => 68, 'rhs' => 1 ), + array( 'lhs' => 68, 'rhs' => 3 ), + array( 'lhs' => 75, 'rhs' => 3 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 3 ), + array( 'lhs' => 73, 'rhs' => 0 ), + array( 'lhs' => 73, 'rhs' => 1 ), array( 'lhs' => 73, 'rhs' => 3 ), - array( 'lhs' => 81, 'rhs' => 1 ), - array( 'lhs' => 81, 'rhs' => 3 ), - array( 'lhs' => 71, 'rhs' => 0 ), - array( 'lhs' => 71, 'rhs' => 1 ), - array( 'lhs' => 71, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 1 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 68, 'rhs' => 1 ), - array( 'lhs' => 68, 'rhs' => 1 ), array( 'lhs' => 84, 'rhs' => 1 ), + array( 'lhs' => 84, 'rhs' => 3 ), array( 'lhs' => 85, 'rhs' => 1 ), - array( 'lhs' => 65, 'rhs' => 1 ), - array( 'lhs' => 65, 'rhs' => 3 ), - array( 'lhs' => 60, 'rhs' => 1 ), - array( 'lhs' => 69, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), array( 'lhs' => 86, 'rhs' => 1 ), array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 77, 'rhs' => 1 ), - array( 'lhs' => 77, 'rhs' => 1 ), - array( 'lhs' => 77, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 3 ), + array( 'lhs' => 61, 'rhs' => 1 ), + array( 'lhs' => 71, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 89, 'rhs' => 1 ), array( 'lhs' => 77, 'rhs' => 1 ), array( 'lhs' => 79, 'rhs' => 1 ), - array( 'lhs' => 80, 'rhs' => 1 ), - array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 81, 'rhs' => 1 ), + array( 'lhs' => 82, 'rhs' => 1 ), array( 'lhs' => 91, 'rhs' => 1 ), array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 74, 'rhs' => 1 ), + array( 'lhs' => 74, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), array( 'lhs' => 72, 'rhs' => 1 ), array( 'lhs' => 72, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), ); /** @@ -1167,93 +1181,97 @@ static public $yy_action = array( 3 => 3, 4 => 4, 5 => 5, - 8 => 5, 6 => 6, + 32 => 6, + 35 => 6, 7 => 7, + 33 => 7, + 36 => 7, + 8 => 8, 9 => 9, + 12 => 9, 10 => 10, 11 => 11, - 12 => 12, - 13 => 12, - 14 => 12, - 15 => 12, - 19 => 12, - 21 => 12, - 23 => 12, - 25 => 12, - 33 => 12, - 35 => 12, - 36 => 12, - 37 => 12, - 38 => 12, - 39 => 12, - 40 => 12, - 41 => 12, - 42 => 12, + 13 => 13, + 14 => 14, + 15 => 15, 16 => 16, - 17 => 17, - 18 => 18, - 20 => 18, - 22 => 18, - 24 => 18, - 26 => 18, - 27 => 27, - 28 => 28, - 31 => 28, - 29 => 29, - 32 => 29, - 30 => 30, + 17 => 16, + 18 => 16, + 19 => 16, + 23 => 16, + 25 => 16, + 27 => 16, + 29 => 16, + 37 => 16, + 39 => 16, + 40 => 16, + 41 => 16, + 42 => 16, + 43 => 16, + 44 => 16, + 45 => 16, + 46 => 16, + 20 => 20, + 21 => 21, + 22 => 22, + 24 => 22, + 26 => 22, + 28 => 22, + 30 => 22, + 31 => 31, 34 => 34, - 43 => 43, - 44 => 43, - 45 => 45, - 46 => 46, + 38 => 38, 47 => 47, - 71 => 47, - 72 => 47, - 73 => 47, - 74 => 47, - 75 => 47, - 76 => 47, - 77 => 47, - 78 => 47, - 79 => 47, - 80 => 47, - 81 => 47, - 82 => 47, - 83 => 47, - 84 => 47, - 85 => 47, - 86 => 47, - 87 => 47, - 88 => 47, - 89 => 47, - 90 => 47, - 91 => 47, - 92 => 47, - 48 => 48, + 48 => 47, 49 => 49, 50 => 50, - 52 => 50, - 53 => 50, - 54 => 50, - 55 => 50, - 56 => 50, - 57 => 50, - 58 => 50, - 59 => 50, - 60 => 50, - 61 => 50, - 62 => 50, - 63 => 50, - 64 => 50, - 65 => 50, - 66 => 50, - 67 => 50, - 68 => 50, - 69 => 50, - 70 => 50, 51 => 51, + 75 => 51, + 76 => 51, + 77 => 51, + 78 => 51, + 79 => 51, + 80 => 51, + 81 => 51, + 82 => 51, + 83 => 51, + 84 => 51, + 85 => 51, + 86 => 51, + 87 => 51, + 88 => 51, + 89 => 51, + 90 => 51, + 91 => 51, + 92 => 51, + 93 => 51, + 94 => 51, + 95 => 51, + 96 => 51, + 52 => 52, + 53 => 53, + 54 => 54, + 56 => 54, + 57 => 54, + 58 => 54, + 59 => 54, + 60 => 54, + 61 => 54, + 62 => 54, + 63 => 54, + 64 => 54, + 65 => 54, + 66 => 54, + 67 => 54, + 68 => 54, + 69 => 54, + 70 => 54, + 71 => 54, + 72 => 54, + 73 => 54, + 74 => 54, + 55 => 55, ); /* Beginning here are the reduction cases. A typical example ** follows: @@ -1263,104 +1281,114 @@ static public $yy_action = array( */ #line 29 "oql-parser.y" function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } -#line 1270 "oql-parser.php" +#line 1288 "oql-parser.php" #line 32 "oql-parser.y" function yy_r2(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1275 "oql-parser.php" +#line 1293 "oql-parser.php" #line 35 "oql-parser.y" function yy_r3(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1280 "oql-parser.php" -#line 48 "oql-parser.y" - function yy_r4(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1283 "oql-parser.php" -#line 49 "oql-parser.y" - function yy_r5(){ $this->_retvalue = null; } -#line 1286 "oql-parser.php" -#line 51 "oql-parser.y" +#line 1298 "oql-parser.php" +#line 39 "oql-parser.y" + function yy_r4(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor); + } +#line 1303 "oql-parser.php" +#line 42 "oql-parser.y" + function yy_r5(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor); + } +#line 1308 "oql-parser.php" +#line 47 "oql-parser.y" function yy_r6(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); + } +#line 1313 "oql-parser.php" +#line 50 "oql-parser.y" + function yy_r7(){ + array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; + } +#line 1319 "oql-parser.php" +#line 55 "oql-parser.y" + function yy_r8(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1322 "oql-parser.php" +#line 56 "oql-parser.y" + function yy_r9(){ $this->_retvalue = null; } +#line 1325 "oql-parser.php" +#line 58 "oql-parser.y" + function yy_r10(){ // insert the join statement on top of the existing list array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); // and return the updated array $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1294 "oql-parser.php" -#line 57 "oql-parser.y" - function yy_r7(){ +#line 1333 "oql-parser.php" +#line 64 "oql-parser.y" + function yy_r11(){ $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); } -#line 1299 "oql-parser.php" -#line 63 "oql-parser.y" - function yy_r9(){ +#line 1338 "oql-parser.php" +#line 70 "oql-parser.y" + function yy_r13(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1305 "oql-parser.php" -#line 68 "oql-parser.y" - function yy_r10(){ +#line 1344 "oql-parser.php" +#line 75 "oql-parser.y" + function yy_r14(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1311 "oql-parser.php" -#line 73 "oql-parser.y" - function yy_r11(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } -#line 1314 "oql-parser.php" -#line 75 "oql-parser.y" - function yy_r12(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1317 "oql-parser.php" +#line 1350 "oql-parser.php" #line 80 "oql-parser.y" - function yy_r16(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1320 "oql-parser.php" -#line 81 "oql-parser.y" - function yy_r17(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } -#line 1323 "oql-parser.php" + function yy_r15(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } +#line 1353 "oql-parser.php" #line 82 "oql-parser.y" - function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1326 "oql-parser.php" -#line 97 "oql-parser.y" - function yy_r27(){ + function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1356 "oql-parser.php" +#line 87 "oql-parser.y" + function yy_r20(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } +#line 1359 "oql-parser.php" +#line 88 "oql-parser.y" + function yy_r21(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } +#line 1362 "oql-parser.php" +#line 89 "oql-parser.y" + function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1365 "oql-parser.php" +#line 104 "oql-parser.y" + function yy_r31(){ $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); } -#line 1331 "oql-parser.php" -#line 100 "oql-parser.y" - function yy_r28(){ - $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); - } -#line 1336 "oql-parser.php" -#line 103 "oql-parser.y" - function yy_r29(){ - array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); - $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; - } -#line 1342 "oql-parser.php" -#line 108 "oql-parser.y" - function yy_r30(){ +#line 1370 "oql-parser.php" +#line 115 "oql-parser.y" + function yy_r34(){ $this->_retvalue = array(); } -#line 1347 "oql-parser.php" -#line 119 "oql-parser.y" - function yy_r34(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1350 "oql-parser.php" -#line 131 "oql-parser.y" - function yy_r43(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1353 "oql-parser.php" -#line 134 "oql-parser.y" - function yy_r45(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1356 "oql-parser.php" -#line 135 "oql-parser.y" - function yy_r46(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } -#line 1359 "oql-parser.php" -#line 136 "oql-parser.y" - function yy_r47(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1362 "oql-parser.php" -#line 139 "oql-parser.y" - function yy_r48(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } -#line 1365 "oql-parser.php" +#line 1375 "oql-parser.php" +#line 126 "oql-parser.y" + function yy_r38(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1378 "oql-parser.php" +#line 138 "oql-parser.y" + function yy_r47(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1381 "oql-parser.php" #line 141 "oql-parser.y" - function yy_r49(){ + function yy_r49(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1384 "oql-parser.php" +#line 142 "oql-parser.y" + function yy_r50(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } +#line 1387 "oql-parser.php" +#line 143 "oql-parser.y" + function yy_r51(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1390 "oql-parser.php" +#line 146 "oql-parser.y" + function yy_r52(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } +#line 1393 "oql-parser.php" +#line 148 "oql-parser.y" + function yy_r53(){ if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') { $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); @@ -1371,13 +1399,13 @@ static public $yy_action = array( } $this->_retvalue = new OqlName($name, $this->m_iColPrev); } -#line 1378 "oql-parser.php" -#line 153 "oql-parser.y" - function yy_r50(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1381 "oql-parser.php" -#line 154 "oql-parser.y" - function yy_r51(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } -#line 1384 "oql-parser.php" +#line 1406 "oql-parser.php" +#line 160 "oql-parser.y" + function yy_r54(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1409 "oql-parser.php" +#line 161 "oql-parser.y" + function yy_r55(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } +#line 1412 "oql-parser.php" /** * placeholder for the left hand side in a reduce operation. @@ -1492,7 +1520,7 @@ static public $yy_action = array( #line 25 "oql-parser.y" throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); -#line 1500 "oql-parser.php" +#line 1528 "oql-parser.php" } /** @@ -1644,7 +1672,7 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo } } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); } -}#line 204 "oql-parser.y" +}#line 211 "oql-parser.y" class OQLParserException extends OQLException @@ -1709,4 +1737,4 @@ class OQLParser extends OQLParserRaw } } -#line 1719 "oql-parser.php" +#line 1747 "oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index e432e7a396..ee28b97282 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -30,20 +30,27 @@ result ::= query(X). { $this->my_result = X; } result ::= condition(X). { $this->my_result = X; } query(A) ::= SELECT class_name(X) join_statement(J) where_statement(W). { - A = new OqlObjectQuery(X, X, W, J); + A = new OqlObjectQuery(X, X, W, J, array(X)); } query(A) ::= SELECT class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). { - A = new OqlObjectQuery(X, Y, W, J); + A = new OqlObjectQuery(X, Y, W, J, array(Y)); } -/* -query(A) ::= SELECT field_id(E) FROM class_name(X) join_statement(J) where_statement(W). { - A = new OqlValueSetQuery(E, X, X, W, J); +query(A) ::= SELECT class_list(E) FROM class_name(X) join_statement(J) where_statement(W). { + A = new OqlObjectQuery(X, X, W, J, E); } -query(A) ::= SELECT field_id(E) FROM class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). { - A = new OqlValueSetQuery(E, X, Y, W, J); +query(A) ::= SELECT class_list(E) FROM class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). { + A = new OqlObjectQuery(X, Y, W, J, E); +} + + +class_list(A) ::= class_name(X). { + A = array(X); +} +class_list(A) ::= class_list(L) COMA class_name(X). { + array_push(L, X); + A = L; } -*/ where_statement(A) ::= WHERE condition(C). { A = C;} where_statement(A) ::= . { A = null;} diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index ef55d03093..2a8ffe3597 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -22,7 +22,8 @@ class OqlInterpreter $this->m_sQuery = $sQuery; } - protected function Parse() + // Note: this function is left public for unit test purposes + public function Parse() { $oLexer = new OQLLexer($this->m_sQuery); $oParser = new OQLParser($this->m_sQuery); @@ -45,18 +46,6 @@ class OqlInterpreter return $oRes; } -/* - public function ParseValueSetQuery() - { - $oRes = $this->Parse(); - if (!$oRes instanceof OqlValueSetQuery) - { - throw new OqlException('Expecting a value set query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlValueSetQuery')); - } - return $oRes; - } -*/ - public function ParseExpression() { $oRes = $this->Parse(); diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index bec211037f..1979cdac57 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -150,16 +150,22 @@ abstract class OqlQuery class OqlObjectQuery extends OqlQuery { + protected $m_aSelect; // array of selected classes protected $m_oClass; protected $m_oClassAlias; - public function __construct($oClass, $oClassAlias = '', $oCondition = null, $aJoins = null) + public function __construct($oClass, $oClassAlias, $oCondition = null, $aJoins = null, $aSelect = null) { + $this->m_aSelect = $aSelect; $this->m_oClass = $oClass; $this->m_oClassAlias = $oClassAlias; parent::__construct($oCondition, $aJoins); } + public function GetSelectedClasses() + { + return $this->m_aSelect; + } public function GetClass() { return $this->m_oClass->GetValue(); @@ -179,20 +185,4 @@ class OqlObjectQuery extends OqlQuery } } - -class OqlValueSetQuery extends OqlObjectQuery -{ - protected $m_oSelectExpr; - - public function __construct($oSelectExpr, $oClass, $oClassAlias = '', $oCondition = null, $aJoins = null) - { - $this->m_oSelectExpr = $oSelectExpr; - parent::__construct($oClass, $oClassAlias, $oCondition, $aJoins); - } - - public function GetSelectExpression() - { - return $this->m_oSelectExpr; - } -} ?> diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 137fad97db..2e439c8e0c 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -6,7 +6,7 @@ class TestSQLQuery extends TestScenarioOnDB static public function GetDescription() {return 'SQLQuery does not depend on the rest of the framework, therefore it makes sense to have a separate test framework for it';} static public function GetDBHost() {return 'localhost';} - static public function GetDBUser() {return 'RomainDBLogin';} + static public function GetDBUser() {return 'root';} static public function GetDBPwd() {return '';} static public function GetDBName() {return 'TestSQLQuery';} static public function GetDBSubName() {return 'taratata';} @@ -160,6 +160,18 @@ class TestOQLParser extends TestFunction "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, + + // Several objects in a row... + // + 'SELECT A FROM A' => true, + 'SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A, B,C FROM A JOIN B ON A.myB = B.id' => false, + 'SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => false, ); $iErrors = 0; @@ -933,6 +945,11 @@ class TestQueriesOnFarm extends MyFarm 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, + // Specifying multiple objects + 'SELECT Animal FROM Animal' => true, + 'SELECT yelele FROM Animal' => false, + 'SELECT Animal FROM Animal AS A' => false, + 'SELECT A FROM Animal AS A' => true, ); //$aQueries = array( // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, From c81ac981c968d1d0735b040be77603f95c50f6a3 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 13 Apr 2010 10:35:43 +0000 Subject: [PATCH 245/970] - Enhancement: support of ObjectSets with multiple objects per line. SVN:trunk[328] --- application/cmdbabstract.class.inc.php | 157 +++++++++++++++++++++++-- application/displayblock.class.inc.php | 77 ++++++++---- application/webpage.class.inc.php | 16 ++- 3 files changed, 212 insertions(+), 38 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index f4c2a88e0a..ef89dbc6b2 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -323,6 +323,7 @@ abstract class cmdbAbstractObject extends CMDBObject } $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; + $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; $sHtml = ''; $oAppContext = new ApplicationContext(); @@ -364,7 +365,14 @@ abstract class cmdbAbstractObject extends CMDBObject } if ($bSelectMode) { + if (!$bSingleSelectMode) + { $aAttribs['form::select'] = array('label' => "", 'description' => 'Select / Deselect All'); + } + else + { + $aAttribs['form::select'] = array('label' => "", 'description' => ''); + } } $aAttribs['key'] = array('label' => '', 'description' => 'Click to display'); foreach($aList as $sAttCode) @@ -388,8 +396,15 @@ abstract class cmdbAbstractObject extends CMDBObject $aRow['key'] = $oObj->GetKey(); if ($bSelectMode) { + if ($bSingleSelectMode) + { + $aRow['form::select'] = "GetKey()."\">"; + } + else + { $aRow['form::select'] = "GetKey()."\">"; } + } $aRow['key'] = $oObj->GetKey(); foreach($aList as $sAttCode) { @@ -398,11 +413,11 @@ abstract class cmdbAbstractObject extends CMDBObject $aValues[] = $aRow; $iMaxObjects--; } - $oMenuBlock = new MenuBlock($oSet->GetFilter()); $sHtml .= ''; $sColspan = ''; if ($bDisplayMenu) { + $oMenuBlock = new MenuBlock($oSet->GetFilter()); $sColspan = 'colspan="2"'; $aMenuExtraParams = $aExtraParams; if (!empty($sLinkageAttribute)) @@ -436,6 +451,98 @@ abstract class cmdbAbstractObject extends CMDBObject return $sHtml; } + public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) + { + static $iListId = 0; + $iListId++; + $aList = array(); + + // Initialize and check the parameters + $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; + + $sHtml = ''; + $oAppContext = new ApplicationContext(); + $aClasses = $oSet->GetFilter()->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach($aClasses as $sAlias => $sClassName) + { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } + $aAttribs = array(); + foreach($aAuthorizedClasses as $sAlias => $sClassName) // TO DO: check if the user has enough rights to view the classes of the list... + { + $aList[$sClassName] = MetaModel::GetZListItems($sClassName, 'list'); + $aAttribs['key_'.$sAlias] = array('label' => '', 'description' => 'Click to display'); + foreach($aList[$sClassName] as $sAttCode) + { + $aAttribs[$sAttCode.'_'.$sAlias] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode)); + } + } + $aValues = array(); + $oSet->Seek(0); + $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; + $iMaxObjects = -1; + if ($bDisplayLimit) + { + if ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit()) + { + $iMaxObjects = utils::GetConfig()->GetMinDisplayLimit(); + } + } + while (($aObjects = $oSet->FetchAssoc()) && ($iMaxObjects != 0)) + { + $aRow = array(); + foreach($aAuthorizedClasses as $sAlias => $sClassName) // TO DO: check if the user has enough rights to view the classes of the list... + { + $aRow['key_'.$sAlias] = $aObjects[$sAlias]->GetKey(); + foreach($aList[$sClassName] as $sAttCode) + { + $aRow[$sAttCode.'_'.$sAlias] = $aObjects[$sAlias]->GetAsHTML($sAttCode); + } + } + $aValues[] = $aRow; + $iMaxObjects--; + } + $sHtml .= '
    '; + $sColspan = ''; + if ($bDisplayMenu) + { + $oMenuBlock = new MenuBlock($oSet->GetFilter()); + $sColspan = 'colspan="2"'; + $aMenuExtraParams = $aExtraParams; + if (!empty($sLinkageAttribute)) + { + $aMenuExtraParams = $aExtraParams; + } + if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) + { + // list truncated + $divId = $aExtraParams['block_id']; + $sFilter = $oSet->GetFilter()->serialize(); + $aExtraParams['display_limit'] = false; // To expand the full list + $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them + $sHtml .= ''; + } + $sHtml .= "'; + $sHtml .= '
    '.utils::GetConfig()->GetMinDisplayLimit().' object(s) displayed out of '.$oSet->Count().'  Display All'; + $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); + $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); + } + else + { + // Full list + $sHtml .= '
     '.$oSet->Count().' object(s)'; + } + $sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams); + $sHtml .= '
    "; + $sHtml .= $oPage->GetTable($aAttribs, $aValues, array('class'=>$aAuthorizedClasses, 'filter'=>$oSet->GetFilter()->serialize(), 'preview' => true)); + $sHtml .= '
    '; + return $sHtml; + } + static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $oPage->add(self::GetSetAsCSV($oSet, $aParams)); @@ -445,26 +552,43 @@ abstract class cmdbAbstractObject extends CMDBObject { $sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma $sTextQualifier = isset($aParams['text_qualifier']) ? $aParams['text_qualifier'] : '"'; // default text qualifier is double quote + $aList = array(); $oAppContext = new ApplicationContext(); - $sClassName = $oSet->GetFilter()->GetClass(); + $aClasses = $oSet->GetFilter()->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach($aClasses as $sAlias => $sClassName) + { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } $aAttribs = array(); - $aList = MetaModel::GetZListItems($sClassName, 'details'); $aHeader = array(); + foreach($aAuthorizedClasses as $sAlias => $sClassName) + { + $aList[$sClassName] = MetaModel::GetZListItems($sClassName, 'details'); $aHeader[] = MetaModel::GetKeyLabel($sClassName); - foreach($aList as $sAttCode) + foreach($aList[$sClassName] as $sAttCode) { $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); } - $sHtml = implode($sSeparator, $aHeader)."\n"; + } + $sHtml = '#'.$oSet->GetFilter()->ToOQL()."\n"; + $sHtml .= implode($sSeparator, $aHeader)."\n"; $oSet->Seek(0); - while ($oObj = $oSet->Fetch()) + while ($aObjects = $oSet->FetchAssoc()) { $aRow = array(); - $aRow[] = $oObj->GetKey(); - foreach($aList as $sAttCode) + foreach($aAuthorizedClasses as $sAlias => $sClassName) { - $aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, $sTextQualifier); + $oObj = $aObjects[$sAlias]; + $aRow[] = $oObj->GetKey(); + foreach($aList[$sClassName] as $sAttCode) + { + $aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, '\\'); + } } $sHtml .= implode($sSeparator, $aRow)."\n"; } @@ -773,7 +897,7 @@ abstract class cmdbAbstractObject extends CMDBObject return $sHTMLValue; } - public function DisplayModifyForm(WebPage $oPage) + public function DisplayModifyForm(WebPage $oPage, $aExtraParams = array()) { static $iFormId = 0; $iFormId++; @@ -824,13 +948,17 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("\n"); $oPage->add("\n"); $oPage->add("\n"); + foreach($aExtraParams as $sName => $value) + { + $oPage->add("\n"); + } $oPage->add($oAppContext->GetForForm()); $oPage->add("    \n"); $oPage->add("\n"); $oPage->add("\n"); } - public static function DisplayCreationForm(WebPage $oPage, $sClass, $oObjectToClone = null, $aArgs = array()) + public static function DisplayCreationForm(WebPage $oPage, $sClass, $oObjectToClone = null, $aArgs = array(), $aExtraParams = array()) { static $iCreationFormId = 0; @@ -838,7 +966,8 @@ abstract class cmdbAbstractObject extends CMDBObject $oAppContext = new ApplicationContext(); $aDetails = array(); $sOperation = ($oObjectToClone == null) ? 'apply_new' : 'apply_clone'; - $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($oObjectToClone)); + $sClass = ($oObjectToClone == null) ? $sClass : get_class($oObjectToClone); + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); $oPage->add("
    \n"); $aStates = MetaModel::EnumStates($sClass); if ($oObjectToClone == null) @@ -881,6 +1010,10 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("\n"); $oPage->add("\n"); $oPage->add($oAppContext->GetForForm()); + foreach($aExtraParams as $sName => $value) + { + $oPage->add("\n"); + } $oPage->add("    \n"); $oPage->add("\n"); $oPage->add("
    \n"); diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 4df18c1ddb..2f089a822c 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -362,36 +362,70 @@ class DisplayBlock break; case 'list': - if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) + $aClasses = $this->m_oSet->GetSelectedClasses(); + $aAuthorizedClasses = array(); + if (count($aClasses) > 1) { - $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams); + // Check the classes that can be read (i.e authorized) by this user... + foreach($aClasses as $sAlias => $sClassName) + { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } + if (count($aAuthorizedClasses) > 0) + { + if($this->m_oSet->Count() > 0) + { + $sHtml .= cmdbAbstractObject::GetDisplayExtendedSet($oPage, $this->m_oSet, $aExtraParams); + } + else + { + // Empty set + $sHtml .= $oPage->GetP("No object to display."); + } + } + else + { + // Not authorized + $sHtml .= $oPage->GetP("No object to display."); + } } else { - $sHtml .= $oPage->GetP("No object to display."); - $sClass = $this->m_oFilter->GetClass(); - $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; - if ($bDisplayMenu) + // The list is made of only 1 class of objects, actions on the list are possible + if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) ) { - if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) + $sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams); + } + else + { + $sHtml .= $oPage->GetP("No object to display."); + $sClass = $this->m_oFilter->GetClass(); + $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; + if ($bDisplayMenu) { - $oAppContext = new ApplicationContext(); - $sParams = $oAppContext->GetForLink(); - // 1:n links, populate the target object as a default value when creating a new linked object - if (isset($aExtraParams['target_attr'])) + if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) { - $aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id']; - } - $sDefault = ''; - if (!empty($aExtraParams['default'])) - { - foreach($aExtraParams['default'] as $sKey => $sValue) + $oAppContext = new ApplicationContext(); + $sParams = $oAppContext->GetForLink(); + // 1:n links, populate the target object as a default value when creating a new linked object + if (isset($aExtraParams['target_attr'])) { - $sDefault.= "&default[$sKey]=$sValue"; + $aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id']; } + $sDefault = ''; + if (!empty($aExtraParams['default'])) + { + foreach($aExtraParams['default'] as $sKey => $sValue) + { + $sDefault.= "&default[$sKey]=$sValue"; + } + } + + $sHtml .= $oPage->GetP("Click here to create a new ".Metamodel::GetName($sClass)."\n"); } - - $sHtml .= $oPage->GetP("Click here to create a new ".Metamodel::GetName($sClass)."\n"); } } } @@ -464,7 +498,7 @@ class DisplayBlock break; case 'search': - static $iSearchSectionId = 1; + $iSearchSectionId = 1; $sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; $sHtml .= "
    \n"; $oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); @@ -472,7 +506,6 @@ class DisplayBlock $sHtml .= "
    \n"; $sHtml .= "
    \n"; $sHtml .= "
    Search
    \n"; - $iSearchSectionId++; break; case 'pie_chart': diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index 42d7135cc1..9d569f91ad 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -125,17 +125,25 @@ class WebPage } foreach($aConfig as $sName=>$aAttribs) { + $aMatches = array(); $sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : ''; - if ($sName != 'key') + if (preg_match('/^key_(.+)$/', $sName, $aMatches) > 0) { - $sValue = ($aRow[$sName] === '') ? ' ' : $aRow[$sName]; - $sHtml .= "$sValue\n"; + $sAlias = $aMatches[1]; + $sClass = $aParams['class'][$sAlias]; + $sUIPage = cmdbAbstractObject::ComputeUIPage($sClass); + $sHtml .= "GetForLink()."\">\n"; } - else + else if ($sName == 'key') { $sUIPage = cmdbAbstractObject::ComputeUIPage($aParams['class']); $sHtml .= "GetForLink()."\">\n"; } + else + { + $sValue = ($aRow[$sName] === '') ? ' ' : $aRow[$sName]; + $sHtml .= "$sValue\n"; + } } $sHtml .= "\n"; } From b1754b14750c0068cef212694c0320b94e257fc9 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 13 Apr 2010 12:46:07 +0000 Subject: [PATCH 246/970] Finalized queries with multiple objects SVN:trunk[329] --- core/dbobjectsearch.class.php | 22 ++++++++++++++++++++-- core/metamodel.class.php | 29 ++++++++++++++++------------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 4c3c998a81..d66d22dada 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -43,7 +43,7 @@ define('SIBUSQLTHISREGEXP', "/this\\.(.*)/U"); */ class DBObjectSearch { - private $m_aClasses; // queried classes (alias => class name) + private $m_aClasses; // queried classes (alias => class name), the first item is the class corresponding to this filter (the rest is coming from subfilters) private $m_aSelectedClasses; // selected for the output (alias => class name) private $m_oSearchCondition; private $m_aParams; @@ -84,6 +84,16 @@ class DBObjectSearch return key($this->m_aSelectedClasses); } + public function GetFirstJoinedClass() + { + return reset($this->m_aClasses); + } + public function GetFirstJoinedClassAlias() + { + reset($this->m_aClasses); + return key($this->m_aClasses); + } + public function SetSelectedClasses($aNewSet) { $this->m_aSelectedClasses = array(); @@ -322,7 +332,7 @@ class DBObjectSearch { $sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetClass()); $this->m_aSelectedClasses[$sNewAlias] = $this->GetClass(); - unset($sOrigAlias, $this->m_aSelectedClasses[$sNewAlias]); + unset($this->m_aSelectedClasses[$sOrigAlias]); // Translate the condition expression with the new alias $aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias; @@ -513,6 +523,11 @@ class DBObjectSearch $sValue = $this->GetClass()."\n"; $sValue .= $this->GetClassAlias()."\n"; + foreach($this->m_aSelectedClasses as $sClassAlias => $sClass) + { + // A stands for "Aliases" + $sValue .= "S:$sClassAlias:$sClass\n"; + } foreach($this->m_aClasses as $sClassAlias => $sClass) { // A stands for "Aliases" @@ -573,6 +588,9 @@ class DBObjectSearch $aCondition = explode(":", $aValues[$i++]); switch ($aCondition[0]) { + case "S": + $oFilter->m_aSelectedClasses[$aCondition[1]] = $aCondition[2]; + break; case "A": $oFilter->m_aClasses[$aCondition[1]] = $aCondition[2]; break; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 672662d540..e1cfbdcb8a 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1283,22 +1283,25 @@ abstract class MetaModel $aOrderSpec = array(); foreach ($aOrderBy as $sFieldAlias => $bAscending) { - MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); + MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetFirstJoinedClass())); if (!is_bool($bAscending)) { throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); } - $aOrderSpec[$oFilter->GetClassAlias().$sFieldAlias] = $bAscending; + $aOrderSpec[$oFilter->GetFirstJoinedClassAlias().$sFieldAlias] = $bAscending; } // By default, force the name attribute to be the ordering key // if (empty($aOrderSpec)) { - $sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass()); - if (!empty($sNameAttCode)) + foreach ($oFilter->GetSelectedClasses() as $sSelectedAlias => $sSelectedClass) { - // By default, simply order on the "name" attribute, ascending - $aOrderSpec[$oFilter->GetClassAlias().$sNameAttCode] = true; + $sNameAttCode = self::GetNameAttributeCode($sSelectedClass); + if (!empty($sNameAttCode)) + { + // By default, simply order on the "name" attribute, ascending + $aOrderSpec[$sSelectedAlias.$sNameAttCode] = true; + } } } @@ -1377,8 +1380,8 @@ abstract class MetaModel // Note: query class might be different than the class of the filter // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) // $aExpectedAtts is an array of sAttCode=>array of columns - $sClass = $oFilter->GetClass(); - $sClassAlias = $oFilter->GetClassAlias(); + $sClass = $oFilter->GetFirstJoinedClass(); + $sClassAlias = $oFilter->GetFirstJoinedClassAlias(); $bIsOnQueriedClass = array_key_exists($sClassAlias, $aSelectedClasses); if ($bIsOnQueriedClass) @@ -1474,7 +1477,7 @@ abstract class MetaModel //self::DbgTrace($oSelectForeign->RenderSelect(array())); $oSelectForeign = self::MakeQuery($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts); - $sForeignClassAlias = $oForeignFilter->GetClassAlias(); + $sForeignClassAlias = $oForeignFilter->GetFirstJoinedClassAlias(); $sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0]; $sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1]; $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); @@ -1536,8 +1539,8 @@ abstract class MetaModel // // Returns an SQLQuery // - $sTargetClass = $oFilter->GetClass(); - $sTargetAlias = $oFilter->GetClassAlias(); + $sTargetClass = $oFilter->GetFirstJoinedClass(); + $sTargetAlias = $oFilter->GetFirstJoinedClassAlias(); $sTable = self::DBGetTable($sTableClass); $sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable); @@ -1662,8 +1665,8 @@ abstract class MetaModel else { // The aliases should not conflict because normalization occured while building the filter - $sKeyClass = $oExtFilter->GetClass(); - $sKeyClassAlias = $oExtFilter->GetClassAlias(); + $sKeyClass = $oExtFilter->GetFirstJoinedClass(); + $sKeyClassAlias = $oExtFilter->GetFirstJoinedClassAlias(); // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree } From 3e2178db8fbc40c397bcc8126b2f55a2efcdb05a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 13 Apr 2010 12:58:03 +0000 Subject: [PATCH 247/970] - Enhancement: support of ObjectSets with multiple objects per line. SVN:trunk[330] --- application/cmdbabstract.class.inc.php | 45 +++++++++++++++++++------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index ef89dbc6b2..f540b12e65 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -599,24 +599,45 @@ abstract class cmdbAbstractObject extends CMDBObject static function DisplaySetAsXML(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $oAppContext = new ApplicationContext(); - $sClassName = $oSet->GetFilter()->GetClass(); + $aClasses = $oSet->GetFilter()->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach($aClasses as $sAlias => $sClassName) + { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } $aAttribs = array(); - $aList = MetaModel::GetZListItems($sClassName, 'details'); + $aList = array(); + $aList[$sClassName] = MetaModel::GetZListItems($sClassName, 'details'); $oPage->add("\n"); $oSet->Seek(0); - while ($oObj = $oSet->Fetch()) + while ($aObjects = $oSet->FetchAssoc()) { - $sClassName = get_class($oObj); - $oPage->add("<$sClassName id=\"".$oObj->GetKey()."\">\n"); - foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + if (count($aAuthorizedClasses) > 1) { - if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()) && ($sAttCode != 'finalclass') ) - { - $sValue = $oObj->GetAsXML($sAttCode); - $oPage->add("<$sAttCode>$sValue\n"); - } + $oPage->add("\n"); + } + foreach($aAuthorizedClasses as $sAlias => $sClassName) + { + $oObj = $aObjects[$sAlias]; + $sClassName = get_class($oObj); + $oPage->add("<$sClassName alias=\"$sAlias\" id=\"".$oObj->GetKey()."\">\n"); + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + { + if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()) && ($sAttCode != 'finalclass') ) + { + $sValue = $oObj->GetAsXML($sAttCode); + $oPage->add("<$sAttCode>$sValue\n"); + } + } + $oPage->add("\n"); + } + if (count($aAuthorizedClasses) > 1) + { + $oPage->add("\n"); } - $oPage->add("\n"); } $oPage->add("\n"); } From 4e2edaabfa89559da1b226ef34b76355cbca7632 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 14 Apr 2010 08:14:08 +0000 Subject: [PATCH 248/970] Added constructor for DBObjectSet: FromArrayAssoc SVN:trunk[331] --- core/dbobjectset.class.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 903663afdd..b87e04b16a 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -72,6 +72,8 @@ class DBObjectSet return $oRetSet; } + // create an object set ex nihilo + // input = array of objects static public function FromArray($sClass, $aObjects) { $oFilter = new CMDBSearchFilter($sClass); @@ -81,6 +83,28 @@ class DBObjectSet return $oRetSet; } + // create an object set ex nihilo + // aClasses = array of (alias => class) + // input = array of (array of (classalias => object)) + static public function FromArrayAssoc($aClasses, $aObjects) + { + // In a perfect world, we should create a complete tree of DBObjectSearch, + // but as we lack most of the information related to the objects, + // let's create one search definition + $sClass = reset($this->m_aClasses); + $sAlias = key($this->m_aClasses); + $oFilter = new CMDBSearchFilter($sClass, $sAlias); + + $oRetSet = new self($oFilter); + $oRetSet->m_bLoaded = true; // no DB load + + foreach($aObjects as $rowIndex => $aObjectsByClassAlias) + { + $oRetSet->AddObjectExtended($aObjectsByClassAlias); + } + return $oRetSet; + } + static public function FromLinkSet($oObject, $sLinkSetAttCode, $sExtKeyToRemote) { $oLinkAttCode = MetaModel::GetAttributeDef(get_class($oObject), $sLinkSetAttCode); From 0a28faa9df2ddc4f5509376acf40d4d313b35641 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 14 Apr 2010 09:42:11 +0000 Subject: [PATCH 249/970] - New implementation for the "join" display group, based on a complex OQL query and the display of several objects in the same row. SVN:trunk[332] --- application/cmdbabstract.class.inc.php | 5 +- application/displayblock.class.inc.php | 94 +++++++++++++++++++------- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index f540b12e65..276d8b3d55 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -459,6 +459,8 @@ abstract class cmdbAbstractObject extends CMDBObject // Initialize and check the parameters $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; + // Check if there is a list of aliases to limit the display to... + $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']) : array(); $sHtml = ''; $oAppContext = new ApplicationContext(); @@ -466,7 +468,8 @@ abstract class cmdbAbstractObject extends CMDBObject $aAuthorizedClasses = array(); foreach($aClasses as $sAlias => $sClassName) { - if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) + if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) && + ( (count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases))) ) { $aAuthorizedClasses[$sAlias] = $sClassName; } diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 2f089a822c..a25b662cd5 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -324,40 +324,84 @@ class DisplayBlock break; case 'join': - if (!isset($aExtraParams['oql2'])) + $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']): array(); + if (!isset($aExtraParams['group_by'])) { - $sHtml .= $oPage->GetP("parameter oql2 is mandatory."); + $sHtml .= $oPage->GetP("parameter group_by is mandatory."); } else { - $sOql2 = $aExtraParams['oql2']; - $sGroupByField = $aExtraParams['group_by']; - $aExtraParams['menu'] = false; - $oFilter1 = CMDBSearchFilter::FromOQL($sOql2); - $oSet1 = new CMDBObjectSet($oFilter1); - if (!isset($aExtraParams['group_by'])) + $aGroupByFields = array(); + $aGroupBy = explode(',', $aExtraParams['group_by']); + foreach($aGroupBy as $sGroupBy) { - // No "group by" specified, use the name of the second class - $sGroupByField = MetaModel::GetNameAttributeCode($oFilter1->GetClass()); - } - $aResults = array(); - while($oObj = $oSet1->Fetch()) - { - $aResult[$oObj->Get($sGroupByField)] = array(); - $oSet2 = new CMDBObjectSet($this->m_oFilter, array(), array('oql2' => $oObj->GetKey())); - while($oObj2 = $oSet2->Fetch()) + $aMatches = array(); + if (preg_match('/^(.+)\.(.+)$/', $sGroupBy, $aMatches) > 0) { - $aResults[$oObj->Get($sGroupByField)][$oObj2->GetKey()] = $oObj2; + $aGroupByFields[] = array('alias' => $aMatches[1], 'att_code' => $aMatches[2]); } } - $sHtml .= "\n"; - foreach($aResults as $sCategory => $aObjects) + if (count($aGroupByFields) == 0) { - $sHtml .= "\n"; - $oSet = CMDBObjectSet::FromArray($this->m_oFilter->GetClass(), $aObjects); - $sHtml .= "\n"; - } - $sHtml .= "

    $sCategory

    ".cmdbAbstractObject::GetDisplaySet($oPage, $oSet, $aExtraParams)."
    \n"; + $sHtml .= $oPage->GetP("Invalid list of fields to group by: '".$aExtraParams['group_by']."'."); + } + else + { + $aResults = array(); + $aCriteria = array(); + while($aObjects = $this->m_oSet->FetchAssoc()) + { + $aKeys = array(); + foreach($aGroupByFields as $aField) + { + $aKeys[$aField['alias'].'.'.$aField['att_code']] = $aObjects[$aField['alias']]->Get($aField['att_code']); + } + $sCategory = implode($aKeys, ' '); + $aResults[$sCategory][] = $aObjects; + $aCriteria[$sCategory] = $aKeys; + } + + $sHtml .= "\n"; + // Construct a new (parametric) query that will return the content of this block + $oBlockFilter = clone $this->m_oFilter; + $aExpressions = array(); + $index = 0; + foreach($aGroupByFields as $aField) + { + $aExpressions[] = '`'.$aField['alias'].'`.`'.$aField['att_code'].'` = :param'.$index++; + } + $sExpression = implode(' AND ', $aExpressions); + $oExpression = Expression::FromOQL($sExpression); + $oBlockFilter->AddConditionExpression($oExpression); + $aExtraParams['menu'] = false; + foreach($aResults as $sCategory => $aObjects) + { + $sHtml .= "\n"; + if (count($aDisplayAliases) == 1) + { + $aSimpleArray = array(); + foreach($aObjects as $aRow) + { + $aSimpleArray[] = $aRow[$aDisplayAliases[0]]; + } + $oSet = CMDBObjectSet::FromArray($this->m_oFilter->GetClass(), $aSimpleArray); + $sHtml .= "\n"; + } + else + { + $index = 0; + $aArgs = array(); + foreach($aGroupByFields as $aField) + { + $aArgs['param'.$index] = $aCriteria[$sCategory][$aField['alias'].'.'.$aField['att_code']]; + $index++; + } + $oSet = new CMDBObjectSet($oBlockFilter, array(), $aArgs); + $sHtml .= "\n"; + } + } + $sHtml .= "

    $sCategory

    ".cmdbAbstractObject::GetDisplaySet($oPage, $oSet, $aExtraParams)."
    ".cmdbAbstractObject::GetDisplayExtendedSet($oPage, $oSet, $aExtraParams)."
    \n"; + } } break; From bf34791a800bda0694f20af220e2f3860419705e Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 15 Apr 2010 12:09:08 +0000 Subject: [PATCH 250/970] CSV export: added external fields (all scalar fields are given) SVN:trunk[333] --- application/cmdbabstract.class.inc.php | 53 +++++++++++++++++--------- core/attributedef.class.inc.php | 6 +++ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 276d8b3d55..91128416a4 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -214,7 +214,7 @@ abstract class cmdbAbstractObject extends CMDBObject } function GetBareDetails(WebPage $oPage) - { + { $sHtml = ''; $oAppContext = new ApplicationContext(); $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); @@ -266,7 +266,7 @@ abstract class cmdbAbstractObject extends CMDBObject if (!empty($sTemplate)) { $oTemplate = new DisplayTemplate($sTemplate); - $sNameAttCode = MetaModel::GetNameAttributeCode(get_class($this)); + $sNameAttCode = MetaModel::GetNameAttributeCode(get_class($this)); $oTemplate->Render($oPage, array('class_name'=> MetaModel::GetName(get_class($this)),'class'=> get_class($this),'pkey'=> $this->GetKey(), 'name' => $this->Get($sNameAttCode))); } else @@ -365,12 +365,12 @@ abstract class cmdbAbstractObject extends CMDBObject } if ($bSelectMode) { - if (!$bSingleSelectMode) - { + if (!$bSingleSelectMode) + { $aAttribs['form::select'] = array('label' => "", 'description' => 'Select / Deselect All'); } else - { + { $aAttribs['form::select'] = array('label' => "", 'description' => ''); } } @@ -396,12 +396,12 @@ abstract class cmdbAbstractObject extends CMDBObject $aRow['key'] = $oObj->GetKey(); if ($bSelectMode) { - if ($bSingleSelectMode) - { + if ($bSingleSelectMode) + { $aRow['form::select'] = "GetKey()."\">"; - } - else - { + } + else + { $aRow['form::select'] = "GetKey()."\">"; } } @@ -571,12 +571,27 @@ abstract class cmdbAbstractObject extends CMDBObject $aHeader = array(); foreach($aAuthorizedClasses as $sAlias => $sClassName) { - $aList[$sClassName] = MetaModel::GetZListItems($sClassName, 'details'); - $aHeader[] = MetaModel::GetKeyLabel($sClassName); - foreach($aList[$sClassName] as $sAttCode) - { - $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); - } + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) + { + if ($oAttDef->IsScalar()) + { + $aList[$sClassName][$sAttCode] = $oAttDef; + } + } + $aHeader[] = MetaModel::GetKeyLabel($sClassName); + foreach($aList[$sClassName] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalField()) + { + $sExtKeyLabel = MetaModel::GetLabel($sClassName, $oAttDef->GetKeyAttCode()); + $sRemoteAttLabel = MetaModel::GetLabel($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode()); + $aHeader[] = $sExtKeyLabel.'->'.$sRemoteAttLabel; + } + else + { + $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); + } + } } $sHtml = '#'.$oSet->GetFilter()->ToOQL()."\n"; $sHtml .= implode($sSeparator, $aHeader)."\n"; @@ -587,9 +602,9 @@ abstract class cmdbAbstractObject extends CMDBObject foreach($aAuthorizedClasses as $sAlias => $sClassName) { $oObj = $aObjects[$sAlias]; - $aRow[] = $oObj->GetKey(); - foreach($aList[$sClassName] as $sAttCode) - { + $aRow[] = $oObj->GetKey(); + foreach($aList[$sClassName] as $sAttCode => $oAttDef) + { $aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, '\\'); } } diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 94d4abfa75..d453748740 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1185,6 +1185,12 @@ class AttributeExternalField extends AttributeDefinition return $oExtAttDef->IsNullAllowed(); } + public function IsScalar() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->IsScalar(); + } + public function GetBasicFilterOperators() { $oExtAttDef = $this->GetExtAttDef(); From 0f8f9662170f50f75a5d64fd0ca21c7fb61d197d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 15 Apr 2010 13:05:09 +0000 Subject: [PATCH 251/970] CSV import: improved handling of external keys SVN:trunk[334] --- core/bulkchange.class.inc.php | 172 +++++++++++++++++++++++++++------- webservices/import.php | 4 +- 2 files changed, 139 insertions(+), 37 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 8bafa10c2d..1452272f05 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -125,6 +125,32 @@ class CellChangeSpec_Issue extends CellChangeSpec_Modify } } +class CellChangeSpec_Ambiguous extends CellChangeSpec_Modify +{ + protected $m_iCount; + protected $m_sOql; + + public function __construct($previousValue, $iCount, $sOql) + { + $this->m_iCount = $iCount; + $this->m_sQuery = $sOql; + parent::__construct(null, $previousValue); + } + + public function GetDescription($bHtml = false) + { + if ($bHtml) + { + $sCount = ''.$this->m_iCount.''; + } + else + { + $sCount = $this->m_iCount; + } + return "Ambiguous: found $sCount objects"; + } +} + /** * RowStatus @@ -230,10 +256,10 @@ class BulkChange protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string) // #@# todo: rename the variables to sColIndex protected $m_aAttList; // attcode => iCol - protected $m_aReconcilKeys;// iCol => attcode (attcode = 'id' for the pkey) protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol; + protected $m_aReconcilKeys;// attcode (attcode = 'id' for the pkey) - public function __construct($sClass, $aData, $aAttList, $aReconcilKeys, $aExtKeys) + public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys) { $this->m_sClass = $sClass; $this->m_aData = $aData; @@ -256,6 +282,22 @@ class BulkChange return $oObj; } + protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults) + { + $oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); + $oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass()); + foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol) + { + // The foreign attribute is one of our reconciliation key + $oReconFilter->AddCondition($sForeignAttCode, $aRowData[$iCol], '='); + $aResults["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + } + + $oExtObjects = new CMDBObjectSet($oReconFilter); + $aKeys = $oExtObjects->ToArray(); + return $aKeys; + } + protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors) { $aResults = array(); @@ -265,6 +307,9 @@ class BulkChange // foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) { + // Skip external keys used for the reconciliation process + if (!array_key_exists($sAttCode, $this->m_aAttList)) continue; + $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); $oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass()); foreach ($aKeyConfig as $sForeignAttCode => $iCol) @@ -295,7 +340,7 @@ class BulkChange default: $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $previousValue, "Found ".$oExtObjects->Count()." matches"); + $aResults[$sAttCode]= new CellChangeSpec_Ambiguous($previousValue, $oExtObjects->Count(), $oExtObjects->ToOql()); } // Report @@ -473,49 +518,106 @@ class BulkChange foreach($this->m_aData as $iRow => $aRowData) { $oReconciliationFilter = new CMDBSearchFilter($this->m_sClass); + $bSkipQuery = false; foreach($this->m_aReconcilKeys as $sAttCode) { - $iCol = $this->m_aAttList[$sAttCode]; - $oReconciliationFilter->AddCondition($sAttCode, $aRowData[$iCol], '='); - } - $oReconciliationSet = new CMDBObjectSet($oReconciliationFilter); - switch($oReconciliationSet->Count()) - { - case 0: - $this->CreateObject($aResult, $iRow, $aRowData, $oChange); - // $aResult[$iRow]["__STATUS__"]=> set in CreateObject - $aResult[$iRow]["__RECONCILIATION__"] = "Object not found"; - break; - case 1: - $oTargetObj = $oReconciliationSet->Fetch(); - $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); - $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetHyperLink(); - // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject - break; - default: - // Found several matches, ambiguous - // Render "void" results on any column - foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) + $valuecondition = null; + if (array_key_exists($sAttCode, $this->m_aExtKeys)) { - foreach ($aKeyConfig as $sForeignAttCode => $iCol) + // The value has to be found or verified + $aMatches = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]); + + if (count($aMatches) == 1) { - $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); - } - $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void('n/a'); + $oRemoteObj = reset($aMatches); // first item + $valuecondition = $oRemoteObj->GetKey(); + $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void($oRemoteObj->GetKey()); + } + elseif (count($aMatches) == 0) + { + $aResult[$iRow]["__RECONCILIATION__"] = "Could not find a match for external key '$sAttCode'"; + $aResult[$iRow][$sAttCode] = new CellChangeSpec_Issue(null, null, 'object not found'); + } + else + { + $aResult[$iRow]["__RECONCILIATION__"] = "Ambiguous external key '$sAttCode'"; + $aResult[$iRow][$sAttCode] = new CellChangeSpec_Issue(null, null, 'found '.count($aMatches).' matches'); + } } - foreach ($this->m_aAttList as $sAttCode => $iCol) + else { - $aResult[$iRow]["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]); + // The value is given in the data row + $iCol = $this->m_aAttList[$sAttCode]; + $valuecondition = $aRowData[$iCol]; + } + if (is_null($valuecondition)) + { + $bSkipQuery = true; + } + else + { + $oReconciliationFilter->AddCondition($sAttCode, $valuecondition, '='); + } + } + if ($bSkipQuery) + { + $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("failed to reconcile"); + } + else + { + $oReconciliationSet = new CMDBObjectSet($oReconciliationFilter); + switch($oReconciliationSet->Count()) + { + case 0: + $this->CreateObject($aResult, $iRow, $aRowData, $oChange); + // $aResult[$iRow]["__STATUS__"]=> set in CreateObject + $aResult[$iRow]["__RECONCILIATION__"] = "Object not found"; + break; + case 1: + $oTargetObj = $oReconciliationSet->Fetch(); + $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); + $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetHyperLink(); + // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject + break; + default: + // Found several matches, ambiguous + // Render "void" results on any column + foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) + { + foreach ($aKeyConfig as $sForeignAttCode => $iCol) + { + $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + } + $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void('n/a'); + } + foreach ($this->m_aAttList as $sAttCode => $iCol) + { + $aResult[$iRow]["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]); + } + $aResult[$iRow]["__RECONCILIATION__"] = "Found ".$oReconciliationSet->Count()." matches"; + $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("ambiguous reconciliation"); } - $aResult[$iRow]["__RECONCILIATION__"] = "Found ".$oReconciliationSet->Count()." matches"; - $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("ambiguous reconciliation"); } // Whatever happened, do report the reconciliation values - foreach($this->m_aReconcilKeys as $sAttCode) + foreach($this->m_aAttList as $iCol) { - $iCol = $this->m_aAttList[$sAttCode]; - $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + if (!array_key_exists($iCol, $aResult[$iRow])) + { + $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + } + } + foreach($this->m_aExtKeys as $sAttCode => $aForeignAtts) + { + if (!array_key_exists($sAttCode, $aResult[$iRow])) + { + $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void('n/a'); + foreach ($aForeignAtts as $sForeignAttCode => $iCol) + { + // The foreign attribute is one of our reconciliation key + $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + } + } } } return $aResult; diff --git a/webservices/import.php b/webservices/import.php index eaeb1eb9f5..1e6e5a3d59 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -97,8 +97,8 @@ try $sClass, $aData, $aAttList, - $aReconcilKeys, - $aExtKeys + $aExtKeys, + $aReconcilKeys ); $oMyChange = MetaModel::NewObject("CMDBChange"); From 12b7fb74986d5d4b3340af5642fd489e6e4884df Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 15 Apr 2010 15:12:47 +0000 Subject: [PATCH 252/970] Bulkload reporting ready for integration with the new UI SVN:trunk[335] --- core/bulkchange.class.inc.php | 179 +++++++++++++--------------------- 1 file changed, 70 insertions(+), 109 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 1452272f05..193ddc029b 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -31,10 +31,12 @@ class BulkChangeException extends CoreException abstract class CellChangeSpec { protected $m_proposedValue; + protected $m_sOql; // in case of ambiguity - public function __construct($proposedValue) + public function __construct($proposedValue, $sOql = '') { $this->m_proposedValue = $proposedValue; + $this->m_sOql = $sOql; } static protected function ValueAsHtml($value) @@ -49,47 +51,29 @@ abstract class CellChangeSpec } } - public function GetValue($bHtml = false) + public function GetValue() { - if ($bHtml) - { - return self::ValueAsHtml($this->m_proposedValue); - } - else - { - return $this->m_proposedValue; - } + return $this->m_proposedValue; } - abstract public function GetDescription($bHtml = false); + public function GetOql() + { + return $this->m_proposedValue; + } + + abstract public function GetDescription(); } -class CellChangeSpec_Void extends CellChangeSpec +class CellStatus_Void extends CellChangeSpec { - public function GetDescription($bHtml = false) + public function GetDescription() { - return $this->GetValue($bHtml); + return ''; } } -class CellChangeSpec_Unchanged extends CellChangeSpec -{ - public function GetDescription($bHtml = false) - { - return $this->GetValue($bHtml)." (unchanged)"; - } -} - -class CellChangeSpec_Init extends CellChangeSpec -{ - public function GetDescription($bHtml = false) - { - return $this->GetValue($bHtml); - } -} - -class CellChangeSpec_Modify extends CellChangeSpec +class CellStatus_Modify extends CellChangeSpec { protected $m_previousValue; @@ -99,13 +83,18 @@ class CellChangeSpec_Modify extends CellChangeSpec parent::__construct($proposedValue); } - public function GetDescription($bHtml = false) + public function GetDescription() { - return $this->GetValue($bHtml)." (previous: ".self::ValueAsHtml($this->m_previousValue).")"; + return 'Modified'; + } + + public function GetPreviousValue() + { + return $this->m_previousValue; } } -class CellChangeSpec_Issue extends CellChangeSpec_Modify +class CellStatus_Issue extends CellStatus_Modify { protected $m_sReason; @@ -115,38 +104,30 @@ class CellChangeSpec_Issue extends CellChangeSpec_Modify parent::__construct($proposedValue, $previousValue); } - public function GetDescription($bHtml = false) + public function GetDescription() { if (is_null($this->m_proposedValue)) { return 'Could not be changed - reason: '.$this->m_sReason; } - return 'Could not be changed to "'.$this->GetValue($bHtml).'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')'; + return 'Could not be changed to '.$this->m_proposedValue.' - reason: '.$this->m_sReason; } } -class CellChangeSpec_Ambiguous extends CellChangeSpec_Modify +class CellStatus_Ambiguous extends CellStatus_Issue { protected $m_iCount; - protected $m_sOql; public function __construct($previousValue, $iCount, $sOql) { $this->m_iCount = $iCount; $this->m_sQuery = $sOql; - parent::__construct(null, $previousValue); + parent::__construct(null, $previousValue, ''); } - public function GetDescription($bHtml = false) + public function GetDescription() { - if ($bHtml) - { - $sCount = ''.$this->m_iCount.''; - } - else - { - $sCount = $this->m_iCount; - } + $sCount = $this->m_iCount; return "Ambiguous: found $sCount objects"; } } @@ -169,12 +150,12 @@ abstract class RowStatus { } - abstract public function GetDescription($bHtml = false); + abstract public function GetDescription(); } class RowStatus_NoChange extends RowStatus { - public function GetDescription($bHtml = false) + public function GetDescription() { return "unchanged"; } @@ -182,32 +163,9 @@ class RowStatus_NoChange extends RowStatus class RowStatus_NewObj extends RowStatus { - protected $m_iObjKey; - - public function __construct($sClass = '', $iObjKey = null) + public function GetDescription() { - $this->m_iObjKey = $iObjKey; - $this->m_sClass = $sClass; - } - - public function GetDescription($bHtml = false) - { - if (is_null($this->m_iObjKey)) - { - return "Create"; - } - else - { - if (!empty($this->m_sClass)) - { - $oObj = MetaModel::GetObject($this->m_sClass, $this->m_iObjKey); - return 'Created '.$oObj->GetHyperLink(); - } - else - { - return 'Created (id: '.$this->m_iObjKey.')'; - } - } + return "created"; } } @@ -220,9 +178,9 @@ class RowStatus_Modify extends RowStatus $this->m_iChanged = $iChanged; } - public function GetDescription($bHtml = false) + public function GetDescription() { - return "update ".$this->m_iChanged." cols"; + return "updated ".$this->m_iChanged." cols"; } } @@ -235,9 +193,9 @@ class RowStatus_Issue extends RowStatus $this->m_sReason = $sReason; } - public function GetDescription($bHtml = false) + public function GetDescription() { - return 'Skipped - reason:'.$this->m_sReason; + return 'Issue: '.$this->m_sReason; } } @@ -290,12 +248,12 @@ class BulkChange { // The foreign attribute is one of our reconciliation key $oReconFilter->AddCondition($sForeignAttCode, $aRowData[$iCol], '='); - $aResults["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + $aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]); } $oExtObjects = new CMDBObjectSet($oReconFilter); $aKeys = $oExtObjects->ToArray(); - return $aKeys; + return array($oReconFilter->ToOql(), $aKeys); } protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors) @@ -316,7 +274,7 @@ class BulkChange { // The foreign attribute is one of our reconciliation key $oReconFilter->AddCondition($sForeignAttCode, $aRowData[$iCol], '='); - $aResults["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + $aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]); } $oExtObjects = new CMDBObjectSet($oReconFilter); switch($oExtObjects->Count()) @@ -329,7 +287,7 @@ class BulkChange else { $aErrors[$sAttCode] = "Object not found"; - $aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found - check the spelling (no space before/after)'); + $aResults[$sAttCode]= new CellStatus_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found - check the spelling (no space before/after)'); } break; case 1: @@ -340,7 +298,7 @@ class BulkChange default: $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); - $aResults[$sAttCode]= new CellChangeSpec_Ambiguous($previousValue, $oExtObjects->Count(), $oExtObjects->ToOql()); + $aResults[$sAttCode]= new CellStatus_Ambiguous($previousValue, $oExtObjects->Count(), $oExtObjects->ToOql()); } // Report @@ -351,17 +309,17 @@ class BulkChange { if ($oTargetObj->IsNew()) { - $aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj); + $aResults[$sAttCode]= new CellStatus_Void($oForeignObj); } else { $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->GetOriginal($sAttCode)); - $aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj, $previousValue); + $aResults[$sAttCode]= new CellStatus_Modify($oForeignObj, $previousValue); } } else { - $aResults[$sAttCode]= new CellChangeSpec_Unchanged($oForeignObj); + $aResults[$sAttCode]= new CellStatus_Void($oForeignObj); } } } @@ -392,33 +350,33 @@ class BulkChange { if ($aRowData[$iCol] == $oTargetObj->GetKey()) { - $aResults["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]); + $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); } else { - $aResults["col$iCol"]= new CellChangeSpec_Init($aRowData[$iCol]); + $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); } } if (isset($aErrors[$sAttCode])) { - $aResults["col$iCol"]= new CellChangeSpec_Issue($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode), $aErrors[$sAttCode]); + $aResults[$iCol]= new CellStatus_Issue($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode), $aErrors[$sAttCode]); } elseif (array_key_exists($sAttCode, $aChangedFields)) { if ($oTargetObj->IsNew()) { - $aResults["col$iCol"]= new CellChangeSpec_Init($oTargetObj->Get($sAttCode)); + $aResults[$iCol]= new CellStatus_Void($oTargetObj->Get($sAttCode)); } else { - $aResults["col$iCol"]= new CellChangeSpec_Modify($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); + $aResults[$iCol]= new CellStatus_Modify($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode)); } } else { // By default... nothing happens - $aResults["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]); + $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); } } @@ -469,12 +427,15 @@ class BulkChange { $newID = $oTargetObj->DBInsertTrackedNoReload($oChange); $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($this->m_sClass, $newID); + $aResult[$iRow]["finalclass"] = get_class($oTargetObj); + $aResult[$iRow]["id"] = CellStatus_Void($newID); } else { $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj(); + $aResult[$iRow]["finalclass"] = get_class($oTargetObj); + $aResult[$iRow]["id"] = CellStatus_Void(0); } - } protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null) @@ -483,6 +444,9 @@ class BulkChange // Reporting // + $aResult[$iRow]["finalclass"] = get_class($oTargetObj); + $aResult[$iRow]["id"] = CellStatus_Void($aRowData[$iCol]); + if (count($aErrors) > 0) { $sErrors = implode(', ', $aErrors); @@ -525,23 +489,21 @@ class BulkChange if (array_key_exists($sAttCode, $this->m_aExtKeys)) { // The value has to be found or verified - $aMatches = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]); + list($sQuery, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]); if (count($aMatches) == 1) { $oRemoteObj = reset($aMatches); // first item $valuecondition = $oRemoteObj->GetKey(); - $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void($oRemoteObj->GetKey()); + $aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey()); } elseif (count($aMatches) == 0) { - $aResult[$iRow]["__RECONCILIATION__"] = "Could not find a match for external key '$sAttCode'"; - $aResult[$iRow][$sAttCode] = new CellChangeSpec_Issue(null, null, 'object not found'); + $aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, null, 'object not found'); } else { - $aResult[$iRow]["__RECONCILIATION__"] = "Ambiguous external key '$sAttCode'"; - $aResult[$iRow][$sAttCode] = new CellChangeSpec_Issue(null, null, 'found '.count($aMatches).' matches'); + $aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $sQuery); } } else @@ -571,12 +533,10 @@ class BulkChange case 0: $this->CreateObject($aResult, $iRow, $aRowData, $oChange); // $aResult[$iRow]["__STATUS__"]=> set in CreateObject - $aResult[$iRow]["__RECONCILIATION__"] = "Object not found"; break; case 1: $oTargetObj = $oReconciliationSet->Fetch(); $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); - $aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetHyperLink(); // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject break; default: @@ -586,16 +546,17 @@ class BulkChange { foreach ($aKeyConfig as $sForeignAttCode => $iCol) { - $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]); } - $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void('n/a'); + $aResult[$iRow][$sAttCode] = new CellStatus_Void('n/a'); } foreach ($this->m_aAttList as $sAttCode => $iCol) { - $aResult[$iRow]["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]); + $aResult[$iRow][$iCol]= new CellStatus_Void($aRowData[$iCol]); } - $aResult[$iRow]["__RECONCILIATION__"] = "Found ".$oReconciliationSet->Count()." matches"; $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("ambiguous reconciliation"); + $aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->ToOql()); + $aResult[$iRow]["finalclass"]= 'n/a'; } } @@ -604,18 +565,18 @@ class BulkChange { if (!array_key_exists($iCol, $aResult[$iRow])) { - $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]); } } foreach($this->m_aExtKeys as $sAttCode => $aForeignAtts) { if (!array_key_exists($sAttCode, $aResult[$iRow])) { - $aResult[$iRow][$sAttCode] = new CellChangeSpec_Void('n/a'); + $aResult[$iRow][$sAttCode] = new CellStatus_Void('n/a'); foreach ($aForeignAtts as $sForeignAttCode => $iCol) { // The foreign attribute is one of our reconciliation key - $aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]); + $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]); } } } From 2c90ebcc13f2bac0528b2e1ba38c262da7b86f0d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 15 Apr 2010 15:21:41 +0000 Subject: [PATCH 253/970] - Integrating... SVN:trunk[336] --- core/bulkchange.class.inc.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 193ddc029b..3c7c4b88c9 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -428,13 +428,13 @@ class BulkChange $newID = $oTargetObj->DBInsertTrackedNoReload($oChange); $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($this->m_sClass, $newID); $aResult[$iRow]["finalclass"] = get_class($oTargetObj); - $aResult[$iRow]["id"] = CellStatus_Void($newID); + $aResult[$iRow]["id"] = new CellStatus_Void($newID); } else { $aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj(); $aResult[$iRow]["finalclass"] = get_class($oTargetObj); - $aResult[$iRow]["id"] = CellStatus_Void(0); + $aResult[$iRow]["id"] = new CellStatus_Void(0); } } @@ -445,7 +445,7 @@ class BulkChange // Reporting // $aResult[$iRow]["finalclass"] = get_class($oTargetObj); - $aResult[$iRow]["id"] = CellStatus_Void($aRowData[$iCol]); + $aResult[$iRow]["id"] = new CellStatus_Void($oTargetObj->GetKey()); if (count($aErrors) > 0) { From b82521994826e81e6c15e19646fd6a86073dba20 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 15 Apr 2010 15:54:26 +0000 Subject: [PATCH 254/970] CSV load quick fix for integration SVN:trunk[337] --- core/bulkchange.class.inc.php | 53 ++++++----------------------------- 1 file changed, 8 insertions(+), 45 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 3c7c4b88c9..696d5dad68 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -226,20 +226,6 @@ class BulkChange $this->m_aExtKeys = $aExtKeys; } - static protected function MakeSpecObject($sClass, $iId) - { - try - { - $oObj = MetaModel::GetObject($sClass, $iId); - } - catch(CoreException $e) - { - // in case an ext key is 0 (which is currently acceptable) - return $iId; - } - return $oObj; - } - protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults) { $oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); @@ -287,7 +273,7 @@ class BulkChange else { $aErrors[$sAttCode] = "Object not found"; - $aResults[$sAttCode]= new CellStatus_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found - check the spelling (no space before/after)'); + $aResults[$sAttCode]= new CellStatus_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found'); } break; case 1: @@ -297,32 +283,30 @@ class BulkChange break; default: $aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches"; - $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->Get($sAttCode)); - $aResults[$sAttCode]= new CellStatus_Ambiguous($previousValue, $oExtObjects->Count(), $oExtObjects->ToOql()); + $aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $oExtObjects->Count(), $oExtObjects->ToOql()); } // Report if (!array_key_exists($sAttCode, $aResults)) { - $oForeignObj = $oTargetObj->Get($sAttCode); + $iForeignObj = $oTargetObj->Get($sAttCode); if (array_key_exists($sAttCode, $oTargetObj->ListChanges())) { if ($oTargetObj->IsNew()) { - $aResults[$sAttCode]= new CellStatus_Void($oForeignObj); + $aResults[$sAttCode]= new CellStatus_Void($iForeignObj); } else { - $previousValue = self::MakeSpecObject($oExtKey->GetTargetClass(), $oTargetObj->GetOriginal($sAttCode)); - $aResults[$sAttCode]= new CellStatus_Modify($oForeignObj, $previousValue); + $aResults[$sAttCode]= new CellStatus_Modify($iForeignObj, $oTargetObj->GetOriginal($sAttCode)); } } else { - $aResults[$sAttCode]= new CellStatus_Void($oForeignObj); + $aResults[$sAttCode]= new CellStatus_Void($iForeignObj); } } - } + } // Set the object attributes // @@ -348,15 +332,7 @@ class BulkChange { if ($sAttCode == 'id') { - if ($aRowData[$iCol] == $oTargetObj->GetKey()) - { - $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); - } - else - { - $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); - } - + $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); } if (isset($aErrors[$sAttCode])) { @@ -541,19 +517,6 @@ class BulkChange break; default: // Found several matches, ambiguous - // Render "void" results on any column - foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) - { - foreach ($aKeyConfig as $sForeignAttCode => $iCol) - { - $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]); - } - $aResult[$iRow][$sAttCode] = new CellStatus_Void('n/a'); - } - foreach ($this->m_aAttList as $sAttCode => $iCol) - { - $aResult[$iRow][$iCol]= new CellStatus_Void($aRowData[$iCol]); - } $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("ambiguous reconciliation"); $aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->ToOql()); $aResult[$iRow]["finalclass"]= 'n/a'; From dd02ce60b5cd4080ef2796cb2afaca83ceeb01c2 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 19 Apr 2010 13:32:30 +0000 Subject: [PATCH 255/970] Localization: first step (class Dict and handling of Metamodel) SVN:trunk[338] --- core/attributedef.class.inc.php | 17 +- core/cmdbobject.class.inc.php | 2 + core/config.class.inc.php | 47 +- core/dict.class.inc.php | 181 ++++ core/metamodel.class.php | 102 +- core/stimulus.class.inc.php | 28 +- dictionaries/dictionary.itop.core.php | 354 ++++++ dictionaries/dictionary.itop.model.php | 1383 ++++++++++++++++++++++++ dictionaries/dictionary.itop.ui.php | 266 +++++ pages/ITopConsultant.php | 7 + 10 files changed, 2380 insertions(+), 7 deletions(-) create mode 100644 core/dict.class.inc.php create mode 100644 dictionaries/dictionary.itop.core.php create mode 100644 dictionaries/dictionary.itop.model.php create mode 100644 dictionaries/dictionary.itop.ui.php diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index d453748740..54ab4d1d98 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -132,8 +132,10 @@ abstract class AttributeDefinition public function IsNullAllowed() {return true;} public function GetNullValue() {return null;} public function GetCode() {return $this->m_sCode;} - public function GetLabel() {return $this->Get("label");} - public function GetDescription() {return $this->Get("description");} + public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);} + public function Obsolete_GetLabel() {return $this->Get("label");} + public function GetDescription() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'+', '');} + public function Obsolete_GetDescription() {return $this->Get("description");} public function GetValuesDef() {return null;} public function GetPrerequisiteAttributes() {return array();} //public function IsSearchableStd() {return $this->Get("search_std");} @@ -828,6 +830,17 @@ class AttributeEnum extends AttributeString // later, we could imagine a detailed description in the title return "".parent::GetAsHtml($sLabel).""; } + + public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') + { + $aRawValues = parent::GetAllowedValues($aArgs, $sBeginsWith); + $aLocalizedValues = array(); + foreach ($aRawValues as $sKey => $sValue) + { + $aLocalizedValues[$sKey] = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sKey, $sKey); + } + return $aLocalizedValues; + } } /** diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 5f490e9e5d..498e7e73d4 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -17,6 +17,8 @@ require_once('coreexception.class.inc.php'); require_once('config.class.inc.php'); +require_once('dict.class.inc.php'); + require_once('attributedef.class.inc.php'); require_once('filterdef.class.inc.php'); require_once('stimulus.class.inc.php'); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index cc57e0cb37..dd9be7628b 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -2,7 +2,7 @@ require_once('coreexception.class.inc.php'); /** * Config - * configuration data + * configuration data (this class cannot not be localized, because it is responsible for loading the dictionaries) * * @package iTopORM * @author Romain Quetiez @@ -30,6 +30,7 @@ class Config protected $m_aAppModules; protected $m_aDataModels; protected $m_aAddons; + protected $m_aDictionaries; protected $m_sDBHost; protected $m_sDBUser; @@ -59,13 +60,19 @@ class Config * @var boolean Whether or not a secure connection is required for using the application */ protected $m_bSecureConnectionRequired; - + + /** + * @var string Langage code, default if the user language is undefined + */ + protected $m_sDefaultLanguage; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; $this->m_aAppModules = array(); $this->m_aDataModels = array(); $this->m_aAddons = array(); + $this->m_aDictionaries = array(); $this->m_sDBHost = ''; $this->m_sDBUser = ''; @@ -77,6 +84,7 @@ class Config $this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL; $this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED; + $this->m_sDefaultLanguage = 'EN US'; if ($bLoadConfig) { $this->Load($sConfigFile); @@ -144,9 +152,14 @@ class Config { $MyModules['addons']['user rights'] = '../addons/userrights/userrightsnull.class.inc.php'; } + if (!array_key_exists('dictionaries', $MyModules)) + { + throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'dictionaries\']')); + } $this->m_aAppModules = $MyModules['application']; $this->m_aDataModels = $MyModules['business']; $this->m_aAddons = $MyModules['addons']; + $this->m_aDictionaries = $MyModules['dictionaries']; $this->m_sDBHost = trim($MySettings['db_host']); $this->m_sDBUser = trim($MySettings['db_user']); @@ -159,6 +172,8 @@ class Config $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL; $this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED; + + $this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US'; } protected function Verify() @@ -175,6 +190,10 @@ class Config { $this->CheckFile('addon module', $sToInclude); } + foreach ($this->m_aDictionaries as $sModule => $sToInclude) + { + $this->CheckFile('dictionary', $sToInclude); + } } public function GetAppModules() @@ -192,6 +211,11 @@ class Config return $this->m_aAddons; } + public function GetDictionaries() + { + return $this->m_aDictionaries; + } + public function GetDBHost() { return $this->m_sDBHost; @@ -242,6 +266,11 @@ class Config return $this->m_bSecureConnectionRequired; } + public function GetDefaultLanguage() + { + return $this->m_sDefaultLanguage; + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -292,6 +321,11 @@ class Config $this->m_bSecureConnectionRequired = $bSecureConnectionRequired; } + public function SetDefaultLanguage($sLanguageCode) + { + $this->m_sDefaultLanguage = $sLanguageCode; + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -334,6 +368,7 @@ class Config fwrite($hFile, "\t'standard_reload_interval' => {$this->m_iStandardReloadInterval},\n"); fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n"); fwrite($hFile, "\t'secure_connection_required' => ".($this->m_bSecureConnectionRequired ? 'true' : 'false').",\n"); + fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n/**\n"); @@ -362,7 +397,13 @@ class Config fwrite($hFile, "\t'addons' => array (\n"); fwrite($hFile, "\t\t'user rights' => '../addons/userrights/userrightsprofile.class.inc.php',\n"); fwrite($hFile, "\t\t// other modules to come later\n"); - fwrite($hFile, "\t)\n"); + fwrite($hFile, "\t),\n"); + fwrite($hFile, "\t'dictionaries' => array (\n"); + fwrite($hFile, "\t\t'../dictionaries/dictionary.itop.model.php',\n"); + fwrite($hFile, "\t\t'../dictionaries/dictionary.itop.core.php',\n"); + fwrite($hFile, "\t\t'../dictionaries/dictionary.itop.ui.php',\n"); + fwrite($hFile, "\t\t// to be continued...\n"); + fwrite($hFile, "\t),\n"); fwrite($hFile, ");\n"); fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting ! return fclose($hFile); diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php new file mode 100644 index 0000000000..294c55103b --- /dev/null +++ b/core/dict.class.inc.php @@ -0,0 +1,181 @@ + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ + + +class DictException extends CoreException +{ +} + +class DictExceptionUnknownLanguage extends DictException +{ + public function __construct($sLanguageCode) + { + $aContext = array(); + $aContext['language_code'] = $sLanguageCode; + parent::__construct('Unknown localization language', $aContext); + } +} + +class DictExceptionMissingString extends DictException +{ + public function __construct($sLanguageCode, $sStringCode) + { + $aContext = array(); + $aContext['language_code'] = $sLanguageCode; + $aContext['string_code'] = $sStringCode; + parent::__construct('Missing localized string', $aContext); + } +} + + +define('DICT_ERR_STRING', 1); // when a string is missing, return the identifier +define('DICT_ERR_EXCEPTION', 2); // when a string is missing, throw an exception +//define('DICT_ERR_LOG', 3); // when a string is missing, log an error + + +class Dict +{ + protected static $m_iErrorMode = DICT_ERR_STRING; + protected static $m_sCurrentLanguage = 'EN US'; + + protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...) + protected static $m_aData = array(); + + + public static function SetLanguage($sLanguageCode) + { + if (!array_key_exists($sLanguageCode, self::$m_aLanguages)) + { + throw new DictExceptionUnknownLanguage($sLanguageCode); + } + self::$m_sCurrentLanguage = $sLanguageCode; + } + + + //returns a hash array( code => array( 'description' => '...', 'localized_description' => '...') ...) + public static function GetLanguages() + { + return self::$m_aLanguages; + } + + // iErrorMode from {DICT_ERR_STRING, DICT_ERR_EXCEPTION} + public static function SetErrorMode($iErrorMode) + { + self::$m_iErrorMode = $iErrorMode; + } + + + public static function S($sStringCode, $sDefault = null) + { + $aCurrentDictionary = self::$m_aData[self::$m_sCurrentLanguage]; + if (!array_key_exists($sStringCode, $aCurrentDictionary)) + { + switch (self::$m_iErrorMode) + { + case DICT_ERR_STRING: + if (is_null($sDefault)) + { + return $sStringCode; + } + else + { + return $sDefault; + } + break; + + case DICT_ERR_EXCEPTION: + default: + throw new DictExceptionMissingString(self::$m_sCurrentLanguage, $sStringCode); + break; + } + } + return $aCurrentDictionary[$sStringCode]; + } + + + public static function Format($sFormatCode /*, ... arguments ....*/) + { + $sLocalizedFormat = self::S($sFormatCode); + + $aArguments = func_get_args(); + array_shift($aArguments); + return vsprintf($sFormat, $aArguments); + } + + + // sLanguageCode: Code identifying the language i.e. FR-FR + // sEnglishLanguageDesc: Description of the language code, in English. i.e. French (France) + // sLocalizedLanguageDesc: Description of the language code, in its own language. i.e. Français (France) + // aEntries: Hash array of dictionnary entries + public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries) + { + if (!array_key_exists($sLanguageCode, self::$m_aLanguages)) + { + self::$m_aLanguages[$sLanguageCode] = array($sEnglishLanguageDesc, $sLocalizedLanguageDesc); + self::$m_aData[$sLanguageCode] = array(); + } + self::$m_aData[$sLanguageCode] = array_merge(self::$m_aData[$sLanguageCode], $aEntries); + } + + public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US') + { + $aMissing = array(); // Strings missing for the target language + $aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary + $aNotTranslated = array(); // Strings having the same value in both dictionaries + $aOK = array(); // Strings having different values in both dictionaries + + foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue) + { + if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode])) + { + $aMissing[$sStringCode] = $sValue; + } + } + + foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue) + { + if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef])) + { + $aUnexpected[$sStringCode] = $sValue; + } + else + { + // The value exists in the reference + $sRefValue = self::$m_aData[$sLanguageRef][$sStringCode]; + if ($sValue == $sRefValue) + { + $aNotTranslated[$sStringCode] = $sValue; + } + else + { + $aOK[$sStringCode] = $sValue; + } + } + } + return array($aMissing, $aUnexpected, $aNotTranslated, $aOK); + } + + public static function Dump() + { + MyHelpers::var_dump_html(self::$m_aData); + } +} + +/* +Dans les templates, les chaines localizables seront remplacées par un tag code_de_la_chaine +Modifier les profils utilisateurs pour stocker le langage de l'utilisateur. +*/ + + +?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index e1cfbdcb8a..f114438b8d 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -194,7 +194,13 @@ abstract class MetaModel final static public function GetName($sClass) { - self::_check_subclass($sClass); + self::_check_subclass($sClass); + $sStringCode = 'Class:'.$sClass; + return Dict::S($sStringCode, $sClass); + } + final static public function Obsolete_GetName($sClass) + { + self::_check_subclass($sClass); return self::$m_aClassParams[$sClass]["name"]; } final static public function GetCategory($sClass) @@ -208,6 +214,12 @@ abstract class MetaModel return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); } final static public function GetClassDescription($sClass) + { + self::_check_subclass($sClass); + $sStringCode = 'Class:'.$sClass.'+'; + return Dict::S($sStringCode, ''); + } + final static public function Obsolete_GetClassDescription($sClass) { self::_check_subclass($sClass); return self::$m_aClassParams[$sClass]["description"]; @@ -1130,6 +1142,11 @@ abstract class MetaModel { $sTargetClass = self::GetCallersPHPClass("Init"); self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; + + // I wanted to simplify the syntax of the declaration of objects in the biz model + // Therefore, the reference to the host class is set there + $oStimulus->SetHostClass($sTargetClass); + $oStimulus->SetCode($sStimulusCode); } public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) @@ -2089,6 +2106,83 @@ abstract class MetaModel return $aDataDump; } + public static function MakeDictionaryTemplate() + { + $sRes = ''; + + foreach (Dict::GetLanguages() as $sLanguageCode => $aLanguageData) + { + list($aMissing, $aUnexpected, $aNotTranslated, $aOK) = Dict::MakeStats($sLanguageCode, 'EN US'); + echo "

    Stats for language: $sLanguageCode

    \n"; + echo "
    • Missing:".count($aMissing)."
    • Unexpected:".count($aUnexpected)."
    • NotTranslated:".count($aNotTranslated)."
    • OK:".count($aOK)."
    \n"; + } + $sRes .= "// Dictionnay conventions\n"; + $sRes .= htmlentities("// Class:\n"); + $sRes .= htmlentities("// Class:+\n"); + $sRes .= htmlentities("// Class:/Attribute:\n"); + $sRes .= htmlentities("// Class:/Attribute:+\n"); + $sRes .= htmlentities("// Class:/Attribute:/Value:\n"); + $sRes .= htmlentities("// Class:/Attribute:/Value:+\n"); + $sRes .= htmlentities("// Class:/Stimulus:\n"); + $sRes .= htmlentities("// Class:/Stimulus:+\n"); + $sRes .= "\n"; + + // Note: I did not use EnumCategories(), because a given class maybe found in several categories + // Need to invent the "module", to characterize the origins of a class + $aModules = array('bizmodel', 'core/cmdb', 'gui' , 'application', 'addon/userrights'); + + $sRes .= "//////////////////////////////////////////////////////////////////////\n"; + $sRes .= "// Note: The classes have been grouped by categories: ".implode(', ', $aModules)."\n"; + $sRes .= "//////////////////////////////////////////////////////////////////////\n"; + + foreach ($aModules as $sCategory) + { + $sRes .= "//////////////////////////////////////////////////////////////////////\n"; + $sRes .= "// Classes in '$sCategory'\n"; + $sRes .= "//////////////////////////////////////////////////////////////////////\n"; + $sRes .= "//\n"; + $sRes .= "\n"; + foreach (self::GetClasses($sCategory) as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sRes .= "//\n"; + $sRes .= "// Class: $sClass\n"; + $sRes .= "//\n"; + $sRes .= "\n"; + $sRes .= "Dict::Add('EN US', 'English', 'English', array(\n"; + $sRes .= " 'Class:$sClass' => '".self::GetName($sClass)."',\n"; + $sRes .= " 'Class:$sClass+' => '".self::GetClassDescription($sClass)."',\n"; + foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + { + // Skip this attribute if not originaly defined in this class + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + $sRes .= " 'Class:$sClass/Attribute:$sAttCode' => '".$oAttDef->GetLabel()."',\n"; + $sRes .= " 'Class:$sClass/Attribute:$sAttCode+' => '".$oAttDef->GetDescription()."',\n"; + if ($oAttDef instanceof AttributeEnum) + { + foreach ($oAttDef->GetAllowedValues() as $sKey => $value) + { + $sValue = str_replace("'", "\\'", $value); + $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey' => '$sValue',\n"; + $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey+' => '$sValue',\n"; + } + } + } + foreach(self::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode' => '".$oStimulus->Get('label')."',\n"; + $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode+' => '".$oStimulus->Get('description')."',\n"; + } + + $sRes .= "));\n"; + $sRes .= "\n"; + } + } + return $sRes; + } + public static function DBCheckFormat() { $aErrors = array(); @@ -2625,6 +2719,12 @@ abstract class MetaModel { self::Plugin($sConfigFile, 'addons', $sToInclude); } + foreach ($oConfig->GetDictionaries() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'dictionaries', $sToInclude); + } + // Set the language... after the dictionaries have been loaded! + Dict::SetLanguage($oConfig->GetDefaultLanguage()); $sServer = $oConfig->GetDBHost(); $sUser = $oConfig->GetDBUser(); diff --git a/core/stimulus.class.inc.php b/core/stimulus.class.inc.php index d2326f6fc2..76ce978d4e 100644 --- a/core/stimulus.class.inc.php +++ b/core/stimulus.class.inc.php @@ -12,16 +12,42 @@ * @version 1.1.1.1 $ */ +// #@# Really dirty !!! +// #@# TO BE CLEANED -> ALIGN WITH OTHER METAMODEL DECLARATIONS + class ObjectStimulus { private $m_aParams = array(); + private $m_sHostClass = null; + private $m_sCode = null; public function __construct($aParams) { - $this->m_aParams = $aParams; + // obsolete: $this->m_aParams = $aParams; + $this->m_aParams['label'] = 'foo'; + $this->m_aParams['description'] = 'foo'; $this->ConsistencyCheck(); } + public function SetHostClass($sHostClass) + { + $this->m_sHostClass = $sHostClass; + } + public function GetHostClass() + { + return $this->m_sHostClass; + } + public function SetCode($sCode) + { + $this->m_sCode = $sCode; + $this->m_aParams['label'] = Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode, $this->m_sCode); + $this->m_aParams['description'] = Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode.'+', ''); + } + public function GetCode() + { + return $this->m_sCode; + } + public function Get($sParamName) {return $this->m_aParams[$sParamName];} // Note: I could factorize this code with the parameter management made for the AttributeDef class diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php new file mode 100644 index 0000000000..838dbe8b8e --- /dev/null +++ b/dictionaries/dictionary.itop.core.php @@ -0,0 +1,354 @@ + 'change', + 'Class:CMDBChange+' => 'Changes tracking', + 'Class:CMDBChange/Attribute:date' => 'date', + 'Class:CMDBChange/Attribute:date+' => 'date and time at which the changes have been recorded', + 'Class:CMDBChange/Attribute:userinfo' => 'misc. info', + 'Class:CMDBChange/Attribute:userinfo+' => 'caller\'s defined information', +)); + +// +// Class: CMDBChangeOp +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOp' => 'change operation', + 'Class:CMDBChangeOp+' => 'Change operations tracking', + 'Class:CMDBChangeOp/Attribute:change' => 'change', + 'Class:CMDBChangeOp/Attribute:change+' => 'change', + 'Class:CMDBChangeOp/Attribute:date' => 'date', + 'Class:CMDBChangeOp/Attribute:date+' => 'date and time of the change', + 'Class:CMDBChangeOp/Attribute:userinfo' => 'user', + 'Class:CMDBChangeOp/Attribute:userinfo+' => 'who made this change', + 'Class:CMDBChangeOp/Attribute:objclass' => 'object class', + 'Class:CMDBChangeOp/Attribute:objclass+' => 'object class', + 'Class:CMDBChangeOp/Attribute:objkey' => 'object id', + 'Class:CMDBChangeOp/Attribute:objkey+' => 'object id', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'Class', + 'Class:CMDBChangeOp/Attribute:finalclass+' => 'Real (final) object class', +)); + +// +// Class: CMDBChangeOpCreate +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOpCreate' => 'object creation', + 'Class:CMDBChangeOpCreate+' => 'Object creation tracking', +)); + +// +// Class: CMDBChangeOpDelete +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOpDelete' => 'object deletion', + 'Class:CMDBChangeOpDelete+' => 'Object deletion tracking', +)); + +// +// Class: CMDBChangeOpSetAttribute +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOpSetAttribute' => 'object change', + 'Class:CMDBChangeOpSetAttribute+' => 'Object properties change tracking', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Attribute', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => 'code of the modified property', +)); + +// +// Class: CMDBChangeOpSetAttributeScalar +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOpSetAttributeScalar' => 'property change', + 'Class:CMDBChangeOpSetAttributeScalar+' => 'Object scalar properties change tracking', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Previous value', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => 'previous value of the attribute', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'New value', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'new value of the attribute', +)); + +// +// Class: CMDBChangeOpSetAttributeBlob +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOpSetAttributeBlob' => 'data change', + 'Class:CMDBChangeOpSetAttributeBlob+' => 'data change tracking', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Previous data', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'previous contents of the attribute', +)); + +// +// Class: CMDBChangeOpSetAttributeText +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:CMDBChangeOpSetAttributeText' => 'text change', + 'Class:CMDBChangeOpSetAttributeText+' => 'text change tracking', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Previous data', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'previous contents of the attribute', +)); + +// +// Class: Event +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:Event' => 'Log Event', + 'Class:Event+' => 'An application internal event', + 'Class:Event/Attribute:message' => 'message', + 'Class:Event/Attribute:message+' => 'short description of the event', + 'Class:Event/Attribute:date' => 'date', + 'Class:Event/Attribute:date+' => 'date and time at which the changes have been recorded', + 'Class:Event/Attribute:userinfo' => 'user info', + 'Class:Event/Attribute:userinfo+' => 'identification of the user that was doing the action that triggered this event', + 'Class:Event/Attribute:finalclass' => 'Class', + 'Class:Event/Attribute:finalclass+' => 'Real (final) object class', +)); + +// +// Class: EventNotification +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:EventNotification' => 'Notification event', + 'Class:EventNotification+' => 'Trace of a notification that has been sent', + 'Class:EventNotification/Attribute:trigger_id' => 'Trigger', + 'Class:EventNotification/Attribute:trigger_id+' => 'user account', + 'Class:EventNotification/Attribute:action_id' => 'user', + 'Class:EventNotification/Attribute:action_id+' => 'user account', + 'Class:EventNotification/Attribute:object_id' => 'Object id', + 'Class:EventNotification/Attribute:object_id+' => 'object id (class defined by the trigger ?)', +)); + +// +// Class: EventNotificationEmail +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:EventNotificationEmail' => 'Email emission event', + 'Class:EventNotificationEmail+' => 'Trace of an email that has been sent', + 'Class:EventNotificationEmail/Attribute:to' => 'TO', + 'Class:EventNotificationEmail/Attribute:to+' => 'TO', + 'Class:EventNotificationEmail/Attribute:cc' => 'CC', + 'Class:EventNotificationEmail/Attribute:cc+' => 'CC', + 'Class:EventNotificationEmail/Attribute:bcc' => 'BCC', + 'Class:EventNotificationEmail/Attribute:bcc+' => 'BCC', + 'Class:EventNotificationEmail/Attribute:from' => 'From', + 'Class:EventNotificationEmail/Attribute:from+' => 'Sender of the message', + 'Class:EventNotificationEmail/Attribute:subject' => 'Subject', + 'Class:EventNotificationEmail/Attribute:subject+' => 'Subject', + 'Class:EventNotificationEmail/Attribute:body' => 'Body', + 'Class:EventNotificationEmail/Attribute:body+' => 'Body', +)); + +// +// Class: EventIssue +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:EventIssue' => 'Issue event', + 'Class:EventIssue+' => 'Trace of an issue (warning, error, etc.)', + 'Class:EventIssue/Attribute:issue' => 'Issue', + 'Class:EventIssue/Attribute:issue+' => 'What happened', + 'Class:EventIssue/Attribute:impact' => 'Impact', + 'Class:EventIssue/Attribute:impact+' => 'What are the consequences', + 'Class:EventIssue/Attribute:page' => 'Page', + 'Class:EventIssue/Attribute:page+' => 'HTTP entry point', + 'Class:EventIssue/Attribute:arguments_post' => 'Posted arguments', + 'Class:EventIssue/Attribute:arguments_post+' => 'HTTP POST arguments', + 'Class:EventIssue/Attribute:arguments_get' => 'URL arguments', + 'Class:EventIssue/Attribute:arguments_get+' => 'HTTP GET arguments', + 'Class:EventIssue/Attribute:callstack' => 'Callstack', + 'Class:EventIssue/Attribute:callstack+' => 'Call stack', + 'Class:EventIssue/Attribute:data' => 'Data', + 'Class:EventIssue/Attribute:data+' => 'More information', +)); + +// +// Class: EventWebService +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:EventWebService' => 'Web service event', + 'Class:EventWebService+' => 'Trace of an web service call', + 'Class:EventWebService/Attribute:verb' => 'Verb', + 'Class:EventWebService/Attribute:verb+' => 'Name of the operation', + 'Class:EventWebService/Attribute:result' => 'Result', + 'Class:EventWebService/Attribute:result+' => 'Overall success/failure', + 'Class:EventWebService/Attribute:log_info' => 'Info log', + 'Class:EventWebService/Attribute:log_info+' => 'Result info log', + 'Class:EventWebService/Attribute:log_warning' => 'Warning log', + 'Class:EventWebService/Attribute:log_warning+' => 'Result warning log', + 'Class:EventWebService/Attribute:log_error' => 'Error log', + 'Class:EventWebService/Attribute:log_error+' => 'Result error log', + 'Class:EventWebService/Attribute:data' => 'Data', + 'Class:EventWebService/Attribute:data+' => 'Result data', +)); + +// +// Class: Action +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:Action' => 'action', + 'Class:Action+' => 'Custom action', + 'Class:Action/Attribute:name' => 'Name', + 'Class:Action/Attribute:name+' => 'label', + 'Class:Action/Attribute:description' => 'Description', + 'Class:Action/Attribute:description+' => 'one line description', + 'Class:Action/Attribute:status' => 'Status', + 'Class:Action/Attribute:status+' => 'In production or ?', + 'Class:Action/Attribute:status/Value:test' => 'Being tested', + 'Class:Action/Attribute:status/Value:test+' => 'Being tested', + 'Class:Action/Attribute:status/Value:enabled' => 'In production', + 'Class:Action/Attribute:status/Value:enabled+' => 'In production', + 'Class:Action/Attribute:status/Value:disabled' => 'Inactive', + 'Class:Action/Attribute:status/Value:disabled+' => 'Inactive', + 'Class:Action/Attribute:related_triggers' => 'Related Triggers', + 'Class:Action/Attribute:related_triggers+' => 'Triggers linked to this action', + 'Class:Action/Attribute:finalclass' => 'Class', + 'Class:Action/Attribute:finalclass+' => 'Real (final) object class', +)); + +// +// Class: ActionNotification +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:ActionNotification' => 'notification', + 'Class:ActionNotification+' => 'Notification (abstract)', +)); + +// +// Class: ActionEmail +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:ActionEmail' => 'email notification', + 'Class:ActionEmail+' => 'Action: Email notification', + 'Class:ActionEmail/Attribute:test_recipient' => 'Test recipient', + 'Class:ActionEmail/Attribute:test_recipient+' => 'Detination in case status is set to "Test"', + 'Class:ActionEmail/Attribute:from' => 'From', + 'Class:ActionEmail/Attribute:from+' => 'Will be sent into the email header', + 'Class:ActionEmail/Attribute:reply_to' => 'Reply to', + 'Class:ActionEmail/Attribute:reply_to+' => 'Will be sent into the email header', + 'Class:ActionEmail/Attribute:to' => 'To', + 'Class:ActionEmail/Attribute:to+' => 'Destination of the email', + 'Class:ActionEmail/Attribute:cc' => 'Cc', + 'Class:ActionEmail/Attribute:cc+' => 'Carbon Copy', + 'Class:ActionEmail/Attribute:bcc' => 'bcc', + 'Class:ActionEmail/Attribute:bcc+' => 'Blind Carbon Copy', + 'Class:ActionEmail/Attribute:subject' => 'subject', + 'Class:ActionEmail/Attribute:subject+' => 'Title of the email', + 'Class:ActionEmail/Attribute:body' => 'body', + 'Class:ActionEmail/Attribute:body+' => 'Contents of the email', + 'Class:ActionEmail/Attribute:importance' => 'importance', + 'Class:ActionEmail/Attribute:importance+' => 'Importance flag', + 'Class:ActionEmail/Attribute:importance/Value:low' => 'low', + 'Class:ActionEmail/Attribute:importance/Value:low+' => 'low', + 'Class:ActionEmail/Attribute:importance/Value:normal' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:normal+' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:high' => 'high', + 'Class:ActionEmail/Attribute:importance/Value:high+' => 'high', +)); + +// +// Class: Trigger +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:Trigger' => 'trigger', + 'Class:Trigger+' => 'Custom event handler', + 'Class:Trigger/Attribute:description' => 'Description', + 'Class:Trigger/Attribute:description+' => 'one line description', + 'Class:Trigger/Attribute:linked_actions' => 'Triggered actions', + 'Class:Trigger/Attribute:linked_actions+' => 'Actions performed when the trigger is activated', + 'Class:Trigger/Attribute:finalclass' => 'Class', + 'Class:Trigger/Attribute:finalclass+' => 'Real (final) object class', +)); + +// +// Class: TriggerOnObject +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:TriggerOnObject' => 'Trigger on a class of objects', + 'Class:TriggerOnObject+' => 'Trigger on a given class of objects', + 'Class:TriggerOnObject/Attribute:target_class' => 'Target class', + 'Class:TriggerOnObject/Attribute:target_class+' => 'label', +)); + +// +// Class: TriggerOnStateChange +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:TriggerOnStateChange' => 'Trigger on object state change', + 'Class:TriggerOnStateChange+' => 'Trigger on object state change', + 'Class:TriggerOnStateChange/Attribute:state' => 'State', + 'Class:TriggerOnStateChange/Attribute:state+' => 'label', +)); + +// +// Class: TriggerOnStateEnter +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:TriggerOnStateEnter' => 'Trigger on object entering a state', + 'Class:TriggerOnStateEnter+' => 'Trigger on object state change - entering', +)); + +// +// Class: TriggerOnStateLeave +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:TriggerOnStateLeave' => 'Trigger on object leaving a state', + 'Class:TriggerOnStateLeave+' => 'Trigger on object state change - leaving', +)); + +// +// Class: TriggerOnObjectCreate +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:TriggerOnObjectCreate' => 'Trigger on object creation', + 'Class:TriggerOnObjectCreate+' => 'Trigger on object creation of [a child class of] the given class', +)); + +// +// Class: lnkTriggerAction +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkTriggerAction' => 'Actions-Trigger', + 'Class:lnkTriggerAction+' => 'Link between a trigger and an action', + 'Class:lnkTriggerAction/Attribute:action_id' => 'Action', + 'Class:lnkTriggerAction/Attribute:action_id+' => 'The action to be executed', + 'Class:lnkTriggerAction/Attribute:action_name' => 'Action Name', + 'Class:lnkTriggerAction/Attribute:action_name+' => 'Name of the action', + 'Class:lnkTriggerAction/Attribute:trigger_id' => 'Trigger', + 'Class:lnkTriggerAction/Attribute:trigger_id+' => 'Trigger', + 'Class:lnkTriggerAction/Attribute:trigger_name' => 'Trigger Name', + 'Class:lnkTriggerAction/Attribute:trigger_name+' => 'Name of the trigger', + 'Class:lnkTriggerAction/Attribute:order' => 'Order', + 'Class:lnkTriggerAction/Attribute:order+' => 'Actions execution order', +)); + + +?> diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php new file mode 100644 index 0000000000..a98604f388 --- /dev/null +++ b/dictionaries/dictionary.itop.model.php @@ -0,0 +1,1383 @@ + +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Note: The classes have been grouped by categories: bizmodel, core/cmdb, gui, application, addon/userrights +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: bizOrganization +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizOrganization' => 'Organization', + 'Class:bizOrganization+' => 'Organizational structure: can be Company and/or Department', + 'Class:bizOrganization/Attribute:name' => 'Name', + 'Class:bizOrganization/Attribute:name+' => 'Common name', + 'Class:bizOrganization/Attribute:code' => 'Code', + 'Class:bizOrganization/Attribute:code+' => 'Organization code (Siret, DUNS,...)', + 'Class:bizOrganization/Attribute:status' => 'Status', + 'Class:bizOrganization/Attribute:status+' => 'Lifecycle status', + 'Class:bizOrganization/Attribute:status/Value:production' => 'production', + 'Class:bizOrganization/Attribute:status/Value:production+' => 'production', + 'Class:bizOrganization/Attribute:status/Value:implementation' => 'implementation', + 'Class:bizOrganization/Attribute:status/Value:implementation+' => 'implementation', + 'Class:bizOrganization/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:bizOrganization/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:bizOrganization/Attribute:parent_id' => 'Parent', + 'Class:bizOrganization/Attribute:parent_id+' => 'Parent organization', + 'Class:bizOrganization/Attribute:parent_name' => 'Parent Name', + 'Class:bizOrganization/Attribute:parent_name+' => 'Name of the parent organization', +)); + +// +// Class: logRealObject +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:logRealObject' => 'Object', + 'Class:logRealObject+' => 'Any CMDB object', + 'Class:logRealObject/Attribute:name' => 'Name', + 'Class:logRealObject/Attribute:name+' => 'Common name', + 'Class:logRealObject/Attribute:status' => 'Status', + 'Class:logRealObject/Attribute:status+' => 'Lifecycle status', + 'Class:logRealObject/Attribute:status/Value:production' => 'production', + 'Class:logRealObject/Attribute:status/Value:production+' => 'production', + 'Class:logRealObject/Attribute:status/Value:implementation' => 'implementation', + 'Class:logRealObject/Attribute:status/Value:implementation+' => 'implementation', + 'Class:logRealObject/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:logRealObject/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:logRealObject/Attribute:status/Value:off' => 'off', + 'Class:logRealObject/Attribute:status/Value:off+' => 'off', + 'Class:logRealObject/Attribute:status/Value:left company' => 'left company', + 'Class:logRealObject/Attribute:status/Value:left company+' => 'left company', + 'Class:logRealObject/Attribute:status/Value:available' => 'available', + 'Class:logRealObject/Attribute:status/Value:available+' => 'available', + 'Class:logRealObject/Attribute:org_id' => 'Organization', + 'Class:logRealObject/Attribute:org_id+' => 'ID of the object owner organization', + 'Class:logRealObject/Attribute:org_name' => 'Organization', + 'Class:logRealObject/Attribute:org_name+' => 'Company / Department owning this object', + 'Class:logRealObject/Attribute:finalclass' => 'Class', + 'Class:logRealObject/Attribute:finalclass+' => 'Real (final) object class', +)); + +// +// Class: bizContact +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizContact' => 'Contact', + 'Class:bizContact+' => 'Contact', + 'Class:bizContact/Attribute:status' => 'Status', + 'Class:bizContact/Attribute:status+' => 'Lifecycle status', + 'Class:bizContact/Attribute:status/Value:off' => 'off', + 'Class:bizContact/Attribute:status/Value:off+' => 'off', + 'Class:bizContact/Attribute:status/Value:left company' => 'left company', + 'Class:bizContact/Attribute:status/Value:left company+' => 'left company', + 'Class:bizContact/Attribute:status/Value:available' => 'available', + 'Class:bizContact/Attribute:status/Value:available+' => 'available', + 'Class:bizContact/Attribute:org_name' => 'Organization', + 'Class:bizContact/Attribute:org_name+' => 'Company / Department of the contact', + 'Class:bizContact/Attribute:email' => 'eMail', + 'Class:bizContact/Attribute:email+' => 'Email address', + 'Class:bizContact/Attribute:phone' => 'Phone', + 'Class:bizContact/Attribute:phone+' => 'Telephone', + 'Class:bizContact/Attribute:location_id' => 'Location', + 'Class:bizContact/Attribute:location_id+' => 'Id of the location where the contact is located', + 'Class:bizContact/Attribute:location_name' => 'Location Name', + 'Class:bizContact/Attribute:location_name+' => 'Name of the location where the contact is located', +)); + +// +// Class: bizPerson +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizPerson' => 'Person', + 'Class:bizPerson+' => 'Person', + 'Class:bizPerson/Attribute:first_name' => 'First Name', + 'Class:bizPerson/Attribute:first_name+' => 'First name', + 'Class:bizPerson/Attribute:employee_number' => 'Employee Number', + 'Class:bizPerson/Attribute:employee_number+' => 'employee number', +)); + +// +// Class: bizTeam +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizTeam' => 'Team', + 'Class:bizTeam+' => 'A group of contacts', +)); + +// +// Class: lnkContactTeam +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkContactTeam' => 'TeamsLinks', + 'Class:lnkContactTeam+' => 'A link between a contact and a Team', + 'Class:lnkContactTeam/Attribute:contact_id' => 'Contact', + 'Class:lnkContactTeam/Attribute:contact_id+' => 'The contact', + 'Class:lnkContactTeam/Attribute:contact_name' => 'Contact Name', + 'Class:lnkContactTeam/Attribute:contact_name+' => 'name of the contact', + 'Class:lnkContactTeam/Attribute:contact_phone' => 'Phone', + 'Class:lnkContactTeam/Attribute:contact_phone+' => 'Phone number of the contact', + 'Class:lnkContactTeam/Attribute:contact_email' => 'eMail', + 'Class:lnkContactTeam/Attribute:contact_email+' => 'eMail address of the contact', + 'Class:lnkContactTeam/Attribute:team_id' => 'Team', + 'Class:lnkContactTeam/Attribute:team_id+' => 'Team linked', + 'Class:lnkContactTeam/Attribute:team_name' => 'Team', + 'Class:lnkContactTeam/Attribute:team_name+' => 'name of the Team', + 'Class:lnkContactTeam/Attribute:role' => 'Role', + 'Class:lnkContactTeam/Attribute:role+' => 'Role of the contact', +)); + +// +// Class: bizDocument +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizDocument' => 'Document', + 'Class:bizDocument+' => 'Document', + 'Class:bizDocument/Attribute:status' => 'Status', + 'Class:bizDocument/Attribute:status+' => 'Lifecycle status', + 'Class:bizDocument/Attribute:status/Value:production' => 'production', + 'Class:bizDocument/Attribute:status/Value:production+' => 'production', + 'Class:bizDocument/Attribute:status/Value:implementation' => 'implementation', + 'Class:bizDocument/Attribute:status/Value:implementation+' => 'implementation', + 'Class:bizDocument/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:bizDocument/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:bizDocument/Attribute:org_name' => 'Organization', + 'Class:bizDocument/Attribute:org_name+' => 'Company / Department owning the document', + 'Class:bizDocument/Attribute:type' => 'type', + 'Class:bizDocument/Attribute:type+' => 'usage of the document', + 'Class:bizDocument/Attribute:type/Value:documentation' => 'documentation', + 'Class:bizDocument/Attribute:type/Value:documentation+' => 'documentation', + 'Class:bizDocument/Attribute:type/Value:contract' => 'contract', + 'Class:bizDocument/Attribute:type/Value:contract+' => 'contract', + 'Class:bizDocument/Attribute:type/Value:working instructions' => 'working instructions', + 'Class:bizDocument/Attribute:type/Value:working instructions+' => 'working instructions', + 'Class:bizDocument/Attribute:type/Value:network map' => 'network map', + 'Class:bizDocument/Attribute:type/Value:network map+' => 'network map', + 'Class:bizDocument/Attribute:type/Value:white paper' => 'white paper', + 'Class:bizDocument/Attribute:type/Value:white paper+' => 'white paper', + 'Class:bizDocument/Attribute:type/Value:presentation' => 'presentation', + 'Class:bizDocument/Attribute:type/Value:presentation+' => 'presentation', + 'Class:bizDocument/Attribute:type/Value:training' => 'training', + 'Class:bizDocument/Attribute:type/Value:training+' => 'training', + 'Class:bizDocument/Attribute:description' => 'Description', + 'Class:bizDocument/Attribute:description+' => 'Service Description', + 'Class:bizDocument/Attribute:contents' => 'Contents', + 'Class:bizDocument/Attribute:contents+' => 'File content', +)); + +// +// Class: lnkDocumentRealObject +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkDocumentRealObject' => 'DocumentsLinks', + 'Class:lnkDocumentRealObject+' => 'A link between a document and another object', + 'Class:lnkDocumentRealObject/Attribute:doc_id' => 'Document', + 'Class:lnkDocumentRealObject/Attribute:doc_id+' => 'id of the Document', + 'Class:lnkDocumentRealObject/Attribute:doc_name' => 'Document Name', + 'Class:lnkDocumentRealObject/Attribute:doc_name+' => 'name of the document', + 'Class:lnkDocumentRealObject/Attribute:object_id' => 'Object', + 'Class:lnkDocumentRealObject/Attribute:object_id+' => 'Object linked', + 'Class:lnkDocumentRealObject/Attribute:object_name' => 'Object Name', + 'Class:lnkDocumentRealObject/Attribute:object_name+' => 'name of the linked object', + 'Class:lnkDocumentRealObject/Attribute:link_type' => 'Link Type', + 'Class:lnkDocumentRealObject/Attribute:link_type+' => 'More information', +)); + +// +// Class: lnkContactRealObject +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkContactRealObject' => 'ContactsLinks', + 'Class:lnkContactRealObject+' => 'A link between a contact and another object', + 'Class:lnkContactRealObject/Attribute:contact_id' => 'Contact', + 'Class:lnkContactRealObject/Attribute:contact_id+' => 'The contact', + 'Class:lnkContactRealObject/Attribute:contact_name' => 'Contact Name', + 'Class:lnkContactRealObject/Attribute:contact_name+' => 'name of the contact', + 'Class:lnkContactRealObject/Attribute:contact_phone' => 'Phone', + 'Class:lnkContactRealObject/Attribute:contact_phone+' => 'Phone number of the contact', + 'Class:lnkContactRealObject/Attribute:contact_email' => 'eMail', + 'Class:lnkContactRealObject/Attribute:contact_email+' => 'eMail address of the contact', + 'Class:lnkContactRealObject/Attribute:object_id' => 'Object', + 'Class:lnkContactRealObject/Attribute:object_id+' => 'Object linked', + 'Class:lnkContactRealObject/Attribute:object_name' => 'Object Name', + 'Class:lnkContactRealObject/Attribute:object_name+' => 'name of the linked object', + 'Class:lnkContactRealObject/Attribute:role' => 'Role', + 'Class:lnkContactRealObject/Attribute:role+' => 'Role of the contact', +)); + +// +// Class: logInfra +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:logInfra' => 'Infra', + 'Class:logInfra+' => 'Infrastructure real object', + 'Class:logInfra/Attribute:status' => 'Status', + 'Class:logInfra/Attribute:status+' => 'Lifecycle status', + 'Class:logInfra/Attribute:status/Value:production' => 'production', + 'Class:logInfra/Attribute:status/Value:production+' => 'production', + 'Class:logInfra/Attribute:status/Value:implementation' => 'implementation', + 'Class:logInfra/Attribute:status/Value:implementation+' => 'implementation', + 'Class:logInfra/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:logInfra/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:logInfra/Attribute:severity' => 'Business Criticity', + 'Class:logInfra/Attribute:severity+' => 'Severity for this infrastructure', + 'Class:logInfra/Attribute:severity/Value:high' => 'high', + 'Class:logInfra/Attribute:severity/Value:high+' => 'high', + 'Class:logInfra/Attribute:severity/Value:medium' => 'medium', + 'Class:logInfra/Attribute:severity/Value:medium+' => 'medium', + 'Class:logInfra/Attribute:severity/Value:low' => 'low', + 'Class:logInfra/Attribute:severity/Value:low+' => 'low', +)); + +// +// Class: lnkContactInfra +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkContactInfra' => 'ContactsInfraLinks', + 'Class:lnkContactInfra+' => 'A link between a contact and an infrastructure', + 'Class:lnkContactInfra/Attribute:contact_id' => 'Contact', + 'Class:lnkContactInfra/Attribute:contact_id+' => 'The contact', + 'Class:lnkContactInfra/Attribute:contact_name' => 'Contact Name', + 'Class:lnkContactInfra/Attribute:contact_name+' => 'name of the contact', + 'Class:lnkContactInfra/Attribute:contact_phone' => 'Phone', + 'Class:lnkContactInfra/Attribute:contact_phone+' => 'Phone number of the contact', + 'Class:lnkContactInfra/Attribute:contact_email' => 'eMail', + 'Class:lnkContactInfra/Attribute:contact_email+' => 'eMail address of the contact', + 'Class:lnkContactInfra/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkContactInfra/Attribute:infra_id+' => 'Infrastructure linked', + 'Class:lnkContactInfra/Attribute:infra_name' => 'Infrastructure', + 'Class:lnkContactInfra/Attribute:infra_name+' => 'name of the linked infrastructure', + 'Class:lnkContactInfra/Attribute:role' => 'Role', + 'Class:lnkContactInfra/Attribute:role+' => 'Role of the contact', +)); + +// +// Class: bizLocation +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizLocation' => 'Location', + 'Class:bizLocation+' => 'Any type of location: Region, Country, City, Site, Building, Floor, Room, Rack,...', + 'Class:bizLocation/Attribute:address' => 'Address', + 'Class:bizLocation/Attribute:address+' => 'The postal address of the location', + 'Class:bizLocation/Attribute:country' => 'Country', + 'Class:bizLocation/Attribute:country+' => 'Country of the location', + 'Class:bizLocation/Attribute:parent_location_id' => 'Parent Location', + 'Class:bizLocation/Attribute:parent_location_id+' => 'where is the real object physically located', + 'Class:bizLocation/Attribute:parent_location_name' => 'Parent location (Name)', + 'Class:bizLocation/Attribute:parent_location_name+' => 'name of the parent location', +)); + +// +// Class: bizCircuit +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizCircuit' => 'Circuit', + 'Class:bizCircuit+' => 'Any type of circuit', + 'Class:bizCircuit/Attribute:speed' => 'speed', + 'Class:bizCircuit/Attribute:speed+' => 'speed of the circuit', + 'Class:bizCircuit/Attribute:location1_id' => 'Location 1', + 'Class:bizCircuit/Attribute:location1_id+' => 'Id of the location 1', + 'Class:bizCircuit/Attribute:location1_name' => 'Location 1', + 'Class:bizCircuit/Attribute:location1_name+' => 'Name of the location', + 'Class:bizCircuit/Attribute:location2_id' => 'Location 2', + 'Class:bizCircuit/Attribute:location2_id+' => 'Id of the location 2', + 'Class:bizCircuit/Attribute:location2_name' => 'Location 2', + 'Class:bizCircuit/Attribute:location2_name+' => 'Name of the location', + 'Class:bizCircuit/Attribute:interface1_id' => 'Interface 1', + 'Class:bizCircuit/Attribute:interface1_id+' => 'id of the interface 1', + 'Class:bizCircuit/Attribute:interface1_name' => 'Interface', + 'Class:bizCircuit/Attribute:interface1_name+' => 'Name of the interface 1', + 'Class:bizCircuit/Attribute:device1_name' => 'Device 1', + 'Class:bizCircuit/Attribute:device1_name+' => 'Name of the device 1', + 'Class:bizCircuit/Attribute:interface2_id' => 'Interface 2', + 'Class:bizCircuit/Attribute:interface2_id+' => 'id of the interface 2', + 'Class:bizCircuit/Attribute:interface2_name' => 'Interface', + 'Class:bizCircuit/Attribute:interface2_name+' => 'Name of the interface 2', + 'Class:bizCircuit/Attribute:device2_name' => 'Interface', + 'Class:bizCircuit/Attribute:device2_name+' => 'Name of the device 2', + 'Class:bizCircuit/Attribute:provider_id' => 'Carrier ID', + 'Class:bizCircuit/Attribute:provider_id+' => 'Organization ID of the provider of the Circuit', + 'Class:bizCircuit/Attribute:carrier_name' => 'Carrier', + 'Class:bizCircuit/Attribute:carrier_name+' => 'Name of the carrier', + 'Class:bizCircuit/Attribute:carrier_ref' => 'Carrier reference', + 'Class:bizCircuit/Attribute:carrier_ref+' => 'reference of the circuit used by the carrier', +)); + +// +// Class: bizInterface +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizInterface' => 'Interface', + 'Class:bizInterface+' => 'Interface', + 'Class:bizInterface/Attribute:device_id' => 'Device', + 'Class:bizInterface/Attribute:device_id+' => 'Device on which the interface is physically located', + 'Class:bizInterface/Attribute:device_name' => 'Device', + 'Class:bizInterface/Attribute:device_name+' => 'name of the device on which the interface is located', + 'Class:bizInterface/Attribute:device_location_id' => 'Device location', + 'Class:bizInterface/Attribute:device_location_id+' => 'location of the device on which the interface is located', + 'Class:bizInterface/Attribute:device_location_name' => 'Device location', + 'Class:bizInterface/Attribute:device_location_name+' => 'name of the location of the device on which the interface is located', + 'Class:bizInterface/Attribute:logical_type' => 'Logical type', + 'Class:bizInterface/Attribute:logical_type+' => 'Logical type of interface', + 'Class:bizInterface/Attribute:logical_type/Value:primary' => 'primary', + 'Class:bizInterface/Attribute:logical_type/Value:primary+' => 'primary', + 'Class:bizInterface/Attribute:logical_type/Value:secondary' => 'secondary', + 'Class:bizInterface/Attribute:logical_type/Value:secondary+' => 'secondary', + 'Class:bizInterface/Attribute:logical_type/Value:backup' => 'backup', + 'Class:bizInterface/Attribute:logical_type/Value:backup+' => 'backup', + 'Class:bizInterface/Attribute:logical_type/Value:port' => 'port', + 'Class:bizInterface/Attribute:logical_type/Value:port+' => 'port', + 'Class:bizInterface/Attribute:logical_type/Value:logical' => 'logical', + 'Class:bizInterface/Attribute:logical_type/Value:logical+' => 'logical', + 'Class:bizInterface/Attribute:physical_type' => 'Physical type', + 'Class:bizInterface/Attribute:physical_type+' => 'Physical type of interface', + 'Class:bizInterface/Attribute:physical_type/Value:ethernet' => 'ethernet', + 'Class:bizInterface/Attribute:physical_type/Value:ethernet+' => 'ethernet', + 'Class:bizInterface/Attribute:physical_type/Value:framerelay' => 'framerelay', + 'Class:bizInterface/Attribute:physical_type/Value:framerelay+' => 'framerelay', + 'Class:bizInterface/Attribute:physical_type/Value:atm' => 'atm', + 'Class:bizInterface/Attribute:physical_type/Value:atm+' => 'atm', + 'Class:bizInterface/Attribute:physical_type/Value:vlan' => 'vlan', + 'Class:bizInterface/Attribute:physical_type/Value:vlan+' => 'vlan', + 'Class:bizInterface/Attribute:ip_address' => 'IP address', + 'Class:bizInterface/Attribute:ip_address+' => 'address IP for this interface', + 'Class:bizInterface/Attribute:mask' => 'Subnet Mask', + 'Class:bizInterface/Attribute:mask+' => 'Subnet mask for this interface', + 'Class:bizInterface/Attribute:mac' => 'MAC address', + 'Class:bizInterface/Attribute:mac+' => 'MAC address for this interface', + 'Class:bizInterface/Attribute:speed' => 'Speed (Kb/s)', + 'Class:bizInterface/Attribute:speed+' => 'speed of this interface', + 'Class:bizInterface/Attribute:duplex' => 'Duplex', + 'Class:bizInterface/Attribute:duplex+' => 'Duplex configured for this interface', + 'Class:bizInterface/Attribute:duplex/Value:half' => 'half', + 'Class:bizInterface/Attribute:duplex/Value:half+' => 'half', + 'Class:bizInterface/Attribute:duplex/Value:full' => 'full', + 'Class:bizInterface/Attribute:duplex/Value:full+' => 'full', + 'Class:bizInterface/Attribute:duplex/Value:unknown' => 'unknown', + 'Class:bizInterface/Attribute:duplex/Value:unknown+' => 'unknown', + 'Class:bizInterface/Attribute:if_connected_id' => 'Connected interface', + 'Class:bizInterface/Attribute:if_connected_id+' => 'interface connected to this one', + 'Class:bizInterface/Attribute:if_connected_name' => 'Connected interface', + 'Class:bizInterface/Attribute:if_connected_name+' => 'name of the interface connected to this one', + 'Class:bizInterface/Attribute:if_connected_device' => 'Connected device', + 'Class:bizInterface/Attribute:if_connected_device+' => 'name of the device connected to this interface', +)); + +// +// Class: bizSubnet +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizSubnet' => 'Subnet', + 'Class:bizSubnet+' => 'Logical or physical subnet', + 'Class:bizSubnet/Attribute:ip' => 'IP', + 'Class:bizSubnet/Attribute:ip+' => 'IP', + 'Class:bizSubnet/Attribute:mask' => 'IP mask', + 'Class:bizSubnet/Attribute:mask+' => 'IP mask', +)); + +// +// Class: bizDevice +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizDevice' => 'Device', + 'Class:bizDevice+' => 'Electronic devices', + 'Class:bizDevice/Attribute:location_id' => 'Location', + 'Class:bizDevice/Attribute:location_id+' => 'where is the located object physically located', + 'Class:bizDevice/Attribute:location_name' => 'Location Name', + 'Class:bizDevice/Attribute:location_name+' => 'name of the location', + 'Class:bizDevice/Attribute:country' => 'Country', + 'Class:bizDevice/Attribute:country+' => 'country where the device is located', + 'Class:bizDevice/Attribute:brand' => 'Brand', + 'Class:bizDevice/Attribute:brand+' => 'The manufacturer of the device', + 'Class:bizDevice/Attribute:model' => 'Model', + 'Class:bizDevice/Attribute:model+' => 'The model number of the device', + 'Class:bizDevice/Attribute:serial_number' => 'Serial Number', + 'Class:bizDevice/Attribute:serial_number+' => 'The serial number of the device', + 'Class:bizDevice/Attribute:mgmt_ip' => 'Mgmt IP', + 'Class:bizDevice/Attribute:mgmt_ip+' => 'Management IP', +)); + +// +// Class: bizPC +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizPC' => 'PC', + 'Class:bizPC+' => 'Personal Computers', + 'Class:bizPC/Attribute:type' => 'Type', + 'Class:bizPC/Attribute:type+' => 'Type of computer', + 'Class:bizPC/Attribute:type/Value:desktop PC' => 'desktop PC', + 'Class:bizPC/Attribute:type/Value:desktop PC+' => 'desktop PC', + 'Class:bizPC/Attribute:type/Value:laptop' => 'laptop', + 'Class:bizPC/Attribute:type/Value:laptop+' => 'laptop', + 'Class:bizPC/Attribute:memory_size' => 'Memory Size', + 'Class:bizPC/Attribute:memory_size+' => 'Size of the memory', + 'Class:bizPC/Attribute:cpu' => 'CPU', + 'Class:bizPC/Attribute:cpu+' => 'CPU type', + 'Class:bizPC/Attribute:hdd_size' => 'HDD Size', + 'Class:bizPC/Attribute:hdd_size+' => 'Size of the hard drive', + 'Class:bizPC/Attribute:os_family' => 'OS Family', + 'Class:bizPC/Attribute:os_family+' => 'Type of operating system', + 'Class:bizPC/Attribute:os_version' => 'OS Version', + 'Class:bizPC/Attribute:os_version+' => 'Detailed version number of the operating system', + 'Class:bizPC/Attribute:shipment_number' => 'Shipment Code', + 'Class:bizPC/Attribute:shipment_number+' => 'Number for tracking shipment', + 'Class:bizPC/Attribute:default_gateway' => 'Default Gateway', + 'Class:bizPC/Attribute:default_gateway+' => 'Default Gateway for this device', +)); + +// +// Class: bizServer +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizServer' => 'Server', + 'Class:bizServer+' => 'Computer Servers', + 'Class:bizServer/Attribute:memory_size' => 'Memory Size', + 'Class:bizServer/Attribute:memory_size+' => 'Size of the memory', + 'Class:bizServer/Attribute:cpu' => 'CPU type', + 'Class:bizServer/Attribute:cpu+' => 'CPU type', + 'Class:bizServer/Attribute:number_of_cpus' => 'Number of CPUs', + 'Class:bizServer/Attribute:number_of_cpus+' => 'Number of CPUs', + 'Class:bizServer/Attribute:hdd_size' => 'HDD Size', + 'Class:bizServer/Attribute:hdd_size+' => 'Size of the hard drive', + 'Class:bizServer/Attribute:hdd_free_size' => 'Free HDD Size', + 'Class:bizServer/Attribute:hdd_free_size+' => 'Size of the free space on the hard drive(s)', + 'Class:bizServer/Attribute:os_family' => 'OS Family', + 'Class:bizServer/Attribute:os_family+' => 'Type of operating system', + 'Class:bizServer/Attribute:os_version' => 'OS Version', + 'Class:bizServer/Attribute:os_version+' => 'Detailed version number of the operating system', + 'Class:bizServer/Attribute:shipment_number' => 'Shipment number', + 'Class:bizServer/Attribute:shipment_number+' => 'Number for tracking shipment', + 'Class:bizServer/Attribute:default_gateway' => 'Default Gateway', + 'Class:bizServer/Attribute:default_gateway+' => 'Default Gateway for this device', +)); + +// +// Class: bizNetworkDevice +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizNetworkDevice' => 'Network Device', + 'Class:bizNetworkDevice+' => 'A network device', + 'Class:bizNetworkDevice/Attribute:type' => 'Type', + 'Class:bizNetworkDevice/Attribute:type+' => 'Type of device', + 'Class:bizNetworkDevice/Attribute:type/Value:switch' => 'switch', + 'Class:bizNetworkDevice/Attribute:type/Value:switch+' => 'switch', + 'Class:bizNetworkDevice/Attribute:type/Value:router' => 'router', + 'Class:bizNetworkDevice/Attribute:type/Value:router+' => 'router', + 'Class:bizNetworkDevice/Attribute:type/Value:firewall' => 'firewall', + 'Class:bizNetworkDevice/Attribute:type/Value:firewall+' => 'firewall', + 'Class:bizNetworkDevice/Attribute:type/Value:load balancer' => 'load balancer', + 'Class:bizNetworkDevice/Attribute:type/Value:load balancer+' => 'load balancer', + 'Class:bizNetworkDevice/Attribute:type/Value:hub' => 'hub', + 'Class:bizNetworkDevice/Attribute:type/Value:hub+' => 'hub', + 'Class:bizNetworkDevice/Attribute:type/Value:WAN accelerator' => 'WAN accelerator', + 'Class:bizNetworkDevice/Attribute:type/Value:WAN accelerator+' => 'WAN accelerator', + 'Class:bizNetworkDevice/Attribute:default_gateway' => 'Default Gateway', + 'Class:bizNetworkDevice/Attribute:default_gateway+' => 'Default Gateway for this device', + 'Class:bizNetworkDevice/Attribute:ios_version' => 'IOS version', + 'Class:bizNetworkDevice/Attribute:ios_version+' => 'IOS (software) version', + 'Class:bizNetworkDevice/Attribute:memory' => 'Memory', + 'Class:bizNetworkDevice/Attribute:memory+' => 'Memory description', + 'Class:bizNetworkDevice/Attribute:snmp_read' => 'SNMP Community (Read)', + 'Class:bizNetworkDevice/Attribute:snmp_read+' => 'SNMP Read Community String', + 'Class:bizNetworkDevice/Attribute:snmp_write' => 'SNMP Community (Write)', + 'Class:bizNetworkDevice/Attribute:snmp_write+' => 'SNMP Write Community String', +)); + +// +// Class: bizInfraGroup +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizInfraGroup' => 'Infra Group', + 'Class:bizInfraGroup+' => 'A group of infrastructure elements', + 'Class:bizInfraGroup/Attribute:type' => 'Type', + 'Class:bizInfraGroup/Attribute:type+' => 'Type of groupe', + 'Class:bizInfraGroup/Attribute:type/Value:Monitoring' => 'Monitoring', + 'Class:bizInfraGroup/Attribute:type/Value:Monitoring+' => 'Monitoring', + 'Class:bizInfraGroup/Attribute:type/Value:Reporting' => 'Reporting', + 'Class:bizInfraGroup/Attribute:type/Value:Reporting+' => 'Reporting', + 'Class:bizInfraGroup/Attribute:type/Value:list' => 'list', + 'Class:bizInfraGroup/Attribute:type/Value:list+' => 'list', + 'Class:bizInfraGroup/Attribute:description' => 'Description', + 'Class:bizInfraGroup/Attribute:description+' => 'usage of the Group', + 'Class:bizInfraGroup/Attribute:parent_group_id' => 'Parent Group', + 'Class:bizInfraGroup/Attribute:parent_group_id+' => 'including group', + 'Class:bizInfraGroup/Attribute:parent_group_name' => 'Parent Group (Name)', + 'Class:bizInfraGroup/Attribute:parent_group_name+' => 'name of the parent group', +)); + +// +// Class: bizApplication +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizApplication' => 'Application', + 'Class:bizApplication+' => 'General application', + 'Class:bizApplication/Attribute:device_id' => 'Hosting device', + 'Class:bizApplication/Attribute:device_id+' => 'The device where application is installed', + 'Class:bizApplication/Attribute:device_name' => 'Hosting device', + 'Class:bizApplication/Attribute:device_name+' => 'Name of the device where application is installed', + 'Class:bizApplication/Attribute:install_date' => 'Installation Date', + 'Class:bizApplication/Attribute:install_date+' => 'Date when application was installed', + 'Class:bizApplication/Attribute:version' => 'Version', + 'Class:bizApplication/Attribute:version+' => 'Application version', + 'Class:bizApplication/Attribute:function' => 'Function', + 'Class:bizApplication/Attribute:function+' => 'Function provided by this application', +)); + +// +// Class: lnkInfraGrouping +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraGrouping' => 'Infra Grouping', + 'Class:lnkInfraGrouping+' => 'Infra part of an Infra Group', + 'Class:lnkInfraGrouping/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkInfraGrouping/Attribute:infra_id+' => 'Infrastructure part of the group', + 'Class:lnkInfraGrouping/Attribute:infra_name' => 'Infrastructure Name', + 'Class:lnkInfraGrouping/Attribute:infra_name+' => 'Name of the impacted infrastructure', + 'Class:lnkInfraGrouping/Attribute:infra_status' => 'Status', + 'Class:lnkInfraGrouping/Attribute:infra_status+' => 'Status of the impacted infrastructure', + 'Class:lnkInfraGrouping/Attribute:infra_group_id' => 'Group', + 'Class:lnkInfraGrouping/Attribute:infra_group_id+' => 'Name of the group', + 'Class:lnkInfraGrouping/Attribute:group_name' => 'Group Name', + 'Class:lnkInfraGrouping/Attribute:group_name+' => 'Name of the group containing infrastructure', + 'Class:lnkInfraGrouping/Attribute:impact' => 'Relation', + 'Class:lnkInfraGrouping/Attribute:impact+' => 'Relation between this group and infra', +)); + +// +// Class: lnkClientServer +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkClientServer' => 'ClientServerLinks', + 'Class:lnkClientServer+' => 'Link between client server application', + 'Class:lnkClientServer/Attribute:status' => 'Status', + 'Class:lnkClientServer/Attribute:status+' => 'Lifecycle status', + 'Class:lnkClientServer/Attribute:status/Value:production' => 'production', + 'Class:lnkClientServer/Attribute:status/Value:production+' => 'production', + 'Class:lnkClientServer/Attribute:status/Value:implementation' => 'implementation', + 'Class:lnkClientServer/Attribute:status/Value:implementation+' => 'implementation', + 'Class:lnkClientServer/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:lnkClientServer/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:lnkClientServer/Attribute:client_id' => 'Client', + 'Class:lnkClientServer/Attribute:client_id+' => 'The client part of the link', + 'Class:lnkClientServer/Attribute:client_name' => 'Client', + 'Class:lnkClientServer/Attribute:client_name+' => 'Name of the client', + 'Class:lnkClientServer/Attribute:server_id' => 'Server', + 'Class:lnkClientServer/Attribute:server_id+' => 'the server part of the link', + 'Class:lnkClientServer/Attribute:server_name' => 'Server', + 'Class:lnkClientServer/Attribute:server_name+' => 'Name of the server', + 'Class:lnkClientServer/Attribute:relation' => 'Relation', + 'Class:lnkClientServer/Attribute:relation+' => 'Type of relation between both application', +)); + +// +// Class: bizPatch +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizPatch' => 'Patch', + 'Class:bizPatch+' => 'Patch installed on infrastucture', + 'Class:bizPatch/Attribute:status' => 'Status', + 'Class:bizPatch/Attribute:status+' => 'Lifecycle status', + 'Class:bizPatch/Attribute:status/Value:production' => 'production', + 'Class:bizPatch/Attribute:status/Value:production+' => 'production', + 'Class:bizPatch/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:bizPatch/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:bizPatch/Attribute:device_id' => 'Device', + 'Class:bizPatch/Attribute:device_id+' => 'The Device where patch is installed', + 'Class:bizPatch/Attribute:device_name' => 'Device Name', + 'Class:bizPatch/Attribute:device_name+' => 'Name of the impacted device', + 'Class:bizPatch/Attribute:install_date' => 'Installation Date', + 'Class:bizPatch/Attribute:install_date+' => 'Date when application was installed', + 'Class:bizPatch/Attribute:description' => 'Description', + 'Class:bizPatch/Attribute:description+' => 'description du patch', + 'Class:bizPatch/Attribute:patch_type' => 'Type', + 'Class:bizPatch/Attribute:patch_type+' => 'type de patch', +)); + +// +// Class: bizIncidentTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizIncidentTicket' => 'Incident', + 'Class:bizIncidentTicket+' => 'Incident ticket', + 'Class:bizIncidentTicket/Attribute:name' => 'Ticket Ref', + 'Class:bizIncidentTicket/Attribute:name+' => 'Refence number ofr this incident', + 'Class:bizIncidentTicket/Attribute:title' => 'Title', + 'Class:bizIncidentTicket/Attribute:title+' => 'Overview of the Incident', + 'Class:bizIncidentTicket/Attribute:type' => 'Type', + 'Class:bizIncidentTicket/Attribute:type+' => 'Type of the Incident', + 'Class:bizIncidentTicket/Attribute:type/Value:Network' => 'Network', + 'Class:bizIncidentTicket/Attribute:type/Value:Network+' => 'Network', + 'Class:bizIncidentTicket/Attribute:type/Value:Server' => 'Server', + 'Class:bizIncidentTicket/Attribute:type/Value:Server+' => 'Server', + 'Class:bizIncidentTicket/Attribute:type/Value:Desktop' => 'Desktop', + 'Class:bizIncidentTicket/Attribute:type/Value:Desktop+' => 'Desktop', + 'Class:bizIncidentTicket/Attribute:type/Value:Application' => 'Application', + 'Class:bizIncidentTicket/Attribute:type/Value:Application+' => 'Application', + 'Class:bizIncidentTicket/Attribute:org_id' => 'Customer', + 'Class:bizIncidentTicket/Attribute:org_id+' => 'who is impacted by the ticket', + 'Class:bizIncidentTicket/Attribute:customer_name' => 'Customer', + 'Class:bizIncidentTicket/Attribute:customer_name+' => 'Name of the customer impacted by this ticket', + 'Class:bizIncidentTicket/Attribute:ticket_status' => 'Status', + 'Class:bizIncidentTicket/Attribute:ticket_status+' => 'Status of the ticket', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New' => 'New', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New+' => 'New', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned' => 'Assigned', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned+' => 'Assigned', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress' => 'WorkInProgress', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress+' => 'WorkInProgress', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved' => 'Resolved', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved+' => 'Resolved', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Closed' => 'Closed', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Closed+' => 'Closed', + 'Class:bizIncidentTicket/Attribute:initial_situation' => 'Initial Situation', + 'Class:bizIncidentTicket/Attribute:initial_situation+' => 'Initial situation of the Incident', + 'Class:bizIncidentTicket/Attribute:current_situation' => 'Current Situation', + 'Class:bizIncidentTicket/Attribute:current_situation+' => 'Current situation of the Incident', + 'Class:bizIncidentTicket/Attribute:start_date' => 'Starting date', + 'Class:bizIncidentTicket/Attribute:start_date+' => 'Incident starting date', + 'Class:bizIncidentTicket/Attribute:last_update' => 'Last update', + 'Class:bizIncidentTicket/Attribute:last_update+' => 'last time the Ticket was modified', + 'Class:bizIncidentTicket/Attribute:next_update' => 'Next update', + 'Class:bizIncidentTicket/Attribute:next_update+' => 'next time the Ticket is expected to be modified', + 'Class:bizIncidentTicket/Attribute:end_date' => 'Closure Date', + 'Class:bizIncidentTicket/Attribute:end_date+' => 'Date when the Ticket was closed', + 'Class:bizIncidentTicket/Attribute:caller_id' => 'Caller', + 'Class:bizIncidentTicket/Attribute:caller_id+' => 'person that trigger incident', + 'Class:bizIncidentTicket/Attribute:caller_mail' => 'Caller', + 'Class:bizIncidentTicket/Attribute:caller_mail+' => 'Person that trigger this incident', + 'Class:bizIncidentTicket/Attribute:impact' => 'Impact', + 'Class:bizIncidentTicket/Attribute:impact+' => 'Impact of the Incident', + 'Class:bizIncidentTicket/Attribute:workgroup_id' => 'Workgroup', + 'Class:bizIncidentTicket/Attribute:workgroup_id+' => 'which workgroup is owning ticket', + 'Class:bizIncidentTicket/Attribute:workgroup_name' => 'Workgroup', + 'Class:bizIncidentTicket/Attribute:workgroup_name+' => 'name of workgroup managing the Ticket', + 'Class:bizIncidentTicket/Attribute:agent_id' => 'Agent', + 'Class:bizIncidentTicket/Attribute:agent_id+' => 'who is managing the ticket', + 'Class:bizIncidentTicket/Attribute:agent_mail' => 'Agent', + 'Class:bizIncidentTicket/Attribute:agent_mail+' => 'mail of agent managing the Ticket', + 'Class:bizIncidentTicket/Attribute:action_log' => 'Action Logs', + 'Class:bizIncidentTicket/Attribute:action_log+' => 'List all action performed during the incident', + 'Class:bizIncidentTicket/Attribute:severity' => 'Severity', + 'Class:bizIncidentTicket/Attribute:severity+' => 'Field defining the criticity if the incident', + 'Class:bizIncidentTicket/Attribute:severity/Value:critical' => 'critical', + 'Class:bizIncidentTicket/Attribute:severity/Value:critical+' => 'critical', + 'Class:bizIncidentTicket/Attribute:severity/Value:medium' => 'medium', + 'Class:bizIncidentTicket/Attribute:severity/Value:medium+' => 'medium', + 'Class:bizIncidentTicket/Attribute:severity/Value:low' => 'low', + 'Class:bizIncidentTicket/Attribute:severity/Value:low+' => 'low', + 'Class:bizIncidentTicket/Attribute:assignment_count' => 'Assignment Count', + 'Class:bizIncidentTicket/Attribute:assignment_count+' => 'Number of times this ticket was assigned or reassigned', + 'Class:bizIncidentTicket/Attribute:resolution' => 'Resolution', + 'Class:bizIncidentTicket/Attribute:resolution+' => 'Description of the resolution', + 'Class:bizIncidentTicket/Attribute:impacted_infra_manual' => 'Impacted Infrastructure', + 'Class:bizIncidentTicket/Attribute:impacted_infra_manual+' => 'CIs that are not meeting the SLA', + 'Class:bizIncidentTicket/Attribute:related_tickets' => 'Related Tickets', + 'Class:bizIncidentTicket/Attribute:related_tickets+' => 'Other incident tickets related to this one', + 'Class:bizIncidentTicket/Attribute:contacts_a_notifier' => 'contacts auto', + 'Class:bizIncidentTicket/Attribute:contacts_a_notifier+' => 'blah', + 'Class:bizIncidentTicket/Stimulus:ev_assign' => 'Assign this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_assign+' => 'Assign this ticket to a group and an agent', + 'Class:bizIncidentTicket/Stimulus:ev_reassign' => 'Reassign this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_reassign+' => 'Reassign this ticket to a different group and agent', + 'Class:bizIncidentTicket/Stimulus:ev_start_working' => 'Work on this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_start_working+' => 'Start working on this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_resolve' => 'Resolve this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_resolve+' => 'Resolve this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_close' => 'Close this ticket', + 'Class:bizIncidentTicket/Stimulus:ev_close+' => 'Close this ticket', +)); + +// +// Class: lnkRelatedTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkRelatedTicket' => 'Related Ticket', + 'Class:lnkRelatedTicket+' => 'Ticket related to a ticket', + 'Class:lnkRelatedTicket/Attribute:rel_ticket_id' => 'Related Ticket', + 'Class:lnkRelatedTicket/Attribute:rel_ticket_id+' => 'The related ticket', + 'Class:lnkRelatedTicket/Attribute:rel_ticket_name' => 'Related ticket', + 'Class:lnkRelatedTicket/Attribute:rel_ticket_name+' => 'Name of the related ticket', + 'Class:lnkRelatedTicket/Attribute:ticket_id' => 'Ticket', + 'Class:lnkRelatedTicket/Attribute:ticket_id+' => 'Ticket number', + 'Class:lnkRelatedTicket/Attribute:ticket_name' => 'Ticket Name', + 'Class:lnkRelatedTicket/Attribute:ticket_name+' => 'Name of the ticket', + 'Class:lnkRelatedTicket/Attribute:impact' => 'Impact', + 'Class:lnkRelatedTicket/Attribute:impact+' => 'Impact on the related ticket', +)); + +// +// Class: lnkInfraTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraTicket' => 'Infra Ticket', + 'Class:lnkInfraTicket+' => 'Infra impacted by a ticket', + 'Class:lnkInfraTicket/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkInfraTicket/Attribute:infra_id+' => 'The infrastructure impacted', + 'Class:lnkInfraTicket/Attribute:infra_name' => 'Infrastructure Name', + 'Class:lnkInfraTicket/Attribute:infra_name+' => 'Name of the impacted infrastructure', + 'Class:lnkInfraTicket/Attribute:ticket_id' => 'Ticket #', + 'Class:lnkInfraTicket/Attribute:ticket_id+' => 'Ticket number', + 'Class:lnkInfraTicket/Attribute:ticket_name' => 'Ticket Name', + 'Class:lnkInfraTicket/Attribute:ticket_name+' => 'Name of the ticket', + 'Class:lnkInfraTicket/Attribute:impact' => 'Impact', + 'Class:lnkInfraTicket/Attribute:impact+' => 'Level of impact of the infra by the related ticket', +)); + +// +// Class: lnkContactTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkContactTicket' => 'Contact Ticket', + 'Class:lnkContactTicket+' => 'Contacts to be notify for a ticket', + 'Class:lnkContactTicket/Attribute:contact_id' => 'Contact', + 'Class:lnkContactTicket/Attribute:contact_id+' => 'Contact to Notify', + 'Class:lnkContactTicket/Attribute:contact_email' => 'Contact email', + 'Class:lnkContactTicket/Attribute:contact_email+' => 'Mail for the contact', + 'Class:lnkContactTicket/Attribute:ticket_id' => 'Ticket #', + 'Class:lnkContactTicket/Attribute:ticket_id+' => 'Ticket number', + 'Class:lnkContactTicket/Attribute:ticket_name' => 'Ticket Name', + 'Class:lnkContactTicket/Attribute:ticket_name+' => 'Name of the ticket', + 'Class:lnkContactTicket/Attribute:role' => 'Role', + 'Class:lnkContactTicket/Attribute:role+' => 'Role of the contact', +)); + +// +// Class: bizWorkgroup +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizWorkgroup' => 'Workgroup', + 'Class:bizWorkgroup+' => 'Call tracking workgroup', + 'Class:bizWorkgroup/Attribute:status' => 'Status', + 'Class:bizWorkgroup/Attribute:status+' => 'Lifecycle status', + 'Class:bizWorkgroup/Attribute:status/Value:production' => 'production', + 'Class:bizWorkgroup/Attribute:status/Value:production+' => 'production', + 'Class:bizWorkgroup/Attribute:status/Value:implementation' => 'implementation', + 'Class:bizWorkgroup/Attribute:status/Value:implementation+' => 'implementation', + 'Class:bizWorkgroup/Attribute:status/Value:obsolete' => 'obsolete', + 'Class:bizWorkgroup/Attribute:status/Value:obsolete+' => 'obsolete', + 'Class:bizWorkgroup/Attribute:org_name' => 'Organization', + 'Class:bizWorkgroup/Attribute:org_name+' => 'Company / Department owning this object', + 'Class:bizWorkgroup/Attribute:team_id' => 'Team', + 'Class:bizWorkgroup/Attribute:team_id+' => 'Team owning the workgroup', + 'Class:bizWorkgroup/Attribute:team_name' => 'Team Name', + 'Class:bizWorkgroup/Attribute:team_name+' => 'name of the team', + 'Class:bizWorkgroup/Attribute:role' => 'Role', + 'Class:bizWorkgroup/Attribute:role+' => 'Role of this work group', +)); + +// +// Class: bizService +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizService' => 'Service', + 'Class:bizService+' => 'Service provided by an organization', + 'Class:bizService/Attribute:name' => 'Name', + 'Class:bizService/Attribute:name+' => 'Name of the service', + 'Class:bizService/Attribute:org_id' => 'Provider', + 'Class:bizService/Attribute:org_id+' => 'Provider for this service', + 'Class:bizService/Attribute:provider_name' => 'Provider', + 'Class:bizService/Attribute:provider_name+' => 'name of the Provider', + 'Class:bizService/Attribute:service_category' => 'Service Category', + 'Class:bizService/Attribute:service_category+' => 'Category for this contract', + 'Class:bizService/Attribute:service_category/Value:Server' => 'Server', + 'Class:bizService/Attribute:service_category/Value:Server+' => 'Server', + 'Class:bizService/Attribute:service_category/Value:Network' => 'Network', + 'Class:bizService/Attribute:service_category/Value:Network+' => 'Network', + 'Class:bizService/Attribute:service_category/Value:End-User' => 'End-User', + 'Class:bizService/Attribute:service_category/Value:End-User+' => 'End-User', + 'Class:bizService/Attribute:service_category/Value:Desktop' => 'Desktop', + 'Class:bizService/Attribute:service_category/Value:Desktop+' => 'Desktop', + 'Class:bizService/Attribute:service_category/Value:Application' => 'Application', + 'Class:bizService/Attribute:service_category/Value:Application+' => 'Application', + 'Class:bizService/Attribute:description' => 'Description', + 'Class:bizService/Attribute:description+' => 'Description of this service', + 'Class:bizService/Attribute:status' => 'Status', + 'Class:bizService/Attribute:status+' => 'Status of the service', + 'Class:bizService/Attribute:status/Value:New' => 'New', + 'Class:bizService/Attribute:status/Value:New+' => 'New', + 'Class:bizService/Attribute:status/Value:Implementation' => 'Implementation', + 'Class:bizService/Attribute:status/Value:Implementation+' => 'Implementation', + 'Class:bizService/Attribute:status/Value:Production' => 'Production', + 'Class:bizService/Attribute:status/Value:Production+' => 'Production', + 'Class:bizService/Attribute:status/Value:Obsolete' => 'Obsolete', + 'Class:bizService/Attribute:status/Value:Obsolete+' => 'Obsolete', + 'Class:bizService/Attribute:type' => 'Type', + 'Class:bizService/Attribute:type+' => 'Type of the service', + 'Class:bizService/Attribute:type/Value:Hardware' => 'Hardware', + 'Class:bizService/Attribute:type/Value:Hardware+' => 'Hardware', + 'Class:bizService/Attribute:type/Value:Software' => 'Software', + 'Class:bizService/Attribute:type/Value:Software+' => 'Software', + 'Class:bizService/Attribute:type/Value:Support' => 'Support', + 'Class:bizService/Attribute:type/Value:Support+' => 'Support', +)); + +// +// Class: bizContract +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizContract' => 'Contract', + 'Class:bizContract+' => 'Contract signed by an organization', + 'Class:bizContract/Attribute:name' => 'Name', + 'Class:bizContract/Attribute:name+' => 'Name of the contract', + 'Class:bizContract/Attribute:org_id' => 'Customer', + 'Class:bizContract/Attribute:org_id+' => 'Customer for this contract', + 'Class:bizContract/Attribute:customer_name' => 'Customer', + 'Class:bizContract/Attribute:customer_name+' => 'name of the Customer', + 'Class:bizContract/Attribute:service_id' => 'Service', + 'Class:bizContract/Attribute:service_id+' => 'Provider for this contract', + 'Class:bizContract/Attribute:provider_name' => 'Provider', + 'Class:bizContract/Attribute:provider_name+' => 'name of the service provider', + 'Class:bizContract/Attribute:service_name' => 'Service', + 'Class:bizContract/Attribute:service_name+' => 'name of the service', + 'Class:bizContract/Attribute:team_id' => 'Team', + 'Class:bizContract/Attribute:team_id+' => 'Team managing this contract', + 'Class:bizContract/Attribute:team_name' => 'Team', + 'Class:bizContract/Attribute:team_name+' => 'name of the team managing this contract', + 'Class:bizContract/Attribute:service_level' => 'Service Level', + 'Class:bizContract/Attribute:service_level+' => 'Level of service for this contract', + 'Class:bizContract/Attribute:service_level/Value:Gold' => 'Gold', + 'Class:bizContract/Attribute:service_level/Value:Gold+' => 'Gold', + 'Class:bizContract/Attribute:service_level/Value:Silver' => 'Silver', + 'Class:bizContract/Attribute:service_level/Value:Silver+' => 'Silver', + 'Class:bizContract/Attribute:service_level/Value:Bronze' => 'Bronze', + 'Class:bizContract/Attribute:service_level/Value:Bronze+' => 'Bronze', + 'Class:bizContract/Attribute:cost_unit' => 'Cost Unit', + 'Class:bizContract/Attribute:cost_unit+' => 'Cost unit to compute global cost for this contract', + 'Class:bizContract/Attribute:cost_unit/Value:Devices' => 'Devices', + 'Class:bizContract/Attribute:cost_unit/Value:Devices+' => 'Devices', + 'Class:bizContract/Attribute:cost_unit/Value:Persons' => 'Persons', + 'Class:bizContract/Attribute:cost_unit/Value:Persons+' => 'Persons', + 'Class:bizContract/Attribute:cost_unit/Value:Applications' => 'Applications', + 'Class:bizContract/Attribute:cost_unit/Value:Applications+' => 'Applications', + 'Class:bizContract/Attribute:cost_unit/Value:Global' => 'Global', + 'Class:bizContract/Attribute:cost_unit/Value:Global+' => 'Global', + 'Class:bizContract/Attribute:cost_freq' => 'Billing frequency', + 'Class:bizContract/Attribute:cost_freq+' => 'Frequency of cost for this contract', + 'Class:bizContract/Attribute:cost_freq/Value:Monthly' => 'Monthly', + 'Class:bizContract/Attribute:cost_freq/Value:Monthly+' => 'Monthly', + 'Class:bizContract/Attribute:cost_freq/Value:Yearly' => 'Yearly', + 'Class:bizContract/Attribute:cost_freq/Value:Yearly+' => 'Yearly', + 'Class:bizContract/Attribute:cost_freq/Value:Once' => 'Once', + 'Class:bizContract/Attribute:cost_freq/Value:Once+' => 'Once', + 'Class:bizContract/Attribute:cost' => 'Cost', + 'Class:bizContract/Attribute:cost+' => 'Cost of this contract', + 'Class:bizContract/Attribute:currency' => 'Currency', + 'Class:bizContract/Attribute:currency+' => 'Currency of cost for this contract', + 'Class:bizContract/Attribute:currency/Value:Euros' => 'Euros', + 'Class:bizContract/Attribute:currency/Value:Euros+' => 'Euros', + 'Class:bizContract/Attribute:currency/Value:Dollars' => 'Dollars', + 'Class:bizContract/Attribute:currency/Value:Dollars+' => 'Dollars', + 'Class:bizContract/Attribute:description' => 'Description', + 'Class:bizContract/Attribute:description+' => 'Description of this contract', + 'Class:bizContract/Attribute:move2prod_date' => 'Date of Move To Production', + 'Class:bizContract/Attribute:move2prod_date+' => 'Date when the contract is on production', + 'Class:bizContract/Attribute:end_prod' => 'Date of End Of Production', + 'Class:bizContract/Attribute:end_prod+' => 'Date when the contract is stopped', + 'Class:bizContract/Attribute:status' => 'Status', + 'Class:bizContract/Attribute:status+' => 'Status of the contract', + 'Class:bizContract/Attribute:status/Value:New' => 'New', + 'Class:bizContract/Attribute:status/Value:New+' => 'New', + 'Class:bizContract/Attribute:status/Value:Negotiating' => 'Negotiating', + 'Class:bizContract/Attribute:status/Value:Negotiating+' => 'Negotiating', + 'Class:bizContract/Attribute:status/Value:Signed' => 'Signed', + 'Class:bizContract/Attribute:status/Value:Signed+' => 'Signed', + 'Class:bizContract/Attribute:status/Value:Production' => 'Production', + 'Class:bizContract/Attribute:status/Value:Production+' => 'Production', + 'Class:bizContract/Attribute:status/Value:Finished' => 'Finished', + 'Class:bizContract/Attribute:status/Value:Finished+' => 'Finished', + 'Class:bizContract/Attribute:type' => 'Type', + 'Class:bizContract/Attribute:type+' => 'Type of the contract', + 'Class:bizContract/Attribute:type/Value:Hardware' => 'Hardware', + 'Class:bizContract/Attribute:type/Value:Hardware+' => 'Hardware', + 'Class:bizContract/Attribute:type/Value:Software' => 'Software', + 'Class:bizContract/Attribute:type/Value:Software+' => 'Software', + 'Class:bizContract/Attribute:type/Value:Support' => 'Support', + 'Class:bizContract/Attribute:type/Value:Support+' => 'Support', + 'Class:bizContract/Attribute:type/Value:Licence' => 'Licence', + 'Class:bizContract/Attribute:type/Value:Licence+' => 'Licence', + 'Class:bizContract/Attribute:version_number' => 'Version', + 'Class:bizContract/Attribute:version_number+' => 'Revision number for this contract', +)); + +// +// Class: lnkInfraContract +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraContract' => 'InfraContractLinks', + 'Class:lnkInfraContract+' => 'Infra covered by a contract', + 'Class:lnkInfraContract/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkInfraContract/Attribute:infra_id+' => 'The infrastructure impacted', + 'Class:lnkInfraContract/Attribute:infra_name' => 'Infrastructure Name', + 'Class:lnkInfraContract/Attribute:infra_name+' => 'Name of the impacted infrastructure', + 'Class:lnkInfraContract/Attribute:infra_status' => 'Status', + 'Class:lnkInfraContract/Attribute:infra_status+' => 'Status of the impacted infrastructure', + 'Class:lnkInfraContract/Attribute:contract_id' => 'Contract', + 'Class:lnkInfraContract/Attribute:contract_id+' => 'Contract id', + 'Class:lnkInfraContract/Attribute:contract_name' => 'Contract Name', + 'Class:lnkInfraContract/Attribute:contract_name+' => 'Name of the contract', + 'Class:lnkInfraContract/Attribute:coverage' => 'Coverage', + 'Class:lnkInfraContract/Attribute:coverage+' => 'coverage for the given infra', + 'Class:lnkInfraContract/Attribute:service_level' => 'Service Level', + 'Class:lnkInfraContract/Attribute:service_level+' => 'service level for the given infra', +)); + +// +// Class: lnkContactContract +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkContactContract' => 'ContactContractLink', + 'Class:lnkContactContract+' => 'Contact associated to a contract', + 'Class:lnkContactContract/Attribute:contact_id' => 'Contact', + 'Class:lnkContactContract/Attribute:contact_id+' => 'The contact linked to contract', + 'Class:lnkContactContract/Attribute:contact_mail' => 'Contact E-mail', + 'Class:lnkContactContract/Attribute:contact_mail+' => 'Mail for the contact', + 'Class:lnkContactContract/Attribute:contract_id' => 'Contract', + 'Class:lnkContactContract/Attribute:contract_id+' => 'Contract ID', + 'Class:lnkContactContract/Attribute:contract_name' => 'Contract Name', + 'Class:lnkContactContract/Attribute:contract_name+' => 'Name of the contract', + 'Class:lnkContactContract/Attribute:role' => 'Role', + 'Class:lnkContactContract/Attribute:role+' => 'Role of this contact for this contract', +)); + +// +// Class: lnkDocumentContract +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkDocumentContract' => 'DocumentsContractLinks', + 'Class:lnkDocumentContract+' => 'A link between a document and another contract', + 'Class:lnkDocumentContract/Attribute:doc_id' => 'Document', + 'Class:lnkDocumentContract/Attribute:doc_id+' => 'id of the Document', + 'Class:lnkDocumentContract/Attribute:doc_name' => 'Document Name', + 'Class:lnkDocumentContract/Attribute:doc_name+' => 'name of the document', + 'Class:lnkDocumentContract/Attribute:contract_id' => 'Contract', + 'Class:lnkDocumentContract/Attribute:contract_id+' => 'Contract linked to this document', + 'Class:lnkDocumentContract/Attribute:contract_name' => 'Contract Name', + 'Class:lnkDocumentContract/Attribute:contract_name+' => 'name of the linked contract', + 'Class:lnkDocumentContract/Attribute:link_type' => 'Link Type', + 'Class:lnkDocumentContract/Attribute:link_type+' => 'More information', +)); + +// +// Class: bizChangeTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizChangeTicket' => 'Change', + 'Class:bizChangeTicket+' => 'Change ticket', + 'Class:bizChangeTicket/Attribute:name' => 'Ticket Ref', + 'Class:bizChangeTicket/Attribute:name+' => 'Refence number ofr this change', + 'Class:bizChangeTicket/Attribute:title' => 'Title', + 'Class:bizChangeTicket/Attribute:title+' => 'Overview of the Change', + 'Class:bizChangeTicket/Attribute:type' => 'Change Type', + 'Class:bizChangeTicket/Attribute:type+' => 'Type of the Change', + 'Class:bizChangeTicket/Attribute:domain' => 'Domain', + 'Class:bizChangeTicket/Attribute:domain+' => 'Domain for the Change', + 'Class:bizChangeTicket/Attribute:reason' => 'Reason For Change', + 'Class:bizChangeTicket/Attribute:reason+' => 'Reason for the Change', + 'Class:bizChangeTicket/Attribute:requestor_id' => 'Requestor', + 'Class:bizChangeTicket/Attribute:requestor_id+' => 'who is requesting this change', + 'Class:bizChangeTicket/Attribute:requestor_mail' => 'Requestor', + 'Class:bizChangeTicket/Attribute:requestor_mail+' => 'mail of user requesting this change', + 'Class:bizChangeTicket/Attribute:org_id' => 'Customer', + 'Class:bizChangeTicket/Attribute:org_id+' => 'who is impacted by the ticket', + 'Class:bizChangeTicket/Attribute:customer_name' => 'Customer', + 'Class:bizChangeTicket/Attribute:customer_name+' => 'Name of the customer impacted by this ticket', + 'Class:bizChangeTicket/Attribute:ticket_status' => 'Status', + 'Class:bizChangeTicket/Attribute:ticket_status+' => 'Status of the ticket', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:New' => 'New', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:New+' => 'New', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated' => 'Validated', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated+' => 'Validated', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected' => 'Rejected', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected+' => 'Rejected', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned' => 'Assigned', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned+' => 'Assigned', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled' => 'PlannedScheduled', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled+' => 'PlannedScheduled', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved' => 'Approved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved+' => 'Approved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved' => 'NotApproved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved+' => 'NotApproved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented' => 'Implemented', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented+' => 'Implemented', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored' => 'Monitored', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored+' => 'Monitored', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Closed' => 'Closed', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Closed+' => 'Closed', + 'Class:bizChangeTicket/Attribute:creation_date' => 'Creation Date', + 'Class:bizChangeTicket/Attribute:creation_date+' => 'Change creation date', + 'Class:bizChangeTicket/Attribute:last_update' => 'Last Update', + 'Class:bizChangeTicket/Attribute:last_update+' => 'last time the Ticket was modified', + 'Class:bizChangeTicket/Attribute:start_date' => 'Start Date', + 'Class:bizChangeTicket/Attribute:start_date+' => 'Time the change is expected to start', + 'Class:bizChangeTicket/Attribute:end_date' => 'End Date', + 'Class:bizChangeTicket/Attribute:end_date+' => 'Date when the change is supposed to end', + 'Class:bizChangeTicket/Attribute:close_date' => 'Closure Date', + 'Class:bizChangeTicket/Attribute:close_date+' => 'Date when the Ticket was closed', + 'Class:bizChangeTicket/Attribute:impact' => 'Risk Assessment', + 'Class:bizChangeTicket/Attribute:impact+' => 'Impact of the change', + 'Class:bizChangeTicket/Attribute:workgroup_id' => 'Workgroup', + 'Class:bizChangeTicket/Attribute:workgroup_id+' => 'which workgroup is owning ticket', + 'Class:bizChangeTicket/Attribute:workgroup_name' => 'Workgroup', + 'Class:bizChangeTicket/Attribute:workgroup_name+' => 'name of workgroup managing the Ticket', + 'Class:bizChangeTicket/Attribute:agent_id' => 'Agent', + 'Class:bizChangeTicket/Attribute:agent_id+' => 'who is managing the ticket', + 'Class:bizChangeTicket/Attribute:agent_mail' => 'Agent', + 'Class:bizChangeTicket/Attribute:agent_mail+' => 'name of agent managing the Ticket', + 'Class:bizChangeTicket/Attribute:supervisorgroup_id' => 'Supervisor Group', + 'Class:bizChangeTicket/Attribute:supervisorgroup_id+' => 'which workgroup is supervising ticket', + 'Class:bizChangeTicket/Attribute:supervisorgroup_name' => 'Supervisor Group', + 'Class:bizChangeTicket/Attribute:supervisorgroup_name+' => 'name of the group supervising the Ticket', + 'Class:bizChangeTicket/Attribute:supervisor_id' => 'Supervisor', + 'Class:bizChangeTicket/Attribute:supervisor_id+' => 'who is managing the ticket', + 'Class:bizChangeTicket/Attribute:supervisor_mail' => 'Supervisor', + 'Class:bizChangeTicket/Attribute:supervisor_mail+' => 'name of agent supervising the Ticket', + 'Class:bizChangeTicket/Attribute:managergroup_id' => 'Manager Group', + 'Class:bizChangeTicket/Attribute:managergroup_id+' => 'which workgroup is approving ticket', + 'Class:bizChangeTicket/Attribute:managergroup_name' => 'Manager Group', + 'Class:bizChangeTicket/Attribute:managergroup_name+' => 'name of workgroup approving the Ticket', + 'Class:bizChangeTicket/Attribute:manager_id' => 'Manager', + 'Class:bizChangeTicket/Attribute:manager_id+' => 'who is approving the ticket', + 'Class:bizChangeTicket/Attribute:manager_mail' => 'Manager', + 'Class:bizChangeTicket/Attribute:manager_mail+' => 'name of agent approving the Ticket', + 'Class:bizChangeTicket/Attribute:outage' => 'Planned Outage', + 'Class:bizChangeTicket/Attribute:outage+' => 'Flag to define if there is a planned outage', + 'Class:bizChangeTicket/Attribute:outage/Value:Yes' => 'Yes', + 'Class:bizChangeTicket/Attribute:outage/Value:Yes+' => 'Yes', + 'Class:bizChangeTicket/Attribute:outage/Value:No' => 'No', + 'Class:bizChangeTicket/Attribute:outage/Value:No+' => 'No', + 'Class:bizChangeTicket/Attribute:change_request' => 'Change Request', + 'Class:bizChangeTicket/Attribute:change_request+' => 'Description of Change required', + 'Class:bizChangeTicket/Attribute:change_log' => 'Implementation Log', + 'Class:bizChangeTicket/Attribute:change_log+' => 'List all action performed during the change', + 'Class:bizChangeTicket/Attribute:fallback' => 'Fallback Plan', + 'Class:bizChangeTicket/Attribute:fallback+' => 'Instruction to come back to former situation', + 'Class:bizChangeTicket/Attribute:assignment_count' => 'Assignment Count', + 'Class:bizChangeTicket/Attribute:assignment_count+' => 'Number of times this ticket was assigned or reassigned', + 'Class:bizChangeTicket/Attribute:impacted_infra_manual' => 'Impacted Infrastructure', + 'Class:bizChangeTicket/Attribute:impacted_infra_manual+' => 'CIs that are impacted by this change', + 'Class:bizChangeTicket/Stimulus:ev_validate' => 'Validate this change', + 'Class:bizChangeTicket/Stimulus:ev_validate+' => 'Make sure it is a valid change request', + 'Class:bizChangeTicket/Stimulus:ev_reject' => 'Reject this change', + 'Class:bizChangeTicket/Stimulus:ev_reject+' => 'This change request is rejected because it is a non valid one', + 'Class:bizChangeTicket/Stimulus:ev_assign' => 'Assign this change', + 'Class:bizChangeTicket/Stimulus:ev_assign+' => 'This change request is assigned', + 'Class:bizChangeTicket/Stimulus:ev_reopen' => 'Modify this change', + 'Class:bizChangeTicket/Stimulus:ev_reopen+' => 'Update change request to make it valid', + 'Class:bizChangeTicket/Stimulus:ev_plan' => 'Plan this change', + 'Class:bizChangeTicket/Stimulus:ev_plan+' => 'Plan and Schedule this change for validation', + 'Class:bizChangeTicket/Stimulus:ev_approve' => 'Approve this change', + 'Class:bizChangeTicket/Stimulus:ev_approve+' => 'This change is approved by CAB', + 'Class:bizChangeTicket/Stimulus:ev_replan' => 'Update planning and schedule', + 'Class:bizChangeTicket/Stimulus:ev_replan+' => 'Modify Plan and Schedule in order to have this change re-validated', + 'Class:bizChangeTicket/Stimulus:ev_notapprove' => 'Not approve this change', + 'Class:bizChangeTicket/Stimulus:ev_notapprove+' => 'This change is not approved by CAB', + 'Class:bizChangeTicket/Stimulus:ev_implement' => 'Implement this change', + 'Class:bizChangeTicket/Stimulus:ev_implement+' => 'Implementation pahse for current change', + 'Class:bizChangeTicket/Stimulus:ev_monitor' => 'Monitor this change', + 'Class:bizChangeTicket/Stimulus:ev_monitor+' => 'Starting monitoring period for this change', + 'Class:bizChangeTicket/Stimulus:ev_finish' => 'Close change', + 'Class:bizChangeTicket/Stimulus:ev_finish+' => 'Change is done, and can be closed', +)); + +// +// Class: lnkInfraChangeTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraChangeTicket' => 'Infra Change Ticket', + 'Class:lnkInfraChangeTicket+' => 'Infra impacted by a Change ticket', + 'Class:lnkInfraChangeTicket/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkInfraChangeTicket/Attribute:infra_id+' => 'The infrastructure impacted', + 'Class:lnkInfraChangeTicket/Attribute:infra_name' => 'Infrastructure Name', + 'Class:lnkInfraChangeTicket/Attribute:infra_name+' => 'Name of the impacted infrastructure', + 'Class:lnkInfraChangeTicket/Attribute:ticket_id' => 'Ticket', + 'Class:lnkInfraChangeTicket/Attribute:ticket_id+' => 'Ticket number', + 'Class:lnkInfraChangeTicket/Attribute:ticket_name' => 'Ticket Name', + 'Class:lnkInfraChangeTicket/Attribute:ticket_name+' => 'Name of the ticket', + 'Class:lnkInfraChangeTicket/Attribute:impact' => 'Impact', + 'Class:lnkInfraChangeTicket/Attribute:impact+' => 'Level of impact of the infra by the related ticket', +)); + +// +// Class: lnkContactChange +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkContactChange' => 'ContactChangeLink', + 'Class:lnkContactChange+' => 'Contact associated to a change', + 'Class:lnkContactChange/Attribute:contact_id' => 'Contact', + 'Class:lnkContactChange/Attribute:contact_id+' => 'The contact linked to contract', + 'Class:lnkContactChange/Attribute:contact_mail' => 'Contact E-mail', + 'Class:lnkContactChange/Attribute:contact_mail+' => 'Mail for the contact', + 'Class:lnkContactChange/Attribute:change_id' => 'Change Ticket', + 'Class:lnkContactChange/Attribute:change_id+' => 'Change ticket ID', + 'Class:lnkContactChange/Attribute:change_number' => 'Change Ticket', + 'Class:lnkContactChange/Attribute:change_number+' => 'Ticket number for this change', + 'Class:lnkContactChange/Attribute:role' => 'Role', + 'Class:lnkContactChange/Attribute:role+' => 'Role of this contact for this change', +)); + +// +// Class: bizKnownError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizKnownError' => 'Known Error', + 'Class:bizKnownError+' => 'Error documented for a known issue', + 'Class:bizKnownError/Attribute:name' => 'Name', + 'Class:bizKnownError/Attribute:name+' => 'Name to identify this error', + 'Class:bizKnownError/Attribute:org_id' => 'Organization', + 'Class:bizKnownError/Attribute:org_id+' => 'Organization for this known error', + 'Class:bizKnownError/Attribute:cust_name' => 'Organization', + 'Class:bizKnownError/Attribute:cust_name+' => 'Company / Department owning this object', + 'Class:bizKnownError/Attribute:symptom' => 'Symptom', + 'Class:bizKnownError/Attribute:symptom+' => 'Description of this error', + 'Class:bizKnownError/Attribute:root_cause' => 'Root cause', + 'Class:bizKnownError/Attribute:root_cause+' => 'Original cause for this known error', + 'Class:bizKnownError/Attribute:workaround' => 'Workaround', + 'Class:bizKnownError/Attribute:workaround+' => 'Work around to fix this error', + 'Class:bizKnownError/Attribute:solution' => 'Solution', + 'Class:bizKnownError/Attribute:solution+' => 'Description of this contract', + 'Class:bizKnownError/Attribute:error_code' => 'Error Code', + 'Class:bizKnownError/Attribute:error_code+' => 'Key word to identify error', + 'Class:bizKnownError/Attribute:domain' => 'Domain', + 'Class:bizKnownError/Attribute:domain+' => 'Domain for this known error, network, desktop, ...', + 'Class:bizKnownError/Attribute:domain/Value:Network' => 'Network', + 'Class:bizKnownError/Attribute:domain/Value:Network+' => 'Network', + 'Class:bizKnownError/Attribute:domain/Value:Server' => 'Server', + 'Class:bizKnownError/Attribute:domain/Value:Server+' => 'Server', + 'Class:bizKnownError/Attribute:domain/Value:Application' => 'Application', + 'Class:bizKnownError/Attribute:domain/Value:Application+' => 'Application', + 'Class:bizKnownError/Attribute:domain/Value:Desktop' => 'Desktop', + 'Class:bizKnownError/Attribute:domain/Value:Desktop+' => 'Desktop', + 'Class:bizKnownError/Attribute:vendor' => 'Vendor', + 'Class:bizKnownError/Attribute:vendor+' => 'Vendor concerned by this known error', + 'Class:bizKnownError/Attribute:model' => 'Model', + 'Class:bizKnownError/Attribute:model+' => 'Model concerned by this known error, it may be an application, a device ...', + 'Class:bizKnownError/Attribute:version' => 'Version', + 'Class:bizKnownError/Attribute:version+' => 'Version related to model impacted by known error', +)); + +// +// Class: lnkInfraError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraError' => 'InfraErrorLinks', + 'Class:lnkInfraError+' => 'Infra related to a known error', + 'Class:lnkInfraError/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkInfraError/Attribute:infra_id+' => 'The infrastructure impacted', + 'Class:lnkInfraError/Attribute:infra_name' => 'Infrastructure Name', + 'Class:lnkInfraError/Attribute:infra_name+' => 'Name of the impacted infrastructure', + 'Class:lnkInfraError/Attribute:infra_status' => 'Status', + 'Class:lnkInfraError/Attribute:infra_status+' => 'Status of the impacted infrastructure', + 'Class:lnkInfraError/Attribute:error_id' => 'Error', + 'Class:lnkInfraError/Attribute:error_id+' => 'Error id', + 'Class:lnkInfraError/Attribute:error_name' => 'Error Name', + 'Class:lnkInfraError/Attribute:error_name+' => 'Name of the error', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkDocumentError' => 'DocumentsErrorLinks', + 'Class:lnkDocumentError+' => 'A link between a document and a known error', + 'Class:lnkDocumentError/Attribute:doc_id' => 'Document', + 'Class:lnkDocumentError/Attribute:doc_id+' => 'id of the Document', + 'Class:lnkDocumentError/Attribute:doc_name' => 'Document Name', + 'Class:lnkDocumentError/Attribute:doc_name+' => 'name of the document', + 'Class:lnkDocumentError/Attribute:error_id' => 'Error', + 'Class:lnkDocumentError/Attribute:error_id+' => 'Error linked to this document', + 'Class:lnkDocumentError/Attribute:error_name' => 'Error Name', + 'Class:lnkDocumentError/Attribute:error_name+' => 'name of the linked error', + 'Class:lnkDocumentError/Attribute:link_type' => 'Link Type', + 'Class:lnkDocumentError/Attribute:link_type+' => 'More information', +)); + +// +// Class: bizServiceCall +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:bizServiceCall' => 'ServiceCall', + 'Class:bizServiceCall+' => 'Service Call from customer', + 'Class:bizServiceCall/Attribute:name' => 'Service Call Ref', + 'Class:bizServiceCall/Attribute:name+' => 'Refence identifier for this service call', + 'Class:bizServiceCall/Attribute:title' => 'Title', + 'Class:bizServiceCall/Attribute:title+' => 'Overview of the service call', + 'Class:bizServiceCall/Attribute:type' => 'Type', + 'Class:bizServiceCall/Attribute:type+' => 'Type of the Incident', + 'Class:bizServiceCall/Attribute:type/Value:Network' => 'Network', + 'Class:bizServiceCall/Attribute:type/Value:Network+' => 'Network', + 'Class:bizServiceCall/Attribute:type/Value:Server' => 'Server', + 'Class:bizServiceCall/Attribute:type/Value:Server+' => 'Server', + 'Class:bizServiceCall/Attribute:type/Value:Desktop' => 'Desktop', + 'Class:bizServiceCall/Attribute:type/Value:Desktop+' => 'Desktop', + 'Class:bizServiceCall/Attribute:type/Value:Application' => 'Application', + 'Class:bizServiceCall/Attribute:type/Value:Application+' => 'Application', + 'Class:bizServiceCall/Attribute:org_id' => 'Customer', + 'Class:bizServiceCall/Attribute:org_id+' => 'Customer concerned by this service call', + 'Class:bizServiceCall/Attribute:customer_name' => 'Customer', + 'Class:bizServiceCall/Attribute:customer_name+' => 'Name of the customer raising this service call', + 'Class:bizServiceCall/Attribute:call_status' => 'Status', + 'Class:bizServiceCall/Attribute:call_status+' => 'Status of the ticket', + 'Class:bizServiceCall/Attribute:call_status/Value:New' => 'New', + 'Class:bizServiceCall/Attribute:call_status/Value:New+' => 'New', + 'Class:bizServiceCall/Attribute:call_status/Value:Assigned' => 'Assigned', + 'Class:bizServiceCall/Attribute:call_status/Value:Assigned+' => 'Assigned', + 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress' => 'WorkInProgress', + 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress+' => 'WorkInProgress', + 'Class:bizServiceCall/Attribute:call_status/Value:Resolved' => 'Resolved', + 'Class:bizServiceCall/Attribute:call_status/Value:Resolved+' => 'Resolved', + 'Class:bizServiceCall/Attribute:call_status/Value:Closed' => 'Closed', + 'Class:bizServiceCall/Attribute:call_status/Value:Closed+' => 'Closed', + 'Class:bizServiceCall/Attribute:call_description' => 'Description', + 'Class:bizServiceCall/Attribute:call_description+' => 'Description of the call as describe by caller', + 'Class:bizServiceCall/Attribute:creation_date' => 'Creation date', + 'Class:bizServiceCall/Attribute:creation_date+' => 'Call creation date', + 'Class:bizServiceCall/Attribute:last_update' => 'Last update', + 'Class:bizServiceCall/Attribute:last_update+' => 'last time the call was modified', + 'Class:bizServiceCall/Attribute:next_update' => 'Next update', + 'Class:bizServiceCall/Attribute:next_update+' => 'next time the Ticket is expected to be modified', + 'Class:bizServiceCall/Attribute:end_date' => 'Closure Date', + 'Class:bizServiceCall/Attribute:end_date+' => 'Date when the call was closed', + 'Class:bizServiceCall/Attribute:caller_id' => 'Caller', + 'Class:bizServiceCall/Attribute:caller_id+' => 'person that trigger this call', + 'Class:bizServiceCall/Attribute:caller_mail' => 'Caller', + 'Class:bizServiceCall/Attribute:caller_mail+' => 'Person that trigger this call', + 'Class:bizServiceCall/Attribute:impact' => 'Impact', + 'Class:bizServiceCall/Attribute:impact+' => 'Impact for this call', + 'Class:bizServiceCall/Attribute:workgroup_id' => 'Workgroup', + 'Class:bizServiceCall/Attribute:workgroup_id+' => 'which workgroup is owning call', + 'Class:bizServiceCall/Attribute:workgroup_name' => 'Workgroup', + 'Class:bizServiceCall/Attribute:workgroup_name+' => 'name of workgroup managing the call', + 'Class:bizServiceCall/Attribute:agent_id' => 'Agent', + 'Class:bizServiceCall/Attribute:agent_id+' => 'who is managing the call', + 'Class:bizServiceCall/Attribute:agent_mail' => 'Agent', + 'Class:bizServiceCall/Attribute:agent_mail+' => 'mail of agent managing the call', + 'Class:bizServiceCall/Attribute:action_log' => 'Action Logs', + 'Class:bizServiceCall/Attribute:action_log+' => 'List all action performed during the call', + 'Class:bizServiceCall/Attribute:severity' => 'Severity', + 'Class:bizServiceCall/Attribute:severity+' => 'Field defining the criticity for the call', + 'Class:bizServiceCall/Attribute:severity/Value:critical' => 'critical', + 'Class:bizServiceCall/Attribute:severity/Value:critical+' => 'critical', + 'Class:bizServiceCall/Attribute:severity/Value:medium' => 'medium', + 'Class:bizServiceCall/Attribute:severity/Value:medium+' => 'medium', + 'Class:bizServiceCall/Attribute:severity/Value:low' => 'low', + 'Class:bizServiceCall/Attribute:severity/Value:low+' => 'low', + 'Class:bizServiceCall/Attribute:resolution' => 'Resolution', + 'Class:bizServiceCall/Attribute:resolution+' => 'Description of the resolution', + 'Class:bizServiceCall/Attribute:source' => 'Source', + 'Class:bizServiceCall/Attribute:source+' => 'source type for this call', + 'Class:bizServiceCall/Attribute:source/Value:phone' => 'phone', + 'Class:bizServiceCall/Attribute:source/Value:phone+' => 'phone', + 'Class:bizServiceCall/Attribute:source/Value:E-mail' => 'E-mail', + 'Class:bizServiceCall/Attribute:source/Value:E-mail+' => 'E-mail', + 'Class:bizServiceCall/Attribute:source/Value:Fax' => 'Fax', + 'Class:bizServiceCall/Attribute:source/Value:Fax+' => 'Fax', + 'Class:bizServiceCall/Attribute:impacted_infra_manual' => 'Impacted Infrastructure', + 'Class:bizServiceCall/Attribute:impacted_infra_manual+' => 'CIs that are not meeting the SLA', + 'Class:bizServiceCall/Attribute:related_tickets' => 'Related Incident', + 'Class:bizServiceCall/Attribute:related_tickets+' => 'Other incident tickets related to this call', + 'Class:bizServiceCall/Stimulus:ev_assign' => 'Assign this call', + 'Class:bizServiceCall/Stimulus:ev_assign+' => 'Assign this call to a group and an agent', + 'Class:bizServiceCall/Stimulus:ev_reassign' => 'Reassign this call', + 'Class:bizServiceCall/Stimulus:ev_reassign+' => 'Reassign this call to a different group and agent', + 'Class:bizServiceCall/Stimulus:ev_start_working' => 'Work on this call', + 'Class:bizServiceCall/Stimulus:ev_start_working+' => 'Start working on this call', + 'Class:bizServiceCall/Stimulus:ev_resolve' => 'Resolve this call', + 'Class:bizServiceCall/Stimulus:ev_resolve+' => 'Resolve this call', + 'Class:bizServiceCall/Stimulus:ev_close' => 'Close this call', + 'Class:bizServiceCall/Stimulus:ev_close+' => 'Close this call', +)); + +// +// Class: lnkCallTicket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkCallTicket' => 'Call Ticket', + 'Class:lnkCallTicket+' => 'Ticket related to a call', + 'Class:lnkCallTicket/Attribute:ticket_id' => 'Related Ticket', + 'Class:lnkCallTicket/Attribute:ticket_id+' => 'The related ticket', + 'Class:lnkCallTicket/Attribute:ticket_name' => 'Related ticket', + 'Class:lnkCallTicket/Attribute:ticket_name+' => 'Name of the related ticket', + 'Class:lnkCallTicket/Attribute:call_id' => 'Call', + 'Class:lnkCallTicket/Attribute:call_id+' => 'Ticket number', + 'Class:lnkCallTicket/Attribute:call_name' => 'Call name', + 'Class:lnkCallTicket/Attribute:call_name+' => 'Name of the call', + 'Class:lnkCallTicket/Attribute:impact' => 'Impact', + 'Class:lnkCallTicket/Attribute:impact+' => 'Impact on the call', +)); + +// +// Class: lnkInfraCall +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraCall' => 'Infra Call', + 'Class:lnkInfraCall+' => 'Infra concerned by a call', + 'Class:lnkInfraCall/Attribute:infra_id' => 'Infrastructure', + 'Class:lnkInfraCall/Attribute:infra_id+' => 'The infrastructure impacted', + 'Class:lnkInfraCall/Attribute:infra_name' => 'Infrastructure Name', + 'Class:lnkInfraCall/Attribute:infra_name+' => 'Name of the impacted infrastructure', + 'Class:lnkInfraCall/Attribute:call_id' => 'Call', + 'Class:lnkInfraCall/Attribute:call_id+' => 'Call number', + 'Class:lnkInfraCall/Attribute:call_name' => 'Call name', + 'Class:lnkInfraCall/Attribute:call_name+' => 'Name of the call', + 'Class:lnkInfraCall/Attribute:impact' => 'Impact', + 'Class:lnkInfraCall/Attribute:impact+' => 'Level of impact of the infra by the related ticket', +)); + + +?> diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php new file mode 100644 index 0000000000..ac02ed2097 --- /dev/null +++ b/dictionaries/dictionary.itop.ui.php @@ -0,0 +1,266 @@ + 'menuNode', + 'Class:menuNode+' => 'Main menu configuration elements', + 'Class:menuNode/Attribute:name' => 'Menu Name', + 'Class:menuNode/Attribute:name+' => 'Short name for this menu', + 'Class:menuNode/Attribute:label' => 'Menu Description', + 'Class:menuNode/Attribute:label+' => 'Long description for this menu', + 'Class:menuNode/Attribute:hyperlink' => 'Hyperlink', + 'Class:menuNode/Attribute:hyperlink+' => 'Hyperlink to the page', + 'Class:menuNode/Attribute:icon_path' => 'Menu Icon', + 'Class:menuNode/Attribute:icon_path+' => 'Path to the icon o the menu', + 'Class:menuNode/Attribute:template' => 'Template', + 'Class:menuNode/Attribute:template+' => 'HTML template for the view', + 'Class:menuNode/Attribute:type' => 'Type', + 'Class:menuNode/Attribute:type+' => 'Type of menu', + 'Class:menuNode/Attribute:type/Value:application' => 'application', + 'Class:menuNode/Attribute:type/Value:application+' => 'application', + 'Class:menuNode/Attribute:type/Value:user' => 'user', + 'Class:menuNode/Attribute:type/Value:user+' => 'user', + 'Class:menuNode/Attribute:type/Value:administrator' => 'administrator', + 'Class:menuNode/Attribute:type/Value:administrator+' => 'administrator', + 'Class:menuNode/Attribute:rank' => 'Display rank', + 'Class:menuNode/Attribute:rank+' => 'Sort order for displaying the menu', + 'Class:menuNode/Attribute:parent_id' => 'Parent Menu Item', + 'Class:menuNode/Attribute:parent_id+' => 'Parent Menu Item', + 'Class:menuNode/Attribute:parent_name' => 'Parent Menu Item', + 'Class:menuNode/Attribute:parent_name+' => 'Parent Menu Item', + 'Class:menuNode/Attribute:user_id' => 'Owner of the menu', + 'Class:menuNode/Attribute:user_id+' => 'User who owns this menu (for user defined menus)', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'application' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: AuditCategory +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:AuditCategory' => 'AuditCategory', + 'Class:AuditCategory+' => 'A section inside the overall audit', + 'Class:AuditCategory/Attribute:name' => 'Category Name', + 'Class:AuditCategory/Attribute:name+' => 'Short name for this category', + 'Class:AuditCategory/Attribute:description' => 'Audit Category Description', + 'Class:AuditCategory/Attribute:description+' => 'Long description for this audit category', + 'Class:AuditCategory/Attribute:definition_set' => 'Definition Set', + 'Class:AuditCategory/Attribute:definition_set+' => 'SibusQL expression defining the set of objects to audit', +)); + +// +// Class: AuditRule +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:AuditRule' => 'AuditRule', + 'Class:AuditRule+' => 'A rule to check for a given Audit category', + 'Class:AuditRule/Attribute:name' => 'Rule Name', + 'Class:AuditRule/Attribute:name+' => 'Short name for this rule', + 'Class:AuditRule/Attribute:description' => 'Audit Rule Description', + 'Class:AuditRule/Attribute:description+' => 'Long description for this audit rule', + 'Class:AuditRule/Attribute:query' => 'Query to Run', + 'Class:AuditRule/Attribute:query+' => 'The SibusQL expression to run', + 'Class:AuditRule/Attribute:valid_flag' => 'Valid objects?', + 'Class:AuditRule/Attribute:valid_flag+' => 'True if the rule returns the valid objects, false otherwise', + 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'true', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => 'true', + 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'false', + 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'false', + 'Class:AuditRule/Attribute:category_id' => 'Category', + 'Class:AuditRule/Attribute:category_id+' => 'The category for this rule', + 'Class:AuditRule/Attribute:category_name' => 'Category', + 'Class:AuditRule/Attribute:category_name+' => 'Name of the category for this rule', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'addon/userrights' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: URP_Users +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_Users' => 'user', + 'Class:URP_Users+' => 'users and credentials', + 'Class:URP_Users/Attribute:userid' => 'Contact (person)', + 'Class:URP_Users/Attribute:userid+' => 'Personal details from the business data', + 'Class:URP_Users/Attribute:last_name' => 'Last name', + 'Class:URP_Users/Attribute:last_name+' => 'Name of the corresponding contact', + 'Class:URP_Users/Attribute:first_name' => 'First name', + 'Class:URP_Users/Attribute:first_name+' => 'First name of the corresponding contact', + 'Class:URP_Users/Attribute:email' => 'Email', + 'Class:URP_Users/Attribute:email+' => 'Email of the corresponding contact', + 'Class:URP_Users/Attribute:login' => 'Login', + 'Class:URP_Users/Attribute:login+' => 'user identification string', + 'Class:URP_Users/Attribute:password' => 'Password', + 'Class:URP_Users/Attribute:password+' => 'user authentication string', + 'Class:URP_Users/Attribute:profiles' => 'Profiles', + 'Class:URP_Users/Attribute:profiles+' => 'roles, granting rights for that person', +)); + +// +// Class: URP_Profiles +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_Profiles' => 'profile', + 'Class:URP_Profiles+' => 'usage profiles', + 'Class:URP_Profiles/Attribute:name' => 'Name', + 'Class:URP_Profiles/Attribute:name+' => 'label', + 'Class:URP_Profiles/Attribute:description' => 'Description', + 'Class:URP_Profiles/Attribute:description+' => 'one line description', + 'Class:URP_Profiles/Attribute:users' => 'Users', + 'Class:URP_Profiles/Attribute:users+' => 'persons having this role', +)); + +// +// Class: URP_Dimensions +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_Dimensions' => 'dimension', + 'Class:URP_Dimensions+' => 'application dimension (defining silos)', + 'Class:URP_Dimensions/Attribute:name' => 'Name', + 'Class:URP_Dimensions/Attribute:name+' => 'label', + 'Class:URP_Dimensions/Attribute:description' => 'Description', + 'Class:URP_Dimensions/Attribute:description+' => 'one line description', + 'Class:URP_Dimensions/Attribute:type' => 'Type', + 'Class:URP_Dimensions/Attribute:type+' => 'class name or data type (projection unit)', +)); + +// +// Class: URP_UserProfile +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_UserProfile' => 'User to profile', + 'Class:URP_UserProfile+' => 'user profiles', + 'Class:URP_UserProfile/Attribute:userid' => 'User', + 'Class:URP_UserProfile/Attribute:userid+' => 'user account', + 'Class:URP_UserProfile/Attribute:userlogin' => 'Login', + 'Class:URP_UserProfile/Attribute:userlogin+' => 'User\'s login', + 'Class:URP_UserProfile/Attribute:profileid' => 'Profile', + 'Class:URP_UserProfile/Attribute:profileid+' => 'usage profile', + 'Class:URP_UserProfile/Attribute:profile' => 'Profile', + 'Class:URP_UserProfile/Attribute:profile+' => 'Profile name', + 'Class:URP_UserProfile/Attribute:reason' => 'Reason', + 'Class:URP_UserProfile/Attribute:reason+' => 'explain why this person may have this role', +)); + +// +// Class: URP_ProfileProjection +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_ProfileProjection' => 'profile_projection', + 'Class:URP_ProfileProjection+' => 'profile projections', + 'Class:URP_ProfileProjection/Attribute:dimensionid' => 'Dimension', + 'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'application dimension', + 'Class:URP_ProfileProjection/Attribute:dimension' => 'Dimension', + 'Class:URP_ProfileProjection/Attribute:dimension+' => 'application dimension', + 'Class:URP_ProfileProjection/Attribute:profileid' => 'Profile', + 'Class:URP_ProfileProjection/Attribute:profileid+' => 'usage profile', + 'Class:URP_ProfileProjection/Attribute:profile' => 'Profile', + 'Class:URP_ProfileProjection/Attribute:profile+' => 'Profile name', + 'Class:URP_ProfileProjection/Attribute:value' => 'Value expression', + 'Class:URP_ProfileProjection/Attribute:value+' => 'OQL expression (using $user) | constant | | +attribute code', + 'Class:URP_ProfileProjection/Attribute:attribute' => 'Attribute', + 'Class:URP_ProfileProjection/Attribute:attribute+' => 'Target attribute code (optional)', +)); + +// +// Class: URP_ClassProjection +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_ClassProjection' => 'class_projection', + 'Class:URP_ClassProjection+' => 'class projections', + 'Class:URP_ClassProjection/Attribute:dimensionid' => 'Dimension', + 'Class:URP_ClassProjection/Attribute:dimensionid+' => 'application dimension', + 'Class:URP_ClassProjection/Attribute:dimension' => 'Dimension', + 'Class:URP_ClassProjection/Attribute:dimension+' => 'application dimension', + 'Class:URP_ClassProjection/Attribute:class' => 'Class', + 'Class:URP_ClassProjection/Attribute:class+' => 'Target class', + 'Class:URP_ClassProjection/Attribute:value' => 'Value expression', + 'Class:URP_ClassProjection/Attribute:value+' => 'OQL expression (using $this) | constant | | +attribute code', + 'Class:URP_ClassProjection/Attribute:attribute' => 'Attribute', + 'Class:URP_ClassProjection/Attribute:attribute+' => 'Target attribute code (optional)', +)); + +// +// Class: URP_ActionGrant +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_ActionGrant' => 'action_permission', + 'Class:URP_ActionGrant+' => 'permissions on classes', + 'Class:URP_ActionGrant/Attribute:profileid' => 'Profile', + 'Class:URP_ActionGrant/Attribute:profileid+' => 'usage profile', + 'Class:URP_ActionGrant/Attribute:profile' => 'Profile', + 'Class:URP_ActionGrant/Attribute:profile+' => 'usage profile', + 'Class:URP_ActionGrant/Attribute:class' => 'Class', + 'Class:URP_ActionGrant/Attribute:class+' => 'Target class', + 'Class:URP_ActionGrant/Attribute:permission' => 'Permission', + 'Class:URP_ActionGrant/Attribute:permission+' => 'allowed or not allowed?', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes' => 'yes', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => 'yes', + 'Class:URP_ActionGrant/Attribute:permission/Value:no' => 'no', + 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => 'no', + 'Class:URP_ActionGrant/Attribute:action' => 'Action', + 'Class:URP_ActionGrant/Attribute:action+' => 'operations to perform on the given class', +)); + +// +// Class: URP_StimulusGrant +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_StimulusGrant' => 'stimulus_permission', + 'Class:URP_StimulusGrant+' => 'permissions on stimilus in the life cycle of the object', + 'Class:URP_StimulusGrant/Attribute:profileid' => 'Profile', + 'Class:URP_StimulusGrant/Attribute:profileid+' => 'usage profile', + 'Class:URP_StimulusGrant/Attribute:profile' => 'Profile', + 'Class:URP_StimulusGrant/Attribute:profile+' => 'usage profile', + 'Class:URP_StimulusGrant/Attribute:class' => 'Class', + 'Class:URP_StimulusGrant/Attribute:class+' => 'Target class', + 'Class:URP_StimulusGrant/Attribute:permission' => 'Permission', + 'Class:URP_StimulusGrant/Attribute:permission+' => 'allowed or not allowed?', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'yes', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => 'yes', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'no', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => 'no', + 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Stimulus', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'stimulus code', +)); + +// +// Class: URP_AttributeGrant +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:URP_AttributeGrant' => 'attribute_permission', + 'Class:URP_AttributeGrant+' => 'permissions at the attributes level', + 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'Action grant', + 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => 'action grant', + 'Class:URP_AttributeGrant/Attribute:attcode' => 'Attribute', + 'Class:URP_AttributeGrant/Attribute:attcode+' => 'attribute code', +)); + + +?> diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index 3efbd68900..9020e46b88 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -263,6 +263,7 @@ function printMenu($sConfigFile) echo "

    Target database: $sConfigFile

    \n"; echo "

    $sClassCount classes referenced in the model

    \n"; echo "
      "; + echo "
    • Dictionary
    • "; echo "
    • Biz model consistency
    • "; echo "
    • Show ZLists
    • "; echo "
    • Browse business model
    • "; @@ -471,6 +472,12 @@ else printMenu($sConfigFile); echo $sRes; break; + case "checkdictionary": + echo "Dictionary template...
      \n"; + echo "
      \n";
      +			echo MetaModel::MakeDictionaryTemplate();
      +			echo "
      \n"; + break; case "checkmodel": echo "Check definitions...
      \n"; MetaModel::CheckDefinitions(); From 466f6b9c6d59fc3f73e95e7c80ad2ae11387a26e Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 19 Apr 2010 14:21:34 +0000 Subject: [PATCH 256/970] Localization: finalized the transfer of metamodel strings into the localizable files SVN:trunk[339] --- dictionaries/dictionary.itop.core.php | 16 +++++------ dictionaries/dictionary.itop.model.php | 39 ++++++++++++-------------- dictionaries/dictionary.itop.ui.php | 1 - 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 838dbe8b8e..67c50d1cbc 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -35,8 +35,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:CMDBChangeOp/Attribute:objclass+' => 'object class', 'Class:CMDBChangeOp/Attribute:objkey' => 'object id', 'Class:CMDBChangeOp/Attribute:objkey+' => 'object id', - 'Class:CMDBChangeOp/Attribute:finalclass' => 'Class', - 'Class:CMDBChangeOp/Attribute:finalclass+' => 'Real (final) object class', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'finalclass', + 'Class:CMDBChangeOp/Attribute:finalclass+' => '', )); // @@ -116,8 +116,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Event/Attribute:date+' => 'date and time at which the changes have been recorded', 'Class:Event/Attribute:userinfo' => 'user info', 'Class:Event/Attribute:userinfo+' => 'identification of the user that was doing the action that triggered this event', - 'Class:Event/Attribute:finalclass' => 'Class', - 'Class:Event/Attribute:finalclass+' => 'Real (final) object class', + 'Class:Event/Attribute:finalclass' => 'finalclass', + 'Class:Event/Attribute:finalclass+' => '', )); // @@ -221,8 +221,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Action/Attribute:status/Value:disabled+' => 'Inactive', 'Class:Action/Attribute:related_triggers' => 'Related Triggers', 'Class:Action/Attribute:related_triggers+' => 'Triggers linked to this action', - 'Class:Action/Attribute:finalclass' => 'Class', - 'Class:Action/Attribute:finalclass+' => 'Real (final) object class', + 'Class:Action/Attribute:finalclass' => 'finalclass', + 'Class:Action/Attribute:finalclass+' => '', )); // @@ -278,8 +278,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Trigger/Attribute:description+' => 'one line description', 'Class:Trigger/Attribute:linked_actions' => 'Triggered actions', 'Class:Trigger/Attribute:linked_actions+' => 'Actions performed when the trigger is activated', - 'Class:Trigger/Attribute:finalclass' => 'Class', - 'Class:Trigger/Attribute:finalclass+' => 'Real (final) object class', + 'Class:Trigger/Attribute:finalclass' => 'finalclass', + 'Class:Trigger/Attribute:finalclass+' => '', )); // diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index a98604f388..8c6a8be325 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -10,9 +10,6 @@ // Class:/Stimulus: // Class:/Stimulus:+ -////////////////////////////////////////////////////////////////////// -// Note: The classes have been grouped by categories: bizmodel, core/cmdb, gui, application, addon/userrights -////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // Classes in 'bizmodel' ////////////////////////////////////////////////////////////////////// @@ -70,8 +67,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:logRealObject/Attribute:org_id+' => 'ID of the object owner organization', 'Class:logRealObject/Attribute:org_name' => 'Organization', 'Class:logRealObject/Attribute:org_name+' => 'Company / Department owning this object', - 'Class:logRealObject/Attribute:finalclass' => 'Class', - 'Class:logRealObject/Attribute:finalclass+' => 'Real (final) object class', + 'Class:logRealObject/Attribute:finalclass' => 'finalclass', + 'Class:logRealObject/Attribute:finalclass+' => '', )); // @@ -657,12 +654,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizIncidentTicket/Attribute:customer_name+' => 'Name of the customer impacted by this ticket', 'Class:bizIncidentTicket/Attribute:ticket_status' => 'Status', 'Class:bizIncidentTicket/Attribute:ticket_status+' => 'Status of the ticket', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New' => 'New', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New+' => 'New', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New' => 'New (Unassigned)', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New+' => 'New (Unassigned)', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned' => 'Assigned', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned+' => 'Assigned', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress' => 'WorkInProgress', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress+' => 'WorkInProgress', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress' => 'Work In Progress', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress+' => 'Work In Progress', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved' => 'Resolved', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved+' => 'Resolved', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Closed' => 'Closed', @@ -1031,22 +1028,22 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizChangeTicket/Attribute:customer_name+' => 'Name of the customer impacted by this ticket', 'Class:bizChangeTicket/Attribute:ticket_status' => 'Status', 'Class:bizChangeTicket/Attribute:ticket_status+' => 'Status of the ticket', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:New' => 'New', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:New+' => 'New', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:New' => 'New (Unassigned)', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:New+' => 'New (Unassigned)', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated' => 'Validated', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated+' => 'Validated', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected' => 'Rejected', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected+' => 'Rejected', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned' => 'Assigned', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned+' => 'Assigned', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled' => 'PlannedScheduled', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled+' => 'PlannedScheduled', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled' => 'Planned&Scheduled', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled+' => 'Planned&Scheduled', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved' => 'Approved', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved+' => 'Approved', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved' => 'NotApproved', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved+' => 'NotApproved', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented' => 'Implemented', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented+' => 'Implemented', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved' => 'Not Approved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved+' => 'Not Approved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented' => 'Implementation', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented+' => 'Implementation', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored' => 'Monitored', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored+' => 'Monitored', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Closed' => 'Closed', @@ -1271,12 +1268,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizServiceCall/Attribute:customer_name+' => 'Name of the customer raising this service call', 'Class:bizServiceCall/Attribute:call_status' => 'Status', 'Class:bizServiceCall/Attribute:call_status+' => 'Status of the ticket', - 'Class:bizServiceCall/Attribute:call_status/Value:New' => 'New', - 'Class:bizServiceCall/Attribute:call_status/Value:New+' => 'New', + 'Class:bizServiceCall/Attribute:call_status/Value:New' => 'New (Unassigned)', + 'Class:bizServiceCall/Attribute:call_status/Value:New+' => 'New (Unassigned)', 'Class:bizServiceCall/Attribute:call_status/Value:Assigned' => 'Assigned', 'Class:bizServiceCall/Attribute:call_status/Value:Assigned+' => 'Assigned', - 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress' => 'WorkInProgress', - 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress+' => 'WorkInProgress', + 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress' => 'Work In Progress', + 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress+' => 'Work In Progress', 'Class:bizServiceCall/Attribute:call_status/Value:Resolved' => 'Resolved', 'Class:bizServiceCall/Attribute:call_status/Value:Resolved+' => 'Resolved', 'Class:bizServiceCall/Attribute:call_status/Value:Closed' => 'Closed', diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index ac02ed2097..881d553ded 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -5,7 +5,6 @@ ////////////////////////////////////////////////////////////////////// // - // // Class: menuNode // From 0714a08359237b08794cf8cda13ed4ba5512599c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 19 Apr 2010 14:27:06 +0000 Subject: [PATCH 257/970] Localization... really finalized the metamodel (description on object states) SVN:trunk[340] --- dictionaries/dictionary.itop.model.php | 40 +++++++++++++------------- dictionaries/dictionary.itop.ui.php | 1 + 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index 8c6a8be325..da64da8a95 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -655,15 +655,15 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizIncidentTicket/Attribute:ticket_status' => 'Status', 'Class:bizIncidentTicket/Attribute:ticket_status+' => 'Status of the ticket', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New' => 'New (Unassigned)', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New+' => 'New (Unassigned)', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:New+' => 'Newly created ticket', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned' => 'Assigned', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned+' => 'Assigned', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Assigned+' => 'Ticket is assigned to somebody', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress' => 'Work In Progress', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress+' => 'Work In Progress', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:WorkInProgress+' => 'Work is in progress', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved' => 'Resolved', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved+' => 'Resolved', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Resolved+' => 'Ticket is resolved', 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Closed' => 'Closed', - 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Closed+' => 'Closed', + 'Class:bizIncidentTicket/Attribute:ticket_status/Value:Closed+' => 'Ticket is closed', 'Class:bizIncidentTicket/Attribute:initial_situation' => 'Initial Situation', 'Class:bizIncidentTicket/Attribute:initial_situation+' => 'Initial situation of the Incident', 'Class:bizIncidentTicket/Attribute:current_situation' => 'Current Situation', @@ -1029,25 +1029,25 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizChangeTicket/Attribute:ticket_status' => 'Status', 'Class:bizChangeTicket/Attribute:ticket_status+' => 'Status of the ticket', 'Class:bizChangeTicket/Attribute:ticket_status/Value:New' => 'New (Unassigned)', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:New+' => 'New (Unassigned)', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:New+' => 'Newly created ticket', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated' => 'Validated', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated+' => 'Validated', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Validated+' => 'Ticket is validated', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected' => 'Rejected', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected+' => 'Rejected', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Rejected+' => 'This ticket is not approved', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned' => 'Assigned', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned+' => 'Assigned', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Assigned+' => 'Ticket is assigned', 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled' => 'Planned&Scheduled', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled+' => 'Planned&Scheduled', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:PlannedScheduled+' => 'Evaluation is done for this change', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved' => 'Approved', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved+' => 'Approved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Approved+' => 'Ticket is approved by CAB', 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved' => 'Not Approved', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved+' => 'Not Approved', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:NotApproved+' => 'Ticket has not been approved by CAB', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented' => 'Implementation', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented+' => 'Implementation', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Implemented+' => 'Work is in progress for this ticket', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored' => 'Monitored', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored+' => 'Monitored', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Monitored+' => 'Change performed is now monitored', 'Class:bizChangeTicket/Attribute:ticket_status/Value:Closed' => 'Closed', - 'Class:bizChangeTicket/Attribute:ticket_status/Value:Closed+' => 'Closed', + 'Class:bizChangeTicket/Attribute:ticket_status/Value:Closed+' => 'Ticket is closed', 'Class:bizChangeTicket/Attribute:creation_date' => 'Creation Date', 'Class:bizChangeTicket/Attribute:creation_date+' => 'Change creation date', 'Class:bizChangeTicket/Attribute:last_update' => 'Last Update', @@ -1269,15 +1269,15 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizServiceCall/Attribute:call_status' => 'Status', 'Class:bizServiceCall/Attribute:call_status+' => 'Status of the ticket', 'Class:bizServiceCall/Attribute:call_status/Value:New' => 'New (Unassigned)', - 'Class:bizServiceCall/Attribute:call_status/Value:New+' => 'New (Unassigned)', + 'Class:bizServiceCall/Attribute:call_status/Value:New+' => 'Newly created call', 'Class:bizServiceCall/Attribute:call_status/Value:Assigned' => 'Assigned', - 'Class:bizServiceCall/Attribute:call_status/Value:Assigned+' => 'Assigned', + 'Class:bizServiceCall/Attribute:call_status/Value:Assigned+' => 'Call is assigned to somebody', 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress' => 'Work In Progress', - 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress+' => 'Work In Progress', + 'Class:bizServiceCall/Attribute:call_status/Value:WorkInProgress+' => 'Work is in progress', 'Class:bizServiceCall/Attribute:call_status/Value:Resolved' => 'Resolved', - 'Class:bizServiceCall/Attribute:call_status/Value:Resolved+' => 'Resolved', + 'Class:bizServiceCall/Attribute:call_status/Value:Resolved+' => 'Call is resolved', 'Class:bizServiceCall/Attribute:call_status/Value:Closed' => 'Closed', - 'Class:bizServiceCall/Attribute:call_status/Value:Closed+' => 'Closed', + 'Class:bizServiceCall/Attribute:call_status/Value:Closed+' => 'Call is closed', 'Class:bizServiceCall/Attribute:call_description' => 'Description', 'Class:bizServiceCall/Attribute:call_description+' => 'Description of the call as describe by caller', 'Class:bizServiceCall/Attribute:creation_date' => 'Creation date', diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 881d553ded..820bbad9f0 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -262,4 +262,5 @@ Dict::Add('EN US', 'English', 'English', array( )); + ?> From a65a4f9c40322c6e6254f07c72ba1e35a985cae5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 08:32:47 +0000 Subject: [PATCH 258/970] - Added jquery blockUI, useful when performing asynchronous (ajax) calls. SVN:trunk[341] --- application/itopwebpage.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 53b861809c..48eb8d700e 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -35,6 +35,7 @@ class iTopWebPage extends NiceWebPage $this->add_linked_script("../js/date.js"); $this->add_linked_script("../js/jquery.date.picker.js"); $this->add_linked_script("../js/jquery.tablesorter.min.js"); + $this->add_linked_script("../js/jquery.blockUI.js"); $this->add_linked_script("../js/utils.js"); //$this->add_linked_script("../js/jquery-ui-personalized-1.5.3.js"); $this->add_linked_script("../js/swfobject.js"); From a693d3f2abeff392493064412a291491a46b1ca5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 08:33:43 +0000 Subject: [PATCH 259/970] - new icons for the CSV import SVN:trunk[342] --- images/added.png | Bin 0 -> 463 bytes images/csv.png | Bin 0 -> 7048 bytes images/error.png | Bin 0 -> 543 bytes images/modified.png | Bin 0 -> 660 bytes images/unchanged.png | Bin 0 -> 688 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/added.png create mode 100644 images/csv.png create mode 100644 images/error.png create mode 100644 images/modified.png create mode 100644 images/unchanged.png diff --git a/images/added.png b/images/added.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb5042f2be71e2fbc5bc1beef88d79df0003c0b GIT binary patch literal 463 zcmV;=0WkiFP)hG98X{hTw1Nz|2if|P_3OO^-P3|u|S*iLP7I%Yp0e29LRxeuHV1P zn;#QK6OoeHd-rLc&b*ljIXB$lpriXd!O%<7@x`002ovPDHLk FV1m0e&c6Tv literal 0 HcmV?d00001 diff --git a/images/csv.png b/images/csv.png new file mode 100644 index 0000000000000000000000000000000000000000..0be1bcad0529e41fc86350d3da8f7084d525aaef GIT binary patch literal 7048 zcmWkz2Rs~I5Z^1@>AeN#h#;bOae9xaLG&)sqC_v@bU~01Bt$QX8bphBL>E1Y&c$gV zI??&=d%Lr{@^bN?X=WY@EYIFlv~2^ipW#L)Eoc!)c*of;>ilY--P*SYOBI#p)@oEOrz}a zM*zSK*Hlq744B=v4D_Krd2w_3L|5~k^Xo6vUG!jEB%G5JMQ-2s3im_%gH*S;de&ON zOXibz>d%teC44PpG*hus64ESv&Th|982-laH0(6Lnv@!xofBhYPnoWOB=V{9d)y+U zelz`UX6gG#h2P`4jIrIeKpD~hx7^W_^UF|ClElV+iN)Tz0A>%|wy|3`=EcD2_CseH zH4~j%ptxQ!9rO{E!dT`TH2{nT*%-W_j%*tWNLbhm1hO|DX%K=q_l5z!9}XoNLgYc4 zamesdFx-HKnS$uM7d3`6!X1`Ef`p-cp}IngZW@T1Ix#D~FLJ<3998Qo6v*FqI42=15YNTK!VY;S2W_Ree$IVrMl}a&5F8?;V)3N= zS@E_?(1W1~e`x*{JfCYnGv4vIChj?Fe)50=`R&l-sW}8~4H3XdFF#{)lAHk;klWDl z%a3lnPt)t@)*BLIBu3XO6vJuvWofwHsc-}a^SlJVk1E@q*e2vT9D!gNgoXQ<V>5w85W94aGdBknTCpVrSez_QJPIIoYCwRppV-D&gk0l+%_( zUB==DpXdKx{E1Y4%1YBLtDKhq(&34*eZgMb1?jL_fZNx7K3LrXSSyE>tDFhUCQHH2 zd-9;qVK#`)SWrKOBLyMt!%bH35!ZGk^KSVZ*U3%?9dip@jsCnctMHwauUP`FPpz+A zZWylH=TfhgVe!Q*IdPA&Y^*@zJP#EUB>yCFN_0OxJV@koR}nvfp8^1dBe~SrLRhy_ zMPJ-^r!nHm!!^e+tEWL<=U2sz4-heOxyH{pO~db=08#AunSvWjs9~upzxsy`rX_wi z)SdqryhuPX|EFTkZ%ToB`<>0!Px&=z})Q(J%EE zdCLc4p7%ZD@t==r@wlPARn->D|J0gb2nGV!j$fej(ahwoa)6EBfQ2RayWh{jL%z|1 z;+6qDGI^I{M;=fBs5Q3-yXxLlJr8BGq0`K(TAi$VPc*ggMb!Qail8v}I1(r(XK26r zRLX&Q+-RXCfH2?FH3^wBX#Sg4ajI3wCfpp14~z7RCfr zrX7=gH?h&l982kf!KF8CmlBJv^6^&I6kID(HATzrmySQ5cE(LCW~_xk)`LfEcs2+D zl-_aR*@VEcB_`~{1?&C!?E(VohXR@Lu}`o0pU^*s06Sl=tXNzYlHEPHfxJ(3pcBJ+ z7|`EHZ}Ys<@C#W3ue)h`J@7XA(duf+$axwUW#I`B0S?P_TcbP;^viU60*{b5B{c#G z-8z<1{^V)qZk_JH^z4I;nHSw!+VJbq?Vr8YwsU`$aJX>eCJ3Zy+5OwjVKwf!t|PUv zCTrV7%K1~P#cDQUvEyuAZ+3Vs7 z-BkDXwP*sM@(x&gE2D2=8Q$gW_<3y|Elj0N0T?mbFcwGOdn2z##}Oy7;nUpca6o<5 zs^edeHR;R!mLI_}-13;bFsw;;(eJ@;vB1SAwf7E~D|-(VTfJ;$7Qcp2dRWTEWi7h% zl6pGjcVSq_z+?OJKX;b)C>7x03UI!&f1X?(9Jc(H)rhI`C8jbr`vRYxvEm%~!T06Q zciQLtR35Dmy;i{Vp5(;L&~ECH7}T_RG{ID*?*Qn&_3FO%WWVLY#X~Gq$7|u$hhwK7 zUJKeE$V`GRtov66>!h$mhP2@n54X z&gid?k>Q0wu3pAGk5`@rUA=tB&TOV}}zMKQyY1fmJyT)kU#?j$(zOo^?d`1S! z*USp#9aeGuOm@z#7~8pvtbMlKP?A`)lfwF&!ngz72Z-HIO%zL3=MZU!v2DZ05Tk?W zq0Sk@vkdZ+TrmovfK@I=lE}-s1k4_^yQ1vG+0^*wg1M-(@eiE55KifWJz0Q$YaIX-bNd399U$uX?&gU$cLuz&3p3~K}=XBrdj+}zOqo|-Lvc9 zEo{u>(+_)E;u_SACaX*;V$KnG2fQ7*D-~R5I<-BT+XUA!rvmbpmJ&)vVt9Xf zDwd_+_gy&jlE%1&LsM^aKy;)8H`u%7=O%omaNZ`rf!&r}u9Vu#l)(1;+S zk=R>{p=jZXSqX?i^bb%9Jt34RWlcx9@(7CxzyE+5d+iB}h3)NngtGBZ>9}%Pw6$9m zpV)}WF<}odsumm4f?G7@_|SY>-j065!cf7*{J@)gnPl>@jnVPcihXbdB0KNC+sH}w z+w~6P!{nx|hG4qol$V)$mIev6w{#=s{fHp#dV<;)9 z2)G7$Sj?uwdl@^DI>|4wdxs*g?611&-{x8!u@+hg zKCXM|!zM2sMU6MDh8pU!e%rSl9OVDHG7<6;#`itv4?J}lwm%)r_UK||gbI4%*;>Vi z_G*7ZZUHChD`5kov->}ch(IoZ#+1msY4V6t)5*s7g#|42ioQ44_(?IId&*PgOb#? zD)HV$Z1BDT?r>v&VeIO|iHF#|v|gWLzE$Tx$A*f%}Bj1OcfPSqec%~wA!|-Y?SU!$Z75lH{IYl4m9Faj zR~f6Pp02H52HZR$+2J$Kq}o}^q|CcjEIPfI5T0PiWMZS9GX#Q1>>w$e$W9fc#8yoH zn&{VoTQRss?T*)~tkPlVnD)G~!&qsyYo9ZU)5%qNPM=pfSjt5=S+t}%MdE$K>-A5$ z15wEPD(t4}0PjVEhNDe5>ys=~+F*?pIPz7gw*!HG895_ivS1)fabR)4)zL3K=h9kx6^ZvHEkGE^zJf_|0$gG5Nrvhx;(fP30c41#cccIf@3=(8^V*{*5r)u zad<@t+FXu1D*K|H1OkgRWxtm81=GL;FDVwko^qM6GoAq(a3X7o=A{}%Kn-_x|3mFV zW71ETis`96x(F6>tJBDT*R(-CbojaUf6E=}yf(ZAIf!LAIy0Y^Bd+~pwR7%6r#{>@LbI?#_MB4N1 z3o#SP0iG#xpw@K8*r_|c$_;qdhO-UtA0rGk zgUM~;ic8^_hM-(h4kq7+-V@W6khRW8nDC;V$54jgXcearBWb~YH_^14#E)OUy6o!J zQt}%bB>0$;g|4pu(uUaCEw3Lda__y}{1tWL;pBuVP)(Sq4O3QD{&y#we|VUfHShUg zS{#af@yV^#rsDD5A)nn`Bh5TB7;^l<7Zcs*X;5sST=~f;FT2yfzcaPuSfBfy%$RHQ z83?7)n@z>UVG!VnN~X>rQL($`sSKNka5l1tpYHEX54Lzw2=yufO9QrL=4RU z=l}s$*$nc5Ru+OIIKn-_TqcA*onfD*bKqo&c7Vgxd6fO%D$5qJo=g+m!N>=h8oQqO zDEBS=S=}f^tlS6v$;c??PUe0@ZsLkdtE4A%icJ`S$wMI*u%+c;$eAcn0V3MDztY|g ztTsyeRM5MiuhE^O4%N02DHHi`V%PP}%!nzegVL=>-h-1i}(PiVgQZR7SxfmA3^F*(GBy=#dl`&#$V|K~Q-1vHBM+95cUVa0;)6@wM@VGkTbQ zY^;1>NL}#olPqO+>YN z+G~_rMTe^ohf^k{%10UjLBXjfF7Zq0j!|351pQ8&ar9tnJP8eA2F5`Jpf`#$zI-cV z30pyE*Gk=VSf1od$(>R|_ydNx!uk{w;769-FpQcig0uR8B^$R+2nVN-3T!W^?Y*Sc z_B9c@E%+pf;l%69#cqdn>NHm>QHG%PwM}OP!D#$(m?Rp3XW!7*v`Q|r0Q=s>RsKF8 z!Mrw_E0_#J7LI|IS$}GUl_E0ObmtkMAQ}Ii?t|4K``QqGUC)g%cGMmQA{-0Jp&0Er z9I^e($9-)Zg94zDk&zqQ+pJd2&sPNj(u7B7>M*;1;Fwr69!b1D=s%OCPhZuK*SF{N z)9e;yupFS0$13tPs0kE83*N5>LTBxViM-O(~h=0r6?UtKX)Q8L*>EYK!f|jdDe@Ie;){hG_(=IJEiha zcm(=DGgTqwe5&wO125yNt;fLNA^R2?Ar%~oJr1D7OwtV1ir7jeMb5aXDJrr*_x9F% z^e9pRiHyAYW$v*Zw)*z*rpg$s+Zmoe!gd!Hw^fq$2>rmgzr9A|OHB%%8VH=^aw+iU z5g9`XqH)r2j#Cx}5tCAHFrL&>GBQf1tQV@<*#x(yIg2Fug%g5c?BZHKU*Xz@6BMJ=eDV`xXU1%)HwgY)A<*k zJsM?fqleQi{(|ZJ<|X}`e*&GCrN}#ND6Og~+)t3_7Z<`}Vo|Y(AdN#w>7>aoenDTo zaB3$pWXx87x3;#Z6n7u@Ts75Qr)>eEPrmo_-b+%wm?9>~S4o16dc&bgrY(rO&U?pl z7Z(?>oWQkr40CgHGIty0KSDygpf-u^FaHDx*nIicXwNi5LqY^q99&bTL(jM+5rrI! ziHWVuHF@E5&CJYZRk0VJns&7ekF{RHK7NpqtbjCGpnY!F34s#dLR-tOpPJJ14dJb! zg03cz(uU`b_FW%jbX=-hS}qU_9;AXppO*oODRpLgk|{;wgZ3cH(Vu;^;8RZ`MtZaq zEiJtonr-;_;wm(BuHjkr&O%G$yV}c8;*gHlSY~>#$j=6Qx-d14&VF_zAJ~h9!C=?K zI8;Gxf>7=Kzul#q^R?lun~LB6{+Q*85?>ar05fJ?s^&?E3uRs9=!LL2Xaw?qFQG=0 zGxyG}LRqAruV6~FvlbIMj-ao;gedz0xuc~U6mcc6L{(OW+G_sR#_ z4M*+QD%Y#2lm(1a@^4!2HM@TOCGUv^5FvY=ap%^R>#?=|6z8z>@yW?ZXCouw@UZL4 zvtBZ02{(HFj$Gc8B7~fTDVF;VR7^smpsI?zROf?|xw*MWQLAqA&u$ zqlLA#B3wh(Z9&IcEPZ4jqY%C)?i1JET;lf?ZY|bjXn>_A?h_C>oIM5v_psgW<`Rri zL#uCKo*UZgNh~uR|Bpy2X{&Vp8mTtkm`lZOBT$U@ZB^oRAD=9yu(EW>(n}4o(pk$A zp-69JwEERp9)dH_me!1f#n!V`KD zySkLL%TLZi{^H>s%AX!&CTtDP)h0L{po-|#V`6M$A*EPAfGKu1SM7`AymXU^wz?>B|` zie+VHZf(;$6f63DDNSun5oBxu6WYNR9UX1b8c-7fEa022{MAu%R#w)W7^YM;CoM9U z77P{h0TyCa?Y_^ynpc-Sl6g-^xt)qQSwB3(?gwB!r4+iK-p2D@l~vm;p8LKs%+AhM zmCs+N#B^n(cQA;fpc?lTB$8l^+9p&>zu5b35*P$?%jvW+<*$FR{Dqp-hMRQl zVQ-K%@C8~AQCQ;coV zjz#O$zqyN?pv_N~dVZTu;iIL@muY{18H24YP?e>$1~vTx3tE*(%=q8!|yUTC8_u3 zDW?EwiAbc`8D*O8=7Rx*Y&`evI$Q5-hS8S{;(S&W$PshZnhj$lT1*qV_cAY-$E#1b zCs;03kY`aOg@Fj!M#IHp)ub5-*w+&oUenNOBHN0Usr^NGCv{_BXeoIvBd z%6B~Ql;reflQT!eGyIYfB Xgk?OIl*t9ZOa^GG>ZnvH*+l#Y*s(qS literal 0 HcmV?d00001 diff --git a/images/error.png b/images/error.png new file mode 100644 index 0000000000000000000000000000000000000000..dbfda229750f238e5ea15ea9c6de43b9630a5e57 GIT binary patch literal 543 zcmV+)0^t3LP)b-Q zag`tu6obp!L~)B0F04hs!e-fWL9np!FIZTLT3Lt|f`$77+}0w6){1Cgk(Djfu;K@q znfLacmk$ORL`dN9PR=>^<=&eap|wUkpV-OjRHUJl8gxYMiagMXAt_}gDkq~vtrP;U zc9@+hfgq>$t_wJl4*aF=-Vgb%v1t(G)S||f5x?FkocMZf?mH@HmlovI=)HLX+f=@H zJW%)wxc{)Sd1^sUjo#}~WT*kxgpDQf_ z=WBp>izV$c&8R?krb){CiP!g!m=3GV{oeqv0>Jz|F2%SjxZ@ zxGsL|j9QS_wUo`+tf^tGR7!93O%@RMewtR3tzslx+}~C$$Xl0~4703ZtvFdN=!tK= zjZKRB$-`mmjcxlz2)L}~=IZ2h?+;>uuTkE-HHgciWXH=58>$m+)aUP_=fNurq~c9Jfr+{g4OA6VysNJCVr!>1 zyO(ukr-@eVVQOd_eeEr`d;&*7#Zd36d)>ub=%lXvJGs$=On&Pn97vP9zKEPnrBRKfD#EKdHd|_ZeufOm#a)m@NAF&m3;5bVE$Q(zLZS1JKPk-x02D-XgTe_RX z#5m7GmzbE!!E}bj^$Yyl#_Q*HW-gz-5F#kVUOZxW=<7_hAg}KDjh>#B%CwB&uQ)_iwz;@6pmo1X^%*Tz443SFZSDVxCn000035|9caTI*Luw5zW52e|CA8yDT^%Dun9m3t}Dh#;a7R}xz(7P0BdmY8wWOe8az zjOV?X385+aFv_h)?cgw>ul0Q_kA*6r0xm#^e>-Jl={$nWTXw>h=C`NKCn0 zo)E)7R@vl;zt>gtjIxoLVWrpeB{Up{iG~zKk(BFp$@4q~v0%W$U@gfA5Hylz zaSX*$uIrMn>jVZYT3Eus!Z475$OH&*7>3dahN`NPq9_vX^K8I?g+U$D_Wi#}sY4ks z7!0HlKuyyaR^>Bh)9rR?&cPh1KDkC@4}t+3`7&b)qcvoOiK=R{3d1lbcaItD@;#Ff z7H1w;fnLYu8XRm$z%(tENtR{O>}(#pAr}k=Oj3}4OWZSl^1|{e7Im%=*az4$sZ+`O@K4<1J%S#PB z)<-tczwUjh@lFU^*jk}bAi)M0*v@t>sO0R{lf WyFd!DFBVq-0000OA=X literal 0 HcmV?d00001 From 9854842436a58a3621e32373314176fcf154051f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 08:35:10 +0000 Subject: [PATCH 260/970] - new styles for the CSV import SVN:trunk[343] --- css/light-grey.css | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/css/light-grey.css b/css/light-grey.css index 3a768a5d74..e62f61fac9 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -692,3 +692,40 @@ table.listResults.truncated { border-bottom: 0; padding-bottom: 0; } + +tr.csv_row0 td { + padding-top:5px; + padding-bottom:5px; + padding-left:10px; + padding-right:10px; + background: #fff; + border-left: #000 1px solid; + +} +tr.csv_row1 td { + padding-top:5px; + padding-bottom:5px; + padding-left:10px; + padding-right:10px; + background: #f9f9f1; + border-left: #000 1px solid; + +} +tr.csv_row1 th, tr.csv_row0 th { + padding-top:5px; + padding-bottom:5px; + padding-left:10px; + padding-right:10px; + border-left: #000 1px solid; + +} + +td.cell_modified { + font-weight: bold; + color: #000; +} + +td.cell_error { + font-weight: bold; + color: #D81515; +} From 6ebe6da4ebd9e4320a9a8e730401838f162ddaa6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 08:43:24 +0000 Subject: [PATCH 261/970] - Static functions must be duplicated since they are not inherited... SVN:trunk[344] --- core/cmdbobject.class.inc.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 498e7e73d4..70f24506be 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -488,6 +488,24 @@ class CMDBObjectSet extends DBObjectSet return $oRetSet; } + static public function FromArrayAssoc($aClasses, $aObjects) + { + // In a perfect world, we should create a complete tree of DBObjectSearch, + // but as we lack most of the information related to the objects, + // let's create one search definition + $sClass = reset($aClasses); + $sAlias = key($aClasses); + $oFilter = new CMDBSearchFilter($sClass, $sAlias); + + $oRetSet = new CMDBObjectSet($oFilter); + $oRetSet->m_bLoaded = true; // no DB load + + foreach($aObjects as $rowIndex => $aObjectsByClassAlias) + { + $oRetSet->AddObjectExtended($aObjectsByClassAlias); + } + return $oRetSet; + } } /** From 7b72cb8ee2b1e4f9d103e4634f5729bfddd8f9d4 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 08:44:55 +0000 Subject: [PATCH 262/970] - bug fix... SVN:trunk[345] --- core/dbobjectset.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index b87e04b16a..a8ae635d93 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -91,8 +91,8 @@ class DBObjectSet // In a perfect world, we should create a complete tree of DBObjectSearch, // but as we lack most of the information related to the objects, // let's create one search definition - $sClass = reset($this->m_aClasses); - $sAlias = key($this->m_aClasses); + $sClass = reset($aClasses); + $sAlias = key($aClasses); $oFilter = new CMDBSearchFilter($sClass, $sAlias); $oRetSet = new self($oFilter); From 23ee727fc5008f3c45d6bd6b0b11f5e5bbd9e277 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 08:46:03 +0000 Subject: [PATCH 263/970] - bug fix: external keys must be set - in case the object has to be created SVN:trunk[346] --- core/bulkchange.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 696d5dad68..c063a9bd1a 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -252,7 +252,7 @@ class BulkChange foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig) { // Skip external keys used for the reconciliation process - if (!array_key_exists($sAttCode, $this->m_aAttList)) continue; + // if (!array_key_exists($sAttCode, $this->m_aAttList)) continue; $oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode); $oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass()); From 3f9a9424e9932158abc98968a5cf874c59d71cde Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 09:16:54 +0000 Subject: [PATCH 264/970] - New version of the CSV import wizard. First implementation... SVN:trunk[347] --- pages/ajax.csvimport.php | 230 ++++++ pages/csvimport.php | 1592 ++++++++++++++++++++++---------------- 2 files changed, 1144 insertions(+), 678 deletions(-) create mode 100644 pages/ajax.csvimport.php diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php new file mode 100644 index 0000000000..05851feebd --- /dev/null +++ b/pages/ajax.csvimport.php @@ -0,0 +1,230 @@ + '-- select one --'); + $aChoices[':none:'] = '------ n/a ------'; + $aChoices['id'] = 'id (Primary Key)'; + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aChoices[$sAttCode] = $oAttDef->GetLabel(); + // Get fields of the external class that are considered as reconciliation keys + $sTargetClass = $oAttDef->GetTargetClass(); + foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef) + { + if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode)) + { + $aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); + } + } + } + else if ($oAttDef->IsWritable() && ($sAttCode != 'finalclass')) // finalclass should not be considered as 'writable' isn't it ? + { + $aChoices[$sAttCode] = $oAttDef->GetLabel(); + } + } + asort($aChoices); + + $sHtml = "\n"; + return $sHtml; +} + +require_once('../application/startup.inc.php'); +session_start(); +if (isset($_SESSION['auth_user'])) +{ + $sAuthUser = $_SESSION['auth_user']; + $sAuthPwd = $_SESSION['auth_pwd']; + // Attempt to login, fails silently + UserRights::Login($sAuthUser, $sAuthPwd); +} +else +{ + // No session information + echo "

      No session information

      \n"; +} + + +$oContext = new UserContext(); +$sOperation = utils::ReadParam('operation', ''); + +switch($sOperation) +{ + case 'parser_preview': + $oPage = new ajax_page(""); + $oPage->no_cache(); + $sSeparator = utils::ReadParam('separator', ','); + if ($sSeparator == 'tab') $sSeparator = "\t"; + $sTextQualifier = utils::ReadParam('qualifier', '"'); + $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0); + $bFirstLineAsHeader = utils::ReadParam('header_line', true); + $sData = stripslashes(utils::ReadParam('csvdata', true)); + $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier); + $aData = $oCSVParser->ToArray($iLinesToSkip); + $iTarget = count($aData); + if ($iTarget == 0) + { + $oPage->p("Empty data set..., please provide some data!"); + } + else + { + $sMaxLen = (strlen(''.$iTarget) < 3) ? 3 : strlen(''.$iTarget); // Pad line numbers to the appropriate number of chars, but at least 3 + $sFormat = '%0'.$sMaxLen.'d'; + $oPage->p("

      Data Preview

      \n"); + $oPage->p("
      \n"); + $oPage->add(""); + $iMaxIndex= 10; // Display maximum 10 lines for the preview + $index = 1; + foreach($aData as $aRow) + { + $sCSSClass = 'csv_row'.($index % 2); + if ( ($bFirstLineAsHeader) && ($index == 1)) + { + $oPage->add("\n"); + $iNbCols = count($aRow); + + } + else + { + if ($index == 1) $iNbCols = count($aRow); + $oPage->add("\n"); + } + $index++; + if ($index > $iMaxIndex) break; + } + $oPage->add("
      ".sprintf($sFormat, $index).""); + $oPage->add(implode('', $aRow)); + $oPage->add("
      ".sprintf($sFormat, $index).""); + $oPage->add(implode('', $aRow)); + $oPage->add("
      \n"); + $oPage->add("
      \n"); + if($iNbCols == 1) + { + $oPage->p(' Error: The data contains only one column. Did you select the appropriate separator character ?'); + } + else + { + $oPage->p(' '); + } + } + break; + + case 'display_mapping_form': + $oPage = new ajax_page(""); + $oPage->no_cache(); + $sSeparator = utils::ReadParam('separator', ','); + $sTextQualifier = utils::ReadParam('qualifier', '"'); + $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0); + $bFirstLineAsHeader = utils::ReadParam('header_line', true); + $sData = stripslashes(utils::ReadParam('csvdata', true)); + $sClassName = utils::ReadParam('class_name', ''); + + $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier); + $aData = $oCSVParser->ToArray($iLinesToSkip); + $iTarget = count($aData); + if ($iTarget == 0) + { + $oPage->p("Empty data set..., please provide some data!"); + } + else + { + $oPage->add(""); + $index = 1; + $aFirstLine = $aData[0]; // Use the first row to determine the number of columns + $iStartLine = 0; + $iNbColumns = count($aFirstLine); + if ($bFirstLineAsHeader) + { $iStartLine = 1; + foreach($aFirstLine as $sField) + { + $aHeader[] = $sField; + } + } + else + { + // Build some conventional name for the fields: field1...fieldn + $index= 1; + foreach($aFirstLine as $sField) + { + $aHeader[] = 'Field'+$index; + $index++; + } + } + $oPage->add("
      \n"); + $oPage->add(''); + $oPage->add(""); + $oPage->add(''); + foreach($aHeader as $sField) + { + $oPage->add(''); + $oPage->add(""); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $index++; + } + $oPage->add("
      FieldsMapping Search ?Data line 1Data line 2
      $sField'.GetMappingForField($sClassName, $sField, $index).' '.(isset($aData[$iStartLine][$index]) ? htmlentities($aData[$iStartLine][$index-1]) : ' ').''.(isset($aData[$iStartLine+1][$index]) ? htmlentities($aData[$iStartLine+1][$index-1]) : ' ').'
      \n"); + } + break; + + case 'get_csv_template': + $sClassName = utils::ReadParam('class_name'); + $oSearch = new DBObjectSearch($sClassName); + $oSearch->AddCondition('id', 0); // Make sure we create an empty set + $oSet = new CMDBObjectSet($oSearch); + $sCSV = cmdbAbstractObject::GetSetAsCSV($oSet); + $aCSV = explode("\n", $sCSV); + // If there are more than one line, let's assume that the first line is a comment and skip it. + if (count($aCSV) > 1) + { + $sResult = $aCSV[1]; + } + else + { + $sResult = $sCSV; + } + + $sClassDisplayName = MetaModel::GetName($sClassName); + $sDisposition = utils::ReadParam('disposition', 'inline'); + if ($sDisposition == 'attachment') + { + $oPage = new CSVPage(""); + $oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\""); + $oPage->no_cache(); + $oPage->add($sResult); + } + else + { + $oPage = new ajax_page(""); + $oPage->no_cache(); + $oPage->add('


      '.$sClassDisplayName.'.csv

      '); + $oPage->add('

      '); + } + break; +} +$oPage->output(); +?> diff --git a/pages/csvimport.php b/pages/csvimport.php index 7fb11789bb..20af65f81c 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -1,678 +1,914 @@ - '); - -/////////////////////////////////////////////////////////////////////////////// -// External key/field naming conventions (sharing the naming space with std attributes -/////////////////////////////////////////////////////////////////////////////// - -function IsExtKeyField($sColDesc) -{ - return ($iPos = strpos($sColDesc, EXTKEY_SEP)); -} - -function GetExtKeyFieldCodes($sColDesc) -{ - $iPos = strpos($sColDesc, EXTKEY_SEP); - return array( - substr($sColDesc, 0, $iPos), - substr($sColDesc, $iPos + strlen(EXTKEY_SEP)) - ); -} - -function MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode) -{ - $oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sExtKeyAttCode); - if ($sForeignAttCode == 'id') - { - $sForeignAttLabel = 'id'; - } - else - { - $oForeignAtt = MetaModel::GetAttributeDef($oExtKeyAtt->GetTargetClass(), $sForeignAttCode); - $sForeignAttLabel = $oForeignAtt->GetLabel(); - } - - return $oExtKeyAtt->GetLabel().EXTKEY_LABELSEP.$sForeignAttLabel; -} - -function MakeExtFieldSelectValue($sAttCode, $sExtAttCode) -{ - return $sAttCode.EXTKEY_SEP.$sExtAttCode; -} - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// - - -function ShowTableForm($oPage, $oCSVParser, $sClass) -{ - $aData = $oCSVParser->ToArray(1, null, 3); - $aColToRow = array(); - foreach($aData as $aRow) - { - foreach ($aRow as $sFieldId=>$sValue) - { - $aColToRow[$sFieldId][] = $sValue; - } - } - - $aFields = array(); - foreach($oCSVParser->ListFields() as $iFieldIndex=>$sFieldName) - { - $sFieldName = trim($sFieldName); - - $aOptions = array(); - $aOptions['id'] = array( - 'LabelHtml' => "Private key", - 'LabelRef' => "Private key", - 'IsReconcKey' => false, - 'Tip' => '', - ); - - $sFoundAttCode = ""; // quick and dirty way to remind if a match has been found and suggest a reconciliation key if possible - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAtt) - { - if ($oAtt->IsExternalField()) continue; - - $bIsThatField = (strcasecmp($sFieldName, $oAtt->GetLabel()) == 0); - $sFoundAttCode = (MetaModel::IsValidFilterCode($sClass, $sAttCode) && $bIsThatField) ? $sAttCode : $sFoundAttCode; - - if ($oAtt->IsExternalKey()) - { - // An external key might be loaded by - // the pkey or a reconciliation key - // - $aOptions[MakeExtFieldSelectValue($sAttCode, 'id')] = array( - 'LabelHtml' => "".$oAtt->GetLabel()." (id)", - 'LabelRef' => $oAtt->GetLabel(), - 'IsReconcKey' => MetaModel::IsReconcKey($sClass, $sAttCode), - 'Tip' => '', - ); - - $sRemoteClass = $oAtt->GetTargetClass(); - foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sExtAttCode) - { - $sValue = MakeExtFieldSelectValue($sAttCode, $sExtAttCode); - - // Create two entries: - // - generic syntax (ext key label -> remote field label) - // - if an ext field exists that corresponds to it, allow its label - $sLabel1 = MakeExtFieldLabel($sClass, $sAttCode, $sExtAttCode); - - $bFoundTwin = false; - foreach (MetaModel::GetExternalFields($sClass, $sAttCode) as $oExtFieldAtt) - { - if ($oExtFieldAtt->GetExtAttCode() == $sExtAttCode) - { - $aOptions[$sValue] = array( - 'LabelHtml' => htmlentities($oExtFieldAtt->GetLabel()), - 'LabelRef' => $oExtFieldAtt->GetLabel(), - 'IsReconcKey' => false, - 'Tip' => "equivalent to '".htmlentities($sLabel1)."'", - ); - $bFoundTwin = true; - $sLabel2 = $oExtFieldAtt->GetLabel(); - break; - } - } - - $aOptions[$sValue] = array( - 'LabelHtml' => htmlentities($sLabel1), - 'LabelRef' => $sLabel1, - 'IsReconcKey' => false, - 'Tip' => $bFoundTwin ? "equivalent to '".htmlentities($sLabel2)."'" : "", - ); - } - } - else - { - $aOptions[$sAttCode] = array( - 'LabelHtml' => htmlentities($oAtt->GetLabel()), - 'LabelRef' => $oAtt->GetLabel(), - 'IsReconcKey' => MetaModel::IsReconcKey($sClass, $sAttCode), - 'Tip' => '', - ); - } - } - - // Find the best match - $iMin = strlen($sFieldName); - $sBestValue = null; - foreach ($aOptions as $sValue => $aData) - { - $iDist = levenshtein(strtolower($sFieldName), strtolower($aData['LabelRef'])); - if (($iDist != -1) && ($iDist < $iMin)) - { - $iMin = $iDist; - $sBestValue = $sValue; - } - } - - $sSelField = ""; - $aFields["field$iFieldIndex"]["label"] = $sSelField; - - $sCHECKED = ($sFieldName == "id" || MetaModel::IsReconcKey($sClass, $sFoundAttCode)) ? " CHECKED" : ""; - $aFields["field$iFieldIndex"]["label"] .= ""; - - if (array_key_exists($iFieldIndex, $aColToRow)) - { - $aFields["field$iFieldIndex"]["value"] = $aColToRow[$iFieldIndex]; - } - else - { - // Houston... - } - } - $oPage->details($aFields); -} - - -function ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, CMDBChange $oChange = null) -{ - // Note: $oChange can be null, in which case the aim is to check what would be done - - // Setup field mapping: sort out between values and other specific columns - // - $aReconcilKeys = array(); - $aAttList = array(); - $aExtKeys = array(); - foreach($aFieldMap as $sFieldId=>$sColDesc) - { - $iFieldId = (int) substr($sFieldId, strlen("field")); - - if (array_key_exists($sFieldId, $aIsReconcKey)) - { - // This column will be used as a reconciliation key - - if (IsExtKeyField($sColDesc)) - { - list($sAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); - } - else - { - $sAttCode = $sColDesc; - } - $aReconcilKeys[$sAttCode] = $iFieldId; - } - - if ($sColDesc == "id") - { - $aAttList['id'] = $iFieldId; - } - elseif ($sColDesc == "__none__") - { - // Skip ! - } - elseif (IsExtKeyField($sColDesc)) - { - // This field is value to search on, to find a value for an external key - list($sExtKeyAttCode, $sExtReconcKeyAttCode) = GetExtKeyFieldCodes($sColDesc); - if ($sExtReconcKeyAttCode == 'id') - { - $aAttList[$sExtKeyAttCode] = $iFieldId; - } - $aExtKeys[$sExtKeyAttCode][$sExtReconcKeyAttCode] = $iFieldId; - } - else - { - // $sColDesc is an attribute code - $aAttList[$sColDesc] = $iFieldId; - } - } - - // Setup result presentation - // - $aDisplayConfig = array(); - $aDisplayConfig["__RECONCILIATION__"] = array("label"=>"Reconciliation", "description"=>""); - $aDisplayConfig["__STATUS__"] = array("label"=>"Import status", "description"=>""); - if (array_key_exists('id', $aAttList)) - { - $sPKeyCol = 'col'.$aAttList['id']; - $aDisplayConfig[$sPKeyCol] = array("label"=>"id", "description"=>""); - } - foreach($aReconcilKeys as $sAttCode => $iCol) - { - if ($sAttCode == 'id') continue; - - $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); - } - foreach($aExtKeys as $sAttCode=>$aKeyConfig) - { - $oExtKeyAtt = MetaModel::GetAttributeDef($sClass, $sAttCode); - $sLabel = $oExtKeyAtt->GetLabel(); - $aDisplayConfig[$sAttCode] = array("label"=>"$sLabel", "description"=>""); - foreach ($aKeyConfig as $sForeignAttCode => $iCol) - { - // The foreign attribute is one of our reconciliation key - - $sLabel = MakeExtFieldLabel($sClass, $sAttCode, $sForeignAttCode); - $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); - } - } - foreach ($aAttList as $sAttCode => $iCol) - { - if ($sAttCode != 'id') - { - $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - $aDisplayConfig["col$iCol"] = array("label"=>"$sLabel", "description"=>""); - } - } - - // Compute the results - // - $aData = $oCSVParser->ToArray(); - - $oBulk = new BulkChange( - $sClass, - $aData, - $aAttList, - array_keys($aReconcilKeys), - $aExtKeys - ); - $aRes = $oBulk->Process($oChange); - $aResultDisp = array(); // to be displayed - foreach($aRes as $iRow => $aRowData) - { - $aRowDisp = array(); - $aRowDisp["__RECONCILIATION__"] = $aRowData["__RECONCILIATION__"]; - $aRowDisp["__STATUS__"] = $aRowData["__STATUS__"]->GetDescription(true); - foreach($aRowData as $sKey => $value) - { - if ($sKey == '__RECONCILIATION__') continue; - if ($sKey == '__STATUS__') continue; - switch (get_class($value)) - { - case 'CellChangeSpec_Unchanged': - $sClass = ''; - break; - case 'CellChangeSpec_Modify': - $sClass = 'csvimport_ok'; - break; - case 'CellChangeSpec_Init': - $sClass = 'csvimport_init'; - break; - case 'CellChangeSpec_Issue': - $sClass = 'csvimport_error'; - break; - - case 'CellChangeSpec_Void': - default: - $sClass = ''; - } - if (empty($sClass)) - { - $aRowDisp[$sKey] = $value->GetDescription(true); - } - else - { - $aRowDisp[$sKey] = "
      ".$value->GetDescription(true)."
      "; - } - } - $aResultDisp[$iRow] = $aRowDisp; - } - $oPage->table($aDisplayConfig, $aResultDisp); -} - -/////////////////////////////////////////////////////////////////////////////// -// Wizard entry points -/////////////////////////////////////////////////////////////////////////////// - -function Do_Welcome($oPage, $sClass) -{ - $sWiztep = "1_welcome"; - $oPage->p("

      Bulk load from CSV data / step 1

      "); - - // Reload values (in case we are reaching this page from the next one - $sCSVData = utils::ReadPostedParam('csvdata'); - $sSep = utils::ReadPostedParam('separator', ','); - $sTQualif = utils::ReadPostedParam('textqualifier', '"'); - - $aSeparators = array(',' => ', (coma)', ';' => ';', ';' => ';', '|' => '|', '#' => '#', '@' => '@', ':' => ':'); - $aTextQualifiers = array('"' => '"', "'" => "'", '`' => '`', '/' => '/'); - - $oPage->add("
      "); - $oPage->MakeClassesSelect("class", $sClass, 50, UR_ACTION_BULK_MODIFY); - $oPage->add("
      "); - $oPage->add(""); - $oPage->add("
      "); - $oPage->add("Separator: "); - $oPage->add_select($aSeparators, 'separator', $sSep, 50); - $oPage->add("
      "); - $oPage->add("Text qualifier: "); - $oPage->add_select($aTextQualifiers, 'textqualifier', $sTQualif, 50); - $oPage->add("
      "); - $oPage->add(""); - $oPage->add("
      \n"); - $oPage->add("
      "); - - // As a help to the end-user, let's display the list of possible fields - // for a class, that can be copied/pasted into the CSV area. - $sCurrentList = ""; - $aHeadersList = array(); - foreach(MetaModel::GetClasses('bizmodel') as $sClassName) - { - $aList = MetaModel::GetZListItems($sClassName, 'details'); - $aHeader = array(); - // $aHeader[] = MetaModel::GetKeyLabel($sClassName); - $aHeader[] = 'id'; // Should be what's coded on the line above... but there is a bug - foreach($aList as $sAttCode) - { - $aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode); - } - - $sAttributes = implode(",", $aHeader); - $aHeadersList[$sClassName] = $sAttributes; - - if($sClassName == $sClass) - { - // this class is currently selected - $sCurrentList = $sAttributes; - } - } - // Store all the values in a variable client-side - $aScript = array(); - foreach($aHeadersList as $sClassName => $sAttributes) - { - $aScript[] = "'$sClassName':'$sAttributes'"; - } - $oPage->add("\n"); - - $oPage->add_ready_script("$('#select_class').change( function() {DisplayFields(this.value);} );"); - $oPage->add("
      "); - $oPage->add("Fields for this object
      "); - -} - -function Do_Format($oPage, $sClass) -{ - $oPage->p("

      Bulk load from CSV data / step 2

      "); - $sWiztep = "2_format"; - - $sCSVData = utils::ReadPostedParam('csvdata'); - $sSep = utils::ReadPostedParam('separator'); - $sTQualif = utils::ReadPostedParam('textqualifier'); - $oCSVParser = new CSVParser($sCSVData, $sSep, $sTQualif); - $iSkip = 1; - - // No data ? - $aData = $oCSVParser->ToArray(); - $iTarget = count($aData); - if ($iTarget == 0) - { - $oPage->p("Empty data set..., please provide some data!"); - $oPage->add("\n"); - return; - } - - // Expected format - to be improved - $oPage->p("Separator: '$sSep'"); - $oPage->p("Text qualifier: '$sTQualif'"); - $oPage->p("The first line will be skipped (considered as being the list of fields)"); - - $oPage->p("Target: $iTarget rows"); - - $oPage->add("
      "); - ShowTableForm($oPage, $oCSVParser, $sClass); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add("
      "); -} - -function DoProcessOrVerify($oPage, $sClass, CMDBChange $oChange = null) -{ - $sCSVData = utils::ReadPostedParam('csvdata'); - $sSep = utils::ReadPostedParam('separator'); - $sTQualif = utils::ReadPostedParam('textqualifier'); - $iSkip = utils::ReadPostedParam('skiplines'); - $aFieldMap = utils::ReadPostedParam('fmap'); - $aIsReconcKey = utils::ReadPostedParam('iskey'); - - if (empty($aIsReconcKey)) - { - $oPage->p("Error: no reconciliation key has been specified. Please specify which field(s) will be used to identify the object"); - - $oPage->add("\n"); - $oPage->add("\n"); - return; - } - - $oCSVParser = new CSVParser($sCSVData, $sSep, $sTQualif); - $aData = $oCSVParser->ToArray($iSkip, null); - $iTarget = count($aData); - - $oPage->p("

      Goal summary

      "); - $oPage->p("Target: $iTarget rows"); - - $aSampleData = $oCSVParser->ToArray($iSkip, array_keys($aFieldMap), 5); - - $aDisplayConfig = array(); - $aExtKeys = array(); - foreach ($aFieldMap as $sFieldId=>$sColDesc) - { - if (array_key_exists($sFieldId, $aIsReconcKey)) - { - $sReconcKey = "
      [key]"; - } - else - { - $sReconcKey = ""; - } - - if ($sColDesc == "id") - { - $aDisplayConfig[$sFieldId] = array("label"=>"Private key $sReconcKey", "description"=>""); - } - elseif ($sColDesc == "__none__") - { - // Skip ! - } - else if (MetaModel::IsValidAttCode($sClass, $sColDesc)) - { - $sAttCode = $sColDesc; - $sLabel = MetaModel::GetAttributeDef($sClass, $sAttCode)->GetLabel(); - $aDisplayConfig[$sFieldId] = array("label"=>"$sLabel$sReconcKey", "description"=>""); - if (MetaModel::IsValidKeyAttCode($sClass, $sAttCode)) - { - $aExtKeys[] = $sAttCode; - } - } - elseif (IsExtKeyField($sColDesc)) - { - list($sExtKeyAttCode, $sForeignAttCode) = GetExtKeyFieldCodes($sColDesc); - $sLabel = MakeExtFieldLabel($sClass, $sExtKeyAttCode, $sForeignAttCode); - $aDisplayConfig[$sFieldId] = array("label"=>"$sLabel$sReconcKey", "description"=>""); - $aExtKeys[] = $sExtKeyAttCode; - } - else - { - // ??? - $aDisplayConfig[$sFieldId] = array("label"=>"-?-?-$sColDesc-?-?-", "description"=>""); - } - } - - $oPage->table($aDisplayConfig, $aSampleData); - - if ($oChange) - { - $oPage->p("

      Processing...

      "); - } - else - { - $oPage->p("

      Column consistency

      "); - $aMissingKeys = array(); - foreach (MetaModel::GetExternalKeys($sClass) as $sExtKeyAttCode => $oExtKey) - { - if (!in_array($sExtKeyAttCode, $aExtKeys) && !$oExtKey->IsNullAllowed()) - { - $aMissingKeys[$sExtKeyAttCode] = $oExtKey; - } - } - if (count($aMissingKeys) > 0) - { - $oPage->p("Warning: the objects could not be created, due to some missing mandatory external keys in the field list: "); - $oPage->add("
        "); - foreach($aMissingKeys as $sAttCode => $oAttDef) - { - $oPage->add("
      • ".$oAttDef->GetLabel()."
      • "); - } - $oPage->add("
      "); - } - else - { - $oPage->p("ok - required external keys (if any) have been found in the field list"); - } - $oPage->p("Note: the procedure will fail if any line has not the same number of columns as the first line"); - - $oPage->p("

      Check...

      "); - } - ProcessData($oPage, $sClass, $oCSVParser, $aFieldMap, $aIsReconcKey, $oChange); - - $oPage->add("
      "); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add_input_hidden("fmap", $aFieldMap); - $oPage->add_input_hidden("iskey", $aIsReconcKey); - - return true; -} - -function Do_Verify($oPage, $sClass) -{ - $oPage->p("

      Bulk load from CSV data / step 3

      "); - $sWiztep = "3_verify"; - - if (DoProcessOrVerify($oPage, $sClass, null)) - { - // FORM started by DoProcessOrVerify... - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - $oPage->add("
      "); - } -} - -function Do_Execute($oPage, $sClass) -{ - $oPage->p("

      Bulk load from CSV data / step 4

      "); - $sWiztep = "4_execute"; - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $iUser = UserRights::GetContactId(); - if ($iUser != null) - { - // Ok, that's dirty, I admit :-) - $oUser = MetaModel::GetObject('bizContact', $iUser); - $sUser = $oUser->GetName(); - $oMyChange->Set("userinfo", "CSV Import, by ".$sUser); - } - else - { - $oMyChange->Set("userinfo", "CSV Import"); - } - $iChangeId = $oMyChange->DBInsert(); - - if (DoProcessOrVerify($oPage, $sClass, $oMyChange)) - { - // FORM started by DoProcessOrVerify... - $oPage->add(""); - $oPage->add(""); - $oPage->add(""); - } -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// -// M a i n P r o g r a m -// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -$sFromWiztep = utils::ReadPostedParam('fromwiztep', ''); -$sClass = utils::ReadPostedParam('class', ''); -$sTodo = utils::ReadPostedParam('todo', ''); - -switch($sFromWiztep) -{ - case '': - Do_Welcome($oPage, $sClass); - break; - - case '1_welcome': - if ($sTodo == "Next") Do_Format($oPage, $sClass); - else trigger_error("Wrong argument todo='$sTodo'", E_USER_ERROR); - break; - - case '2_format': - if ($sTodo == "Next") Do_Verify($oPage, $sClass); - else Do_Welcome($oPage, $sClass); - break; - - case '3_verify': - if ($sTodo == "Next") Do_Execute($oPage, $sClass); - else Do_Format($oPage, $sClass); - break; - - case '4_execute': - if ($sTodo == "Next") trigger_error("Wrong argument todo='$sTodo'", E_USER_ERROR); - else Do_Verify($oPage, $sClass); - break; - - default: - trigger_error("Wrong argument fromwiztep='$sFromWiztep'", E_USER_ERROR); -} - -$oPage->output(); -?> + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-3.0.html LGPL + * @link http://www.combodo.com/itop iTop + */ +require_once('../application/application.inc.php'); +require_once('../application/itopwebpage.class.inc.php'); + +require_once('../application/startup.inc.php'); + +require_once('../application/loginwebpage.class.inc.php'); +LoginWebPage::DoLogin(); // Check user rights and prompt if needed + +$oContext = new UserContext(); +$oAppContext = new ApplicationContext(); +$currentOrganization = utils::ReadParam('org_id', 1); +$iStep = utils::ReadParam('step', 1); + +$oPage = new iTopWebPage("iTop - Bulk import", $currentOrganization); + +/** + * Helper function to build a select from the list of valid classes for a given action + * @param string $sName The name of the select in the HTML form + * @param string $sDefaulfValue The defaut value (i.e the value selected by default) + * @param integer $iWidthPx The width (in pixels) of the drop-down list + * @param integer $iActionCode The ActionCode (from UserRights) to check for authorization for the classes + * @return string The HTML fragment corresponding to the select tag + */ +function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null) +{ + $sHtml = ""; + return $sHtml; +} + +/** + * Helper to 'check' an input in an HTML form if the current value equals the value given + * @param mixed $sCurrentValue The current value to be chacked against the value of the input + * @param mixed $sProposedValue The value of the input + * @return string Either ' checked' or an empty string + */ +function IsChecked($sCurrentValue, $sProposedValue) +{ + return ($sCurrentValue == $sProposedValue) ? ' checked' : ''; +} + +/** + * Get the user friendly name for an 'extended' attribute code i.e 'name', becomes 'Name' and 'org_id->name' becomes 'Organization->Name' + * @param string $sClassName The name of the class + * @param string $sAttCodeEx Either an attribute code of ext_key_name->att_code + * @return string A user friendly format of the string: AttributeName or AttributeName->ExtAttributeName + */ +function GetFriendlyAttCodeName($sClassName, $sAttCodeEx) +{ + $sFriendlyName = ''; + if (preg_match('/(.+)->(.+)/', $sAttCodeEx, $aMatches) > 0) + { + $Attribute = $aMatches[1]; + $sField = $aMatches[2]; + $oAttDef = MetaModel::GetAttributeDef($sClassName, $Attribute); + if ($oAttDef->IsExternalKey()) + { + $sTargetClass = $oAttDef->GetTargetClass(); + $oTargetAttDef = MetaModel::GetAttributeDef($sTargetClass, $sField); + $sFriendlyName = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); + } + else + { + // hum, hum... should never happen, we'd better raise an exception + throw(new Exception("Internal error: '$sAttCodeEx' is an incorrect code because '$sAttribute' is NOT an external key of the class '$sClassName'.")); + } + + } + else + { + if ($sAttCodeEx == 'id') + { + $sFriendlyName = 'id (Primary Key)'; + } + else + { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCodeEx); + $sFriendlyName = $oAttDef->GetLabel(); + } + } + return $sFriendlyName; +} + +/** + * Returns the number of occurences of each char from the set in the specified string + * @param string $sString The input data + * @param array $aSet The set of characters to count + * @return hash 'char' => nb of occurences + */ +function CountCharsFromSet($sString, $aSet) +{ + $aResult = array(); + $aCount = count_chars($sString); + foreach($aSet as $sChar) + { + $aResult[$sChar] = isset($aCount[ord($sChar)]) ? $aCount[ord($sChar)] : 0; + } + return $aResult; +} + +/** + * Return the most frequent (and regularly occuring) character among the given set, in the specified lines + * @param array $aCSVData The input data, one entry per line + * @param array $aPossibleSeparators The list of characters to count + * @return string The most frequent character from the set + */ +function GuessFromFrequency($aCSVData, $aPossibleSeparators) +{ + $iLine = 0; + $iMaxLine = 20; // Process max 20 lines to guess the parameters + foreach($aPossibleSeparators as $sSep) + { + $aGuesses[$sSep]['total'] = $aGuesses[$sSep]['max'] = 0; + $aGuesses[$sSep]['min'] = 999; + } + $aStats = array(); + while(($iLine < count($aCSVData)) && ($iLine < $iMaxLine) ) + { + if (strlen($aCSVData[$iLine]) > 0) + { + $aStats[$iLine] = CountCharsFromSet($aCSVData[$iLine], $aPossibleSeparators); + } + $iLine++; + } + $iLine = 1; + foreach($aStats as $aLineStats) + { + foreach($aPossibleSeparators as $sSep) + { + $aGuesses[$sSep]['total'] += $aLineStats[$sSep]; + if ($aLineStats[$sSep] > $aGuesses[$sSep]['max']) $aGuesses[$sSep]['max'] = $aLineStats[$sSep]; + if ($aLineStats[$sSep] < $aGuesses[$sSep]['min']) $aGuesses[$sSep]['min'] = $aLineStats[$sSep]; + } + $iLine++; + } + + $aScores = array(); + foreach($aGuesses as $sSep => $aData) + { + $aScores[$sSep] = $aData['total'] + $aData['max'] - $aData['min']; + } + arsort($aScores, SORT_NUMERIC); // Sort the array, higher scores first + $aKeys = array_keys($aScores); + $sSeparator = $aKeys[0]; // Take the first key, the one with the best score + return $sSeparator; +} + +/** + * Try to predict the CSV parameters based on the input data + * @param string $sCSVData The input data + * @return hash 'separator' => the_guessed_separator, 'qualifier' => the_guessed_text_qualifier + */ +function GuessParameters($sCSVData) +{ + $aData = explode("\n", $sCSVData); + $sSeparator = GuessFromFrequency($aData, array("\t", ',', ';', '|')); // Guess the most frequent (and regular) character on each line + $sQualifier = GuessFromFrequency($aData, array('"', "'")); // Guess the most frequent (and regular) character on each line + + return array('separator' => $sSeparator, 'qualifier' => $sQualifier); +} + +/** + * Process the CSV data, for real or as a simulation + * @param WebPage $oPage The page used to display the wizard + * @param UserContext $oContext The current user context + * @param bool $bSimulate Whether or not to simulate the data load + * @return array The CSV lines in error that were rejected from the load (with the header line - if any) or null + */ +function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true) +{ + $aResult = array(); + $sCSVData = utils::ReadParam('csvdata', ''); + $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', ''); + $sSeparator = utils::ReadParam('separator', ','); + $sTextQualifier = utils::ReadParam('text_qualifier', '"'); + $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); + $iRealSkippedLines = $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); + $sClassName = utils::ReadParam('class_name', ''); + $aFieldsMapping = utils::ReadParam('field', array()); + $aSearchFields = utils::ReadParam('search_field', array()); + $iCurrentStep = $bSimulate ? 4 : 5; + + // Parse the data set + $oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier); + $aData = $oCSVParser->ToArray($iSkippedLines); + if ($bHeaderLine) + { + $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, array_shift($aData)).$sTextQualifier; // Remove the first line and store it in case of error + $iRealSkippedLines++; + } + + // Format for the line numbers + $sMaxLen = (strlen(''.count($aData)) < 3) ? 3 : strlen(''.count($aData)); // Pad line numbers to the appropriate number of chars, but at least 3 + + // Compute the list of search/reconciliation criteria + $aSearchKeys = array(); + foreach($aSearchFields as $index => $sDummy) + { + $sSearchField = $aFieldsMapping[$index]; + $aMatches = array(); + if (preg_match('/(.+)->(.+)/', $sSearchField, $aMatches) > 0) + { + $sSearchField = $aMatches[1]; + $aSearchKeys[$aMatches[1]] = ''; + } + else + { + $aSearchKeys[$sSearchField] = ''; + } + if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField)) + { + // Remove invalid or unmapped search fields + $aSearchFields[$index] = null; + unset($aSearchKeys[$sSearchField]); + } + } + + // Compute the list of fields and external keys to process + $aExtKeys = array(); + $aAttributes = array(); + $aExternalKeysByColumn = array(); + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + $iIndex = $iNumber-1; + if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) + { + if (preg_match('/(.+)->(.+)/', $sAttCode, $aMatches) > 0) + { + $sAttribute = $aMatches[1]; + $sField = $aMatches[2]; + $aExtKeys[$sAttribute][$sField] = $iIndex; + $aExternalKeysByColumn[$iIndex] = $sAttribute; + } + else + { + if ($sAttCode == 'id') + { + $aAttributes['id'] = $iIndex; + } + else + { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $aExtKeys[$sAttCode]['id'] = $iIndex; + $aExternalKeysByColumn[$iIndex] = $sAttCode; + } + else + { + $aAttributes[$sAttCode] = $iIndex; + } + } + } + } + } + + $oMyChange = null; + if (!$bSimulate) + { + // We're doing it for real, let's create a change + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + } + + $oBulk = new BulkChange( + $sClassName, + $aData, + $aAttributes, + $aExtKeys, + array_keys($aSearchKeys) + ); + + $oPage->add(''); + $aRes = $oBulk->Process($oMyChange); + + $sHtml = ''; + $sHtml .= ''; + $sHtml .= ''; + $sHtml .= ''; + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) + { + $sHtml .= ""; + } + } + $sHtml .= ''; + $sHtml .= ''; + $iLine = 0; + + $iErrors = 0; + $iCreated = 0; + $iModified = 0; + $iUnchanged = 0; + + foreach($aData as $aRow) + { + $oStatus = $aRes[$iLine]['__STATUS__']; + $sUrl = ''; + $sMessage = ''; + $sCSSRowClass = ''; + $sCSSMessageClass = 'cell_ok'; + switch(get_class($oStatus)) + { + case 'RowStatus_NoChange': + $iUnchanged++; + $sFinalClass = $aRes[$iLine]['finalclass']; + $oObj = $oContext->GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sStatus = ''; + $sCSSRowClass = 'row_unchanged'; + break; + + case 'RowStatus_Modify': + $iModified++; + $sFinalClass = $aRes[$iLine]['finalclass']; + $oObj = $oContext->GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sStatus = ''; + $sCSSRowClass = 'row_modified'; + break; + + case 'RowStatus_NewObj': + $iCreated++; + $sFinalClass = $aRes[$iLine]['finalclass']; + $sStatus = ''; + $sCSSRowClass = 'row_added'; + if ($bSimulate) + { + $sMessage = 'Object will be created'; + } + else + { + $sFinalClass = $aRes[$iLine]['finalclass']; + $oObj = $oContext->GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sMessage = 'Object created'; + } + break; + + case 'RowStatus_Issue': + $iErrors++; + $sMessage .= $oPage->GetP($oStatus->GetDescription()); + $sStatus = ''; + $sCSSMessageClass = 'cell_error'; + $sCSSRowClass = 'row_error'; + $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error + break; + } + $sHtml .= ''; + $sHtml .= ""; + $sHtml .= ""; + $sHtml .= ""; + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) + { + $oCellStatus = $aRes[$iLine][$iNumber -1]; + $sCellMessage = ''; + if (isset($aExternalKeysByColumn[$iNumber -1])) + { + $sExtKeyName = $aExternalKeysByColumn[$iNumber -1]; + $oExtKeyCellStatus = $aRes[$iLine][$sExtKeyName]; + switch(get_class($oExtKeyCellStatus)) + { + case 'CellStatus_Issue': + $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); + break; + + case 'CellStatus_Ambiguous': + $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); + break; + + default: + // Do nothing + } + } + switch(get_class($oCellStatus)) + { + case 'CellStatus_Issue': + $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); + $sHtml .= ''; + break; + + case 'CellStatus_Ambiguous': + $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); + $sHtml .= ''; + break; + + case 'CellStatus_Modify': + $sHtml .= ''; + break; + + default: + $sHtml .= ''; + } + } + } + $sHtml .= ""; + $iLine++; + $sHtml .= ''; + } + $sHtml .= '
      LineStatusObject".GetFriendlyAttCodeName($sClassName, $sAttCode)."Message
      ".sprintf("%0{$sMaxLen}d", 1+$iLine+$iRealSkippedLines)."$sStatus$sUrlERROR: '.htmlentities($aData[$iLine][$iNumber-1]).$sCellMessage.'AMBIGUOUS: '.htmlentities($aData[$iLine][$iNumber-1]).$sCellMessage.''.htmlentities($aData[$iLine][$iNumber-1]).''.htmlentities($aData[$iLine][$iNumber-1]).$sCellMessage.'$sMessage
      '; + $oPage->add('
      '); + $oPage->add('
      '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + $oPage->add(''); + } + foreach($aSearchFields as $index => $sDummy) + { + $oPage->add(''); + } + $aFieldsMapping = utils::ReadParam('field', array()); + $aSearchFields = utils::ReadParam('search_field', array()); + $aDisplayFilters = array(); + if ($bSimulate) + { + $aDisplayFilters['unchanged'] = '%d objects(s) will stay unchanged.'; + $aDisplayFilters['modified'] = '%d objects(s) will stay be modified.'; + $aDisplayFilters['added'] = '%d objects(s) will be added.'; + $aDisplayFilters['errors'] = '%d objects(s) will have errors.'; + } + else + { + $aDisplayFilters['unchanged'] = '%d objects(s) remained unchanged.'; + $aDisplayFilters['modified'] = '%d objects(s) were modified.'; + $aDisplayFilters['added'] = '%d objects(s) were added.'; + $aDisplayFilters['errors'] = '%d objects(s) had errors.'; + } + $oPage->add('

        '.sprintf($aDisplayFilters['unchanged'], $iUnchanged).'  '); + $oPage->add('  '.sprintf($aDisplayFilters['modified'], $iModified).'  '); + $oPage->add('  '.sprintf($aDisplayFilters['added'], $iCreated).'  '); + $oPage->add('  '.sprintf($aDisplayFilters['errors'], $iErrors).'

      '); + $oPage->add('
      '); + $oPage->add($sHtml); + $oPage->add('
      '); + $oPage->add('

        '); + if ($bSimulate) + { + $oPage->add('

      '); + } + else + { + $oPage->add('

      '); + } + $oPage->add('
      '); + $oPage->add('
      '); + $oPage->add_script( +<<< EOF + function CSVGoBack() + { + $('input[name=step]').val($iCurrentStep-1); + $('#wizForm').submit(); + + } + + function ToggleRows(sCSSClass) + { + $('.'+sCSSClass).toggle(); + } +EOF +); + if ($iErrors > 0) + { + return $aResult; + } + else + { + return null; + } + +} +/** + * Perform the actual load of the CSV data and display the results + * @param WebPage $oPage The web page to display the wizard + * @param UserContext $oContext Current user's context + * @return void + */ +function LoadData(WebPage $oPage, UserContext $oContext) +{ + $oPage->add('

      Step 5 of 5: Import completed

      '); + $aResult = ProcessCSVData($oPage, $oContext, false /* simulate = false */); + if (is_array($aResult)) + { + $oPage->StartCollapsibleSection("Lines that could not be loaded:", false); + $oPage->p('The following lines have not been imported because they contain errors'); + $oPage->add(''); + $oPage->EndCollapsibleSection(); + } +} + +/** + * Simulate the load of the CSV data and display the results + * @param WebPage $oPage The web page to display the wizard + * @param UserContext $oContext Current user's context + * @return void + */ +function Preview(WebPage $oPage, UserContext $oContext) +{ + $oPage->add('

      Step 4 of 5: Import simulation

      '); + ProcessCSVData($oPage, $oContext, true /* simulate */); +} + +/** + * Select the mapping between the CSV column and the fields of the objects + * @param WebPage $oPage The web page to display the wizard + * @return void + */ +function SelectMapping(WebPage $oPage) +{ + $sCSVData = utils::ReadParam('csvdata', ''); + $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '');; + $sSeparator = utils::ReadParam('separator', ','); + if ($sSeparator == 'tab') $sSeparator = "\t"; + if ($sSeparator == 'other') + { + $sSeparator = utils::ReadParam('other_separator', ','); + } + $sTextQualifier = utils::ReadParam('text_qualifier', '"'); + if ($sTextQualifier == 'other') + { + $sTextQualifier = utils::ReadParam('other_qualifier', '"'); + } + $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); + $iSkippedLines = 0; + if (utils::ReadParam('box_skiplines', '0') == 1) + { + $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); + } + $sClassName = utils::ReadParam('class_name', ''); + + $oPage->add('

      Step 3 of 5: Data mapping

      '); + $oPage->add('
      '); + $oPage->add('

      Select the class to import: '); + $oPage->add(GetClassesSelect('class_name', $sClassName, 300, UR_ACTION_BULK_MODIFY).'

      '); + $oPage->add('


      Select a class to configure the mapping

      '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add('

        '); + $oPage->add('

      '); + $oPage->add('
      '); + $oPage->add('
      '); + $oPage->add_ready_script( +<<add_ready_script("DoMapping();"); // There is already a class selected, run the mapping + } + + $oPage->add_script( +<<IsEmpty()) + { + $sCSVData = $oDocument->GetData(); + } + break; + + default: + $sCSVData = utils::ReadParam('csvdata', '', 'post'); + } + + $aGuesses = GuessParameters($sCSVData); // Try to predict the parameters, based on the input data + + $sSeparator = utils::ReadParam('separator', $aGuesses['separator']); + if ($sSeparator == 'tab') $sSeparator = "\t"; + $sTextQualifier = utils::ReadParam('qualifier', $aGuesses['qualifier']); + $bHeaderLine = utils::ReadParam('header_line', 0); + // Create a truncated version of the data used for the fast preview + // Take about 20 lines of data... knowing that some lines may contain carriage returns + $iMaxLines = 20; + $iMaxLen = strlen($sCSVData); + $iCurPos = true; + while ( ($iCurPos > 0) && ($iMaxLines > 0)) + { + $pos = strpos($sCSVData, "\n", $iCurPos); + if ($pos !== false) + { + $iCurPos = 1+$pos; + } + else + { + $iCurPos = strlen($sCSVData); + $iMaxLines = 1; + } + $iMaxLines--; + } + $sCSVDataTruncated = substr($sCSVData, 0, $iCurPos); + + $oPage->add('

      Step 2 of 5: CSV data options

      '); + $oPage->add('
      '); + $oPage->add('
      '); + $oPage->add('
      '); + $oPage->add('

      Separator character:

      '); + $oPage->add('

      , (comma)
      '); + $oPage->add(' ; (semicolon)
      '); + $oPage->add(' tab
      '); + $oPage->add(' other: '); + $oPage->add('

      '); + $oPage->add('
      '); + $oPage->add('

      Text qualifier character:

      '); + $oPage->add('

      " (double quote)
      '); + $oPage->add(' \' (simple quote)
      '); + $oPage->add(' other: '); + $oPage->add('

      '); + $oPage->add('
      '); + $oPage->add('

      Comments and header:

      '); + $oPage->add('

      Treat the first line as a header (column names)

      '); + $oPage->add('

      Skip line(s) at the beginning of the file

      '); + $oPage->add('

      '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add('
      '); + $oPage->add('

      Data Preview

      '); + $oPage->add('
      '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add('
      '); + + $oPage->add_script( +<<add_ready_script('DoPreview();'); +} + +/** + * Prompt for the data to be loaded (either via a file or a copy/paste) + * @param WebPage $oPage The current web page + * @return void + */ +function Welcome(iTopWebPage $oPage) +{ + $oPage->add("

      CSV import wizard

      \n"); + $oPage->AddTabContainer('tabs1'); + + $sFileLoadHtml = '

      Select the file to import:

      '. + '

      '. + '

      '. + '

      '. + '

      '. + '
      '; + + $oPage->AddToTab('tabs1', "Load from a file", $sFileLoadHtml); + $sCSVData = utils::ReadParam('csvdata', ''); + $sPasteDataHtml = '

      Paste the data to import:

      '. + '

      '. + '

      '. + '

      '. + '

      '. + '
      '; + $oPage->AddToTab('tabs1', "Copy and paste data", $sPasteDataHtml); + + $sTemplateHtml = '

      Pick the template do download: '; + $sTemplateHtml .= GetClassesSelect('template_class', '', 300, UR_ACTION_BULK_MODIFY); + $sTemplateHtml .= '

      '; + $sTemplateHtml .= '
      '; + $sTemplateHtml .= '
      '; + $oPage->AddToTab('tabs1', "Templates", $sTemplateHtml); + $oPage->add_script( +<<add_ready_script( +<<output(); +?> From 1bdfbbb2849f2d0e39633d4c912002cc4ec34f24 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 20 Apr 2010 09:21:46 +0000 Subject: [PATCH 265/970] Localization: cleanup in the DataModel + aligned some API (e.g. GetStateLabel) SVN:trunk[348] --- .../userrights/userrightsmatrix.class.inc.php | 38 +-- .../userrightsprofile.class.inc.php | 86 ++--- application/audit.category.class.inc.php | 6 +- application/audit.rule.class.inc.php | 12 +- application/cmdbabstract.class.inc.php | 9 +- application/displayblock.class.inc.php | 4 +- application/iotask.class.inc.php | 20 +- application/loginwebpage.class.inc.php | 88 +++-- application/menunode.class.inc.php | 24 +- application/ui.linkswidget.class.inc.php | 2 +- business/ChangeMgmt.business.php | 130 +++---- business/KEDB.business.php | 44 +-- business/ServiceDesk.business.php | 88 ++--- business/ServiceMgmt.business.php | 118 +++---- business/ServiceRequest.business.php | 84 ++--- business/business_itopbegins.class.inc.php | 28 +- business/business_test.class.inc.php | 52 +-- business/incidentMgmt.business.php | 153 ++++++--- business/itop.business.class.inc.php | 321 +++++++++--------- business/test_farm.class.inc.php | 36 +- core/action.class.inc.php | 26 +- core/attributedef.class.inc.php | 6 +- core/cmdbchange.class.inc.php | 4 +- core/cmdbchangeop.class.inc.php | 20 +- core/dbobject.class.php | 31 +- core/event.class.inc.php | 52 +-- core/metamodel.class.php | 72 ++-- core/stimulus.class.inc.php | 26 +- core/trigger.class.inc.php | 18 +- pages/UI.php | 35 +- pages/graphviz.php | 10 +- pages/schema.php | 12 +- pages/testlist.inc.php | 5 +- 33 files changed, 891 insertions(+), 769 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index eda5978114..c114fc353a 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -34,9 +34,9 @@ class UserRightsMatrixUsers extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeInteger("userid", array("label"=>"User id", "description"=>"User identifier (depends on the business model)", "allowed_values"=>null, "sql"=>"userid", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("password", array("label"=>"password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("userid", array("allowed_values"=>null, "sql"=>"userid", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); @@ -64,12 +64,12 @@ class UserRightsMatrixClassGrant extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); @@ -99,12 +99,12 @@ class UserRightsMatrixClassStimulusGrant extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); @@ -134,13 +134,13 @@ class UserRightsMatrixAttributeGrant extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); - MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeString("class", array("allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 86e4858b1e..bb89718d4c 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -62,15 +62,15 @@ class URP_Users extends UserRightsBaseClass MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"bizPerson", "label"=>"Contact (person)", "description"=>"Personal details from the business data", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("last_name", array("label"=>"Last name", "description"=>"Name of the corresponding contact", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("first_name", array("label"=>"First name", "description"=>"First name of the corresponding contact", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"first_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("email", array("label"=>"Email", "description"=>"Email of the corresponding contact", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"bizPerson", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("last_name", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("first_name", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"first_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("email", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"Login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributePassword("password", array("label"=>"Password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributePassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profiles", array("label"=>"Profiles", "description"=>"roles, granting rights for that person", "linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profiles", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); @@ -118,7 +118,7 @@ class URP_Users extends UserRightsBaseClass { if (UserRights::IsStimulusAllowed($sClass, $sStimulusCode, null, $iUserId)) { - $aStimuli[] = ''.htmlentities($oStimulus->Get('label')).''; + $aStimuli[] = ''.htmlentities($oStimulus->GetLabel()).''; } } $sStimuli = implode(', ', $aStimuli); @@ -196,10 +196,10 @@ class URP_Profiles extends UserRightsBaseClass ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("users", array("label"=>"Users", "description"=>"persons having this role", "linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"profileid", "ext_key_to_remote"=>"userid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("users", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"profileid", "ext_key_to_remote"=>"userid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -247,7 +247,7 @@ class URP_Profiles extends UserRightsBaseClass $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) { - $aStimuli[] = ''.htmlentities($oStimulus->Get('label')).''; + $aStimuli[] = ''.htmlentities($oStimulus->GetLabel()).''; } } $sStimuli = implode(', ', $aStimuli); @@ -305,9 +305,9 @@ class URP_Dimensions extends UserRightsBaseClass ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeClass("type", array("label"=>"Type", "description"=>"class name or data type (projection unit)", "class_category"=>"bizmodel", "more_values"=>"String,Integer", "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("type", array("class_category"=>"bizmodel", "more_values"=>"String,Integer", "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -426,13 +426,13 @@ class URP_UserProfile extends UserRightsBaseClass ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"URP_Users", "jointype"=> "", "label"=>"User", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("label"=>"Login", "description"=>"User's login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"URP_Users", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason", "description"=>"explain why this person may have this role", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("userid"); @@ -474,14 +474,14 @@ class URP_ProfileProjection extends UserRightsBaseClass ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"Profile name", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$user) | constant | | +attribute code", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attribute", array("allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("dimensionid"); @@ -554,13 +554,13 @@ class URP_ClassProjection extends UserRightsBaseClass ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("label"=>"Dimension", "description"=>"application dimension", "allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeClass("class", array("label"=>"Class", "description"=>"Target class", "class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("label"=>"Value expression", "description"=>"OQL expression (using \$this) | constant | | +attribute code", "allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attribute", array("label"=>"Attribute", "description"=>"Target attribute code (optional)", "allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attribute", array("allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("dimensionid"); @@ -640,12 +640,12 @@ class URP_ActionGrant extends UserRightsBaseClass //MetaModel::Init_InheritAttributes(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeClass("class", array("label"=>"Class", "description"=>"Target class", "class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"Permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"Action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) @@ -689,12 +689,12 @@ class URP_StimulusGrant extends UserRightsBaseClass //MetaModel::Init_InheritAttributes(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("label"=>"Profile", "description"=>"usage profile", "allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeClass("class", array("label"=>"Class", "description"=>"Target class", "class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"Permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"Stimulus", "description"=>"stimulus code", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) @@ -737,8 +737,8 @@ class URP_AttributeGrant extends UserRightsBaseClass MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "label"=>"Action grant", "description"=>"action grant", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"Attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("actiongrantid"); diff --git a/application/audit.category.class.inc.php b/application/audit.category.class.inc.php index 715db9d798..85b203ea7d 100644 --- a/application/audit.category.class.inc.php +++ b/application/audit.category.class.inc.php @@ -26,9 +26,9 @@ class AuditCategory extends cmdbAbstractObject "display_template" => "../application/templates/audit_category.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Category Name", "description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Audit Category Description", "description"=>"Long description for this audit category", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("definition_set", array("label"=>"Definition Set", "description"=>"SibusQL expression defining the set of objects to audit", "allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("definition_set", array("allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("description"); diff --git a/application/audit.rule.class.inc.php b/application/audit.rule.class.inc.php index 652cba90e4..f00f7c43bc 100644 --- a/application/audit.rule.class.inc.php +++ b/application/audit.rule.class.inc.php @@ -27,12 +27,12 @@ class AuditRule extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Rule Name", "description"=>"Short name for this rule", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Audit Rule Description", "description"=>"Long description for this audit rule", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("query", array("label"=>"Query to Run", "description"=>"The SibusQL expression to run", "allowed_values"=>null, "sql"=>"query", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", array("label"=>"Valid objects?", "description"=>"True if the rule returns the valid objects, false otherwise", "allowed_values"=>new ValueSetEnum('true,false'), "sql"=>"valid_flag", "default_value"=>"true", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("label"=>"Category", "description"=>"The category for this rule", "allowed_values"=>null, "sql"=>"category_id", "targetclass"=>"AuditCategory", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("label"=>"Category", "description"=>"Name of the category for this rule", "allowed_values"=>null, "extkey_attcode"=> 'category_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("query", array("allowed_values"=>null, "sql"=>"query", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", array("allowed_values"=>new ValueSetEnum('true,false'), "sql"=>"valid_flag", "default_value"=>"true", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("allowed_values"=>null, "sql"=>"category_id", "targetclass"=>"AuditCategory", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("allowed_values"=>null, "extkey_attcode"=> 'category_id', "target_attcode"=>"name"))); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("description"); diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 91128416a4..3fc03fa039 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -83,8 +83,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); if ($sStateAttCode == $sAttCode) { - $aStates = MetaModel::EnumStates(get_class($this)); - $sDisplayValue = $aStates[$this->Get($sAttCode)]['label']; + $sDisplayValue = MetaModel::GetStateLabel(get_class($this), $this->Get($sAttCode)); } else { @@ -231,7 +230,7 @@ abstract class cmdbAbstractObject extends CMDBObject if ($sStateAttCode == $sAttCode) { // Special display for the 'state' attribute itself - $sDisplayValue = $this->GetState(); + $sDisplayValue = $this->GetStateLabel(); } else { @@ -954,7 +953,7 @@ abstract class cmdbAbstractObject extends CMDBObject else if ($sStateAttCode == $sAttCode) { // State attribute is always read-only from the UI - $sHTMLValue = $this->GetState(); + $sHTMLValue = $this->GetStateLabel(); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } else if (!$oAttDef->IsExternalField()) @@ -1027,7 +1026,7 @@ abstract class cmdbAbstractObject extends CMDBObject else if ($sStateAttCode == $sAttCode) { // State attribute is always read-only from the UI - $sHTMLValue = $oObjectToClone->GetState(); + $sHTMLValue = $oObjectToClone->GetStateLabel(); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } else if (!$oAttDef->IsExternalField()) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index a25b662cd5..6eae0ff4b6 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -914,11 +914,11 @@ class MenuBlock extends DisplayBlock switch($iActionAllowed) { case UR_ALLOWED_YES: - $aActions[] = array('label' => $aStimuli[$sStimulusCode]->Get('label'), 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext"); + $aActions[] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext"); break; case UR_ALLOWED_DEPENDS: - $aActions[] = array('label' => $aStimuli[$sStimulusCode]->Get('label').' (*)', 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext"); + $aActions[] = array('label' => $aStimuli[$sStimulusCode]->GetLabel().' (*)', 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext"); break; default: diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index e882a48ee2..8ca3b3f37e 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -25,16 +25,16 @@ class InputOutputTask extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Task Name", "description"=>"Short name for this task", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Task Description", "description"=>"Long description for this task", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("category", array("label"=>"Category", "description"=>"Type of task", "allowed_values"=>new ValueSetEnum('Input, Ouput'), "sql"=>"category", "default_value"=>"Input", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("label"=>"Source Type", "description"=>"Type of data source", "allowed_values"=>new ValueSetEnum('File, Database, Web Service'), "sql"=>"source_type", "default_value"=>"File", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype", array("label"=>"Source Subtype", "description"=>"Subtype of Data Source", "allowed_values"=>new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql"=>"source_subtype", "default_value"=>"CSV", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("source_path", array("label"=>"Source Path", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"source_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeClass("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "class_category"=>"", "more_values"=>"", "sql"=>"objects_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("label"=>"Test Mode", "description"=>"If set to 'Yes' the modifications are not applied", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"test_mode", "default_value"=>'No', "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("label"=>"Verbose Mode", "description"=>"If set to 'Yes' extra debug information is added to the log", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"verbose_mode", "default_value" => 'No', "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("options", array("label"=>"Options", "description"=>"Reconciliation options", "allowed_values"=>new ValueSetEnum('Full, Update Only, Creation Only'), "sql"=>"options", "default_value"=> 'Full', "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("category", array("allowed_values"=>new ValueSetEnum('Input, Ouput'), "sql"=>"category", "default_value"=>"Input", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("allowed_values"=>new ValueSetEnum('File, Database, Web Service'), "sql"=>"source_type", "default_value"=>"File", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype", array("allowed_values"=>new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql"=>"source_subtype", "default_value"=>"CSV", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("source_path", array("allowed_values"=>null, "sql"=>"source_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("objects_class", array("class_category"=>"", "more_values"=>"", "sql"=>"objects_class", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"test_mode", "default_value"=>'No', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"verbose_mode", "default_value" => 'No', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("options", array("allowed_values"=>new ValueSetEnum('Full, Update Only, Creation Only'), "sql"=>"options", "default_value"=> 'Full', "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("description"); diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 430b98ac81..82904c2b65 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -75,42 +75,75 @@ h1 { } // Finally, destroy the session. session_destroy(); - } - - static function SecureConnectionRequired() - { - $oConfig = new Config(ITOP_CONFIG_FILE); - return $oConfig->GetSecureConnectionRequired(); } - - static function IsConnectionSecure() - { - $bSecured = false; - - if ( !empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!= 'off') ) - { - $bSecured = true; - } - return $bSecured; + + static function SecureConnectionRequired() + { + $oConfig = new Config(ITOP_CONFIG_FILE); + return $oConfig->GetSecureConnectionRequired(); + } + + static function IsConnectionSecure() + { + $bSecured = false; + + if ( !empty($_SERVER['HTTPS']) && ($_SERVER['HTTPS']!= 'off') ) + { + $bSecured = true; + } + return $bSecured; } static function DoLogin() { - if (self::SecureConnectionRequired() && !self::IsConnectionSecure()) - { - // Non secured URL... redirect to a secured one - $sUrl = Utils::GetAbsoluteUrl(true /* query string */, true /* force HTTPS */); - header("Location: $sUrl"); - exit; + if (self::SecureConnectionRequired() && !self::IsConnectionSecure()) + { + // Non secured URL... redirect to a secured one + $sUrl = Utils::GetAbsoluteUrl(true /* query string */, true /* force HTTPS */); + header("Location: $sUrl"); + exit; } - $operation = utils::ReadParam('loginop', ''); + $bHTTPBasicAuthentication = (utils::ReadParam('auth', '', 'get') == 'http_basic'); + if ($bHTTPBasicAuthentication) + { + // Basic HTTP/PHP authentication mecanism + // + // meme avec ca c'est pourri - return; + if (!isset($_SERVER['PHP_AUTH_USER'])) + { + header('WWW-Authenticate: Basic realm="iTop access is restricted"'); + header('HTTP/1.0 401 Unauthorized'); + // Note: accessed when the user will click on Cancel + echo '

      iTop access is restricted. Please, contact an iTop administrator.

      '; + exit; + } + else + { + $sAuthUser = $_SERVER['PHP_AUTH_USER']; + $sAuthPwd = $_SERVER['PHP_AUTH_PW']; + if (!UserRights::Login($sAuthUser, $sAuthPwd)) + { + header('WWW-Authenticate: Basic realm="Unknown user \''.$sAuthUser.'\'"'); + header('HTTP/1.0 401 Unauthorized'); + // Note: accessed when the user will click on Cancel + // Todo: count the attempts + echo '

      iTop access is restricted. Please, contact an iTop administrator.

      '; + exit; + } + } + return; + } + + // Home-made authentication mecanism + // + $operation = utils::ReadParam('loginop', ''); session_start(); if ($operation == 'logoff') { self::ResetSession(); } - + if (!isset($_SESSION['auth_user']) || !isset($_SESSION['auth_pwd'])) { if ($operation == 'loginurl') @@ -135,9 +168,9 @@ h1 { { $sAuthUser = $_SESSION['auth_user']; $sAuthPwd = $_SESSION['auth_pwd']; - } + } if (!UserRights::Login($sAuthUser, $sAuthPwd)) - { + { self::ResetSession(); $oPage = new LoginWebPage(); $oPage->DisplayLoginForm( true /* failed attempt */); @@ -148,8 +181,7 @@ h1 { { $_SESSION['auth_user'] = $sAuthUser ; $_SESSION['auth_pwd'] = $sAuthPwd; - - } + } } } // End of class ?> diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 943bd393cc..f48517ae9d 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -35,18 +35,18 @@ class menuNode extends DBObject "db_finalclass_field" => "", ); MetaModel::Init_Params($aParams); -// MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("label"=>"change", "description"=>"change", "allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "jointype"=>"closed"))); -// MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("label"=>"date", "description"=>"date and time of the change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date"))); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Menu Name", "description"=>"Short name for this menu", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("label", array("label"=>"Menu Description", "description"=>"Long description for this menu", "allowed_values"=>null, "sql"=>"label", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("hyperlink", array("label"=>"Hyperlink", "description"=>"Hyperlink to the page", "allowed_values"=>null, "sql"=>"hyperlink", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("icon_path", array("label"=>"Menu Icon", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"icon_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("template", array("label"=>"Template", "description"=>"HTML template for the view", "allowed_values"=>null, "sql"=>"template", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of menu", "allowed_values"=>new ValueSetEnum('application,user,administrator'), "sql"=>"type", "default_value"=>"application", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("rank", array("label"=>"Display rank", "description"=>"Sort order for displaying the menu", "allowed_values"=>null, "sql"=>"rank", "default_value" => 999, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "sql"=>"parent_id", "targetclass"=>"menuNode", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeInteger("user_id", array("label"=>"Owner of the menu", "description"=>"User who owns this menu (for user defined menus)", "allowed_values"=>null, "sql"=>"user_id", "default_value" => 0, "is_null_allowed"=>false, "depends_on"=>array()))); +// MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "jointype"=>"closed"))); +// MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("label", array("allowed_values"=>null, "sql"=>"label", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hyperlink", array("allowed_values"=>null, "sql"=>"hyperlink", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("icon_path", array("allowed_values"=>null, "sql"=>"icon_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("template", array("allowed_values"=>null, "sql"=>"template", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('application,user,administrator'), "sql"=>"type", "default_value"=>"application", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("rank", array("allowed_values"=>null, "sql"=>"rank", "default_value" => 999, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("allowed_values"=>null, "sql"=>"parent_id", "targetclass"=>"menuNode", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeInteger("user_id", array("allowed_values"=>null, "sql"=>"user_id", "default_value" => 0, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("label"); MetaModel::Init_AddFilterFromAttribute("parent_id"); diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index c01ea9d220..5e692b54c3 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -287,7 +287,7 @@ EOF; if ($sStateAttCode == $sAttCode) { // State attribute is always hidden from the UI - //$sHTMLValue = $this->GetState(); + //$sHTMLValue = $this->GetStateLabel(); //$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote)) diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index e7ad10ffcd..26bc5f04c9 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -25,52 +25,52 @@ class bizChangeTicket extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Ticket Ref", "description"=>"Refence number ofr this change", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Change", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Change Type", "description"=>"Type of the Change", "allowed_values"=>new ValueSetEnum("Routine, Normal, Emergency"), "sql"=>"type", "default_value"=>"Routine", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("domain", array("label"=>"Domain", "description"=>"Domain for the Change", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"domain", "default_value"=>"Desktop", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("type", array("allowed_values"=>new ValueSetEnum("Routine, Normal, Emergency"), "sql"=>"type", "default_value"=>"Routine", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("domain", array("allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"domain", "default_value"=>"Desktop", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason For Change", "description"=>"Reason for the Change", "allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("requestor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Requestor", "description"=>"who is requesting this change", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"requestor_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("requestor_mail", array("label"=>"Requestor", "description"=>"mail of user requesting this change", "allowed_values"=>null, "extkey_attcode"=> 'requestor_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("requestor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"requestor_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("requestor_mail", array("allowed_values"=>null, "extkey_attcode"=> 'requestor_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Validated,Rejected,Assigned,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("allowed_values"=>new ValueSetEnum("New, Validated,Rejected,Assigned,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); - MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("label"=>"Creation Date", "description"=>"Change creation date", "allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last Update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Start Date", "description"=>"Time the change is expected to start", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"End Date", "description"=>"Date when the change is supposed to end", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("close_date", array("label"=>"Closure Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Risk Assessment", "description"=>"Impact of the change", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"name of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("close_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisorgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Supervisor Group", "description"=>"which workgroup is supervising ticket", "allowed_values"=>null, "sql"=>"supervisorgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("supervisorgroup_name", array("label"=>"Supervisor Group", "description"=>"name of the group supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisorgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Supervisor", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->supervisorgroup_id'), "sql"=>"supervisor_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('supervisorgroup_id')))); - MetaModel::Init_AddAttribute(new AttributeExternalField("supervisor_mail", array("label"=>"Supervisor", "description"=>"name of agent supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisor_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisorgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"supervisorgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("supervisorgroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'supervisorgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->supervisorgroup_id'), "sql"=>"supervisor_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('supervisorgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("supervisor_mail", array("allowed_values"=>null, "extkey_attcode"=> 'supervisor_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("managergroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Manager Group", "description"=>"which workgroup is approving ticket", "allowed_values"=>null, "sql"=>"managergroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("managergroup_name", array("label"=>"Manager Group", "description"=>"name of workgroup approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'managergroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Manager", "description"=>"who is approving the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->managergroup_id'), "sql"=>"manager_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('managergroup_id')))); - MetaModel::Init_AddAttribute(new AttributeExternalField("manager_mail", array("label"=>"Manager", "description"=>"name of agent approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'manager_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("label"=>"Planned Outage", "description"=>"Flag to define if there is a planned outage", "allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("managergroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"managergroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("managergroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'managergroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->managergroup_id'), "sql"=>"manager_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('managergroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("manager_mail", array("allowed_values"=>null, "extkey_attcode"=> 'manager_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("change_request", array("label"=>"Change Request", "description"=>"Description of Change required", "allowed_values"=>null, "sql"=>"change_req", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("change_log", array("label"=>"Implementation Log", "description"=>"List all action performed during the change", "allowed_values"=>null, "sql"=>"change_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("fallback", array("label"=>"Fallback Plan", "description"=>"Instruction to come back to former situation", "allowed_values"=>null, "sql"=>"fallback", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("label"=>"Assignment Count", "description"=>"Number of times this ticket was assigned or reassigned", "allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("change_request", array("allowed_values"=>null, "sql"=>"change_req", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("change_log", array("allowed_values"=>null, "sql"=>"change_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("fallback", array("allowed_values"=>null, "sql"=>"fallback", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are impacted by this change", "linked_class"=>"lnkInfraChangeTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("linked_class"=>"lnkInfraChangeTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -106,42 +106,42 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'org_id', 'ticket_status','type', 'outage','workgroup_id','agent_id')); // Criteria of the advanced search form // State machine - MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_MANDATORY, 'title' => OPT_ATT_MANDATORY, 'reason' => OPT_ATT_MANDATORY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_HIDDEN, 'supervisorgroup_id' => OPT_ATT_HIDDEN, 'managergroup_id' => OPT_ATT_HIDDEN, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN ))); - MetaModel::Init_DefineState("Validated", array("label"=>"Validated", "description"=>"Ticket is validated", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Validated", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY, 'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_MUSTCHANGE, 'supervisorgroup_id' => OPT_ATT_MUSTCHANGE, 'managergroup_id' => OPT_ATT_MUSTCHANGE, 'supervisor_id' => OPT_ATT_HIDDEN, 'manager_id' => OPT_ATT_HIDDEN, 'agent_id' => OPT_ATT_HIDDEN))); - MetaModel::Init_DefineState("Rejected", array("label"=>"Rejected", "description"=>"This ticket is not approved", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Rejected", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN))); - MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Ticket is assigned", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Assigned", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY, 'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY, 'impacted_infra_manual' => OPT_ATT_MANDATORY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'workgroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY, 'managergroup_id' => OPT_ATT_MANDATORY, 'supervisor_id' => OPT_ATT_MUSTCHANGE, 'manager_id' => OPT_ATT_MUSTCHANGE, 'agent_id' => OPT_ATT_MUSTCHANGE))); - MetaModel::Init_DefineState("PlannedScheduled", array("label"=>"Planned&Scheduled", "description"=>"Evaluation is done for this change", "attribute_inherit"=>null, + MetaModel::Init_DefineState("PlannedScheduled", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_MANDATORY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_MANDATORY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_MANDATORY, 'manager_id' => OPT_ATT_MANDATORY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); - MetaModel::Init_DefineState("Approved", array("label"=>"Approved", "description"=>"Ticket is approved by CAB", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Approved", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); - MetaModel::Init_DefineState("NotApproved", array("label"=>"Not Approved", "description"=>"Ticket has not been approved by CAB", "attribute_inherit"=>null, + MetaModel::Init_DefineState("NotApproved", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); - MetaModel::Init_DefineState("Implemented", array("label"=>"Implementation", "description"=>"Work is in progress for this ticket", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Implemented", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MANDATORY,'fallback' => OPT_ATT_MANDATORY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); - MetaModel::Init_DefineState("Monitored", array("label"=>"Monitored", "description"=>"Change performed is now monitored", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Monitored", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_READONLY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_READONLY, 'change_log' => OPT_ATT_READONLY,'fallback' => OPT_ATT_READONLY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); - MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'close_date' => OPT_ATT_READONLY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_READONLY, 'change_log' => OPT_ATT_READONLY,'fallback' => OPT_ATT_READONLY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); + MetaModel::Init_DefineState("Closed", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'outage' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN,'start_date' => OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'close_date' => OPT_ATT_READONLY, 'impact' => OPT_ATT_READONLY, 'workgroup_id' => OPT_ATT_READONLY,'supervisorgroup_id' => OPT_ATT_READONLY, 'managergroup_id' => OPT_ATT_READONLY, 'supervisor_id' => OPT_ATT_READONLY, 'manager_id' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_READONLY, 'change_log' => OPT_ATT_READONLY,'fallback' => OPT_ATT_READONLY,'title' => OPT_ATT_READONLY, 'reason' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY,'requestor_id' => OPT_ATT_READONLY,'domain' => OPT_ATT_READONLY,'change_request' => OPT_ATT_READONLY,'creation_date' => OPT_ATT_READONLY))); - MetaModel::Init_DefineStimulus("ev_validate", new StimulusUserAction(array("label"=>"Validate this change", "description"=>"Make sure it is a valid change request"))); - MetaModel::Init_DefineStimulus("ev_reject", new StimulusUserAction(array("label"=>"Reject this change", "description"=>"This change request is rejected because it is a non valid one"))); - MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this change", "description"=>"This change request is assigned"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_validate", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reject", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_assign", array())); - MetaModel::Init_DefineStimulus("ev_reopen", new StimulusUserAction(array("label"=>"Modify this change", "description"=>"Update change request to make it valid"))); - MetaModel::Init_DefineStimulus("ev_plan", new StimulusUserAction(array("label"=>"Plan this change", "description"=>"Plan and Schedule this change for validation"))); - MetaModel::Init_DefineStimulus("ev_approve", new StimulusUserAction(array("label"=>"Approve this change", "description"=>"This change is approved by CAB"))); - MetaModel::Init_DefineStimulus("ev_replan", new StimulusUserAction(array("label"=>"Update planning and schedule", "description"=>"Modify Plan and Schedule in order to have this change re-validated"))); - MetaModel::Init_DefineStimulus("ev_notapprove", new StimulusUserAction(array("label"=>"Not approve this change", "description"=>"This change is not approved by CAB"))); - MetaModel::Init_DefineStimulus("ev_implement", new StimulusUserAction(array("label"=>"Implement this change", "description"=>"Implementation pahse for current change"))); - MetaModel::Init_DefineStimulus("ev_monitor", new StimulusUserAction(array("label"=>"Monitor this change", "description"=>"Starting monitoring period for this change"))); - MetaModel::Init_DefineStimulus("ev_finish", new StimulusUserAction(array("label"=>"Close change", "description"=>"Change is done, and can be closed"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reopen", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_plan", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_approve", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_replan", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_notapprove", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_implement", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_monitor", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_finish", array())); MetaModel::Init_DefineTransition("New", "ev_validate", array("target_state"=>"Validated", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("New", "ev_reject", array("target_state"=>"Rejected", "actions"=>array('SetLastUpDate'), "user_restriction"=>null)); @@ -227,11 +227,11 @@ class lnkInfraChangeTicket extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure Name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "label"=>"Ticket", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket Name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("infra_id"); MetaModel::Init_AddFilterFromAttribute("ticket_id"); @@ -276,11 +276,11 @@ class lnkContactChange extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"The contact linked to contract", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("label"=>"Contact E-mail", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("change_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "label"=>"Change Ticket", "description"=>"Change ticket ID", "allowed_values"=>null, "sql"=>"change_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("change_number", array("label"=>"Change Ticket", "description"=>"Ticket number for this change", "allowed_values"=>null, "extkey_attcode"=> 'change_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this contact for this change", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("change_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"change_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("change_number", array("allowed_values"=>null, "extkey_attcode"=> 'change_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("change_id"); MetaModel::Init_AddFilterFromAttribute("contact_id"); diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 23ee63e735..4dfcceffed 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -26,21 +26,21 @@ class bizKnownError extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name to identify this error", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization", "description"=>"Organization for this known error", "allowed_values"=>null, "sql"=>"cust_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("cust_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"cust_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("cust_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeText("symptom", array("label"=>"Symptom", "description"=>"Description of this error", "allowed_values"=>null, "sql"=>"symptom", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("root_cause", array("label"=>"Root cause", "description"=>"Original cause for this known error", "allowed_values"=>null, "sql"=>"rootcause", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("workaround", array("label"=>"Workaround", "description"=>"Work around to fix this error", "allowed_values"=>null, "sql"=>"workaround", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("solution", array("label"=>"Solution", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"solution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("symptom", array("allowed_values"=>null, "sql"=>"symptom", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("root_cause", array("allowed_values"=>null, "sql"=>"rootcause", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("workaround", array("allowed_values"=>null, "sql"=>"workaround", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("solution", array("allowed_values"=>null, "sql"=>"solution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("error_code", array("label"=>"Error Code", "description"=>"Key word to identify error", "allowed_values"=>null, "sql"=>"error_code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("domain", array("label"=>"Domain", "description"=>"Domain for this known error, network, desktop, ...", "allowed_values"=>new ValueSetEnum("Network, Server, Application, Desktop"), "sql"=>"domain", "default_value"=>"Application", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("vendor", array("label"=>"Vendor", "description"=>"Vendor concerned by this known error", "allowed_values"=>null, "sql"=>"vendor", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"Model concerned by this known error, it may be an application, a device ...", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("version", array("label"=>"Version", "description"=>"Version related to model impacted by known error", "allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("error_code", array("allowed_values"=>null, "sql"=>"error_code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("domain", array("allowed_values"=>new ValueSetEnum("Network, Server, Application, Desktop"), "sql"=>"domain", "default_value"=>"Application", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("vendor", array("allowed_values"=>null, "sql"=>"vendor", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("model", array("allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -96,11 +96,11 @@ class lnkInfraError extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure Name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "jointype"=> '', "label"=>"Error", "description"=>"Error id", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("label"=>"Error Name", "description"=>"Name of the error", "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "jointype"=> '', "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); MetaModel::Init_AddFilterFromAttribute("infra_id"); MetaModel::Init_AddFilterFromAttribute("error_id"); @@ -141,11 +141,11 @@ class lnkDocumentError extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document Name", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "label"=>"Error", "description"=>"Error linked to this document", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("label"=>"Error Name", "description"=>"name of the linked error", "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"Link Type", "description"=>"More information", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("doc_id"); MetaModel::Init_AddFilterFromAttribute("doc_name"); diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index ddb7244b6d..de312c33c0 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -39,37 +39,37 @@ class bizServiceCall extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Service Call Ref", "description"=>"Refence identifier for this service call", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the service call", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"Customer concerned by this service call", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer raising this service call", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("call_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress,Resolved,Closed"), "sql"=>"call_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("call_status", array("allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress,Resolved,Closed"), "sql"=>"call_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); - MetaModel::Init_AddAttribute(new AttributeText("call_description", array("label"=>"Description", "description"=>"Description of the call as describe by caller", "allowed_values"=>null, "sql"=>"call_description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("label"=>"Creation date", "description"=>"Call creation date", "allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("call_description", array("allowed_values"=>null, "sql"=>"call_description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the call was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closure Date", "description"=>"Date when the call was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger this call", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('org_id')))); - MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this call", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('org_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact for this call", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning call", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the call", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the call", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"mail of agent managing the call", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); // Comment afficher le first + last name de l'agent ? Est-ce utile d'ajouter ce champ? - MetaModel::Init_AddAttribute(new AttributeText("action_log", array("label"=>"Action Logs", "description"=>"List all action performed during the call", "allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Severity", "description"=>"Field defining the criticity for the call", "allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"criticity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("resolution", array("label"=>"Resolution", "description"=>"Description of the resolution", "allowed_values"=>null, "sql"=>"resolution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("source", array("label"=>"Source", "description"=>"source type for this call", "allowed_values"=>new ValueSetEnum("phone,E-mail,Fax"), "sql"=>"source", "default_value"=>"phone", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("action_log", array("allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"criticity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("resolution", array("allowed_values"=>null, "sql"=>"resolution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("source", array("allowed_values"=>new ValueSetEnum("phone,E-mail,Fax"), "sql"=>"source", "default_value"=>"phone", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are not meeting the SLA", "linked_class"=>"lnkInfraCall", "ext_key_to_me"=>"call_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("label"=>"Related Incident", "description"=>"Other incident tickets related to this call", "linked_class"=>"lnkCallTicket", "ext_key_to_me"=>"call_id", "ext_key_to_remote"=>"ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual')))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("linked_class"=>"lnkInfraCall", "ext_key_to_me"=>"call_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("linked_class"=>"lnkCallTicket", "ext_key_to_me"=>"call_id", "ext_key_to_remote"=>"ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual')))); //MetaModel::Init_InheritFilters(); @@ -97,27 +97,27 @@ class bizServiceCall extends cmdbAbstractObject MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'org_id', 'caller_id','type', 'call_status', 'severity','creation_date', 'last_update','end_date','agent_id')); // Criteria of the advanced search form // State machine - MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created call", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_READONLY, "title"=>OPT_ATT_MANDATORY, "org_id"=>OPT_ATT_MANDATORY, "caller_id"=>OPT_ATT_MANDATORY, "call_description"=>OPT_ATT_MANDATORY, "creation_date"=>OPT_ATT_MANDATORY, "workgroup_id"=>OPT_ATT_MANDATORY, "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT))); - MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Call is assigned to somebody", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Assigned", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY,"source"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "call_description"=>OPT_ATT_READONLY, "creation_date"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_MUSTCHANGE))); - MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_READONLY, + MetaModel::Init_DefineState("WorkInProgress", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY,"source"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "call_description"=>OPT_ATT_READONLY, "creation_date"=>OPT_ATT_READONLY, "workgroup_id"=>OPT_ATT_READONLY, "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY,"action_log"=>OPT_ATT_MUSTPROMPT,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT))); - MetaModel::Init_DefineState("Resolved", array("label"=>"Resolved", "description"=>"Call is resolved", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_READONLY, + MetaModel::Init_DefineState("Resolved", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "source"=>OPT_ATT_READONLY,"caller_id"=>OPT_ATT_READONLY, "call_description"=>OPT_ATT_READONLY, "creation_date"=>OPT_ATT_READONLY, "workgroup_id"=>OPT_ATT_READONLY, "severity"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT, "resolution"=>OPT_ATT_MUSTCHANGE))); - MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Call is closed", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_READONLY, 'last_update' => OPT_ATT_READONLY,"next_update"=>OPT_ATT_READONLY, + MetaModel::Init_DefineState("Closed", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'end_date' => OPT_ATT_READONLY, 'last_update' => OPT_ATT_READONLY,"next_update"=>OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY,"source"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "call_description"=>OPT_ATT_READONLY, "creation_date"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"type"=>OPT_ATT_READONLY, "workgroup_id"=>OPT_ATT_READONLY, "severity"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impacted_infra_manual"=>OPT_ATT_READONLY, "related_tickets"=>OPT_ATT_READONLY, "resolution"=>OPT_ATT_READONLY))); - MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this call", "description"=>"Assign this call to a group and an agent"))); - MetaModel::Init_DefineStimulus("ev_reassign", new StimulusUserAction(array("label"=>"Reassign this call", "description"=>"Reassign this call to a different group and agent"))); - MetaModel::Init_DefineStimulus("ev_start_working", new StimulusUserAction(array("label"=>"Work on this call", "description"=>"Start working on this call"))); - MetaModel::Init_DefineStimulus("ev_resolve", new StimulusUserAction(array("label"=>"Resolve this call", "description"=>"Resolve this call"))); - MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this call", "description"=>"Close this call"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_assign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reassign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_start_working", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_resolve", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_close", array())); MetaModel::Init_DefineTransition("New", "ev_assign", array("target_state"=>"Assigned", "actions"=>array('SetLastUpdate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('SetLastUpdate'), "user_restriction"=>null)); @@ -193,11 +193,11 @@ class lnkCallTicket extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Related Ticket", "description"=>"The related ticket", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Related ticket", "description"=>"Name of the related ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("call_id", array("targetclass"=>"bizServiceCall", "jointype"=> '', "label"=>"Call", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"call_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("call_name", array("label"=>"Call name", "description"=>"Name of the call", "allowed_values"=>null, "extkey_attcode"=> 'call_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact on the call", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("call_id", array("targetclass"=>"bizServiceCall", "jointype"=> '', "allowed_values"=>null, "sql"=>"call_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("call_name", array("allowed_values"=>null, "extkey_attcode"=> 'call_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("ticket_id"); MetaModel::Init_AddFilterFromAttribute("call_id"); @@ -244,11 +244,11 @@ class lnkInfraCall extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure Name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("call_id", array("targetclass"=>"bizServiceCall", "jointype"=> '', "label"=>"Call", "description"=>"Call number", "allowed_values"=>null, "sql"=>"call_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("call_name", array("label"=>"Call name", "description"=>"Name of the call", "allowed_values"=>null, "extkey_attcode"=> 'call_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("call_id", array("targetclass"=>"bizServiceCall", "jointype"=> '', "allowed_values"=>null, "sql"=>"call_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("call_name", array("allowed_values"=>null, "extkey_attcode"=> 'call_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("infra_id"); MetaModel::Init_AddFilterFromAttribute("call_id"); diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index 0319fba53e..f733f9b763 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -27,14 +27,14 @@ class bizService extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name of the service", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Provider", "description"=>"Provider for this service", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("label"=>"Provider", "description"=>"name of the Provider", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("service_category", array("label"=>"Service Category", "description"=>"Category for this contract", "allowed_values"=>new ValueSetEnum("Server,Network,End-User,Desktop,Application"), "sql"=>"service_category", "default_value"=>"End-User", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this service", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the service", "allowed_values"=>new ValueSetEnum("New, Implementation,Production,Obsolete"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the service", "allowed_values"=>new ValueSetEnum("Hardware,Software,Support"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("service_category", array("allowed_values"=>new ValueSetEnum("Server,Network,End-User,Desktop,Application"), "sql"=>"service_category", "default_value"=>"End-User", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum("New, Implementation,Production,Obsolete"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("Hardware,Software,Support"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -45,18 +45,18 @@ class bizService extends cmdbAbstractObject /* // Life cycle - MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created service", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Implementation", array("label"=>"Implementing", "description"=>"The service is being worked on", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Implementation", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The service is effective in production", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Production", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The service is no more deleivered", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Obsolete", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineStimulus("ev_implement", new StimulusUserAction(array("label"=>"Implement this service", "description"=>"This service is under construction"))); - MetaModel::Init_DefineStimulus("ev_move2prod", new StimulusUserAction(array("label"=>"Move to production", "description"=>"This service is now on production"))); - MetaModel::Init_DefineStimulus("ev_obsolete", new StimulusUserAction(array("label"=>"Obsolete", "description"=>"Thi service is no more delivered"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_implement", array())); // "Implement this service / This service is under construction" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_move2prod", array())); // "Move to production / This service is now on production" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_obsolete", array())); // "Obsolete / Thi service is no more delivered" MetaModel::Init_DefineTransition("New", "ev_implement", array("target_state"=>"Implementation", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Implementation", "ev_move2prod", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null)); @@ -107,25 +107,25 @@ class bizContract extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name of the contract", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"Customer for this contract", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"name of the Customer", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"bizService", "label"=>"Service", "description"=>"Provider for this contract", "allowed_values"=>null, "sql"=>"service_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("label"=>"Provider", "description"=>"name of the service provider", "allowed_values"=>null, "extkey_attcode"=> 'service_id', "target_attcode"=>"provider_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("label"=>"Service", "description"=>"name of the service", "allowed_values"=>null, "extkey_attcode"=> 'service_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team managing this contract", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team", "description"=>"name of the team managing this contract", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("service_level", array("label"=>"Service Level", "description"=>"Level of service for this contract", "allowed_values"=>new ValueSetEnum("Gold,Silver,Bronze"), "sql"=>"service_level", "default_value"=>"Bronze", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("cost_unit", array("label"=>"Cost Unit", "description"=>"Cost unit to compute global cost for this contract", "allowed_values"=>new ValueSetEnum("Devices,Persons,Applications,Global"), "sql"=>"cost_unit", "default_value"=>"Global", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("cost_freq", array("label"=>"Billing frequency", "description"=>"Frequency of cost for this contract", "allowed_values"=>new ValueSetEnum("Monthly,Yearly,Once"), "sql"=>"cost_freq", "default_value"=>"Once", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("cost", array("label"=>"Cost", "description"=>"Cost of this contract", "allowed_values"=>null, "sql"=>"cost", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("label"=>"Currency", "description"=>"Currency of cost for this contract", "allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("label"=>"Date of Move To Production", "description"=>"Date when the contract is on production", "allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_prod", array("label"=>"Date of End Of Production", "description"=>"Date when the contract is stopped", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the contract", "allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production,Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the contract", "allowed_values"=>new ValueSetEnum("Hardware,Software,Support,Licence"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("version_number", array("label"=>"Version", "description"=>"Revision number for this contract", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"bizService", "allowed_values"=>null, "sql"=>"service_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("allowed_values"=>null, "extkey_attcode"=> 'service_id', "target_attcode"=>"provider_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("allowed_values"=>null, "extkey_attcode"=> 'service_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("service_level", array("allowed_values"=>new ValueSetEnum("Gold,Silver,Bronze"), "sql"=>"service_level", "default_value"=>"Bronze", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("cost_unit", array("allowed_values"=>new ValueSetEnum("Devices,Persons,Applications,Global"), "sql"=>"cost_unit", "default_value"=>"Global", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("cost_freq", array("allowed_values"=>new ValueSetEnum("Monthly,Yearly,Once"), "sql"=>"cost_freq", "default_value"=>"Once", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("cost", array("allowed_values"=>null, "sql"=>"cost", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("end_prod", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production,Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("Hardware,Software,Support,Licence"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("version_number", array("allowed_values"=>null, "sql"=>"version_number", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("service_name"); @@ -144,21 +144,21 @@ class bizContract extends cmdbAbstractObject /* // Life cycle - MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created contract", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_MANDATORY,'org_id' => OPT_ATT_MANDATORY, 'service_id' => OPT_ATT_MANDATORY,'type' => OPT_ATT_MANDATORY, 'description' => OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("Negotiating", array("label"=>"Negotiating", "description"=>"The contract is being worked on", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Negotiating", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY))); - MetaModel::Init_DefineState("Signed", array("label"=>"Signed", "description"=>"The contract has been signed", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Signed", array("attribute_inherit"=>null, "attribute_list"=>array( 'name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'service_id' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, 'service_level' => OPT_ATT_MANDATORY , 'cost_unit' => OPT_ATT_MANDATORY , 'cost_freq' => OPT_ATT_MANDATORY , 'cost' => OPT_ATT_MANDATORY, 'currency' => OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The contract is effective in production", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Production", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'service_id' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, 'service_level' => OPT_ATT_READONLY , 'cost_unit' => OPT_ATT_READONLY , 'cost_freq' => OPT_ATT_READONLY , 'cost' => OPT_ATT_READONLY, 'currency' => OPT_ATT_READONLY,'move2prod_date' => OPT_ATT_MUSTPROMPT,'end_prod' => OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("Finished", array("label"=>"Finished", "description"=>"The contract is terminated", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Finished", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'org_id' => OPT_ATT_READONLY, 'service_id' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, 'service_level' => OPT_ATT_READONLY , 'cost_unit' => OPT_ATT_READONLY , 'cost_freq' => OPT_ATT_READONLY , 'cost' => OPT_ATT_READONLY, 'currency' => OPT_ATT_READONLY,'move2prod_date' => OPT_ATT_READONLY,'end_prod' => OPT_ATT_READONLY,'team_id' => OPT_ATT_READONLY,'description' => OPT_ATT_READONLY))); - MetaModel::Init_DefineStimulus("ev_negociate", new StimulusUserAction(array("label"=>"Negotiate this contract", "description"=>"This version of the contract is published"))); - MetaModel::Init_DefineStimulus("ev_sign", new StimulusUserAction(array("label"=>"Sign this contract", "description"=>"This contract is being signed"))); - MetaModel::Init_DefineStimulus("ev_begin", new StimulusUserAction(array("label"=>"Move to production", "description"=>"The contract becomes applicable in production"))); - MetaModel::Init_DefineStimulus("ev_terminate", new StimulusUserAction(array("label"=>"Ends this contract", "description"=>"The contract is ending"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_negociate", array())); // "Negotiate this contract / This version of the contract is published" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_sign", array())); // "Sign this contract / This contract is being signed" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_begin", array())); // "Move to production / The contract becomes applicable in production" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_terminate", array())); // "Ends this contract / The contract is ending" MetaModel::Init_DefineTransition("New", "ev_negociate", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Negotiating", "ev_sign", array("target_state"=>"Signed", "actions"=>array(), "user_restriction"=>null)); @@ -218,13 +218,13 @@ class lnkInfraContract extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure Name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "label"=>"Contract", "description"=>"Contract id", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract Name", "description"=>"Name of the contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("coverage", array("label"=>"Coverage", "description"=>"coverage for the given infra", "allowed_values"=>null, "sql"=>"coverage", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("service_level", array("label"=>"Service Level", "description"=>"service level for the given infra", "allowed_values"=>null, "sql"=>"sla", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("coverage", array("allowed_values"=>null, "sql"=>"coverage", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("service_level", array("allowed_values"=>null, "sql"=>"sla", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("infra_id"); @@ -265,11 +265,11 @@ class lnkContactContract extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"The contact linked to contract", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("label"=>"Contact E-mail", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "label"=>"Contract", "description"=>"Contract ID", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract Name", "description"=>"Name of the contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this contact for this contract", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("contract_id"); MetaModel::Init_AddFilterFromAttribute("contact_id"); @@ -315,11 +315,11 @@ class lnkDocumentContract extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document Name", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "label"=>"Contract", "description"=>"Contract linked to this document", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract Name", "description"=>"name of the linked contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"Link Type", "description"=>"More information", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("doc_id"); MetaModel::Init_AddFilterFromAttribute("doc_name"); diff --git a/business/ServiceRequest.business.php b/business/ServiceRequest.business.php index c847a44765..4d1f3cc249 100644 --- a/business/ServiceRequest.business.php +++ b/business/ServiceRequest.business.php @@ -29,22 +29,22 @@ class bizServiceRequest extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Request Ref", "description"=>"Refence number ofr this service request", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Service Request", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"Customer for this service request", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"name of the Customer", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this service request", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the service request", "allowed_values"=>new ValueSetEnum("Open,approved,rejected,assigned,pending,closed"), "sql"=>"status", "default_value"=>"Open", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("requester_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Requester", "description"=>"person that trigger service request", "allowed_values"=>null, "sql"=>"requester_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("requester_mail", array("label"=>"Requester", "description"=>"Person that trigger this service request", "allowed_values"=>null, "extkey_attcode"=> 'requester_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeEnum("priority", array("label"=>"Priority", "description"=>"Field defining the priority for this service request", "allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"priority", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("source", array("label"=>"Source", "description"=>"source type for this call", "allowed_values"=>new ValueSetEnum("phone,E-mail,Fax"), "sql"=>"source", "default_value"=>"phone", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum("Open,approved,rejected,assigned,pending,closed"), "sql"=>"status", "default_value"=>"Open", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("requester_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>null, "sql"=>"requester_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("requester_mail", array("allowed_values"=>null, "extkey_attcode"=> 'requester_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeEnum("priority", array("allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"priority", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("source", array("allowed_values"=>new ValueSetEnum("phone,E-mail,Fax"), "sql"=>"source", "default_value"=>"phone", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("coordinator_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Coordinator", "description"=>"which workgroup is controlling this request", "allowed_values"=>null, "sql"=>"coordinator_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("coordinator_name", array("label"=>"Coordinator", "description"=>"name of workgroup coordinating this service request", "allowed_values"=>null, "extkey_attcode"=> 'coordinator_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Coordinator Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("coordinator_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Coordinator Agent", "description"=>"mail of agent coordinating this service request", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("coordinator_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"coordinator_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("coordinator_name", array("allowed_values"=>null, "extkey_attcode"=> 'coordinator_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("coordinator_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -59,18 +59,18 @@ class bizServiceRequest extends cmdbAbstractObject /* // Life cycle - MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created service", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Implementation", array("label"=>"Implementing", "description"=>"The service is being worked on", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Implementation", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The service is effective in production", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Production", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The service is no more deleivered", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Obsolete", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineStimulus("ev_implement", new StimulusUserAction(array("label"=>"Implement this service", "description"=>"This service is under construction"))); - MetaModel::Init_DefineStimulus("ev_move2prod", new StimulusUserAction(array("label"=>"Move to production", "description"=>"This service is now on production"))); - MetaModel::Init_DefineStimulus("ev_obsololete", new StimulusUserAction(array("label"=>"Obsolete", "description"=>"Thi service is no more delivered"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_implement", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_move2prod", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_obsololete", array())); MetaModel::Init_DefineTransition("New", "ev_implement", array("target_state"=>"Implementation", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Implementation", "ev_move2prod", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null)); @@ -120,20 +120,20 @@ class bizServiceItem extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Request Ref", "description"=>"Refence number for this service item", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Service item", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("request_id", array("targetclass"=>"bizServiceRequest", "label"=>"Service Request", "description"=>"Corresponding service request", "allowed_values"=>null, "sql"=>"request_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("request_name", array("label"=>"Service Request", "description"=>"name of the request", "allowed_values"=>null, "extkey_attcode"=> 'request_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "label"=>"Service", "description"=>"Corresponding service", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Service Name", "description"=>"name of the service", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this service request", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the service request", "allowed_values"=>new ValueSetEnum("Open,approved,rejected,assigned,pending,closed"), "sql"=>"status", "default_value"=>"Open", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("priority", array("label"=>"Priority", "description"=>"priority corresponding to service request", "allowed_values"=>null, "extkey_attcode"=> 'request_id', "target_attcode"=>"priority"))); - MetaModel::Init_AddAttribute(new AttributeText("comment", array("label"=>"Comment", "description"=>"Comment of this service item", "allowed_values"=>null, "sql"=>"comment", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup working on this service item", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup working on this service item", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Coordinator Agent", "description"=>"who is managing the service item", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"mail of agent coordinating this service item", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("request_id", array("targetclass"=>"bizServiceRequest", "allowed_values"=>null, "sql"=>"request_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("request_name", array("allowed_values"=>null, "extkey_attcode"=> 'request_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum("Open,approved,rejected,assigned,pending,closed"), "sql"=>"status", "default_value"=>"Open", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("priority", array("allowed_values"=>null, "extkey_attcode"=> 'request_id', "target_attcode"=>"priority"))); + MetaModel::Init_AddAttribute(new AttributeText("comment", array("allowed_values"=>null, "sql"=>"comment", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -148,18 +148,18 @@ class bizServiceItem extends cmdbAbstractObject /* // Life cycle - MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created service", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Implementation", array("label"=>"Implementing", "description"=>"The service is being worked on", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Implementation", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The service is effective in production", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Production", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The service is no more deleivered", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Obsolete", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineStimulus("ev_implement", new StimulusUserAction(array("label"=>"Implement this service", "description"=>"This service is under construction"))); - MetaModel::Init_DefineStimulus("ev_move2prod", new StimulusUserAction(array("label"=>"Move to production", "description"=>"This service is now on production"))); - MetaModel::Init_DefineStimulus("ev_obsololete", new StimulusUserAction(array("label"=>"Obsolete", "description"=>"Thi service is no more delivered"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_implement", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_move2prod", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_obsololete", array())); MetaModel::Init_DefineTransition("New", "ev_implement", array("target_state"=>"Implementation", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Implementation", "ev_move2prod", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null)); diff --git a/business/business_itopbegins.class.inc.php b/business/business_itopbegins.class.inc.php index 5e7be6e07b..94444b79e3 100644 --- a/business/business_itopbegins.class.inc.php +++ b/business/business_itopbegins.class.inc.php @@ -52,9 +52,9 @@ class cmdbContact extends CMDBObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_contact_name", array("label"=>"name of the contact", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeInteger("att_contact_availability", array("label"=>"degree of availability in percent", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"availability"))); - MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("att_contact_name", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeInteger("att_contact_availability", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"availability"))); + MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("att_contact_name"); @@ -94,8 +94,8 @@ class cmdbPerson extends cmdbContact ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_person_email", array("label"=>"iMaile", "description"=>"imelle", "allowed_values"=>$oValsDunsNumber, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeString("att_person_name", array("label"=>"secName", "description"=>"secondary name", "allowed_values"=>new ValueSetEnum(array("nom1", "nom2", "nom10", "no", "noms", "")), "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("att_person_email", array("allowed_values"=>$oValsDunsNumber, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeString("att_person_name", array("allowed_values"=>new ValueSetEnum(array("nom1", "nom2", "nom10", "no", "noms", "")), "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("att_person_email"); @@ -132,14 +132,14 @@ class cmdbSubcontractor extends cmdbPerson ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_contractinfo", array("label"=>"contract info", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"contractinfo"))); + MetaModel::Init_AddAttribute(new AttributeString("att_contractinfo", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"contractinfo"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_provider", array("label"=>"ssii", "description"=>"blah", "allowed_values"=>null, "sql"=>"provider", "targetclass"=>"cmdbProvider", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_provider_ref", array("label"=>"ref", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_provider", "target_attcode"=>"att_provider_ref"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_provider", array("allowed_values"=>null, "sql"=>"provider", "targetclass"=>"cmdbProvider", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_provider_ref", array("allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_provider", "target_attcode"=>"att_provider_ref"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_tutor", array("label"=>"tutor", "description"=>"blah", "allowed_values"=>null, "sql"=>"tutor", "targetclass"=>"cmdbPerson", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_email", array("label"=>"tutor email", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_email"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_secondname", array("label"=>"2ndname (ext field)", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_tutor", array("allowed_values"=>null, "sql"=>"tutor", "targetclass"=>"cmdbPerson", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_email", array("allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_email"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_secondname", array("allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_name"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("extatt_subcontractor_tutor_secondname"); @@ -176,7 +176,7 @@ class cmdbCrowd extends cmdbObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeInteger("att_crowd_peoplecount", array("label"=>"people count", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"peoplecount"))); + MetaModel::Init_AddAttribute(new AttributeInteger("att_crowd_peoplecount", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"peoplecount"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("att_crowd_peoplecount"); @@ -213,7 +213,7 @@ class cmdbCompany extends cmdbCrowd ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_company_dunsnumber", array("label"=>"duns number", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"dunsnumber"))); + MetaModel::Init_AddAttribute(new AttributeString("att_company_dunsnumber", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"dunsnumber"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("att_company_dunsnumber"); @@ -250,7 +250,7 @@ class cmdbProvider extends cmdbCompany ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeInteger("att_provider_ref", array("label"=>"provider ref", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"providerref"))); + MetaModel::Init_AddAttribute(new AttributeInteger("att_provider_ref", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"providerref"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("att_provider_ref"); diff --git a/business/business_test.class.inc.php b/business/business_test.class.inc.php index cf7e8ee116..6781d6dec5 100644 --- a/business/business_test.class.inc.php +++ b/business/business_test.class.inc.php @@ -102,14 +102,14 @@ class cmdbContact extends cmdbObjectHomeMade ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("etat", array("label"=>"l'etat", "description"=>"les etats d'ame d'eric", "allowed_values"=>new ValueSetEnum('justborn, 15, 21'), "sql"=>"etat", "default_value"=>"justborn", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"nom", "description"=>"ze equipe", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"iMaile", "description"=>"imelle", "allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("owner", array("label"=>"owned by", "description"=>"organization owning the team", "allowed_values"=>null, "sql"=>"ownerorg", "targetclass"=>"cmdbOrga", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ownername", array("label"=>"owned by", "description"=>"name of organization owning the team", "allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_name_"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ownertnut", array("label"=>"owntnut", "description"=>"blah tnut blah", "allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_dunsnumber_"))); + MetaModel::Init_AddAttribute(new AttributeString("etat", array("allowed_values"=>new ValueSetEnum('justborn, 15, 21'), "sql"=>"etat", "default_value"=>"justborn", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("email", array("allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("owner", array("allowed_values"=>null, "sql"=>"ownerorg", "targetclass"=>"cmdbOrga", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ownername", array("allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_name_"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ownertnut", array("allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_dunsnumber_"))); - MetaModel::Init_AddAttribute(new AttributeLinkedSet("myworkshops", array("label"=>"held workshops", "description"=>"blah tnut blah", "depends_on"=>array(), "linked_class"=>"cmdbLiens", "ext_key_to_me"=>"tocontact", "count_min"=>1, "count_max"=>10, "allowed_values"=>null))); + MetaModel::Init_AddAttribute(new AttributeLinkedSet("myworkshops", array("depends_on"=>array(), "linked_class"=>"cmdbLiens", "ext_key_to_me"=>"tocontact", "count_min"=>1, "count_max"=>10, "allowed_values"=>null))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("owner"); @@ -120,12 +120,12 @@ class cmdbContact extends cmdbObjectHomeMade MetaModel::Init_SetZListItems("list2", array()); MetaModel::Init_SetZListItems("list3", array("ownername")); - MetaModel::Init_DefineState("justborn", array("label"=>"just born", "description"=>"too young to die", "attribute_inherit"=>null, "attribute_list"=>array("owner"=>OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("15", array("label"=>"student", "description"=>"stupid age", "attribute_inherit"=>"justborn", "attribute_list"=>array("owner"=>OPT_ATT_MUSTPROMPT, "email"=>OPT_ATT_MUSTPROMPT))); - MetaModel::Init_DefineState("21", array("label"=>"old", "description"=>"one foot in the grave", "attribute_inherit"=>"15", "attribute_list"=>array("email"=>OPT_ATT_READONLY|OPT_ATT_MUSTCHANGE))); + MetaModel::Init_DefineState("justborn", array("attribute_inherit"=>null, "attribute_list"=>array("owner"=>OPT_ATT_MANDATORY))); + MetaModel::Init_DefineState("15", array("attribute_inherit"=>"justborn", "attribute_list"=>array("owner"=>OPT_ATT_MUSTPROMPT, "email"=>OPT_ATT_MUSTPROMPT))); + MetaModel::Init_DefineState("21", array("attribute_inherit"=>"15", "attribute_list"=>array("email"=>OPT_ATT_READONLY|OPT_ATT_MUSTCHANGE))); - MetaModel::Init_DefineStimulus("toschool", new StimulusUserAction(array("label"=>"go to school", "description"=>"start learning stupid things"))); - MetaModel::Init_DefineStimulus("raise", new StimulusUserAction(array("label"=>"grow!", "description"=>"eat tons of BigMACs"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("toschool", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("raise", array())); MetaModel::Init_DefineTransition("justborn", "toschool", array("target_state"=>"15", "actions"=>array('MyLifecycleHandler', 'MyLifecycleHandler2'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("15", "raise", array("target_state"=>"21", "actions"=>null, "user_restriction"=>null)); @@ -187,8 +187,8 @@ class cmdbTeam extends cmdbContact ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_OverloadAttributeParams("email", array("label"=>"email2", "description"=>"emailleu22")); - MetaModel::Init_AddAttribute(new AttributeInteger("headcount", array("label"=>"nombre", "description"=>"combien ils sont", "allowed_values"=>null, "sql"=>"headcount", "default_value"=>654321, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_OverloadAttributeParams("email", array()); + MetaModel::Init_AddAttribute(new AttributeInteger("headcount", array("allowed_values"=>null, "sql"=>"headcount", "default_value"=>654321, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("headcount"); @@ -245,10 +245,10 @@ class cmdbOrga extends cmdbObjectHomeMade ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("_name_", array("label"=>"namo", "description"=>"official company name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("_status_", array("label"=>"step", "description"=>"step or status, etc.", "allowed_values"=>null, "sql"=>"status", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumber_", array("label"=>"duns debile number", "description"=>"une bonne idee a OVSD", "allowed_values"=>null, "sql"=>"dunsnumber", "default_value"=>99007, "is_null_allowed"=>false, "depends_on"=>array()))); -// not yet allowed MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumberBY2_", array("label"=>"dummy duns", "description"=>"deux fois plus debile", "allowed_values"=>null, "sql"=>"dunsnumber * 3.141592654"))); + MetaModel::Init_AddAttribute(new AttributeString("_name_", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("_status_", array("allowed_values"=>null, "sql"=>"status", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumber_", array("allowed_values"=>null, "sql"=>"dunsnumber", "default_value"=>99007, "is_null_allowed"=>false, "depends_on"=>array()))); +// not yet allowed MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumberBY2_", array("allowed_values"=>null, "sql"=>"dunsnumber * 3.141592654"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("_name_"); @@ -290,15 +290,15 @@ class cmdbLiens extends cmdbObjectHomeMade ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("function", array("label"=>"fonction", "description"=>"la fonction...", "allowed_values"=>null, "sql"=>"function", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("a1", array("label"=>"a1", "description"=>"a1", "allowed_values"=>null, "sql"=>"a1", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("a2", array("label"=>"a1", "description"=>"a2", "allowed_values"=>null, "sql"=>"a2", "default_value"=>"XXXX", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("function", array("allowed_values"=>null, "sql"=>"function", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("a1", array("allowed_values"=>null, "sql"=>"a1", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("a2", array("allowed_values"=>null, "sql"=>"a2", "default_value"=>"XXXX", "is_null_allowed"=>true, "depends_on"=>array()))); // What makes it being a link... - MetaModel::Init_AddAttribute(new AttributeExternalKey("toworkshop", array("label"=>"participates in", "description"=>"workshop in wich the person is participating", "allowed_values"=>null, "sql"=>"ws_id", "targetclass"=>"cmdbWorkshop", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ws_info", array("label"=>"name", "description"=>"namedescription", "allowed_values"=>null, "extkey_attcode"=>"toworkshop", "target_attcode"=>"namitus"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("tocontact", array("label"=>"is held by", "description"=>"people involved in that mess", "allowed_values"=>null, "sql"=>"contactid", "targetclass"=>"cmdbContact", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_info", array("label"=>"name", "description"=>"namedescription", "allowed_values"=>null, "extkey_attcode"=>"tocontact", "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("toworkshop", array("allowed_values"=>null, "sql"=>"ws_id", "targetclass"=>"cmdbWorkshop", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ws_info", array("allowed_values"=>null, "extkey_attcode"=>"toworkshop", "target_attcode"=>"namitus"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("tocontact", array("allowed_values"=>null, "sql"=>"contactid", "targetclass"=>"cmdbContact", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_info", array("allowed_values"=>null, "extkey_attcode"=>"tocontact", "target_attcode"=>"name"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("function"); @@ -345,7 +345,7 @@ class cmdbWorkshop extends cmdbObjectHomeMade ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("namitus", array("label"=>"namo", "description"=>"nom imbitique pour pondeurs de debilites", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("namitus", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("namitus"); diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index 35edcd4561..0be8120125 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -39,38 +39,39 @@ class bizIncidentTicket extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Ticket Ref", "description"=>"Refence number ofr this incident", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Incident", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("title", array("allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Resolved, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Resolved, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); - MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("label"=>"Initial Situation", "description"=>"Initial situation of the Incident", "allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("label"=>"Current Situation", "description"=>"Current situation of the Incident", "allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closure Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Agent", "description"=>"mail of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p JOIN lnkContactTeam AS l ON l.contact_id=p.id JOIN bizTeam AS t ON l.team_id=t.id JOIN bizWorkgroup AS w ON w.team_id=t.id WHERE w.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('workgroup_id')))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email"))); // Comment afficher le first + last name de l'agent ? Est-ce utile d'ajouter ce champ? - MetaModel::Init_AddAttribute(new AttributeText("action_log", array("label"=>"Action Logs", "description"=>"List all action performed during the incident", "allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Severity", "description"=>"Field defining the criticity if the incident", "allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"criticity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("label"=>"Assignment Count", "description"=>"Number of times this ticket was assigned or reassigned", "allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("resolution", array("label"=>"Resolution", "description"=>"Description of the resolution", "allowed_values"=>null, "sql"=>"resolution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("action_log", array("allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"criticity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("resolution", array("allowed_values"=>null, "sql"=>"resolution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are not meeting the SLA", "linked_class"=>"lnkInfraTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("label"=>"Related Tickets", "description"=>"Other incident tickets related to this one", "linked_class"=>"lnkRelatedTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"rel_ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual')))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("linked_class"=>"lnkInfraTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("linked_class"=>"lnkRelatedTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"rel_ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual')))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("contacts_a_notifier", array("linked_class"=>"lnkContactTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"contact_id", "allowed_values"=>null, "default_value"=>new ValueSetRelatedObjectsFromLinkSet('impacted_infra_manual'/*sLinkSetAttCode*/, 'infra_id'/*sExtKeyToRemote*/, 'impacts'/*sRelationCode*/, 3/*iMaxDepth*/, 'bizContact'/*sTargetClass*/, 'lnkContactTicket'/*sTargetLinkClass*/, 'contact_id'/*sTargetExtKey*/) /* ici plus tard... */, "count_min"=>0, "count_max"=>0, "depends_on"=>array('impacted_infra_manual')))); //MetaModel::Init_InheritFilters(); @@ -98,21 +99,21 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'org_id','caller_id','type','ticket_status', 'severity','start_date', 'last_update', 'end_date','agent_id')); // Criteria of the advanced search form // State machine - MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null, + MetaModel::Init_DefineState("New", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'next_update' => OPT_ATT_HIDDEN, 'last_update' => OPT_ATT_HIDDEN, "title"=>OPT_ATT_MANDATORY, "org_id"=>OPT_ATT_MANDATORY, "caller_id"=>OPT_ATT_MANDATORY, "initial_situation"=>OPT_ATT_MANDATORY, "start_date"=>OPT_ATT_MANDATORY, "workgroup_id"=>OPT_ATT_MANDATORY, "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT,"resolution"=>OPT_ATT_HIDDEN))); - MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Ticket is assigned to somebody", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Assigned", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_MUSTCHANGE,"resolution"=>OPT_ATT_HIDDEN))); - MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY,"action_log"=>OPT_ATT_MANDATORY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY))); - MetaModel::Init_DefineState("Resolved", array("label"=>"Resolved", "description"=>"Ticket is resolved", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_MUSTCHANGE))); - MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_READONLY))); + MetaModel::Init_DefineState("WorkInProgress", array("attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY,"action_log"=>OPT_ATT_MANDATORY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY))); + MetaModel::Init_DefineState("Resolved", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_MUSTCHANGE))); + MetaModel::Init_DefineState("Closed", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_READONLY))); - MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this ticket", "description"=>"Assign this ticket to a group and an agent"))); - MetaModel::Init_DefineStimulus("ev_reassign", new StimulusUserAction(array("label"=>"Reassign this ticket", "description"=>"Reassign this ticket to a different group and agent"))); - MetaModel::Init_DefineStimulus("ev_start_working", new StimulusUserAction(array("label"=>"Work on this ticket", "description"=>"Start working on this ticket"))); - MetaModel::Init_DefineStimulus("ev_resolve", new StimulusUserAction(array("label"=>"Resolve this ticket", "description"=>"Resolve this ticket"))); - MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this ticket", "description"=>"Close this ticket"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_assign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reassign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_start_working", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_resolve", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_close", array())); MetaModel::Init_DefineTransition("New", "ev_assign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount','SetLastUpdate'), "user_restriction"=>null)); @@ -152,6 +153,48 @@ class bizIncidentTicket extends cmdbAbstractObject $this->Set('last_update', time()); return true; } + +/* + EXAMPLE: OnInsert.... + + public function OnInsert() + { + // Romain: ajouter cette ligne + $oToNotify = $this->Get('contacts_a_notifier'); + + // Romain: ca c'etait pour verifier que ca fonctionne bien + // $oFirstContact = MetaModel::GetObject('bizPerson', 6); + // $oNewLink = new lnkContactTicket(); + // $oNewLink->Set('contact_id', 6); + // $oNewLink->Set('role', 'created before'); + // $oToNotify->AddObject($oNewLink); + + $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'impacted_infra_manual', 'infra_id'); + + $aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 2); + + if (array_key_exists('logRealObject', $aComputed)) + { + // Romain: supprimer cette ligne + // $aLinksToCreate = array(); + foreach($aComputed['logRealObject'] as $iKey => $oObject) + { + if (MetaModel::IsParentClass('bizContact', get_class($oObject))) + { + $oNewLink = new lnkContactTicket(); + $oNewLink->Set('contact_id', $iKey); + //$oNewLink->Set('ticket_id', $this->GetKey()); // unkown at that time! + $oNewLink->Set('role', 'concerned by an impacted CI'); + + // Romain: transformer cette ligne + $oToNotify->AddObject($oNewLink); + } + } + // Romain: supprimer cette ligne + // $this->Set('contacts_a_notifier', $oToNotify); + } + } +*/ public function ComputeValues() { @@ -192,11 +235,11 @@ class lnkRelatedTicket extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("rel_ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Related Ticket", "description"=>"The related ticket", "allowed_values"=>null, "sql"=>"rel_ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("rel_ticket_name", array("label"=>"Related ticket", "description"=>"Name of the related ticket", "allowed_values"=>null, "extkey_attcode"=> 'rel_ticket_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket Name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact on the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("rel_ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"rel_ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("rel_ticket_name", array("allowed_values"=>null, "extkey_attcode"=> 'rel_ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("rel_ticket_id"); MetaModel::Init_AddFilterFromAttribute("ticket_id"); @@ -243,11 +286,11 @@ class lnkInfraTicket extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure Name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket Name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("infra_id"); MetaModel::Init_AddFilterFromAttribute("ticket_id"); @@ -294,11 +337,11 @@ class lnkContactTicket extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"Contact to Notify", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"Contact email", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket Name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("contact_id"); MetaModel::Init_AddFilterFromAttribute("ticket_id"); @@ -345,11 +388,11 @@ class bizWorkgroup extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team owning the workgroup", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team Name", "description"=>"name of the team", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this work group", "allowed_values"=>new ValueSetEnum("1st level support,2nd level support,3rd level support"), "sql"=>"role", "default_value"=>"1st level support", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>new ValueSetEnum("1st level support,2nd level support,3rd level support"), "sql"=>"role", "default_value"=>"1st level support", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("org_name"); diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 61ef31a3bc..ac39a54e84 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -70,11 +70,11 @@ class bizOrganization extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array() ))); - MetaModel::Init_AddAttribute(new AttributeString("code", array("label"=>"Code", "description"=>"Organization code (Siret, DUNS,...)", "allowed_values"=>null, "sql"=>"code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array() ))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum(STANDARD_STATUSES), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"bizOrganization", "label"=>"Parent", "description"=>"Parent organization", "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Name", "description"=>"Name of the parent organization", "allowed_values"=>null, "extkey_attcode"=> 'parent_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array() ))); + MetaModel::Init_AddAttribute(new AttributeString("code", array("allowed_values"=>null, "sql"=>"code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array() ))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum(STANDARD_STATUSES), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("allowed_values"=>null, "extkey_attcode"=> 'parent_id', "target_attcode"=>"name"))); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("code"); @@ -142,10 +142,10 @@ class logRealObject extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete,off,left company,available'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization", "description"=>"ID of the object owner organization", "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,implementation,obsolete,off,left company,available'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("status"); @@ -197,12 +197,12 @@ class bizContact extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('off,left company,available'), "sql"=>"status", "default_value"=>"available", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department of the contact", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEmailAddress("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("phone", array("label"=>"Phone", "description"=>"Telephone", "allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"Id of the location where the contact is located", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"Name of the location where the contact is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('off,left company,available'), "sql"=>"status", "default_value"=>"available", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEmailAddress("email", array("allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("phone", array("allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("org_name"); @@ -256,8 +256,8 @@ class bizPerson extends bizContact ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"First Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("employee_number", array("label"=>"Employee Number", "description"=>"employee number", "allowed_values"=>null, "sql"=>"employee_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("first_name", array("allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("employee_number", array("allowed_values"=>null, "sql"=>"employee_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("first_name"); @@ -344,13 +344,13 @@ class lnkContactTeam extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizPerson", "label"=>"Contact", "description"=>"The contact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("label"=>"Contact Name", "description"=>"name of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("label"=>"Phone", "description"=>"Phone number of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"eMail", "description"=>"eMail address of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team linked", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team", "description"=>"name of the Team", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizPerson", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("contact_id"); MetaModel::Init_AddFilterFromAttribute("contact_name"); @@ -392,12 +392,12 @@ class bizDocument extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning the document", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"type", "description"=>"usage of the document", "allowed_values"=>new ValueSetEnum("documentation,contract,working instructions,network map,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("documentation,contract,working instructions,network map,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("label"=>"Contents", "description"=>"File content", "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("type"); @@ -438,11 +438,11 @@ class lnkDocumentRealObject extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document Name", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "label"=>"Object", "description"=>"Object linked", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("label"=>"Object Name", "description"=>"name of the linked object", "allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"Link Type", "description"=>"More information", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("doc_id"); MetaModel::Init_AddFilterFromAttribute("doc_name"); @@ -484,13 +484,13 @@ class lnkContactRealObject extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "label"=>"Contact", "description"=>"The contact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("label"=>"Contact Name", "description"=>"name of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("label"=>"Phone", "description"=>"Phone number of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"eMail", "description"=>"eMail address of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "label"=>"Object", "description"=>"Object linked", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("label"=>"Object Name", "description"=>"name of the linked object", "allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("contact_id"); MetaModel::Init_AddFilterFromAttribute("contact_name"); @@ -534,8 +534,8 @@ class logInfra extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Business Criticity", "description"=>"Severity for this infrastructure", "allowed_values"=>new ValueSetEnum("high,medium,low"), "sql"=>"severity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("allowed_values"=>new ValueSetEnum("high,medium,low"), "sql"=>"severity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("severity"); @@ -567,13 +567,13 @@ class lnkContactInfra extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "label"=>"Contact", "description"=>"The contact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("label"=>"Contact Name", "description"=>"name of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("label"=>"Phone", "description"=>"Phone number of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"eMail", "description"=>"eMail address of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "label"=>"Infrastructure", "description"=>"Infrastructure linked", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure", "description"=>"name of the linked infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("role", array("allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("contact_id"); MetaModel::Init_AddFilterFromAttribute("contact_name"); @@ -615,10 +615,10 @@ class bizLocation extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeText("address", array("label"=>"Address", "description"=>"The postal address of the location", "allowed_values"=>null, "sql"=>"address", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("country", array("label"=>"Country", "description"=>"Country of the location", "allowed_values"=>null, "sql"=>"country", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_location_id", array("targetclass"=>"bizLocation", "label"=>"Parent Location", "description"=>"where is the real object physically located", "allowed_values"=>null, "sql"=>"parent_location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("parent_location_name", array("label"=>"Parent location (Name)", "description"=>"name of the parent location", "allowed_values"=>null, "extkey_attcode"=> 'parent_location_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeText("address", array("allowed_values"=>null, "sql"=>"address", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("country", array("allowed_values"=>null, "sql"=>"country", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_location_id", array("targetclass"=>"bizLocation", "allowed_values"=>null, "sql"=>"parent_location_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_location_name", array("allowed_values"=>null, "extkey_attcode"=> 'parent_location_id', "target_attcode"=>"name"))); // on veut pouvoir rechercher une location qui soit un descendant (pas obligatoirement direct) d'une Location, on fait comment ? @@ -748,23 +748,23 @@ class bizCircuit extends logInfra MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"speed", "description"=>"speed of the circuit", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location1_id", array("targetclass"=>"bizLocation", "label"=>"Location 1", "description"=>"Id of the location 1", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("location1_name", array("label"=>"Location 1", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location1_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location2_id", array("targetclass"=>"bizLocation", "label"=>"Location 2", "description"=>"Id of the location 2", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location2_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL,"depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("location2_name", array("label"=>"Location 2", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location2_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("speed", array("allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location1_id", array("targetclass"=>"bizLocation", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location1_name", array("allowed_values"=>null, "extkey_attcode"=> 'location1_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location2_id", array("targetclass"=>"bizLocation", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location2_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL,"depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location2_name", array("allowed_values"=>null, "extkey_attcode"=> 'location2_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface 1", "description"=>"id of the interface 1", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS Intf JOIN bizDevice AS Dev ON Intf.device_id = Dev.id WHERE Intf.org_id = :this->org_id AND Dev.location_id = :this->location1_id'), "sql"=>"interface1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id", "location1_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("label"=>"Interface", "description"=>"Name of the interface 1", "allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device1_name", array("label"=>"Device 1", "description"=>"Name of the device 1", "allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"device_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS Intf JOIN bizDevice AS Dev ON Intf.device_id = Dev.id WHERE Intf.org_id = :this->org_id AND Dev.location_id = :this->location1_id'), "sql"=>"interface1_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id", "location1_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device1_name", array("allowed_values"=>null, "extkey_attcode"=> 'interface1_id', "target_attcode"=>"device_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface 2", "description"=>"id of the interface 2", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS Intf JOIN bizDevice AS Dev ON Intf.device_id = Dev.id WHERE Intf.org_id = :this->org_id AND Dev.location_id = :this->location2_id'), "sql"=>"interface2_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id", "location2_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("label"=>"Interface", "description"=>"Name of the interface 2", "allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device2_name", array("label"=>"Interface", "description"=>"Name of the device 2", "allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"device_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "allowed_values"=>new ValueSetObjects('SELECT bizInterface AS Intf JOIN bizDevice AS Dev ON Intf.device_id = Dev.id WHERE Intf.org_id = :this->org_id AND Dev.location_id = :this->location2_id'), "sql"=>"interface2_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id", "location2_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device2_name", array("allowed_values"=>null, "extkey_attcode"=> 'interface2_id', "target_attcode"=>"device_name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_id", array("targetclass"=>"bizOrganization", "label"=>"Carrier ID", "description"=>"Organization ID of the provider of the Circuit", "allowed_values"=>null, "sql"=>"provider_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("carrier_name", array("label"=>"Carrier", "description"=>"Name of the carrier", "allowed_values"=>null, "extkey_attcode"=> 'provider_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("carrier_ref", array("label"=>"Carrier reference", "description"=>"reference of the circuit used by the carrier", "allowed_values"=>null, "sql"=>"carrier_ref", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"provider_id", "is_null_allowed"=>false,"on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("carrier_name", array("allowed_values"=>null, "extkey_attcode"=> 'provider_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("carrier_ref", array("allowed_values"=>null, "sql"=>"carrier_ref", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("speed"); @@ -830,21 +830,21 @@ class bizInterface extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "label"=>"Device", "description"=>"Device on which the interface is physically located", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device", "description"=>"name of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_id", array("label"=>"Device location", "description"=>"location of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_id"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_name", array("label"=>"Device location", "description"=>"name of the location of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_id", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_id"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_name", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_name"))); - MetaModel::Init_AddAttribute(new AttributeEnum("logical_type", array("label"=>"Logical type", "description"=>"Logical type of interface", "allowed_values"=>new ValueSetEnum("primary,secondary,backup,port,logical"), "sql"=>"logical_type", "default_value"=>"port", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("physical_type", array("label"=>"Physical type", "description"=>"Physical type of interface", "allowed_values"=>new ValueSetEnum("ethernet,framerelay,atm,vlan"), "sql"=>"physical_type", "default_value"=>"ethernet", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("ip_address", array("label"=>"IP address", "description"=>"address IP for this interface", "allowed_values"=>null, "sql"=>"ip_address", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mask", array("label"=>"Subnet Mask", "description"=>"Subnet mask for this interface", "allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mac", array("label"=>"MAC address", "description"=>"MAC address for this interface", "allowed_values"=>null, "sql"=>"mac", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"Speed (Kb/s)", "description"=>"speed of this interface", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("label"=>"Duplex", "description"=>"Duplex configured for this interface", "allowed_values"=>new ValueSetEnum("half,full,unknown"), "sql"=>"duplex", "default_value"=>"unknown", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("if_connected_id", array("targetclass"=>"bizInterface", "label"=>"Connected interface", "description"=>"interface connected to this one", "allowed_values"=>null, "sql"=>"ext_if_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("if_connected_name", array("label"=>"Connected interface", "description"=>"name of the interface connected to this one", "allowed_values"=>null, "extkey_attcode"=> 'if_connected_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("if_connected_device", array("label"=>"Connected device", "description"=>"name of the device connected to this interface", "allowed_values"=>null, "extkey_attcode"=> 'if_connected_id', "target_attcode"=>"device_name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("logical_type", array("allowed_values"=>new ValueSetEnum("primary,secondary,backup,port,logical"), "sql"=>"logical_type", "default_value"=>"port", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("physical_type", array("allowed_values"=>new ValueSetEnum("ethernet,framerelay,atm,vlan"), "sql"=>"physical_type", "default_value"=>"ethernet", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("ip_address", array("allowed_values"=>null, "sql"=>"ip_address", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mask", array("allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mac", array("allowed_values"=>null, "sql"=>"mac", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("speed", array("allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("allowed_values"=>new ValueSetEnum("half,full,unknown"), "sql"=>"duplex", "default_value"=>"unknown", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("if_connected_id", array("targetclass"=>"bizInterface", "allowed_values"=>null, "sql"=>"ext_if_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("if_connected_name", array("allowed_values"=>null, "extkey_attcode"=> 'if_connected_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("if_connected_device", array("allowed_values"=>null, "extkey_attcode"=> 'if_connected_id', "target_attcode"=>"device_name"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("device_id"); @@ -926,8 +926,8 @@ class bizSubnet extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("ip", array("label"=>"IP", "description"=>"IP", "allowed_values"=>null, "sql"=>"ip", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mask", array("label"=>"IP mask", "description"=>"IP mask", "allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("ip", array("allowed_values"=>null, "sql"=>"ip", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mask", array("allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("ip"); @@ -1009,13 +1009,13 @@ class bizDevice extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"where is the located object physically located", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("country", array("label"=>"Country", "description"=>"country where the device is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"country"))); - MetaModel::Init_AddAttribute(new AttributeString("brand", array("label"=>"Brand", "description"=>"The manufacturer of the device", "allowed_values"=>null, "sql"=>"brand", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"The model number of the device", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("serial_number", array("label"=>"Serial Number", "description"=>"The serial number of the device", "allowed_values"=>null, "sql"=>"serial_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "allowed_values"=>new ValueSetObjects('SELECT bizLocation AS p WHERE p.org_id = :this->org_id'), "sql"=>"location_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("country", array("allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"country"))); + MetaModel::Init_AddAttribute(new AttributeString("brand", array("allowed_values"=>null, "sql"=>"brand", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("model", array("allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("serial_number", array("allowed_values"=>null, "sql"=>"serial_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("location_id"); @@ -1032,9 +1032,6 @@ class bizDevice extends logInfra { case "impacts": $aRels = array( -// commented out, because lnkInterfaces does not exist anymore -// "connected device" => array("sQuery"=>"bizDevice: PKEY IS device_id IN (bizInterface: PKEY IS interface2_id IN (lnkInterfaces: interface1_id IN (bizInterface: device_id = \$[this.pkey::])))", "bPropagate"=>true, "iDistance"=>3), - "hosted app" => array("sQuery"=>"bizApplication: infra_id = \$[this.pkey::]", "bPropagate"=>true, "iDistance"=>3), ); return array_merge($aRels, parent::GetRelationQueries($sRelCode)); } @@ -1083,14 +1080,14 @@ class bizPC extends bizDevice ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of computer", "allowed_values"=>new ValueSetEnum("desktop PC,laptop"), "sql"=>"type", "default_value"=>"desktop PC", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"CPU", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("label"=>"HDD Size", "description"=>"Size of the hard drive", "allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("os_family", array("label"=>"OS Family", "description"=>"Type of operating system", "allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("os_version", array("label"=>"OS Version", "description"=>"Detailed version number of the operating system", "allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("label"=>"Shipment Code", "description"=>"Number for tracking shipment", "allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("desktop PC,laptop"), "sql"=>"type", "default_value"=>"desktop PC", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("cpu", array("allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_family", array("allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_version", array("allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("type"); @@ -1178,16 +1175,16 @@ class bizServer extends bizDevice ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); -// MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("InStore,Shipped,Plugged,ProductionCandidate,InProduction,BeingDeconfigured,Obsolete"), "sql"=>"status", "default_value"=>"InStore", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"CPU type", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("number_of_cpus", array("label"=>"Number of CPUs", "description"=>"Number of CPUs", "allowed_values"=>null, "sql"=>"number_of_cpus", "default_value"=>"1", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("label"=>"HDD Size", "description"=>"Size of the hard drive", "allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("hdd_free_size", array("label"=>"Free HDD Size", "description"=>"Size of the free space on the hard drive(s)", "allowed_values"=>null, "sql"=>"hdd_free_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("os_family", array("label"=>"OS Family", "description"=>"Type of operating system", "allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("os_version", array("label"=>"OS Version", "description"=>"Detailed version number of the operating system", "allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("label"=>"Shipment number", "description"=>"Number for tracking shipment", "allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); +// MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum("InStore,Shipped,Plugged,ProductionCandidate,InProduction,BeingDeconfigured,Obsolete"), "sql"=>"status", "default_value"=>"InStore", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("cpu", array("allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("number_of_cpus", array("allowed_values"=>null, "sql"=>"number_of_cpus", "default_value"=>"1", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("hdd_free_size", array("allowed_values"=>null, "sql"=>"hdd_free_size", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_family", array("allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("os_version", array("allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("status"); @@ -1202,30 +1199,30 @@ class bizServer extends bizDevice /* // Life cycle - MetaModel::Init_DefineState("InStore", array("label"=>"InStore", "description"=>"Device in store", "attribute_inherit"=>null, + MetaModel::Init_DefineState("InStore", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Shipped", array("label"=>"Shipped", "description"=>"The device had been shipped to future location", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Shipped", array("attribute_inherit"=>null, "attribute_list"=>array("location_id"=>OPT_ATT_MANDATORY,"serial_number"=>OPT_ATT_MANDATORY,"shipment_number"=>OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("Plugged", array("label"=>"Plugged", "description"=>"The device is connected to the network", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Plugged", array("attribute_inherit"=>null, "attribute_list"=>array("location_id"=>OPT_ATT_MANDATORY,"mgmt_ip"=>OPT_ATT_MANDATORY,"name"=>OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("ProductionCandidate", array("label"=>"Pre-Production", "description"=>"The device is ready to be move to production", "attribute_inherit"=>null, + MetaModel::Init_DefineState("ProductionCandidate", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("InProduction", array("label"=>"Production", "description"=>"The device is on production", "attribute_inherit"=>null, + MetaModel::Init_DefineState("InProduction", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("BeingDeconfigured", array("label"=>"BeingDeconfigured", "description"=>"The device is about to be removed from is current location", "attribute_inherit"=>null, + MetaModel::Init_DefineState("BeingDeconfigured", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The device is no more used", "attribute_inherit"=>null, + MetaModel::Init_DefineState("Obsolete", array("attribute_inherit"=>null, "attribute_list"=>array())); - MetaModel::Init_DefineStimulus("ev_store", new StimulusUserAction(array("label"=>"Store this server", "description"=>"This server is move to storage"))); - MetaModel::Init_DefineStimulus("ev_ship", new StimulusUserAction(array("label"=>"Ship this server", "description"=>"This server is shipped to futur location"))); - MetaModel::Init_DefineStimulus("ev_plug", new StimulusUserAction(array("label"=>"Plug this server", "description"=>"The server is pluuged on the network"))); - MetaModel::Init_DefineStimulus("ev_configuration_finished", new StimulusUserAction(array("label"=>"Configuration finished", "description"=>"The device is ready to move to production evaluation"))); - MetaModel::Init_DefineStimulus("ev_val_failed", new StimulusUserAction(array("label"=>"Review configuration", "description"=>"The configuration for this server is not completed"))); - MetaModel::Init_DefineStimulus("ev_mtp", new StimulusUserAction(array("label"=>"Move to Production", "description"=>"The server is moved to production"))); - MetaModel::Init_DefineStimulus("ev_decommission", new StimulusUserAction(array("label"=>"Decommission", "description"=>"The server is being decommissioned"))); - MetaModel::Init_DefineStimulus("ev_obsolete", new StimulusUserAction(array("label"=>"Obsolete", "description"=>"The server is no more used"))); - MetaModel::Init_DefineStimulus("ev_recycle", new StimulusUserAction(array("label"=>"Recycle this server", "description"=>"The server is move back to deconfiguration"))); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_store", array())); // "Store this server / This server is moved to storage" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_ship", array())); // "Ship this server / This server is shipped to futur location" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_plug", array())); // "Plug this server / The server is pluuged on the network" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_configuration_finished", array())); // "Configuration finished / The device is ready to move to production evaluation" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_val_failed", array())); // "Review configuration / The configuration for this server is not completed" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_mtp", array())); // "Move to Production / The server is moved to production" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_decommission", array())); // "Decommission / The server is being decommissioned" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_obsolete", array())); // "Obsolete / The server is no more used" + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_recycle", array())); // "Recycle this server / The server is move back to deconfiguration" MetaModel::Init_DefineTransition("InStore", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("InStore", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null)); @@ -1305,7 +1302,6 @@ class bizServer extends bizDevice } - public function Generate(cmdbDataGenerator $oGenerator) { $this->Set('org_id', $oGenerator->GetOrganizationId()); @@ -1350,13 +1346,13 @@ class bizNetworkDevice extends bizDevice ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of device", "allowed_values"=>new ValueSetEnum("switch,router,firewall,load balancer,hub,WAN accelerator"), "sql"=>"type", "default_value"=>"switch", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("ios_version", array("label"=>"IOS version", "description"=>"IOS (software) version", "allowed_values"=>null, "sql"=>"ios_version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("memory", array("label"=>"Memory", "description"=>"Memory description", "allowed_values"=>null, "sql"=>"memory", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("switch,router,firewall,load balancer,hub,WAN accelerator"), "sql"=>"type", "default_value"=>"switch", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("ios_version", array("allowed_values"=>null, "sql"=>"ios_version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("memory", array("allowed_values"=>null, "sql"=>"memory", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("snmp_read", array("label"=>"SNMP Community (Read)", "description"=>"SNMP Read Community String", "allowed_values"=>null, "sql"=>"snmp_read", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("snmp_write", array("label"=>"SNMP Community (Write)", "description"=>"SNMP Write Community String", "allowed_values"=>null, "sql"=>"snmp_write", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("snmp_read", array("allowed_values"=>null, "sql"=>"snmp_read", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("snmp_write", array("allowed_values"=>null, "sql"=>"snmp_write", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("type"); @@ -1414,10 +1410,10 @@ class bizInfraGroup extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of groupe", "allowed_values"=>new ValueSetEnum("Monitoring,Reporting,list"), "sql"=>"type", "default_value"=>"list", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"usage of the Group", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_group_id", array("targetclass"=>"bizInfraGroup", "label"=>"Parent Group", "description"=>"including group", "allowed_values"=>null, "sql"=>"parent_group_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("parent_group_name", array("label"=>"Parent Group (Name)", "description"=>"name of the parent group", "allowed_values"=>null, "extkey_attcode"=> 'parent_group_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("Monitoring,Reporting,list"), "sql"=>"type", "default_value"=>"list", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_group_id", array("targetclass"=>"bizInfraGroup", "allowed_values"=>null, "sql"=>"parent_group_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_group_name", array("allowed_values"=>null, "extkey_attcode"=> 'parent_group_id', "target_attcode"=>"name"))); MetaModel::Init_InheritFilters(); @@ -1497,12 +1493,12 @@ class bizApplication extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Hosting device", "description"=>"The device where application is installed", "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Hosting device", "description"=>"Name of the device where application is installed", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installation Date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("version", array("label"=>"Version", "description"=>"Application version", "allowed_values"=>null, "sql"=>"version", "default_value"=>"undefined", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("function", array("label"=>"Function", "description"=>"Function provided by this application", "allowed_values"=>null, "sql"=>"function", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"undefined", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("function", array("allowed_values"=>null, "sql"=>"function", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); @@ -1529,7 +1525,6 @@ class bizApplication extends logInfra { case "impacts": $aRels = array( - "client app" => array("sQuery"=>"bizApplication: PKEY IS client_id IN (lnkClientServer: server_id = \$[this.pkey::])", "bPropagate"=>true, "iDistance"=>3), ); return array_merge($aRels, parent::GetRelationQueries($sRelCode)); } @@ -1579,12 +1574,12 @@ class lnkInfraGrouping extends cmdbAbstractObject "display_template" => "../business/templates/default.html", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"Infrastructure part of the group", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure Name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_group_id", array("targetclass"=>"bizInfraGroup", "jointype"=> '', "label"=>"Group", "description"=>"Name of the group", "allowed_values"=>null, "sql"=>"infra_group_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("group_name", array("label"=>"Group Name", "description"=>"Name of the group containing infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_group_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Relation", "description"=>"Relation between this group and infra", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"none", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_group_id", array("targetclass"=>"bizInfraGroup", "jointype"=> '', "allowed_values"=>null, "sql"=>"infra_group_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("group_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_group_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"none", "is_null_allowed"=>true, "depends_on"=>array()))); // impact should modelized: enum (eg: if the group si dead when infra is dead) MetaModel::Init_AddFilterFromAttribute("infra_id"); @@ -1638,12 +1633,12 @@ class lnkClientServer extends logRealObject ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("client_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Client", "description"=>"The client part of the link", "allowed_values"=>null, "sql"=>"client_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("client_name", array("label"=>"Client", "description"=>"Name of the client", "allowed_values"=>null, "extkey_attcode"=> 'client_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("server_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Server", "description"=>"the server part of the link", "allowed_values"=>null, "sql"=>"server_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("server_name", array("label"=>"Server", "description"=>"Name of the server", "allowed_values"=>null, "extkey_attcode"=> 'server_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("relation", array("label"=>"Relation", "description"=>"Type of relation between both application", "allowed_values"=>null, "sql"=>"relation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("client_id", array("targetclass"=>"bizApplication", "jointype"=> '', "allowed_values"=>null, "sql"=>"client_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("client_name", array("allowed_values"=>null, "extkey_attcode"=> 'client_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("server_id", array("targetclass"=>"bizApplication", "jointype"=> '', "allowed_values"=>null, "sql"=>"server_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("server_name", array("allowed_values"=>null, "extkey_attcode"=> 'server_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("relation", array("allowed_values"=>null, "sql"=>"relation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("client_id"); MetaModel::Init_AddFilterFromAttribute("server_id"); @@ -1685,13 +1680,13 @@ class bizPatch extends logRealObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>new ValueSetEnum('production,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Device", "description"=>"The Device where patch is installed", "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device Name", "description"=>"Name of the impacted device", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installation Date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"description du patch", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("patch_type", array("label"=>"Type", "description"=>"type de patch", "allowed_values"=>new ValueSetEnum("OS,Application"), "sql"=>"patch_type", "default_value"=>"OS", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("patch_type", array("allowed_values"=>new ValueSetEnum("OS,Application"), "sql"=>"patch_type", "default_value"=>"OS", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); diff --git a/business/test_farm.class.inc.php b/business/test_farm.class.inc.php index 2327d073f1..fbb844c21b 100644 --- a/business/test_farm.class.inc.php +++ b/business/test_farm.class.inc.php @@ -46,11 +46,11 @@ class Animal extends cmdbObject ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeEnum("sex", array("label"=>"sex", "description"=>"sex", "allowed_values"=>new ValueSetEnum('male, female'), "sql"=>"sex", "default_value"=>"male", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("species", array("label"=>"species", "description"=>"species", "allowed_values"=>null, "sql"=>"species", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("speed", array("label"=>"walk speed", "description"=>"maximum possible speed m.s-1", "allowed_values"=>null, "sql"=>"speed", "default_value"=>4, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("mother", array("label"=>"mother", "description"=>"mother", "allowed_values"=>null, "sql"=>"mother", "targetclass"=>"Animal", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("father", array("label"=>"father", "description"=>"father", "allowed_values"=>null, "sql"=>"father", "targetclass"=>"Animal", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("sex", array("allowed_values"=>new ValueSetEnum('male, female'), "sql"=>"sex", "default_value"=>"male", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("species", array("allowed_values"=>null, "sql"=>"species", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("speed", array("allowed_values"=>null, "sql"=>"speed", "default_value"=>4, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("mother", array("allowed_values"=>null, "sql"=>"mother", "targetclass"=>"Animal", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("father", array("allowed_values"=>null, "sql"=>"father", "targetclass"=>"Animal", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("sex"); @@ -82,12 +82,12 @@ class Mammal extends Animal ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("height", array("label"=>"height", "description"=>"size in centimeters", "allowed_values"=>null, "sql"=>"height", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("birth", array("label"=>"birth date", "description"=>"birth date", "allowed_values"=>null, "sql"=>"birth", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("member", array("label"=>"member", "description"=>"leader", "allowed_values"=>null, "sql"=>"member", "targetclass"=>"Group", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("height", array("allowed_values"=>null, "sql"=>"height", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("birth", array("allowed_values"=>null, "sql"=>"birth", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("member", array("allowed_values"=>null, "sql"=>"member", "targetclass"=>"Group", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); -// ? MetaModel::Init_AddAttribute(new AttributeLinkedSet("a2a", array("label"=>"animal to animal", "description"=>"interanimal relations", "depends_on"=>array(), "linked_class"=>"Animal2animal", "ext_key_to_me"=>"animal1", "count_min"=>0, "count_max"=>10, "allowed_values"=>null))); +// ? MetaModel::Init_AddAttribute(new AttributeLinkedSet("a2a", array("depends_on"=>array(), "linked_class"=>"Animal2animal", "ext_key_to_me"=>"animal1", "count_min"=>0, "count_max"=>10, "allowed_values"=>null))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -169,7 +169,7 @@ class FlyingBird extends Bird MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('pie', 'corbeau'))); - MetaModel::Init_AddAttribute(new AttributeInteger("flyingspeed", array("label"=>"flying speed", "description"=>"flying at ms.s-1", "allowed_values"=>null, "sql"=>"headcount", "default_value"=>10, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("flyingspeed", array("allowed_values"=>null, "sql"=>"headcount", "default_value"=>10, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("flyingspeed"); @@ -198,8 +198,8 @@ class AnimalRelation extends cmdbObject //MetaModel::Init_InheritAttributes(); // What makes it being a link... - MetaModel::Init_AddAttribute(new AttributeExternalKey("animal1", array("label"=>"source", "description"=>"the animal which does ...", "allowed_values"=>null, "sql"=>"a1", "targetclass"=>"Animal", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("animal2", array("label"=>"target", "description"=>"the animal to which something is done...", "allowed_values"=>null, "sql"=>"a2", "targetclass"=>"Animal", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("animal1", array("allowed_values"=>null, "sql"=>"a1", "targetclass"=>"Animal", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("animal2", array("allowed_values"=>null, "sql"=>"a2", "targetclass"=>"Animal", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("animal1"); @@ -228,7 +228,7 @@ class EaterToEaten extends AnimalRelation ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEnum("DeadOrAlive", array("label"=>"DeadOrAlive", "description"=>"State in which it is ok for the eater to proceed", "allowed_values"=>new ValueSetEnum('dead, fresh, cooked'), "sql"=>"deadoralive", "default_value"=>"fresh", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("DeadOrAlive", array("allowed_values"=>new ValueSetEnum('dead, fresh, cooked'), "sql"=>"deadoralive", "default_value"=>"fresh", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("DeadOrAlive"); @@ -255,10 +255,10 @@ class Group extends cmdbObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("leader", array("label"=>"leader", "description"=>"leader", "allowed_values"=>null, "sql"=>"leader", "targetclass"=>"Mammal", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("leader_name", array("label"=>"Leader Name", "description"=>"Name of the leader (defined on Mammal)", "allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("leader_speed", array("label"=>"Leader Name", "description"=>"Speed of the leader (defined on Animal)", "allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"speed"))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("leader", array("allowed_values"=>null, "sql"=>"leader", "targetclass"=>"Mammal", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("leader_name", array("allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("leader_speed", array("allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"speed"))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 6f8dfcb0ff..64dbb60e30 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -34,10 +34,10 @@ abstract class Action extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"In production or ?", "allowed_values"=>new ValueSetEnum(array('test'=>'Being tested' ,'enabled'=>'In production', 'disabled'=>'Inactive')), "sql"=>"status", "default_value"=>"test", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_triggers", array("label"=>"Related Triggers", "description"=>"Triggers linked to this action", "linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum(array('test'=>'Being tested' ,'enabled'=>'In production', 'disabled'=>'Inactive')), "sql"=>"status", "default_value"=>"test", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_triggers", array("linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("name"); @@ -156,16 +156,16 @@ class ActionEmail extends ActionNotification MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("label"=>"Test recipient", "description"=>"Detination in case status is set to \"Test\"", "allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("from", array("label"=>"From", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("label"=>"Reply to", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeOQL("to", array("label"=>"To", "description"=>"Destination of the email", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("label"=>"Cc", "description"=>"Carbon Copy", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("label"=>"bcc", "description"=>"Blind Carbon Copy", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("label"=>"subject", "description"=>"Title of the email", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("label"=>"body", "description"=>"Contents of the email", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("label"=>"importance", "description"=>"Importance flag", "allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 54ab4d1d98..daff6191e8 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -96,7 +96,7 @@ abstract class AttributeDefinition // to be overloaded static protected function ListExpectedParams() { - return array("label", "description"); + return array(); } private function ConsistencyCheck() @@ -133,9 +133,7 @@ abstract class AttributeDefinition public function GetNullValue() {return null;} public function GetCode() {return $this->m_sCode;} public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);} - public function Obsolete_GetLabel() {return $this->Get("label");} public function GetDescription() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'+', '');} - public function Obsolete_GetDescription() {return $this->Get("description");} public function GetValuesDef() {return null;} public function GetPrerequisiteAttributes() {return array();} //public function IsSearchableStd() {return $this->Get("search_std");} @@ -1276,7 +1274,7 @@ class AttributeURL extends AttributeString static protected function ListExpectedParams() { //return parent::ListExpectedParams(); - return array_merge(parent::ListExpectedParams(), array("target", "label")); + return array_merge(parent::ListExpectedParams(), array("target")); } public function GetType() {return "Url";} diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index 02d011a040..d2ebfd7803 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -31,8 +31,8 @@ class CMDBChange extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"misc. info", "description"=>"caller's defined information", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("date"); diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 2835992edf..a9ef0047e3 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -32,11 +32,11 @@ class CMDBChangeOp extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("label"=>"change", "description"=>"change", "allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("label"=>"date", "description"=>"date and time of the change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("label"=>"user", "description"=>"who made this change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo"))); - MetaModel::Init_AddAttribute(new AttributeString("objclass", array("label"=>"object class", "description"=>"object class", "allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("objkey", array("label"=>"object id", "description"=>"object id", "allowed_values"=>null, "sql"=>"objkey", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo"))); + MetaModel::Init_AddAttribute(new AttributeString("objclass", array("allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("objkey", array("allowed_values"=>null, "sql"=>"objkey", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("objclass"); MetaModel::Init_AddFilterFromAttribute("objkey"); @@ -174,7 +174,7 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"Attribute", "description"=>"code of the modified property", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("attcode"); @@ -215,8 +215,8 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("label"=>"Previous value", "description"=>"previous value of the attribute", "allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("label"=>"New value", "description"=>"new value of the attribute", "allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("oldvalue"); @@ -317,7 +317,7 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeBlob("prevdata", array("label"=>"Previous data", "description"=>"previous contents of the attribute", "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBlob("prevdata", array("depends_on"=>array()))); MetaModel::Init_InheritFilters(); @@ -387,7 +387,7 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeText("prevdata", array("label"=>"Previous data", "description"=>"previous contents of the attribute", "allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("prevdata", array("allowed_values"=>null, "sql"=>"prevdata", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); diff --git a/core/dbobject.class.php b/core/dbobject.class.php index f272aba729..e2ba903e33 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -454,11 +454,38 @@ abstract class DBObject } else { - $aStates = MetaModel::EnumStates(get_class($this)); - return $aStates[$this->Get($sStateAttCode)]['label']; + return $this->Get($sStateAttCode); + return MetaModel::GetStateLabel(get_class($this), $sStateAttCode); } } + public function GetStateLabel() + { + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (empty($sStateAttCode)) + { + return ''; + } + else + { + $sStateValue = $this->Get($sStateAttCode); + return MetaModel::GetStateLabel(get_class($this), $sStateValue); + } + } + + public function GetStateDescription() + { + $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + if (empty($sStateAttCode)) + { + return ''; + } + else + { + $sStateValue = $this->Get($sStateAttCode); + return MetaModel::GetStateDescription(get_class($this), $sStateValue); + } + } /** * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) * for the given attribute in the current state of the object diff --git a/core/event.class.inc.php b/core/event.class.inc.php index cbac9e792e..56b20db41b 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -32,9 +32,9 @@ class Event extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeText("message", array("label"=>"message", "description"=>"short description of the event", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"user info", "description"=>"identification of the user that was doing the action that triggered this event", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("message"); @@ -70,9 +70,9 @@ class EventNotification extends Event ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> "", "label"=>"Trigger", "description"=>"user account", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("object_id", array("label"=>"Object id", "description"=>"object id (class defined by the trigger ?)", "allowed_values"=>null, "sql"=>"object_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> "", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> "", "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("object_id", array("allowed_values"=>null, "sql"=>"object_id", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("trigger_id"); @@ -110,12 +110,12 @@ class EventNotificationEmail extends EventNotification ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeText("to", array("label"=>"TO", "description"=>"TO", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("cc", array("label"=>"CC", "description"=>"CC", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("bcc", array("label"=>"BCC", "description"=>"BCC", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("from", array("label"=>"From", "description"=>"Sender of the message", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("subject", array("label"=>"Subject", "description"=>"Subject", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("body", array("label"=>"Body", "description"=>"Body", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); // Display lists @@ -150,13 +150,13 @@ class EventIssue extends Event ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("issue", array("label"=>"Issue", "description"=>"What happened", "allowed_values"=>null, "sql"=>"issue", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"What are the consequences", "allowed_values"=>null, "sql"=>"impact", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("page", array("label"=>"Page", "description"=>"HTTP entry point", "allowed_values"=>null, "sql"=>"page", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("arguments_post", array("label"=>"Posted arguments", "description"=>"HTTP POST arguments", "allowed_values"=>null, "sql"=>"arguments_post", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("arguments_get", array("label"=>"URL arguments", "description"=>"HTTP GET arguments", "allowed_values"=>null, "sql"=>"arguments_get", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("callstack", array("label"=>"Callstack", "description"=>"Call stack", "allowed_values"=>null, "sql"=>"callstack", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeBlob("data", array("label"=>"Data", "description"=>"More information", "allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("issue", array("allowed_values"=>null, "sql"=>"issue", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("page", array("allowed_values"=>null, "sql"=>"page", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("arguments_post", array("allowed_values"=>null, "sql"=>"arguments_post", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("arguments_get", array("allowed_values"=>null, "sql"=>"arguments_get", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("callstack", array("allowed_values"=>null, "sql"=>"callstack", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBlob("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("issue"); @@ -193,13 +193,13 @@ class EventWebService extends Event ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("verb", array("label"=>"Verb", "description"=>"Name of the operation", "allowed_values"=>null, "sql"=>"verb", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - //MetaModel::Init_AddAttribute(new AttributeStructure("arguments", array("label"=>"Arguments", "description"=>"Operation arguments", "allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeBoolean("result", array("label"=>"Result", "description"=>"Overall success/failure", "allowed_values"=>null, "sql"=>"result", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("log_info", array("label"=>"Info log", "description"=>"Result info log", "allowed_values"=>null, "sql"=>"log_info", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("log_warning", array("label"=>"Warning log", "description"=>"Result warning log", "allowed_values"=>null, "sql"=>"log_warning", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("log_error", array("label"=>"Error log", "description"=>"Result error log", "allowed_values"=>null, "sql"=>"log_error", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("data", array("label"=>"Data", "description"=>"Result data", "allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("verb", array("allowed_values"=>null, "sql"=>"verb", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + //MetaModel::Init_AddAttribute(new AttributeStructure("arguments", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBoolean("result", array("allowed_values"=>null, "sql"=>"result", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("log_info", array("allowed_values"=>null, "sql"=>"log_info", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("log_warning", array("allowed_values"=>null, "sql"=>"log_warning", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("log_error", array("allowed_values"=>null, "sql"=>"log_error", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index f114438b8d..88c06a0779 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -198,11 +198,6 @@ abstract class MetaModel $sStringCode = 'Class:'.$sClass; return Dict::S($sStringCode, $sClass); } - final static public function Obsolete_GetName($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["name"]; - } final static public function GetCategory($sClass) { self::_check_subclass($sClass); @@ -219,11 +214,6 @@ abstract class MetaModel $sStringCode = 'Class:'.$sClass.'+'; return Dict::S($sStringCode, ''); } - final static public function Obsolete_GetClassDescription($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["description"]; - } final static public function IsAutoIncrementKey($sClass) { self::_check_subclass($sClass); @@ -686,6 +676,25 @@ abstract class MetaModel } } + public static function GetStateLabel($sClass, $sStateValue) + { + $sStateAttrCode = self::GetStateAttributeCode($sClass); + return Dict::S("Class:$sClass/Attribute:$sStateAttrCode/Value:$sStateValue+"); + + // I've decided the current implementation, because I need + // to get the description as well -GetAllowedValues does not render the description, + // so far... + // Could have been implemented the following way (not tested + // $oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode); + // $aAllowedValues = $oStateAttrDef->GetAllowedValues(); + // return $aAllowedValues[$sStateValue]; + } + public static function GetStateDescription($sClass, $sStateValue) + { + $sStateAttrCode = self::GetStateAttributeCode($sClass); + return Dict::S("Class:$sClass/Attribute:$sStateAttrCode/Value:$sStateValue+"); + } + public static function EnumTransitions($sClass, $sStateCode) { if (array_key_exists($sClass, self::$m_aTransitions)) @@ -858,8 +867,6 @@ abstract class MetaModel $sRootClass = self::GetRootClass($sClass); $sDbFinalClassField = self::DBGetClassField($sRootClass); $oClassAtt = new AttributeClass($sClassAttCode, array( - "label"=>"Class", - "description"=>"Real (final) object class", "class_category"=>null, "more_values"=>'', "sql"=>$sDbFinalClassField, @@ -1138,15 +1145,14 @@ abstract class MetaModel self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); } - public static function Init_DefineStimulus($sStimulusCode, $oStimulus) + public static function Init_DefineStimulus($oStimulus) { $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; + self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus; // I wanted to simplify the syntax of the declaration of objects in the biz model // Therefore, the reference to the host class is set there $oStimulus->SetHostClass($sTargetClass); - $oStimulus->SetCode($sStimulusCode); } public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) @@ -2162,18 +2168,31 @@ abstract class MetaModel $sRes .= " 'Class:$sClass/Attribute:$sAttCode+' => '".$oAttDef->GetDescription()."',\n"; if ($oAttDef instanceof AttributeEnum) { - foreach ($oAttDef->GetAllowedValues() as $sKey => $value) + if (self::GetStateAttributeCode($sClass) == $sAttCode) { - $sValue = str_replace("'", "\\'", $value); - $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey' => '$sValue',\n"; - $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey+' => '$sValue',\n"; + foreach (self::EnumStates($sClass) as $sStateCode => $aStateData) + { + $sValue = str_replace("'", "\\'", $aStateData['label']); + $sValuePlus = str_replace("'", "\\'", $aStateData['description']); + $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sStateCode' => '$sValue',\n"; + $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sStateCode+' => '$sValuePlus',\n"; + } + } + else + { + foreach ($oAttDef->GetAllowedValues() as $sKey => $value) + { + $sValue = str_replace("'", "\\'", $value); + $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey' => '$sValue',\n"; + $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey+' => '$sValue',\n"; + } } } } foreach(self::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) { - $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode' => '".$oStimulus->Get('label')."',\n"; - $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode+' => '".$oStimulus->Get('description')."',\n"; + $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode' => '".$oStimulus->GetLabel()."',\n"; + $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode+' => '".$oStimulus->GetDescription()."',\n"; } $sRes .= "));\n"; @@ -2831,14 +2850,19 @@ abstract class MetaModel { self::_check_subclass($sClass); + if (strlen($sClassAlias) == 0) + { + $sClassAlias = $sClass; + } + // Compound objects: if available, get the final object class // - if (!array_key_exists("finalclass", $aRow)) + if (!array_key_exists($sClassAlias."finalclass", $aRow)) { // Either this is a bug (forgot to specify a root class with a finalclass field // Or this is the expected behavior, because the object is not made of several tables } - elseif (empty($aRow["finalclass"])) + elseif (empty($aRow[$sClassAlias."finalclass"])) { // The data is missing in the DB // @#@ possible improvement: check that the class is valid ! @@ -2849,7 +2873,7 @@ abstract class MetaModel else { // do the job for the real target class - $sClass = $aRow["finalclass"]; + $sClass = $aRow[$sClassAlias."finalclass"]; } return new $sClass($aRow, $sClassAlias); } diff --git a/core/stimulus.class.inc.php b/core/stimulus.class.inc.php index 76ce978d4e..47ef17c195 100644 --- a/core/stimulus.class.inc.php +++ b/core/stimulus.class.inc.php @@ -21,11 +21,10 @@ class ObjectStimulus private $m_sHostClass = null; private $m_sCode = null; - public function __construct($aParams) + public function __construct($sCode, $aParams) { - // obsolete: $this->m_aParams = $aParams; - $this->m_aParams['label'] = 'foo'; - $this->m_aParams['description'] = 'foo'; + $this->m_sCode = $sCode; + $this->m_aParams = $aParams; $this->ConsistencyCheck(); } @@ -37,24 +36,27 @@ class ObjectStimulus { return $this->m_sHostClass; } - public function SetCode($sCode) - { - $this->m_sCode = $sCode; - $this->m_aParams['label'] = Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode, $this->m_sCode); - $this->m_aParams['description'] = Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode.'+', ''); - } public function GetCode() { return $this->m_sCode; } - public function Get($sParamName) {return $this->m_aParams[$sParamName];} + public function GetLabel() + { + return Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode, $this->m_sCode); + } + public function GetDescription() + { + return Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode.'+', ''); + } + +// obsolete- public function Get($sParamName) {return $this->m_aParams[$sParamName];} // Note: I could factorize this code with the parameter management made for the AttributeDef class // to be overloaded static protected function ListExpectedParams() { - return array("label", "description"); + return array(); } private function ConsistencyCheck() diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 511bb42a32..cda984a0d3 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -33,9 +33,9 @@ class Trigger extends cmdbAbstractObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("linked_actions", array("label"=>"Triggered actions", "description"=>"Actions performed when the trigger is activated", "linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"trigger_id", "ext_key_to_remote"=>"action_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("linked_actions", array("linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"trigger_id", "ext_key_to_remote"=>"action_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("description"); @@ -85,7 +85,7 @@ class TriggerOnObject extends Trigger ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("label"=>"Target class", "description"=>"label", "class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("target_class", array("class_category"=>"bizmodel", "more_values"=>null, "sql"=>"target_class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("target_class"); @@ -120,7 +120,7 @@ class TriggerOnStateChange extends TriggerOnObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("state", array("label"=>"State", "description"=>"label", "allowed_values"=>null, "sql"=>"state", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("state", array("allowed_values"=>null, "sql"=>"state", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_InheritFilters(); MetaModel::Init_AddFilterFromAttribute("state"); @@ -253,11 +253,11 @@ class lnkTriggerAction extends cmdbAbstractObject "display_template" => "", ); MetaModel::Init_Params($aParams); - MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> '', "label"=>"Action", "description"=>"The action to be executed", "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("action_name", array("label"=>"Action Name", "description"=>"Name of the action", "allowed_values"=>null, "extkey_attcode"=> 'action_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> '', "label"=>"Trigger", "description"=>"Trigger", "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("trigger_name", array("label"=>"Trigger Name", "description"=>"Name of the trigger", "allowed_values"=>null, "extkey_attcode"=> 'trigger_id', "target_attcode"=>"description"))); - MetaModel::Init_AddAttribute(new AttributeInteger("order", array("label"=>"Order", "description"=>"Actions execution order", "allowed_values"=>null, "sql"=>"order", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("action_id", array("targetclass"=>"Action", "jointype"=> '', "allowed_values"=>null, "sql"=>"action_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("action_name", array("allowed_values"=>null, "extkey_attcode"=> 'action_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("trigger_id", array("targetclass"=>"Trigger", "jointype"=> '', "allowed_values"=>null, "sql"=>"trigger_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("trigger_name", array("allowed_values"=>null, "extkey_attcode"=> 'trigger_id', "target_attcode"=>"description"))); + MetaModel::Init_AddAttribute(new AttributeInteger("order", array("allowed_values"=>null, "sql"=>"order", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddFilterFromAttribute("action_id"); MetaModel::Init_AddFilterFromAttribute("trigger_id"); diff --git a/pages/UI.php b/pages/UI.php index db981b88b4..6a889749da 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1,5 +1,5 @@ add("

      Creation of a new $sClassLabel

      "); if (!empty($sStateCode)) { - $aStates = MetaModel::EnumStates($sClass); - $sStateLabel = $aStates[$sStateCode]['label']; + $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); } $aWizardSteps = $oWizard->GetWizardStructure(); @@ -475,7 +474,7 @@ try { $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); if (!is_null($rawValue)) - { + { $aAttributes[$sAttCode] = trim($rawValue); $previousValue = $oObj->Get($sAttCode); if ($previousValue !== $aAttributes[$sAttCode]) @@ -880,18 +879,18 @@ try $aStimuli = MetaModel::EnumStimuli($sClass); if (!isset($aTransitions[$sStimulus])) { - $oP->add("

      Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

      \n"); + $oP->add("

      Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()}.

      \n"); } else { - $sActionLabel = $aStimuli[$sStimulus]->Get('label'); - $sActionDetails = $aStimuli[$sStimulus]->Get('description'); + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); $aTransition = $aTransitions[$sStimulus]; $sTargetState = $aTransition['target_state']; $aTargetStates = MetaModel::EnumStates($sClass); $oP->add("
      \n"); $oP->add("

      $sActionLabel - {$oObj->GetName()}

      \n"); - //$oP->add("

      Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

      \n"); + //$oP->add("

      Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()} to target state: $sTargetState.

      \n"); $oP->add("
      \n"); $oObj->DisplayBareDetails($oP); $aTargetState = $aTargetStates[$sTargetState]; @@ -958,7 +957,7 @@ try $aStimuli = MetaModel::EnumStimuli($sClass); if (!isset($aTransitions[$sStimulus])) { - $oP->add("

      Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetState()}.

      \n"); + $oP->add("

      Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()}.

      \n"); } else if (!utils::IsTransactionValid($sTransactionId)) { @@ -966,15 +965,15 @@ try } else { - $sActionLabel = $aStimuli[$sStimulus]->Get('label'); - $sActionDetails = $aStimuli[$sStimulus]->Get('description'); + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); $aTransition = $aTransitions[$sStimulus]; $sTargetState = $aTransition['target_state']; $aTargetStates = MetaModel::EnumStates($sClass); $oP->add("
      \n"); $oP->add("

      $sActionLabel - {$oObj->GetName()}

      \n"); $oP->add("

      $sActionDetails

      \n"); - $oP->add("

      Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetState()} to target state: $sTargetState.

      \n"); + $oP->add("

      Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()} to target state: $sTargetState.

      \n"); $oP->add("
      \n"); $aTargetState = $aTargetStates[$sTargetState]; //print_r($aTransitions[$sStimulus]); @@ -1144,20 +1143,20 @@ try } } ////MetaModel::ShowQueryTrace(); - $oP->output(); -} + $oP->output(); +} catch(Exception $e) { - require_once('../setup/setuppage.class.inc.php'); - $oP = new SetupWebPage('iTop - fatal error'); + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage('iTop - fatal error'); $oP->add("

      Fatal Error, iTop cannot continue

      \n"); $oP->error("Error: '".$e->getMessage()."'"); $oP->output(); } catch(CoreException $e) { - require_once('../setup/setuppage.class.inc.php'); - $oP = new SetupWebPage('iTop - fatal error'); + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage('iTop - fatal error'); $oP->add("

      Fatal Error, iTop cannot continue

      \n"); $oP->error("Error: '".$e->getHtmlDesc()."'"); $oP->output(); diff --git a/pages/graphviz.php b/pages/graphviz.php index 6552db0850..b3d5874dbb 100644 --- a/pages/graphviz.php +++ b/pages/graphviz.php @@ -35,20 +35,20 @@ function GraphvizLifecycle($sClass) foreach ($aStates as $sStateCode => $aStateDef) { - $sStateLabel = $aStates[$sStateCode]['label']; - $sStateDescription = $aStates[$sStateCode]['description']; + $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); + $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); foreach(MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) { $aStatesLinks[$sStateCode]['out']++; $aStatesLinks[$aTransitionDef['target_state']]['in']++; - $sStimulusLabel = $aStimuli[$sStimulusCode]->Get('label'); - $sTargetStateLabel = $aStates[$aTransitionDef['target_state']]['label']; + $sStimulusLabel = $aStimuli[$sStimulusCode]->GetLabel(); + $sTargetStateLabel = MetaModel::GetStateLabel($sClass, $aTransitionDef['target_state']); $sDotFileContent .= "\t$sStateCode -> {$aTransitionDef['target_state']} [ label=\"$sStimulusLabel\"];\n"; } } foreach($aStates as $sStateCode => $aStateDef) { - $sStateLabel = str_replace(' ', '\n', $aStates[$sStateCode]['label']); + $sStateLabel = str_replace(' ', '\n', MetaModel::GetStateLabel($sClass, $sStateCode)); if ( ($aStatesLinks[$sStateCode]['in'] == 0) || ($aStatesLinks[$sStateCode]['out'] == 0)) { $sDotFileContent .= "\t$sStateCode [ shape=doublecircle,label=\"$sStateLabel\"];\n"; diff --git a/pages/schema.php b/pages/schema.php index d472424d42..bbc9738205 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -165,14 +165,14 @@ function DisplayLifecycle($oPage, $sClass) $oPage->add("
        \n"); foreach ($aStates as $sStateCode => $aStateDef) { - $sStateLabel = $aStates[$sStateCode]['label']; - $sStateDescription = $aStates[$sStateCode]['description']; + $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); + $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); $oPage->add("
      • $sStateLabel ($sStateDescription)
      • \n"); $oPage->add("
          \n"); foreach(MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) { - $sStimulusLabel = $aStimuli[$sStimulusCode]->Get('label'); - $sTargetStateLabel = $aStates[$aTransitionDef['target_state']]['label']; + $sStimulusLabel = $aStimuli[$sStimulusCode]->GetLabel(); + $sTargetStateLabel = MetaModel::GetStateLabel($sClass, $aTransitionDef['target_state']); if (count($aTransitionDef['actions']) > 0) { $sActions = " (".implode(', ', $aTransitionDef['actions']).")"; @@ -191,8 +191,8 @@ function DisplayLifecycle($oPage, $sClass) $oPage->add("
            \n"); foreach ($aStates as $sStateCode => $aStateDef) { - $sStateLabel = $aStates[$sStateCode]['label']; - $sStateDescription = $aStates[$sStateCode]['description']; + $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); + $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); $oPage->add("
          • $sStateLabel ($sStateDescription)
          • \n"); if (count($aStates[$sStateCode]['attribute_list']) > 0) { diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 2e439c8e0c..463dd92679 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -1003,9 +1003,12 @@ class TestBulkChangeOnFarm extends TestBizModel $oBulk = new BulkChange( 'Mammal', $aData, + // attributes array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), - array('name'), + // ext keys array() + // reconciliation + array('name'), ); $oMyChange = MetaModel::NewObject("CMDBChange"); From a84aa1799d9adba943ec19e733f42b2cd8c3d7c8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 20 Apr 2010 09:30:21 +0000 Subject: [PATCH 266/970] Fixed a bug related to the CSV templates SVN:trunk[349] --- pages/ajax.csvimport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index 05851feebd..4fff4d1dd1 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -221,7 +221,7 @@ switch($sOperation) { $oPage = new ajax_page(""); $oPage->no_cache(); - $oPage->add('


            '.$sClassDisplayName.'.csv

            '); + $oPage->add('


            '.$sClassDisplayName.'.csv

            '); $oPage->add('

            '); } break; From 1844ee3663cd2fdafe62d8d33a1311ad5dadf383 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 20 Apr 2010 12:45:21 +0000 Subject: [PATCH 267/970] Localization: Display of enums SVN:trunk[350] --- core/attributedef.class.inc.php | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index daff6191e8..d6188015c2 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -811,20 +811,7 @@ class AttributeEnum extends AttributeString public function GetAsHTML($sValue) { - $oValDef = $this->GetValuesDef(); - if ($oValDef) - { - $aValues = $oValDef->GetValues(array(), ""); - } - if (!empty($aValues) && array_key_exists($sValue, $aValues)) - { - $sLabel = $aValues[$sValue]; - } - else - { - // #@# todo - record an error silently... - $sLabel = $sValue; - } + $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, $sValue); // later, we could imagine a detailed description in the title return "".parent::GetAsHtml($sLabel).""; } From 1150df3e06af736433b102b207a0f4d8b901a85f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 12:59:23 +0000 Subject: [PATCH 268/970] - bug fix: history log was not saved when doing a bulk import. SVN:trunk[351] --- pages/csvimport.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 20af65f81c..1a76b111e5 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -294,7 +294,8 @@ function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true { $sUserString = UserRights::GetUser(); } - $oMyChange->Set("userinfo", $sUserString); + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); } $oBulk = new BulkChange( From cab47b5044d74df3384166d7e9986e11224104ee Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 20 Apr 2010 15:26:37 +0000 Subject: [PATCH 269/970] Localization: tools to report missing entries, and build the dictionary out of a data model made with the previous format SVN:trunk[352] --- core/attributedef.class.inc.php | 24 +++++++ core/metamodel.class.php | 122 ++++++++++++++++++++++++-------- core/stimulus.class.inc.php | 26 +++++++ pages/ITopConsultant.php | 18 ++++- 4 files changed, 157 insertions(+), 33 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index d6188015c2..76491c7f87 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -133,7 +133,31 @@ abstract class AttributeDefinition public function GetNullValue() {return null;} public function GetCode() {return $this->m_sCode;} public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);} + public function GetLabel_Obsolete() + { + // Written for compatibility with a data model written prior to version 0.9.1 + if (array_key_exists('label', $this->m_aParams)) + { + return $this->m_aParams['label']; + } + else + { + return $this->GetLabel(); + } + } public function GetDescription() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode.'+', '');} + public function GetDescription_Obsolete() + { + // Written for compatibility with a data model written prior to version 0.9.1 + if (array_key_exists('description', $this->m_aParams)) + { + return $this->m_aParams['description']; + } + else + { + return $this->GetDescription(); + } + } public function GetValuesDef() {return null;} public function GetPrerequisiteAttributes() {return array();} //public function IsSearchableStd() {return $this->Get("search_std");} diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 88c06a0779..f4470f5712 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -198,6 +198,19 @@ abstract class MetaModel $sStringCode = 'Class:'.$sClass; return Dict::S($sStringCode, $sClass); } + final static public function GetName_Obsolete($sClass) + { + // Written for compatibility with a data model written prior to version 0.9.1 + self::_check_subclass($sClass); + if (array_key_exists('name', self::$m_aClassParams[$sClass])) + { + return self::$m_aClassParams[$sClass]['name']; + } + else + { + return self::GetName($sClass); + } + } final static public function GetCategory($sClass) { self::_check_subclass($sClass); @@ -214,6 +227,19 @@ abstract class MetaModel $sStringCode = 'Class:'.$sClass.'+'; return Dict::S($sStringCode, ''); } + final static public function GetClassDescription_Obsolete($sClass) + { + // Written for compatibility with a data model written prior to version 0.9.1 + self::_check_subclass($sClass); + if (array_key_exists('description', self::$m_aClassParams[$sClass])) + { + return self::$m_aClassParams[$sClass]['description']; + } + else + { + return self::GetDescription($sClass); + } + } final static public function IsAutoIncrementKey($sClass) { self::_check_subclass($sClass); @@ -679,7 +705,7 @@ abstract class MetaModel public static function GetStateLabel($sClass, $sStateValue) { $sStateAttrCode = self::GetStateAttributeCode($sClass); - return Dict::S("Class:$sClass/Attribute:$sStateAttrCode/Value:$sStateValue+"); + return Dict::S("Class:$sClass/Attribute:$sStateAttrCode/Value:$sStateValue", $sStateValue); // I've decided the current implementation, because I need // to get the description as well -GetAllowedValues does not render the description, @@ -692,7 +718,7 @@ abstract class MetaModel public static function GetStateDescription($sClass, $sStateValue) { $sStateAttrCode = self::GetStateAttributeCode($sClass); - return Dict::S("Class:$sClass/Attribute:$sStateAttrCode/Value:$sStateValue+"); + return Dict::S("Class:$sClass/Attribute:$sStateAttrCode/Value:$sStateValue+", ''); } public static function EnumTransitions($sClass, $sStateCode) @@ -2112,16 +2138,25 @@ abstract class MetaModel return $aDataDump; } - public static function MakeDictionaryTemplate() + protected static function MakeDictEntry($sKey, $sValueFromOldSystem, $sDefaultValue, &$bNotInDico) + { + $sValue = Dict::S($sKey, 'x-no-nothing'); + if ($sValue == 'x-no-nothing') + { + $bNotInDico = true; + $sValue = $sValueFromOldSystem; + if (strlen($sValue) == 0) + { + $sValue = $sDefaultValue; + } + } + return " '$sKey' => '".str_replace("'", "\\'", $sValue)."',\n"; + } + + public static function MakeDictionaryTemplate($sModules = '', $sOutputFilter = 'NotInDictionary') { $sRes = ''; - foreach (Dict::GetLanguages() as $sLanguageCode => $aLanguageData) - { - list($aMissing, $aUnexpected, $aNotTranslated, $aOK) = Dict::MakeStats($sLanguageCode, 'EN US'); - echo "

            Stats for language: $sLanguageCode

            \n"; - echo "
            • Missing:".count($aMissing)."
            • Unexpected:".count($aUnexpected)."
            • NotTranslated:".count($aNotTranslated)."
            • OK:".count($aOK)."
            \n"; - } $sRes .= "// Dictionnay conventions\n"; $sRes .= htmlentities("// Class:\n"); $sRes .= htmlentities("// Class:+\n"); @@ -2135,7 +2170,14 @@ abstract class MetaModel // Note: I did not use EnumCategories(), because a given class maybe found in several categories // Need to invent the "module", to characterize the origins of a class - $aModules = array('bizmodel', 'core/cmdb', 'gui' , 'application', 'addon/userrights'); + if (strlen($sModules) == 0) + { + $aModules = array('bizmodel', 'core/cmdb', 'gui' , 'application', 'addon/userrights'); + } + else + { + $aModules = explode(', ', $sModules); + } $sRes .= "//////////////////////////////////////////////////////////////////////\n"; $sRes .= "// Note: The classes have been grouped by categories: ".implode(', ', $aModules)."\n"; @@ -2152,51 +2194,71 @@ abstract class MetaModel { if (self::IsAbstract($sClass)) continue; - $sRes .= "//\n"; - $sRes .= "// Class: $sClass\n"; - $sRes .= "//\n"; - $sRes .= "\n"; - $sRes .= "Dict::Add('EN US', 'English', 'English', array(\n"; - $sRes .= " 'Class:$sClass' => '".self::GetName($sClass)."',\n"; - $sRes .= " 'Class:$sClass+' => '".self::GetClassDescription($sClass)."',\n"; + $bNotInDico = false; + + $sClassRes = "//\n"; + $sClassRes .= "// Class: $sClass\n"; + $sClassRes .= "//\n"; + $sClassRes .= "\n"; + $sClassRes .= "Dict::Add('EN US', 'English', 'English', array(\n"; + $sClassRes .= self::MakeDictEntry("Class:$sClass", self::GetName_Obsolete($sClass), $sClass, $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass+", self::GetClassDescription_Obsolete($sClass), '', $bNotInDico); foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { // Skip this attribute if not originaly defined in this class if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - $sRes .= " 'Class:$sClass/Attribute:$sAttCode' => '".$oAttDef->GetLabel()."',\n"; - $sRes .= " 'Class:$sClass/Attribute:$sAttCode+' => '".$oAttDef->GetDescription()."',\n"; + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode", $oAttDef->GetLabel_Obsolete(), $sAttCode, $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode+", $oAttDef->GetDescription_Obsolete(), '', $bNotInDico); if ($oAttDef instanceof AttributeEnum) { if (self::GetStateAttributeCode($sClass) == $sAttCode) { foreach (self::EnumStates($sClass) as $sStateCode => $aStateData) { - $sValue = str_replace("'", "\\'", $aStateData['label']); - $sValuePlus = str_replace("'", "\\'", $aStateData['description']); - $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sStateCode' => '$sValue',\n"; - $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sStateCode+' => '$sValuePlus',\n"; + if (array_key_exists('label', $aStateData)) + { + $sValue = $aStateData['label']; + } + else + { + $sValue = MetaModel::GetStateLabel($sClass, $sStateCode); + } + if (array_key_exists('description', $aStateData)) + { + $sValuePlus = $aStateData['description']; + } + else + { + $sValuePlus = MetaModel::GetStateDescription($sClass, $sStateCode); + } + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sStateCode", $sValue, '', $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sStateCode+", $sValuePlus, '', $bNotInDico); } } else { foreach ($oAttDef->GetAllowedValues() as $sKey => $value) { - $sValue = str_replace("'", "\\'", $value); - $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey' => '$sValue',\n"; - $sRes .= " 'Class:$sClass/Attribute:$sAttCode/Value:$sKey+' => '$sValue',\n"; + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sKey", $sValue, '', $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sKey+", $sValue, '', $bNotInDico); } } } } foreach(self::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) { - $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode' => '".$oStimulus->GetLabel()."',\n"; - $sRes .= " 'Class:$sClass/Stimulus:$sStimulusCode+' => '".$oStimulus->GetDescription()."',\n"; + $sClassRes .= self::MakeDictEntry("Class:$sClass/Stimulus:$sStimulusCode", $oStimulus->GetLabel_Obsolete(), '', $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass/Stimulus:$sStimulusCode+", $oStimulus->GetDescription_Obsolete(), '', $bNotInDico); } - $sRes .= "));\n"; - $sRes .= "\n"; + $sClassRes .= "));\n"; + $sClassRes .= "\n"; + + if ($bNotInDico || ($sOutputFilter != 'NotInDictionary')) + { + $sRes .= $sClassRes; + } } } return $sRes; diff --git a/core/stimulus.class.inc.php b/core/stimulus.class.inc.php index 47ef17c195..1d27bd0805 100644 --- a/core/stimulus.class.inc.php +++ b/core/stimulus.class.inc.php @@ -50,6 +50,32 @@ class ObjectStimulus return Dict::S('Class:'.$this->m_sHostClass.'/Stimulus:'.$this->m_sCode.'+', ''); } + public function GetLabel_Obsolete() + { + // Written for compatibility with a data model written prior to version 0.9.1 + if (array_key_exists('label', $this->m_aParams)) + { + return $this->m_aParams['label']; + } + else + { + return $this->GetLabel(); + } + } + + public function GetDescription_Obsolete() + { + // Written for compatibility with a data model written prior to version 0.9.1 + if (array_key_exists('description', $this->m_aParams)) + { + return $this->m_aParams['description']; + } + else + { + return $this->GetDescription(); + } + } + // obsolete- public function Get($sParamName) {return $this->m_aParams[$sParamName];} // Note: I could factorize this code with the parameter management made for the AttributeDef class diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index 9020e46b88..a14a058477 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -263,7 +263,8 @@ function printMenu($sConfigFile) echo "

            Target database: $sConfigFile

            \n"; echo "

            $sClassCount classes referenced in the model

            \n"; echo "
              "; - echo "
            • Dictionary
            • "; + echo "
            • Dictionary - missing entries (EN US)
            • "; + echo "
            • Dictionary - statistics by language
            • "; echo "
            • Biz model consistency
            • "; echo "
            • Show ZLists
            • "; echo "
            • Browse business model
            • "; @@ -472,10 +473,21 @@ else printMenu($sConfigFile); echo $sRes; break; + case "dictionarystats": + echo "Dictionary: statistics by language
              \n"; + foreach (Dict::GetLanguages() as $sLanguageCode => $aLanguageData) + { + list($aMissing, $aUnexpected, $aNotTranslated, $aOK) = Dict::MakeStats($sLanguageCode, 'EN US'); + echo "

              Stats for language: $sLanguageCode

              \n"; + echo "
              • Missing:".count($aMissing)."
              • Unexpected:".count($aUnexpected)."
              • NotTranslated:".count($aNotTranslated)."
              • OK:".count($aOK)."
              \n"; + } + break; case "checkdictionary": - echo "Dictionary template...
              \n"; + $sCategories = ReadMandatoryParam("categories"); + $sOutputFilter = ReadParam("outputfilter", ''); + echo "Dictionary: missing entries (categories: $sCategories, output: '$sOutputFilter')
              \n"; echo "
              \n";
              -			echo MetaModel::MakeDictionaryTemplate();
              +			echo MetaModel::MakeDictionaryTemplate($sCategories, $sOutputFilter);
               			echo "
              \n"; break; case "checkmodel": From 419969c0dd52e3449e95f2140463157ce25b5ccd Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 20 Apr 2010 15:33:08 +0000 Subject: [PATCH 270/970] Fixed bug on the last commit SVN:trunk[353] --- core/metamodel.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index f4470f5712..2cad799196 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2240,8 +2240,8 @@ abstract class MetaModel { foreach ($oAttDef->GetAllowedValues() as $sKey => $value) { - $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sKey", $sValue, '', $bNotInDico); - $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sKey+", $sValue, '', $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sKey", $value, '', $bNotInDico); + $sClassRes .= self::MakeDictEntry("Class:$sClass/Attribute:$sAttCode/Value:$sKey+", $value, '', $bNotInDico); } } } From b0ff4a409acf2bf52571627b89336fefdf88cdd0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 20 Apr 2010 15:51:29 +0000 Subject: [PATCH 271/970] - Keep the values when coming back to the "options" step SVN:trunk[354] --- pages/csvimport.php | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 1a76b111e5..cd97e93f21 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -58,11 +58,14 @@ function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null * Helper to 'check' an input in an HTML form if the current value equals the value given * @param mixed $sCurrentValue The current value to be chacked against the value of the input * @param mixed $sProposedValue The value of the input + * @param bool $bInverseCondition Set to true to perform the reversed comparison * @return string Either ' checked' or an empty string */ -function IsChecked($sCurrentValue, $sProposedValue) +function IsChecked($sCurrentValue, $sProposedValue, $bInverseCondition = false) { - return ($sCurrentValue == $sProposedValue) ? ' checked' : ''; + $bCondition = ($sCurrentValue == $sProposedValue); + + return ($bCondition xor $bInverseCondition) ? ' checked' : ''; } /** @@ -443,6 +446,7 @@ function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true $oPage->add(''); $oPage->add(''); $oPage->add(''); + $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); @@ -585,6 +589,7 @@ function SelectMapping(WebPage $oPage) $oPage->add(''); $oPage->add(''); $oPage->add(''); + $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); @@ -704,8 +709,12 @@ function SelectOptions(WebPage $oPage) $aGuesses = GuessParameters($sCSVData); // Try to predict the parameters, based on the input data $sSeparator = utils::ReadParam('separator', $aGuesses['separator']); + $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); + $bBoxSkipLines = utils::ReadParam('box_skiplines', 0); if ($sSeparator == 'tab') $sSeparator = "\t"; - $sTextQualifier = utils::ReadParam('qualifier', $aGuesses['qualifier']); + $sOtherSeparator = in_array($sSeparator, array(',', ';', "\t")) ? '' : $sSeparator; + $sTextQualifier = utils::ReadParam('text_qualifier', $aGuesses['qualifier']); + $sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier; $bHeaderLine = utils::ReadParam('header_line', 0); // Create a truncated version of the data used for the fast preview // Take about 20 lines of data... knowing that some lines may contain carriage returns @@ -736,18 +745,18 @@ function SelectOptions(WebPage $oPage) $oPage->add('

              , (comma)
              '); $oPage->add(' ; (semicolon)
              '); $oPage->add(' tab
              '); - $oPage->add(' other: '); + $oPage->add(' other: '); $oPage->add('

              '); $oPage->add(''); $oPage->add('

              Text qualifier character:

              '); $oPage->add('

              " (double quote)
              '); $oPage->add(' \' (simple quote)
              '); - $oPage->add(' other: '); + $oPage->add(' other: '); $oPage->add('

              '); $oPage->add(''); $oPage->add('

              Comments and header:

              '); $oPage->add('

              Treat the first line as a header (column names)

              '); - $oPage->add('

              Skip line(s) at the beginning of the file

              '); + $oPage->add('

              Skip line(s) at the beginning of the file

              '); $oPage->add(''); $oPage->add(''); $oPage->add(''); From 018da0ddf474a09b51fb1350e7b7a4b60a6476f8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 20 Apr 2010 15:54:13 +0000 Subject: [PATCH 272/970] Localization: cleanup in data model class declarations SVN:trunk[355] --- .../userrights/userrightsmatrix.class.inc.php | 8 ---- .../userrightsprofile.class.inc.php | 18 ------- application/audit.category.class.inc.php | 2 - application/audit.rule.class.inc.php | 2 - application/iotask.class.inc.php | 2 - application/menunode.class.inc.php | 2 - business/ChangeMgmt.business.php | 6 --- business/KEDB.business.php | 6 --- business/ServiceDesk.business.php | 6 --- business/ServiceMgmt.business.php | 10 ---- business/ServiceRequest.business.php | 4 -- business/business_itopbegins.class.inc.php | 12 ----- business/business_test.class.inc.php | 12 ----- business/incidentMgmt.business.php | 10 ---- business/itop.business.class.inc.php | 48 ------------------- business/test_farm.class.inc.php | 16 ------- core/action.class.inc.php | 6 --- core/cmdbchange.class.inc.php | 2 - core/cmdbchangeop.class.inc.php | 14 ------ core/event.class.inc.php | 10 ---- core/metamodel.class.php | 6 --- core/trigger.class.inc.php | 14 ------ 22 files changed, 216 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index c114fc353a..8bf75fb221 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -21,8 +21,6 @@ class UserRightsMatrixUsers extends DBObject $aParams = array ( "category" => "addon/userrights", - "name" => "user", - "description" => "users and credentials", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "login", @@ -51,8 +49,6 @@ class UserRightsMatrixClassGrant extends DBObject $aParams = array ( "category" => "addon/userrights", - "name" => "class_permission", - "description" => "permissions on classes", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -86,8 +82,6 @@ class UserRightsMatrixClassStimulusGrant extends DBObject $aParams = array ( "category" => "addon/userrights", - "name" => "stimulus_permission", - "description" => "permissions on stimilus in the life cycle of the object", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -121,8 +115,6 @@ class UserRightsMatrixAttributeGrant extends DBObject $aParams = array ( "category" => "addon/userrights", - "name" => "attribute_permission", - "description" => "permissions at the attributes level", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index bb89718d4c..c85c36832c 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -47,8 +47,6 @@ class URP_Users extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "user", - "description" => "users and credentials", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "login", @@ -182,8 +180,6 @@ class URP_Profiles extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "profile", - "description" => "usage profiles", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", @@ -291,8 +287,6 @@ class URP_Dimensions extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "dimension", - "description" => "application dimension (defining silos)", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", @@ -412,8 +406,6 @@ class URP_UserProfile extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "User to profile", - "description" => "user profiles", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "userid", @@ -460,8 +452,6 @@ class URP_ProfileProjection extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "profile_projection", - "description" => "profile projections", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "profileid", @@ -540,8 +530,6 @@ class URP_ClassProjection extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "class_projection", - "description" => "class projections", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "dimensionid", @@ -624,8 +612,6 @@ class URP_ActionGrant extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "action_permission", - "description" => "permissions on classes", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "profileid", @@ -673,8 +659,6 @@ class URP_StimulusGrant extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "stimulus_permission", - "description" => "permissions on stimilus in the life cycle of the object", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "profileid", @@ -722,8 +706,6 @@ class URP_AttributeGrant extends UserRightsBaseClass $aParams = array ( "category" => "addon/userrights", - "name" => "attribute_permission", - "description" => "permissions at the attributes level", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "actiongrantid", diff --git a/application/audit.category.class.inc.php b/application/audit.category.class.inc.php index 85b203ea7d..3a96411c0b 100644 --- a/application/audit.category.class.inc.php +++ b/application/audit.category.class.inc.php @@ -13,8 +13,6 @@ class AuditCategory extends cmdbAbstractObject $aParams = array ( "category" => "application", - "name" => "AuditCategory", - "description" => "A section inside the overall audit", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", diff --git a/application/audit.rule.class.inc.php b/application/audit.rule.class.inc.php index f00f7c43bc..92230ce7d3 100644 --- a/application/audit.rule.class.inc.php +++ b/application/audit.rule.class.inc.php @@ -14,8 +14,6 @@ class AuditRule extends cmdbAbstractObject $aParams = array ( "category" => "application", - "name" => "AuditRule", - "description" => "A rule to check for a given Audit category", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index 8ca3b3f37e..7314ccf5a6 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -12,8 +12,6 @@ class InputOutputTask extends cmdbAbstractObject $aParams = array ( "category" => "application", - "name" => "IOTask", - "description" => "Input / Output Task for synchronizing information with external data sources", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index f48517ae9d..6f17e39869 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -23,8 +23,6 @@ class menuNode extends DBObject $aParams = array ( "category" => "gui", - "name" => "menuNode", - "description" => "Main menu configuration elements", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index 26bc5f04c9..f2afe22877 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -11,8 +11,6 @@ class bizChangeTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Change", - "description" => "Change ticket", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -214,8 +212,6 @@ class lnkInfraChangeTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Infra Change Ticket", - "description" => "Infra impacted by a Change ticket", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "impact", // ???? @@ -263,8 +259,6 @@ class lnkContactChange extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ContactChangeLink", - "description" => "Contact associated to a change", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "role", // ???? diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 4dfcceffed..34822ea98d 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -12,8 +12,6 @@ class bizKnownError extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Known Error", - "description" => "Error documented for a known issue", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -83,8 +81,6 @@ class lnkInfraError extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "InfraErrorLinks", - "description" => "Infra related to a known error", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "", // ???? @@ -128,8 +124,6 @@ class lnkDocumentError extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "DocumentsErrorLinks", - "description" => "A link between a document and a known error", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "link_type", diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index de312c33c0..405066beb2 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -25,8 +25,6 @@ class bizServiceCall extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ServiceCall", - "description" => "Service Call from customer", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -180,8 +178,6 @@ class lnkCallTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Call Ticket", - "description" => "Ticket related to a call", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "impact", // ???? @@ -231,8 +227,6 @@ class lnkInfraCall extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Infra Call", - "description" => "Infra concerned by a call", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "impact", // ???? diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index f733f9b763..eec09e628b 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -12,8 +12,6 @@ class bizService extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Service", - "description" => "Service provided by an organization", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -92,8 +90,6 @@ class bizContract extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Contract", - "description" => "Contract signed by an organization", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -205,8 +201,6 @@ class lnkInfraContract extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "InfraContractLinks", - "description" => "Infra covered by a contract", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "coverage", // ???? @@ -252,8 +246,6 @@ class lnkContactContract extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ContactContractLink", - "description" => "Contact associated to a contract", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "role", // ???? @@ -302,8 +294,6 @@ class lnkDocumentContract extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "DocumentsContractLinks", - "description" => "A link between a document and another contract", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "link_type", diff --git a/business/ServiceRequest.business.php b/business/ServiceRequest.business.php index 4d1f3cc249..8532a4d05b 100644 --- a/business/ServiceRequest.business.php +++ b/business/ServiceRequest.business.php @@ -15,8 +15,6 @@ class bizServiceRequest extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ServiceRequest", - "description" => "Service request", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -106,8 +104,6 @@ class bizServiceItem extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ServiceItem", - "description" => "Service Item", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", diff --git a/business/business_itopbegins.class.inc.php b/business/business_itopbegins.class.inc.php index 94444b79e3..f9bb97d411 100644 --- a/business/business_itopbegins.class.inc.php +++ b/business/business_itopbegins.class.inc.php @@ -39,8 +39,6 @@ class cmdbContact extends CMDBObject $aParams = array ( "category" => "blah", - "name" => "klassContact", - "description" => "klass contact description", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "att_contact_name", @@ -81,8 +79,6 @@ class cmdbPerson extends cmdbContact $aParams = array ( "category" => "blah", - "name" => "klassPerson", - "description" => "klass person description", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "att_contact_name", @@ -119,8 +115,6 @@ class cmdbSubcontractor extends cmdbPerson $aParams = array ( "category" => "blah", - "name" => "klassSubcontractor", - "description" => "klass subcontractor description", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "att_contact_name", @@ -163,8 +157,6 @@ class cmdbCrowd extends cmdbObject $aParams = array ( "category" => "blah", - "name" => "klassCrowd", - "description" => "klass crowd description", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "att_crowd_peoplecount", @@ -200,8 +192,6 @@ class cmdbCompany extends cmdbCrowd $aParams = array ( "category" => "blah", - "name" => "klassCompany", - "description" => "klass company description", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "att_company_dunsnumber", @@ -237,8 +227,6 @@ class cmdbProvider extends cmdbCompany $aParams = array ( "category" => "blah", - "name" => "klassProvider", - "description" => "klass provider description", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "att_provider_ref", diff --git a/business/business_test.class.inc.php b/business/business_test.class.inc.php index 6781d6dec5..3d1e0db7c5 100644 --- a/business/business_test.class.inc.php +++ b/business/business_test.class.inc.php @@ -45,8 +45,6 @@ class cmdbObjectHomeMade extends cmdbObject $aParams = array ( "category" => "blah", - "name" => "anyObject", - "description" => "std object", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -89,8 +87,6 @@ class cmdbContact extends cmdbObjectHomeMade $aParams = array ( "category" => "blah", - "name" => "Contact", - "description" => "Un object que l'on peut communiquer avec", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", @@ -174,8 +170,6 @@ class cmdbTeam extends cmdbContact $aParams = array ( "category" => "blah", - "name" => "Equipado", - "description" => "Un regroupement de gens", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "email", @@ -232,8 +226,6 @@ class cmdbOrga extends cmdbObjectHomeMade $aParams = array ( "category" => "blah", - "name" => "Organization", - "description" => "Une entite qui possede des choses", "key_type" => "", "key_label" => "", "name_attcode" => "_name_", @@ -277,8 +269,6 @@ class cmdbLiens extends cmdbObjectHomeMade $aParams = array ( "category" => "blah", - "name" => "Liens_entre_contacts_et_workshop", - "description" => "Une entite qui lie des contacts et workshops", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "function", @@ -332,8 +322,6 @@ class cmdbWorkshop extends cmdbObjectHomeMade $aParams = array ( "category" => "blah", - "name" => "Workshop", - "description" => "Une entite qui pond des theories insensees", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "namitus", diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index 0be8120125..ddaae7a754 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -25,8 +25,6 @@ class bizIncidentTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Incident", - "description" => "Incident ticket", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -222,8 +220,6 @@ class lnkRelatedTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Related Ticket", - "description" => "Ticket related to a ticket", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "impact", // ???? @@ -273,8 +269,6 @@ class lnkInfraTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Infra Ticket", - "description" => "Infra impacted by a ticket", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "impact", // ???? @@ -324,8 +318,6 @@ class lnkContactTicket extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Contact Ticket", - "description" => "Contacts to be notify for a ticket", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "role", // ???? @@ -374,8 +366,6 @@ class bizWorkgroup extends logRealObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Workgroup", - "description" => "Call tracking workgroup", "key_type" => "", "key_label" => "id", "name_attcode" => "name", diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index ac39a54e84..52d2a7b775 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -57,8 +57,6 @@ class bizOrganization extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Organization", - "description" => "Organizational structure: can be Company and/or Department", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -129,8 +127,6 @@ class logRealObject extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Object", - "description" => "Any CMDB object", "key_type" => "autoincrement", "key_label" => "id", "name_attcode" => "name", @@ -183,8 +179,6 @@ class bizContact extends logRealObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Contact", - "description" => "Contact", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -241,8 +235,6 @@ class bizPerson extends bizContact $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Person", - "description" => "Person", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -294,8 +286,6 @@ class bizTeam extends bizContact $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Team", - "description" => "A group of contacts", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -331,8 +321,6 @@ class lnkContactTeam extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "TeamsLinks", - "description" => "A link between a contact and a Team", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "role", @@ -378,8 +366,6 @@ class bizDocument extends logRealObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Document", - "description" => "Document", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -425,8 +411,6 @@ class lnkDocumentRealObject extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "DocumentsLinks", - "description" => "A link between a document and another object", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "link_type", @@ -471,8 +455,6 @@ class lnkContactRealObject extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ContactsLinks", - "description" => "A link between a contact and another object", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "role", @@ -520,8 +502,6 @@ class logInfra extends logRealObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Infra", - "description" => "Infrastructure real object", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -554,8 +534,6 @@ class lnkContactInfra extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ContactsInfraLinks", - "description" => "A link between a contact and an infrastructure", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "role", @@ -601,8 +579,6 @@ class bizLocation extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Location", - "description" => "Any type of location: Region, Country, City, Site, Building, Floor, Room, Rack,...", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -733,8 +709,6 @@ class bizCircuit extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Circuit", - "description" => "Any type of circuit", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -816,8 +790,6 @@ class bizInterface extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Interface", - "description" => "Interface", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -912,8 +884,6 @@ class bizSubnet extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Subnet", - "description" => "Logical or physical subnet", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -995,8 +965,6 @@ class bizDevice extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Device", - "description" => "Electronic devices", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -1066,8 +1034,6 @@ class bizPC extends bizDevice $aParams = array ( "category" => "bizmodel,searchable", - "name" => "PC", - "description" => "Personal Computers", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -1160,8 +1126,6 @@ class bizServer extends bizDevice $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Server", - "description" => "Computer Servers", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -1332,8 +1296,6 @@ class bizNetworkDevice extends bizDevice $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Network Device", - "description" => "A network device", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -1396,8 +1358,6 @@ class bizInfraGroup extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Infra Group", - "description" => "A group of infrastructure elements", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -1479,8 +1439,6 @@ class bizApplication extends logInfra $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Application", - "description" => "General application", "key_type" => "", "key_label" => "id", "name_attcode" => "name", @@ -1561,8 +1519,6 @@ class lnkInfraGrouping extends cmdbAbstractObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Infra Grouping", - "description" => "Infra part of an Infra Group", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "impact", @@ -1619,8 +1575,6 @@ class lnkClientServer extends logRealObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "ClientServerLinks", - "description" => "Link between client server application", "key_type" => "autoincrement", "key_label" => "link_id", "name_attcode" => "relation", // ???? @@ -1666,8 +1620,6 @@ class bizPatch extends logRealObject $aParams = array ( "category" => "bizmodel,searchable", - "name" => "Patch", - "description" => "Patch installed on infrastucture", "key_type" => "", "key_label" => "id", "name_attcode" => "name", diff --git a/business/test_farm.class.inc.php b/business/test_farm.class.inc.php index fbb844c21b..15c2309279 100644 --- a/business/test_farm.class.inc.php +++ b/business/test_farm.class.inc.php @@ -33,8 +33,6 @@ class Animal extends cmdbObject $aParams = array ( "category" => "blah", - "name" => "Animal", - "description" => "An animal", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -69,8 +67,6 @@ class Mammal extends Animal $aParams = array ( "category" => "blah", - "name" => "Mammal", - "description" => "An animal with some characteristics", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", @@ -104,8 +100,6 @@ class Bird extends Animal $aParams = array ( "category" => "blah", - "name" => "Bird", - "description" => "Un regroupement de gens", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -130,8 +124,6 @@ class WalkingBird extends Bird $aParams = array ( "category" => "blah", - "name" => "WalkingBird", - "description" => "A bird which nevers flies", "key_type" => "", "key_label" => "", "name_attcode" => "", @@ -155,8 +147,6 @@ class FlyingBird extends Bird $aParams = array ( "category" => "blah", - "name" => "FlyingBird", - "description" => "A bird which nevers flies", "key_type" => "", "key_label" => "", "name_attcode" => "", @@ -183,8 +173,6 @@ class AnimalRelation extends cmdbObject $aParams = array ( "category" => "blah", - "name" => "AnimalRelation", - "description" => "Link between two animals", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -215,8 +203,6 @@ class EaterToEaten extends AnimalRelation $aParams = array ( "category" => "blah", - "name" => "EaterToEaten", - "description" => "Animal 1 eats animal 2", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -242,8 +228,6 @@ class Group extends cmdbObject $aParams = array ( "category" => "blah", - "name" => "Group", - "description" => "Group of animals", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 64dbb60e30..d174245039 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -20,8 +20,6 @@ abstract class Action extends cmdbAbstractObject $aParams = array ( "category" => "core/cmdb", - "name" => "action", - "description" => "Custom action", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", @@ -97,8 +95,6 @@ abstract class ActionNotification extends Action $aParams = array ( "category" => "core/cmdb", - "name" => "notification", - "description" => "Notification (abstract)", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", @@ -141,8 +137,6 @@ class ActionEmail extends ActionNotification $aParams = array ( "category" => "core/cmdb", - "name" => "email notification", - "description" => "Action: Email notification", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "name", diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index d2ebfd7803..9e1fd21130 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -18,8 +18,6 @@ class CMDBChange extends DBObject $aParams = array ( "category" => "core/cmdb", - "name" => "change", - "description" => "Changes tracking", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "date", diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index a9ef0047e3..324d41868e 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -19,8 +19,6 @@ class CMDBChangeOp extends DBObject $aParams = array ( "category" => "core/cmdb", - "name" => "change operation", - "description" => "Change operations tracking", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "change", @@ -74,8 +72,6 @@ class CMDBChangeOpCreate extends CMDBChangeOp $aParams = array ( "category" => "core/cmdb", - "name" => "object creation", - "description" => "Object creation tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", @@ -118,8 +114,6 @@ class CMDBChangeOpDelete extends CMDBChangeOp $aParams = array ( "category" => "core/cmdb", - "name" => "object deletion", - "description" => "Object deletion tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", @@ -161,8 +155,6 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp $aParams = array ( "category" => "core/cmdb", - "name" => "object change", - "description" => "Object properties change tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", @@ -202,8 +194,6 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute $aParams = array ( "category" => "core/cmdb", - "name" => "property change", - "description" => "Object scalar properties change tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", @@ -304,8 +294,6 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute $aParams = array ( "category" => "core/cmdb", - "name" => "data change", - "description" => "data change tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", @@ -374,8 +362,6 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute $aParams = array ( "category" => "core/cmdb", - "name" => "text change", - "description" => "text change tracking", "key_type" => "", "key_label" => "", "name_attcode" => "change", diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 56b20db41b..8045c798b3 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -18,8 +18,6 @@ class Event extends cmdbAbstractObject $aParams = array ( "category" => "core/cmdb", - "name" => "Log Event", - "description" => "An application internal event", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -56,8 +54,6 @@ class EventNotification extends Event $aParams = array ( "category" => "core/cmdb", - "name" => "Notification event", - "description" => "Trace of a notification that has been sent", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -96,8 +92,6 @@ class EventNotificationEmail extends EventNotification $aParams = array ( "category" => "core/cmdb", - "name" => "Email emission event", - "description" => "Trace of an email that has been sent", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -136,8 +130,6 @@ class EventIssue extends Event $aParams = array ( "category" => "core/cmdb", - "name" => "Issue event", - "description" => "Trace of an issue (warning, error, etc.)", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -179,8 +171,6 @@ class EventWebService extends Event $aParams = array ( "category" => "core/cmdb", - "name" => "Web service event", - "description" => "Trace of an web service call", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 2cad799196..cb8f3c65a6 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -940,8 +940,6 @@ abstract class MetaModel // Check mandatory params $aMandatParams = array( "category" => "group classes by modules defining their visibility in the UI", - "name" => "internal class name, may be different than the PHP class name", - "description" => "detailed (though one line) description of the class", "key_type" => "autoincrement | string", "key_label" => "if set, then display the key as an attribute", "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", @@ -953,10 +951,6 @@ abstract class MetaModel ); $sClass = self::GetCallersPHPClass("Init"); - if (!array_key_exists("name", $aParams)) - { - throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})"); - } foreach($aMandatParams as $sParamName=>$sParamDesc) { diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index cda984a0d3..a1399721c7 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -19,8 +19,6 @@ class Trigger extends cmdbAbstractObject $aParams = array ( "category" => "core/cmdb", - "name" => "trigger", - "description" => "Custom event handler", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "description", @@ -71,8 +69,6 @@ class TriggerOnObject extends Trigger $aParams = array ( "category" => "core/cmdb", - "name" => "Trigger on a class of objects", - "description" => "Trigger on a given class of objects", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -106,8 +102,6 @@ class TriggerOnStateChange extends TriggerOnObject $aParams = array ( "category" => "core/cmdb", - "name" => "Trigger on object state change", - "description" => "Trigger on object state change", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -141,8 +135,6 @@ class TriggerOnStateEnter extends TriggerOnStateChange $aParams = array ( "category" => "core/cmdb", - "name" => "Trigger on object entering a state", - "description" => "Trigger on object state change - entering", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -174,8 +166,6 @@ class TriggerOnStateLeave extends TriggerOnStateChange $aParams = array ( "category" => "core/cmdb", - "name" => "Trigger on object leaving a state", - "description" => "Trigger on object state change - leaving", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -207,8 +197,6 @@ class TriggerOnObjectCreate extends TriggerOnObject $aParams = array ( "category" => "core/cmdb", - "name" => "Trigger on object creation", - "description" => "Trigger on object creation of [a child class of] the given class", "key_type" => "autoincrement", "key_label" => "", "name_attcode" => "", @@ -240,8 +228,6 @@ class lnkTriggerAction extends cmdbAbstractObject $aParams = array ( "category" => "core/cmdb", - "name" => "Actions-Trigger", - "description" => "Link between a trigger and an action", "key_type" => "autoincrement", "key_label" => "Link ID", "name_attcode" => "", From 871e0ef1019914fc19b8423aac2c1af659bd2207 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 21 Apr 2010 07:35:19 +0000 Subject: [PATCH 273/970] Localization: user profile ++ language SVN:trunk[356] --- .../userrights/userrightsmatrix.class.inc.php | 6 ++ .../userrights/userrightsnull.class.inc.php | 5 ++ .../userrightsprofile.class.inc.php | 16 ++++- core/dict.class.inc.php | 64 +++++++++++++------ core/metamodel.class.php | 4 +- core/userrights.class.inc.php | 16 ++++- dictionaries/dictionary.itop.ui.php | 6 ++ 7 files changed, 91 insertions(+), 26 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 8bf75fb221..5343a5fe71 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -336,6 +336,12 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $oLogin->Get('userid'); } + // this module does not handle localization + public function GetUserLanguage($sUserName) + { + return 'EN US'; + } + public function GetContactId($sUserName) { // this module has no link with the business data diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index cb8d351d26..bf43fdebe2 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -47,6 +47,11 @@ class UserRightsNull extends UserRightsAddOnAPI return 1; } + public function GetUserLanguage($sUserName) + { + return 'EN US'; + } + public function GetContactId($sUserName) { // this module has no link with the business data diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index c85c36832c..33889e7b14 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -68,6 +68,8 @@ class URP_Users extends UserRightsBaseClass MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributePassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("language", array("allowed_values"=>new ValueSetEnum('EN US,FR FR'), "sql"=>"language", "default_value"=>"EN US", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profiles", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); //MetaModel::Init_InheritFilters(); @@ -76,7 +78,7 @@ class URP_Users extends UserRightsBaseClass MetaModel::Init_AddFilterFromAttribute("password"); // Display lists - MetaModel::Init_SetZListItems('details', array('userid', 'first_name', 'email', 'login')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('userid', 'first_name', 'email', 'login', 'language')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'userid')); // Criteria of the std search form @@ -942,6 +944,18 @@ exit; return null; } + public function GetUserLanguage($sUserName) + { + if (array_key_exists($sUserName, $this->m_aLogin2UserId)) + { + // This happens really when the list of users is being loaded into the cache!!! + $iUserId = $this->m_aLogin2UserId[$sUserName]; + $oUser = $this->m_aUsers[$iUserId]; + return $oUser->Get('language'); + } + return 'EN US'; + } + public function GetContactId($sUserName) { if (array_key_exists($sUserName, $this->m_aLogin2UserId)) diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index 294c55103b..9afe910833 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -47,13 +47,23 @@ define('DICT_ERR_EXCEPTION', 2); // when a string is missing, throw an exception class Dict { protected static $m_iErrorMode = DICT_ERR_STRING; + protected static $m_sDefaultLanguage = 'EN US'; protected static $m_sCurrentLanguage = 'EN US'; protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...) protected static $m_aData = array(); - public static function SetLanguage($sLanguageCode) + public static function SetDefaultLanguage($sLanguageCode) + { + if (!array_key_exists($sLanguageCode, self::$m_aLanguages)) + { + throw new DictExceptionUnknownLanguage($sLanguageCode); + } + self::$m_sDefaultLanguage = $sLanguageCode; + } + + public static function SetUserLanguage($sLanguageCode) { if (!array_key_exists($sLanguageCode, self::$m_aLanguages)) { @@ -78,29 +88,41 @@ class Dict public static function S($sStringCode, $sDefault = null) { + // Attempt to find the string in the user language + // $aCurrentDictionary = self::$m_aData[self::$m_sCurrentLanguage]; - if (!array_key_exists($sStringCode, $aCurrentDictionary)) + if (array_key_exists($sStringCode, $aCurrentDictionary)) { - switch (self::$m_iErrorMode) - { - case DICT_ERR_STRING: - if (is_null($sDefault)) - { - return $sStringCode; - } - else - { - return $sDefault; - } - break; - - case DICT_ERR_EXCEPTION: - default: - throw new DictExceptionMissingString(self::$m_sCurrentLanguage, $sStringCode); - break; - } + return $aCurrentDictionary[$sStringCode]; } - return $aCurrentDictionary[$sStringCode]; + // Attempt to find the string in the default language + // + $aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage]; + if (array_key_exists($sStringCode, $aDefaultDictionary)) + { + return $aDefaultDictionary[$sStringCode]; + } + // Could not find the string... + // + switch (self::$m_iErrorMode) + { + case DICT_ERR_STRING: + if (is_null($sDefault)) + { + return $sStringCode; + } + else + { + return $sDefault; + } + break; + + case DICT_ERR_EXCEPTION: + default: + throw new DictExceptionMissingString(self::$m_sCurrentLanguage, $sStringCode); + break; + } + return 'bug!'; } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index cb8f3c65a6..deca1f5eec 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -237,7 +237,7 @@ abstract class MetaModel } else { - return self::GetDescription($sClass); + return self::GetClassDescription($sClass); } } final static public function IsAutoIncrementKey($sClass) @@ -2799,7 +2799,7 @@ abstract class MetaModel self::Plugin($sConfigFile, 'dictionaries', $sToInclude); } // Set the language... after the dictionaries have been loaded! - Dict::SetLanguage($oConfig->GetDefaultLanguage()); + Dict::SetDefaultLanguage($oConfig->GetDefaultLanguage()); $sServer = $oConfig->GetDBHost(); $sUser = $oConfig->GetDBUser(); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 03a0dd2e73..6078fa61b8 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -49,6 +49,7 @@ abstract class UserRightsAddOnAPI abstract public function Init(); // loads data (possible optimizations) abstract public function CheckCredentials($sLogin, $sPassword); // returns the id of the user or false + abstract public function GetUserLanguage($sLogin); // returns the language code (e.g "EN US") abstract public function GetUserId($sLogin); // returns the id of the user or false abstract public function GetContactId($sLogin); // returns the id of the "business" user or false abstract public function GetFilter($sLogin, $sClass); // returns a filter object @@ -78,6 +79,7 @@ class UserRights protected static $m_sRealUser; protected static $m_iUserId; protected static $m_iRealUserId; + protected static $m_sUserLanguage; public static function SelectModule($sModuleName) { @@ -97,6 +99,7 @@ class UserRights self::$m_sRealUser = ''; self::$m_iUserId = 0; self::$m_iRealUserId = 0; + self::$m_sUserLanguage = 'EN US'; } public static function GetModuleInstance() @@ -125,11 +128,13 @@ class UserRights public static function Login($sName, $sPassword) { self::$m_iUserId = self::$m_oAddOn->CheckCredentials($sName, $sPassword); - if ( self::$m_iUserId !== false ) + if (self::$m_iUserId !== false) { self::$m_sUser = $sName; self::$m_iRealUserId = self::$m_iUserId; self::$m_sRealUser = $sName; + self::$m_sUserLanguage = self::$m_oAddOn->GetUserLanguage($sName); + Dict::SetUserLanguage(self::GetUserLanguage()); return true; } else @@ -143,9 +148,11 @@ class UserRights if (!self::CheckLogin()) return false; self::$m_iRealUserId = self::$m_oAddOn->CheckCredentials($sName, $sPassword); - if ( self::$m_iRealUserId !== false) + if (self::$m_iRealUserId !== false) { self::$m_sUser = $sName; + self::$m_sUserLanguage = self::$m_oAddOn->GetUserLanguage($sName); + Dict::SetUserLanguage(self::GetUserLanguage()); return true; } else @@ -159,6 +166,11 @@ class UserRights return self::$m_sUser; } + public static function GetUserLanguage() + { + return self::$m_sUserLanguage; + } + public static function GetUserId($sName = '') { if (empty($sName)) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 820bbad9f0..f39613e929 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -109,6 +109,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:URP_Users/Attribute:login+' => 'user identification string', 'Class:URP_Users/Attribute:password' => 'Password', 'Class:URP_Users/Attribute:password+' => 'user authentication string', + 'Class:URP_Users/Attribute:language' => 'Language', + 'Class:URP_Users/Attribute:language+' => 'user language', + 'Class:URP_Users/Attribute:language/Value:EN US' => 'English', + 'Class:URP_Users/Attribute:language/Value:EN US+' => 'English U.S.', + 'Class:URP_Users/Attribute:language/Value:FR FR' => 'French', + 'Class:URP_Users/Attribute:language/Value:FR FR+' => 'FR FR', 'Class:URP_Users/Attribute:profiles' => 'Profiles', 'Class:URP_Users/Attribute:profiles+' => 'roles, granting rights for that person', )); From 942d77a70fecb22209a2782ef14f1b6f5c811a3d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 21 Apr 2010 07:37:41 +0000 Subject: [PATCH 274/970] Aligned bulk load web services with recent changes (status reporting) SVN:trunk[357] --- webservices/import.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/webservices/import.php b/webservices/import.php index 1e6e5a3d59..12c0facd3f 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -150,19 +150,13 @@ try switch (get_class($value)) { - case 'CellChangeSpec_Void': + case 'CellStatus_Void': $sClass = ''; break; - case 'CellChangeSpec_Unchanged': - $sClass = ''; - break; - case 'CellChangeSpec_Modify': + case 'CellStatus_Modify': $sClass = 'csvimport_ok'; break; - case 'CellChangeSpec_Init': - $sClass = 'csvimport_init'; - break; - case 'CellChangeSpec_Issue': + case 'CellStatus_Issue': $sClass = 'csvimport_error'; break; } From 0c328d227d34d986862973db22ac4d4f6b4e1666 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 27 Apr 2010 07:28:56 +0000 Subject: [PATCH 275/970] Fixed a typo in the datamodel SVN:trunk[358] --- dictionaries/dictionary.itop.model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index da64da8a95..5a7648fd0c 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -1117,7 +1117,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:bizChangeTicket/Stimulus:ev_notapprove' => 'Not approve this change', 'Class:bizChangeTicket/Stimulus:ev_notapprove+' => 'This change is not approved by CAB', 'Class:bizChangeTicket/Stimulus:ev_implement' => 'Implement this change', - 'Class:bizChangeTicket/Stimulus:ev_implement+' => 'Implementation pahse for current change', + 'Class:bizChangeTicket/Stimulus:ev_implement+' => 'Implementation phase for current change', 'Class:bizChangeTicket/Stimulus:ev_monitor' => 'Monitor this change', 'Class:bizChangeTicket/Stimulus:ev_monitor+' => 'Starting monitoring period for this change', 'Class:bizChangeTicket/Stimulus:ev_finish' => 'Close change', From 72200521348c9a7e3235dac7583dec7ab79788bf Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 27 Apr 2010 12:38:56 +0000 Subject: [PATCH 276/970] Enabled localized strings (not only enums, but other types... to be defined) SVN:trunk[359] --- application/cmdbabstract.class.inc.php | 61 ++---------------------- application/ui.linkswidget.class.inc.php | 2 +- core/attributedef.class.inc.php | 11 +++++ core/dbobject.class.php | 46 ++++++++++++++++++ pages/UI.php | 2 +- pages/ajax.render.php | 2 +- 6 files changed, 63 insertions(+), 61 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3fc03fa039..479530959f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -77,60 +77,6 @@ abstract class cmdbAbstractObject extends CMDBObject return "GetForLink()."\" title=\"$sHint\">$sLabel"; } - public function GetDisplayValue($sAttCode) - { - $sDisplayValue = ""; - $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); - if ($sStateAttCode == $sAttCode) - { - $sDisplayValue = MetaModel::GetStateLabel(get_class($this), $this->Get($sAttCode)); - } - else - { - $oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - - if ($oAtt->IsExternalKey()) - { - $sTargetClass = $oAtt->GetTargetClass(); - if ($this->IsNew()) - { - // The current object exists only in memory, don't try to query it in the DB ! - // instead let's query for the object pointed by the external key, and get its name - $targetObjId = $this->Get($sAttCode); - $oTargetObj = MetaModel::GetObject($sTargetClass, $targetObjId, false); // false => not sure it exists - if (is_object($oTargetObj)) - { - $sDisplayValue = $oTargetObj->GetName(); - } - } - else - { - // retrieve the "external fields" linked to this external key - foreach (MetaModel::GetExternalFields(get_class($this), $sAttCode) as $oExtField) - { - $aAvailableFields[$oExtField->GetExtAttCode()] = $oExtField->GetAsHTML($this->Get($oExtField->GetCode())); - } - // Use the "name" of the target class as the label of the hyperlink - // unless it's not available in the external fields... - $sExtClassNameAtt = MetaModel::GetNameAttributeCode($sTargetClass); - if (isset($aAvailableFields[$sExtClassNameAtt])) - { - $sDisplayValue = $aAvailableFields[$sExtClassNameAtt]; - } - else - { - $sDisplayValue = implode(' / ', $aAvailableFields); - } - } - } - else - { - $sDisplayValue = $this->GetAsHTML($sAttCode); - } - } - return $sDisplayValue; - } - function DisplayBareHeader(WebPage $oPage) { // Standard Header with name, actions menu and history block @@ -592,8 +538,7 @@ abstract class cmdbAbstractObject extends CMDBObject } } } - $sHtml = '#'.$oSet->GetFilter()->ToOQL()."\n"; - $sHtml .= implode($sSeparator, $aHeader)."\n"; + $sHtml = implode($sSeparator, $aHeader)."\n"; $oSet->Seek(0); while ($aObjects = $oSet->FetchAssoc()) { @@ -973,7 +918,7 @@ abstract class cmdbAbstractObject extends CMDBObject else { $sValue = $this->Get($sAttCode); - $sDisplayValue = $this->GetDisplayValue($sAttCode); + $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this); $sHTMLValue = self::GetFormElementForField($oPage, get_class($this), $sAttCode, $oAttDef, $sValue, $sDisplayValue, '', '', $iFlags, $aArgs); } @@ -1032,7 +977,7 @@ abstract class cmdbAbstractObject extends CMDBObject else if (!$oAttDef->IsExternalField()) { $sValue = ($oObjectToClone == null) ? '' : $oObjectToClone->Get($sAttCode); - $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetDisplayValue($sAttCode); + $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetEditValue($sAttCode); $iOptions = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; $sHTMLValue = self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, '', '', $iOptions, $aArgs); diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 5e692b54c3..c419785874 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -307,7 +307,7 @@ EOF; else { $sValue = ""; //$this->Get($sAttCode); - $sDisplayValue = ""; //$this->GetDisplayValue($sAttCode); + $sDisplayValue = ""; //$this->GetEditValue($sAttCode); $sSubId = $sId.'_'.$index; $aAttrsMap[$sAttCode] = $sSubId; $index++; diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 76491c7f87..6bf2072a57 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -210,6 +210,11 @@ abstract class AttributeDefinition //abstract protected GetBasicFilterHTMLInput(); abstract public function GetBasicFilterSQLExpr($sOpCode, $value); + public function GetEditValue($sValue) + { + return (string)$sValue; + } + public function GetAsHTML($sValue) { return Str::pure2html((string)$sValue); @@ -840,6 +845,12 @@ class AttributeEnum extends AttributeString return "".parent::GetAsHtml($sLabel).""; } + public function GetEditValue($sValue) + { + $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, $sValue); + return $sLabel; + } + public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') { $aRawValues = parent::GetAllowedValues($aArgs, $sBeginsWith); diff --git a/core/dbobject.class.php b/core/dbobject.class.php index e2ba903e33..bb894cadc4 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -382,6 +382,52 @@ abstract class DBObject return $oAtt->GetAsHTML($this->Get($sAttCode)); } + public function GetEditValue($sAttCode) + { + $sClass = get_class($this); + $oAtt = MetaModel::GetAttributeDef($sClass, $sAttCode); + + if ($oAtt->IsExternalKey()) + { + $sTargetClass = $oAtt->GetTargetClass(); + if ($this->IsNew()) + { + // The current object exists only in memory, don't try to query it in the DB ! + // instead let's query for the object pointed by the external key, and get its name + $targetObjId = $this->Get($sAttCode); + $oTargetObj = MetaModel::GetObject($sTargetClass, $targetObjId, false); // false => not sure it exists + if (is_object($oTargetObj)) + { + $sEditValue = $oTargetObj->GetName(); + } + } + else + { + // retrieve the "external fields" linked to this external key + foreach (MetaModel::GetExternalFields(get_class($this), $sAttCode) as $oExtField) + { + $aAvailableFields[$oExtField->GetExtAttCode()] = $oExtField->GetAsHTML($this->Get($oExtField->GetCode())); + } + // Use the "name" of the target class as the label of the hyperlink + // unless it's not available in the external fields... + $sExtClassNameAtt = MetaModel::GetNameAttributeCode($sTargetClass); + if (isset($aAvailableFields[$sExtClassNameAtt])) + { + $sEditValue = $aAvailableFields[$sExtClassNameAtt]; + } + else + { + $sEditValue = implode(' / ', $aAvailableFields); + } + } + } + else + { + $sEditValue = $oAtt->GetEditValue($this->Get($sAttCode)); + } + return $sEditValue; + } + public function GetAsXML($sAttCode) { $oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode); diff --git a/pages/UI.php b/pages/UI.php index 6a889749da..967e6c8fbf 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -913,7 +913,7 @@ try $aAttributesDef = MetaModel::ListAttributeDefs($sClass); $oAttDef = $aAttributesDef[$sAttCode]; $aArgs = array('this' => $oObj); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetDisplayValue($sAttCode), '', '', $iExpectCode, $aArgs); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), '', '', $iExpectCode, $aArgs); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 6a44e7e1aa..665377360f 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -89,7 +89,7 @@ switch($operation) { $sId = $oWizardHelper->GetIdForField($sAttCode); $value = $oObj->Get($sAttCode); - $displayValue = $oObj->GetDisplayValue($sAttCode); + $displayValue = $oObj->GetEditValue($sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, 'att_'.$sId, '', 0, array('this' => $oObj)); From 20020a0be8cb21ac45e0a6a927e6926013b35012 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 27 Apr 2010 12:44:09 +0000 Subject: [PATCH 277/970] Fixed regressions in CSV export (See #105) SVN:trunk[360] --- application/cmdbabstract.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 479530959f..433055af42 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -518,7 +518,7 @@ abstract class cmdbAbstractObject extends CMDBObject { foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) { - if ($oAttDef->IsScalar()) + if (($sAttCode != 'finalclass') && $oAttDef->IsScalar()) { $aList[$sClassName][$sAttCode] = $oAttDef; } From 42ae42d5ee430739bdb1ea82d5048ff0f26933ec Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 27 Apr 2010 13:06:51 +0000 Subject: [PATCH 278/970] Fixed #97, could not delete an Organization SVN:trunk[361] --- business/itop.business.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 52d2a7b775..9be25dd95e 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -74,6 +74,7 @@ class bizOrganization extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"bizOrganization", "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("allowed_values"=>null, "extkey_attcode"=> 'parent_id', "target_attcode"=>"name"))); + MetaModel::Init_AddFilterFromAttribute("parent_id"); MetaModel::Init_AddFilterFromAttribute("name"); MetaModel::Init_AddFilterFromAttribute("code"); MetaModel::Init_AddFilterFromAttribute("status"); From ef5877779d23a2a248fe107c60471bb1244d99e6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 27 Apr 2010 13:10:27 +0000 Subject: [PATCH 279/970] - Implementation of the localization... on going... SVN:trunk[362] --- application/cmdbabstract.class.inc.php | 2 +- application/displayblock.class.inc.php | 62 +++++++++++++------------- application/itopwebpage.class.inc.php | 20 ++++++--- application/menunode.class.inc.php | 4 +- application/nicewebpage.class.inc.php | 8 +++- application/template.class.inc.php | 6 ++- 6 files changed, 61 insertions(+), 41 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 433055af42..af8b75fb48 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -951,7 +951,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sOperation = ($oObjectToClone == null) ? 'apply_new' : 'apply_clone'; $sClass = ($oObjectToClone == null) ? $sClass : get_class($oObjectToClone); $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); - $oPage->add("

              \n"); + $oPage->add("\n"); $aStates = MetaModel::EnumStates($sClass); if ($oObjectToClone == null) { diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 6eae0ff4b6..2656f5dc3f 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -123,12 +123,12 @@ class DisplayBlock if(empty($aParams['object_id'])) { // if 'links' mode is requested the d of the object to link to must be specified - throw new ApplicationException("Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template."); + throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); } if(empty($aParams['target_attr'])) { // if 'links' mode is requested the id of the object to link to must be specified - throw new ApplicationException("Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template."); + throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_link_attr')); } } @@ -234,7 +234,7 @@ class DisplayBlock { // render it as an Ajax (asynchronous) call $sHtml .= "
              \n"; - $sHtml .= $oPage->GetP(" Loading..."); + $sHtml .= $oPage->GetP(" ".Dict::S('UI:Loading')); $sHtml .= "
              \n"; $sHtml .= ' \n"); $oP->add_ready_script("InitForm();"); $oFilter = $oContext->NewFilter($this->m_sClass); @@ -358,7 +371,7 @@ class UILinksWizard $oP->add("  "); $oP->Add("\n"); $oP->Add("
              \n"); - $oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit', function() {var the_form = this; SearchObjectsToAdd(the_form.id); return false;});"); + $oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit.uilinksWizard', SubmitHook);"); } public function SearchObjectsToAdd(WebPage $oP, UserContext $oContext) diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index 4a9c9b0773..9810612493 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -18,7 +18,8 @@ $oP = new iTopWebPage("iTop - Universal search", $currentOrganization); // From now on the context is limited to the the selected organization ?? // Now render the content of the page -$sOQLClass = utils::ReadParam('oql_class', 'bizOrganization'); +$sClass = utils::ReadParam('class', 'bizOrganization'); +$sOQLClass = utils::ReadParam('oql_class', $sClass); $sOQLClause = utils::ReadParam('oql_clause', ''); $sClassName = $sOQLClass; //utils::ReadParam('class', $sOQLClass); $sFilter = utils::ReadParam('filter', ''); @@ -85,85 +86,8 @@ if ($oFilter != null) // Menu node $sFilter = $oFilter->ToOQL(); - $sMenuNodeContent = << -$sFilter - -
              -

              -$sFilter -
              -EOF; - - - if ($sOperation == "add_menu") - { - $oMenuNode = MetaModel::NewObject('menuNode'); - $sClass = utils::ReadPostedParam('class', ''); - $sLabel = utils::ReadPostedParam('label', ''); - $sDescription = utils::ReadPostedParam('description', ''); - $iPreviousNodeId = utils::ReadPostedParam('previous_node_id', 1); - $bChildItem = utils::ReadPostedParam('child_item', false); - $oMenuNode->Set('label', $sDescription); - $oMenuNode->Set('name', $sLabel); - $oMenuNode->Set('icon_path', '../images/std_view.gif'); - $oMenuNode->Set('template', $sMenuNodeContent); - $oMenuNode->Set('hyperlink', 'UI.php'); - $oMenuNode->Set('type', 'user'); - $oMenuNode->Set('user_id', UserRights::GetUserId()); - $oPreviousNode = MetaModel::GetObject('menuNode', $iPreviousNodeId); - if ($bChildItem) - { - // Insert the new item as a child of the previous one - $oMenuNode->Set('parent_id', $iPreviousNodeId); - $oMenuNode->Set('rank', 1); // A new child item is the first one, so let's start the numbering at 1 - // If there are already child nodes, shift their rank by one - // to make room for the newly inserted child node - $oNextNodeSet = $oPreviousNode->GetChildNodesSet(null); // null => don't limit ourselves to the user context - // since we need to update all children in order to keep - // the database consistent - while($oNextNode = $oNextNodeSet->Fetch()) - { - $oNextNode->Set('rank', 1 + $oNextNode->Get('rank')); - $oNextNode->DBUpdate(); - } - } - else - { - // Insert the new item as the next sibling of the previous one - $oMenuNode->Set('parent_id', $oPreviousNode->Get('parent_id')); - $oMenuNode->Set('rank', 1 + $oPreviousNode->Get('rank')); // the new item comes immediatly after the selected one - // Add 1 to the rank of all the nodes currently following the 'selected' one - // to make room for the newly inserted node - $oNextNodeSet = $oPreviousNode->GetNextNodesSet(null); // null => don't limit ourselves to the user context - // since we need to update all children in order to keep - // the database consistent - while($oNextNode = $oNextNodeSet->Fetch()) - { - $oNextNode->Set('rank', 1 + $oNextNode->Get('rank')); - $oNextNode->DBUpdate(); - } - - } - list($bRes, $aIssues) = $oMenuNode->CheckToInsert(); - if ($bRes) - { - $oMenuNode->DBInsert(); - $oP->add("
              "); - $oP->add("

              Menu item created !

              "); - $oP->add(""); - $oP->add(""); - $oP->add(""); - $oP->add(""); - } - } - - $oP->add("\n"); + $oP->add("\n\n"); } -else -{ - $oP->add("\n"); -} - +$oP->add("\n"); $oP->output(); ?> diff --git a/pages/ajax.render.php b/pages/ajax.render.php index ba95c5ea1d..351a326a47 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -287,6 +287,16 @@ switch($operation) DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'attachement'); } break; + + case 'search_form': + $sClass = utils::ReadParam('className', '', 'get'); + $sRootClass = utils::ReadParam('baseClass', '', 'get'); + $currentId = utils::ReadParam('currentId', '', 'get'); + $oFilter = $oContext->NewFilter($sClass); + $oSet = new CMDBObjectSet($oFilter); + $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass)); + $oPage->add($sHtml); + break; default: $oPage->p("Invalid query."); From 9ffb085658761d3c01e2a768c6c47ef61427044d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 2 May 2010 18:42:49 +0000 Subject: [PATCH 307/970] - Creation and CSV import de-activated for abstract classes SVN:trunk[390] --- application/displayblock.class.inc.php | 10 +++++----- pages/csvimport.php | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 5cb3db539a..388b679aba 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -869,16 +869,16 @@ class MenuBlock extends DisplayBlock { case 0: // No object in the set, the only possible action is "new" - $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY); + $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY); if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../page/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); } break; case 1: $oObj = $oSet->Fetch(); $id = $oObj->GetKey(); - $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); + $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); - $bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); + $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); // Just one object in the set, possible actions are "new / clone / modify and delete" if (isset($aExtraParams['link_attr'])) @@ -927,8 +927,8 @@ class MenuBlock extends DisplayBlock default: // Check rights // New / Modify - $bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); - $bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); + $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); + $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); if (isset($aExtraParams['link_attr'])) { diff --git a/pages/csvimport.php b/pages/csvimport.php index ddca29a4e6..8ed3600b05 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -40,7 +40,8 @@ function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null $aValidClasses = array(); foreach(MetaModel::GetClasses('bizmodel') as $sClassName) { - if (is_null($iActionCode) || UserRights::IsActionAllowed($sClassName, $iActionCode)) + if ( (is_null($iActionCode) || UserRights::IsActionAllowed($sClassName, $iActionCode)) && + (!MetaModel::IsAbstract($sClassName)) ) { $sSelected = ($sClassName == $sDefaultValue) ? " selected" : ""; $sDescription = MetaModel::GetClassDescription($sClassName); From cb9331c41413ea62755bee00ba5dd24dd3a4f6b0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 2 May 2010 18:52:46 +0000 Subject: [PATCH 308/970] - Proper usage of htmlentities: specifiy the target character set ! SVN:trunk[391] --- application/itopwebpage.class.inc.php | 2 +- application/nicewebpage.class.inc.php | 2 +- core/bulkchange.class.inc.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 2c8ef4feaa..b5758307d1 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -335,7 +335,7 @@ EOF $i = 0; foreach($m_aTabs as $sTabName => $sTabContent) { - $sTabs .= "
            • ".htmlentities($sTabName)."
            • \n"; + $sTabs .= "
            • ".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."
            • \n"; $i++; } $sTabs .= "
            \n"; diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index ae1ab935f3..26e729e93f 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -59,7 +59,7 @@ class NiceWebPage extends WebPage foreach($aChoices as $sKey => $sValue) { $sSelected = ($sKey == $sDefaultValue) ? " SELECTED" : ""; - $this->add(""); + $this->add(""); } $this->add(""); } diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index c063a9bd1a..7b001124a2 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -47,7 +47,7 @@ abstract class CellChangeSpec } else { - return htmlentities($value); + return htmlentities($value, ENT_QUOTES, 'UTF-8'); } } From 029857b5f2fc3c0d2a7edbdba725658241e81bc6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 May 2010 19:49:49 +0000 Subject: [PATCH 309/970] - Verification of invalid external fields (i.e fields pointing to a non-existing field in the target class). SVN:trunk[392] --- core/attributedef.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 275b8d25f1..3b615cf388 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1239,6 +1239,7 @@ class AttributeExternalField extends AttributeDefinition { $oKeyAttDef = $this->GetKeyAttDef(); $oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->Get("targetclass"), $this->Get("target_attcode")); + if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->Get("targetclass")." has no attribute ".$this->Get("target_attcode")); return $oExtAttDef; } From 2aa049092fb5e468cc707ec5d88496e13338d43d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 12:21:39 +0000 Subject: [PATCH 310/970] - Search forms now display the subclasses of the specified class... SVN:trunk[393] --- js/utils.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/js/utils.js b/js/utils.js index c97e959924..c7cf1fc14f 100644 --- a/js/utils.js +++ b/js/utils.js @@ -49,3 +49,31 @@ function UpdateFileName(id, sNewFileName) $('#'+id).val(sNewFileName); $('#name_'+id).text(sNewFileName); } +/** + * Reload a search form for the specified class + */ +function ReloadSearchForm(divId, sClassName, sBaseClass) +{ + $('#'+divId).block(); + var formEvents = $('#'+divId+' form').data('events'); + var bSubmitHookIsUsed = false; + if ( (formEvents != undefined) && (SubmitHook != undefined)) + { + // Assume that we're using the function submit hook... + bSubmitHookIsUsed = true; + } + $('#'+divId+' form').unbind('submit'); + $.get('ajax.render.php', + { operation: 'search_form', className: sClassName, baseClass: sBaseClass, currentId: divId }, + function(data){ + $('#'+divId).empty(); + $('#'+divId).append(data); + if (bSubmitHookIsUsed) + { + $('#'+divId+' form').bind('submit', SubmitHook); + } + $('#'+divId).unblock(); + } + ); + +} From 65d6adde1c244d26194b8d6cdf092b951b14e0b8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 13:57:14 +0000 Subject: [PATCH 311/970] - Enhancement to UniversalSearch: 1) Localization 2) Fixed two medium issues, see Trac #35 SVN:trunk[394] --- dictionaries/dictionary.itop.ui.php | 4 +++ dictionaries/fr.dictionary.itop.ui.php | 5 ++++ pages/UniversalSearch.php | 36 ++++++++++++-------------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index cdc3845f9e..fb254ea58c 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -427,6 +427,10 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:CSVImport:HeaderSearch' => 'Search?', 'UI:CSVImport:AlertIncompleteMapping' => 'Please select a mapping for every field.', 'UI:CSVImport:AlertNoSearchCriteria' => 'Please select at least one search criteria', + + 'UI:UniversalSearchTitle' => 'iTop - Universal Search', + 'UI:UniversalSearch:Error' => 'Error: %1$s', + 'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 15fa62261f..02a44fbab7 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -427,6 +427,11 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:CSVImport:HeaderSearch' => 'Recherche ?', 'UI:CSVImport:AlertIncompleteMapping' => 'Veuillez choisir le correspondance de chacun des champs.', 'UI:CSVImport:AlertNoSearchCriteria' => 'Veuillez choisir au moins une clef de recherche.', + + 'UI:UniversalSearchTitle' => 'iTop - Recherche Universelle', + 'UI:UniversalSearch:Error' => 'Erreur : %1$s', + 'UI:UniversalSearch:LabelSelectTheClass' => 'Sélectionnez le type d\'objets à rechercher : ', + )); ?> diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index 9810612493..5561571564 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -11,35 +11,34 @@ LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); -$currentOrganization = utils::ReadParam('org_id', 1); +$currentOrganization = utils::ReadParam('org_id', ''); -$oP = new iTopWebPage("iTop - Universal search", $currentOrganization); +$oP = new iTopWebPage(Dict::S('UI:UniversalSearchTitle'), $currentOrganization); // From now on the context is limited to the the selected organization ?? // Now render the content of the page -$sClass = utils::ReadParam('class', 'bizOrganization'); -$sOQLClass = utils::ReadParam('oql_class', $sClass); +$sBaseClass = utils::ReadParam('baseClass', 'bizOrganization'); +$sClass = utils::ReadParam('class', $sBaseClass); $sOQLClause = utils::ReadParam('oql_clause', ''); -$sClassName = $sOQLClass; //utils::ReadParam('class', $sOQLClass); $sFilter = utils::ReadParam('filter', ''); $sOperation = utils::ReadParam('operation', ''); // First part: select the class to search for $oP->add(""); $oP->add(""); -$oP->add("Select the class to search: "); $aClassLabels = array(); -foreach(MetaModel::GetClasses('bizmodel') as $sClass) +foreach(MetaModel::GetClasses('bizmodel') as $sCurrentClass) { - $aClassLabels[$sClass] = MetaModel::GetName($sClass); + $aClassLabels[$sCurrentClass] = MetaModel::GetName($sCurrentClass); } asort($aClassLabels); -foreach($aClassLabels as $sClass => $sLabel) +foreach($aClassLabels as $sCurrentClass => $sLabel) { - $sDescription = MetaModel::GetClassDescription($sClass); - $sSelected = ($sClass == $sClassName) ? " SELECTED" : ""; - $oP->add(""); + $sDescription = MetaModel::GetClassDescription($sCurrentClass); + $sSelected = ($sCurrentClass == $sBaseClass) ? " SELECTED" : ""; + $oP->add(""); } $oP->add(""); @@ -47,7 +46,7 @@ try { if ($sOperation == 'search_form') { - $sOQL = "SELECT $sOQLClass $sOQLClause"; + $sOQL = "SELECT $sClass $sOQLClause"; $oFilter = DBObjectSearch::FromOQL($sOQL); } else @@ -57,17 +56,16 @@ try { $oFilter = CMDBSearchFilter::unserialize($sFilter); } - else if (!empty($sClassName)) + else if (!empty($sClass)) { - $oFilter = new CMDBSearchFilter($sClassName); + $oFilter = new CMDBSearchFilter($sClass); } } } catch (CoreException $e) { - $oFilter = new CMDBSearchFilter($sClassName); - $oP->P("Error:"); - $oP->P($e->getHtmlDesc()); + $oFilter = new CMDBSearchFilter($sClass); + $oP->P("".Dict::Format('UI:UniversalSearch:Error', $e->getHtmlDesc()).""); } if ($oFilter != null) @@ -76,7 +74,7 @@ if ($oFilter != null) $oBlock = new DisplayBlock($oFilter, 'search', false); $aExtraParams = $oAppContext->GetAsHash(); $aExtraParams['open'] = true; - $aExtraParams['oql_class'] = $sOQLClass; + $aExtraParams['baseClass'] = $sBaseClass; //$aExtraParams['class'] = $sClassName; $oBlock->Display($oP, 0, $aExtraParams); From ac1fa17727bb0d4e13126cd3a532359ac4eedc80 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 14:19:38 +0000 Subject: [PATCH 312/970] - Typo SVN:trunk[395] --- pages/testlist.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 56b0e0d57a..1298a5213e 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -987,9 +987,9 @@ class TestBulkChangeOnFarm extends TestBizModel // attributes array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), // ext keys - array() + array(), // reconciliation - array('name'), + array('name') ); $oMyChange = MetaModel::NewObject("CMDBChange"); From bd3e09a9b8dc160941c70c1149bae414ca1d0548 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 14:23:06 +0000 Subject: [PATCH 313/970] Remove unused/obsolete pages SVN:trunk[396] --- pages/advanced_search.php | 310 ------------------------------------ pages/ajax.php | 40 ----- pages/data_generator.php | 321 -------------------------------------- pages/db_importer.php | 171 -------------------- 4 files changed, 842 deletions(-) delete mode 100644 pages/advanced_search.php delete mode 100644 pages/ajax.php delete mode 100644 pages/data_generator.php delete mode 100644 pages/db_importer.php diff --git a/pages/advanced_search.php b/pages/advanced_search.php deleted file mode 100644 index cb2a4ec6b0..0000000000 --- a/pages/advanced_search.php +++ /dev/null @@ -1,310 +0,0 @@ -no_cache(); - - -MetaModel::CheckDefinitions(); -// new API - MetaModel::DBCheckFormat(); -// not necessary, and time consuming! -// MetaModel::DBCheckIntegrity(); - - -function ReadParam($sName, $defaultValue = "") -{ - return isset($_REQUEST[$sName]) ? $_REQUEST[$sName] : $defaultValue; -} - - - -function Page1_AskClass($oPage) -{ - $oPage->add("
            \n"); - //$oPage->add(""); - $oPage->p("Please select the type of object that you want to look for:"); - $oPage->MakeClassesSelect("class", "", 50, UR_ACTION_READ); - $oPage->add("\n"); - $oPage->add("
            \n"); -} - - -function Page2_ConfigFilters($oPage, $oFilter) -{ - $sClass = $oFilter->GetClass(); - - $oPage->p("Objects of class $sClass"); - $oPage->add("
            \n"); - $oPage->add("\n"); - - // Full text input - // - $oPage->add("
            \n"); - $oPage->add("Full text: "); - $sFullText = ""; - foreach($oFilter->GetCriteria_FullText() as $sFullText) - { - // #@# Known limitation: do not consider other full text conditions... - continue; - } - - $oPage->add("\n"); - $oPage->add("
            \n"); - - // Attribute-related criteria - // - foreach (MetaModel::GetClassFilterDefs($sClass) as $sFltCode => $oFltDef) - { - // Set its current values - $sOpCode = "__none__"; - $sValue = ""; - foreach($oFilter->GetCriteria() as $aCritInfo) - { - if ($aCritInfo["filtercode"] == "id") - { - // ??? - } - elseif ($aCritInfo["filtercode"] == $sFltCode) - { - $sOpCode = $aCritInfo["opcode"]; - $sValue = $aCritInfo["value"]; - break; - } - } - - $oPage->add("
            \n"); - //$oPage->add($oFltDef->GetType()." (".$oFltDef->GetTypeDesc().")"); - $oPage->add(" ".$oFltDef->GetLabel()." "); - - $aOperators = array_merge(array("__none__" => ""), $oFltDef->GetOperators()); - $oPage->add_select($aOperators, "flt_ops[$sFltCode]", $sOpCode, 100); - $oPage->add("\n"); - - $oPage->add("\n"); - $oPage->add("
            \n"); - } - - // Ext key criteria - // - foreach (MetaModel::EnumReferencedClasses($sClass) as $sExtKeyAttCode => $sRemoteClass) - { - // Set its current values - $oSubFilter = $oFilter->GetCriteria_PointingTo($sExtKeyAttCode); - if (!$oSubFilter) - { - $oSubFilter = new CMDBSearchFilter($sRemoteClass); - } - - $oPage->add("
            \n"); - $oAtt = MetaModel::GetAttributeDef($oFilter->GetClass(), $sExtKeyAttCode); - $oPage->add($oAtt->GetLabel()." having ({$oSubFilter->DescribeConditions()})"); - //$oPage->add("having $oFilter->DescribeConditionPointTo($sExtKeyAttCode)); - $oPage->add("\n"); - - $oPage->add(dialogstack::RenderEditableField("Edit...", "flt_pointto[$sExtKeyAttCode]", $oSubFilter->serialize(), true)); - $oPage->add("
            \n"); - } - - // Ext key criteria, the other way - // - foreach (MetaModel::EnumReferencingClasses($sClass, true) as $sRemoteClass => $aRemoteKeys) - { - foreach ($aRemoteKeys as $sExtKeyAttCode => $oExtKeyAttDef) - { - // Set its current values - $oSubFilter = $oFilter->GetCriteria_ReferencedBy($sRemoteClass, $sExtKeyAttCode); - if (!$oSubFilter) - { - $oSubFilter = new CMDBSearchFilter($sRemoteClass); - } - - $oPage->add("
            \n"); - //$oPage->add($oFilter->DescribeConditionRefBy($sRemoteClass, $sExtKeyAttCode)); - $oAtt = MetaModel::GetAttributeDef($sRemoteClass, $sExtKeyAttCode); - $oPage->add("being ".$oAtt->GetLabel()." for ".$sRemoteClass."(e)s in ({$oSubFilter->DescribeConditions()})"); - $oPage->add("\n"); - - $oPage->add(dialogstack::RenderEditableField("Edit...", "flt_refedby[$sRemoteClass][$sExtKeyAttCode]", $oSubFilter->serialize(), true)); - $oPage->add("
            \n"); - } - } - - // Ext key criteria -> link objects - // - foreach (MetaModel::EnumLinkingClasses($sClass) as $sLinkClass => $aRemoteClasses) - { - foreach($aRemoteClasses as $sExtKeyAttCode => $sRemoteClass) - { - // Set its current values - //$oSubFilter = $oFilter->GetCriteria_PointingTo($sExtKeyAttCode); - $oSubFilter = null; - if (!$oSubFilter) - { - $oSubFilter = new CMDBSearchFilter($sRemoteClass); - } - $oPage->add("
            \n"); - //$oPage->add(" ".MetaModel::GetName($sRemoteClass)." "); - $oPage->add(" Linked to '".MetaModel::GetLinkLabel($sLinkClass, $sExtKeyAttCode)."' by "); - $oSubFilter = new CMDBSearchFilter($sRemoteClass); - $oPage->add($oSubFilter->__DescribeHTML()); - $oPage->add("\n"); - - $oPage->add(dialogstack::RenderEditableField("Edit...", "flt_linkedwith[$sRemoteClass][$sExtKeyAttCode]", $oSubFilter->serialize(), true)); - $oPage->add("
            \n"); - } - } - - $oPage->add("\n"); - $oPage->add("
            \n"); -} - -function MakeFilterFromArgs() -{ - $sClass = ReadParam("class"); - $sFilterFullText = ReadParam("flt_fulltext", ""); - $aFilterOps = ReadParam("flt_ops", array()); - $aFilterValues = ReadParam("flt_values", array()); - $aPointTo = ReadParam("flt_pointto", array()); - $aRefedBy = ReadParam("flt_refedby", array()); - $aLinkedWith = ReadParam("flt_linkedwith", array()); - - $oFilter = new CMDBSearchFilter($sClass); - - if (!empty($sFilterFullText)) - { - $oFilter->AddCondition_FullText($sFilterFullText); - } - - foreach($aFilterOps as $sFltCode=>$sOpCode) - { - if ($sOpCode == "__none__") continue; - $oFilter->AddCondition($sFltCode, $aFilterValues[$sFltCode], $sOpCode); - } - - foreach($aPointTo as $sExtKeyAttCode=>$sFilterShortcut) - { - $oSubFilter = CMDBSearchFilter::unserialize($sFilterShortcut); - $oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode); - } - - foreach($aRefedBy as $sForeignClass=>$aExtKeys) - { - foreach($aExtKeys as $sForeignExtKey=>$sFilterShortcut) - { - //MyHelpers::var_dump_html("$sForeignClass / $sForeignExtKey / $sFilterShortcut"); - $oSubFilter = CMDBSearchFilter::unserialize($sFilterShortcut); - //MyHelpers::var_dump_html($oSubFilter); - $oFilter->AddCondition_ReferencedBy($oSubFilter, $sForeignExtKey); - } - } - -// $oFilter->AddCondition_LinkedTo(DBObjectSearch $oLinkFilter, $sExtKeyAttCodeToMe, $sExtKeyAttCodeTarget, DBObjectSearch $oFilterTarget); - - return $oFilter; -} - -function Page3_ViewResults($oPage, $oFilter) -{ - // Output results in various forms... - // - if ($oFilter->IsAny()) - { - $oPage->p("You are considering the ENTIRE set of objects..."); - } - else - { - $oPage->p($oFilter->__DescribeHTML()); - - $oSet = new CMDBObjectSet($oFilter); - $oPage->p("Found ".$oSet->Count()." items"); - - $sFilterPhrase = urlencode($oFilter->serialize()); - $oPage->p("See detailed results"); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// -// M a i n P r o g r a m -// -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -$oPage->p("

            Advanced search

            "); - - -// Page 1 - Ask class -// -// Page 2 - Class is given, enum existing filters/possible links -// -// Page 3 - Interpret user choices, create a filter and render its string representation -// - -//MyHelpers::arg_dump_html(); -//MyHelpers::var_dump_html($_SESSION); - -if (ReadParam('userconfig', false)) -{ - $sTodo = 'userconfig'; -} -if (ReadParam('makeit', false)) -{ - $sTodo = 'makeit'; -} -else -{ - if (dialogstack::IsDialogStartup()) - { - $sInit = dialogstack::StartDialog(); - $oFilter = CMDBSearchFilter::unserialize($sInit); - $sTodo = 'userconfig'; - } - else - { - $sClass = ReadParam('class', ''); - if (empty($sClass)) - { - $sTodo = 'selectclass'; - } - else - { - $oFilter = MakeFilterFromArgs(); - $sTodo = 'userconfig'; - } - } -} - -switch ($sTodo) -{ -case "selectclass": - Page1_AskClass($oPage); - break; - -case "userconfig": - dialogstack::DeclareCaller("Define filter for ".$oFilter->GetClass()); - $oPage->add(implode(" / ", dialogstack::GetCurrentStack())); - Page2_ConfigFilters($oPage, $oFilter); - break; - -case "makeit": - $oFilter = MakeFilterFromArgs(); - Page3_ViewResults($oPage, $oFilter); - $oPage->add(dialogstack::RenderEndDialogForm(DLGSTACK_OK, "Use filter", $oFilter->serialize())); - $oPage->add(dialogstack::RenderEndDialogForm(DLGSTACK_CANCEL, "Annuler")); - break; - -default: - trigger_error("Wrong value for arg todo ($sTodo)", E_USER_ERROR); -} - -$oPage->output(); - -?> diff --git a/pages/ajax.php b/pages/ajax.php deleted file mode 100644 index 76cef861a8..0000000000 --- a/pages/ajax.php +++ /dev/null @@ -1,40 +0,0 @@ -no_cache(); -$oPage->add("

            Asynchronous versus asynchronous DisplayBlocks

            \n"); - -$oContext = new UserContext(); -$operation = ReadParam('operation', ''); -$sClassName = ReadParam('class', 'bizContact'); -$sOrganizationCode = ReadParam('org', 'ITOP'); - -$oPage->p("[Synchronous] Count of all $sClassName objects for organization '$sOrganizationCode'"); -$oFilter = $oContext->NewFilter($sClassName); -$oFilter ->AddCondition('organization', $sOrganizationCode, '='); -$oBlock = new DisplayBlock($oFilter, 'count', false); -$oBlock->Display($oPage, "block1"); - -$oPage->p("[Asynchronous] All $sClassName objects for organization '$sOrganizationCode'"); -$oFilter = $oContext->NewFilter($sClassName); -$oFilter ->AddCondition('organization', $sOrganizationCode, '='); -$oBlock = new DisplayBlock($oFilter, 'list', true); -$oBlock->Display($oPage, "block2"); - -$oPage->p("[Asynchronous] Details of all $sClassName objects for organization '$sOrganizationCode'"); -$oFilter = $oContext->NewFilter($sClassName); -$oFilter ->AddCondition('organization', $sOrganizationCode, '='); -$oBlock = new DisplayBlock($oFilter, 'details', true); -$oBlock->Display($oPage, "block3"); - -$oPage->output(); -?> diff --git a/pages/data_generator.php b/pages/data_generator.php deleted file mode 100644 index c804788121..0000000000 --- a/pages/data_generator.php +++ /dev/null @@ -1,321 +0,0 @@ -\n"; - echo "DEBUG: \$_SERVER\n"; - print_r($_SERVER); - echo "\n"; - exit; -} -if (!UserRights::Login($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) -{ - header('WWW-Authenticate: Basic realm="iTop"'); - header('HTTP/1.0 401 Unauthorized'); - echo 'Authentication failed !'; - exit; -} - -$oPage = new iTopWebPage("iTop Data Generator", $currentOrganization); -$oPage->no_cache(); - -// From now on the context is limited to the the selected organization ?? - -// Retrieve the root node for the menu -$oSearchFilter = $oContext->NewFilter("menuNode"); -$oSearchFilter->AddCondition('parent_id', 0, '='); -// There may be more criteria added later to have a specific menu based on the user's profile -$oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true)); -while ($oRootMenuNode = $oSet->Fetch()) -{ - $oRootMenuNode->DisplayMenu($oPage, $oContext, $iActiveNodeId, null, $oAppContext->GetAsHash()); -} -/** - * The (ordered) list of classes for which to generate objects - * - * Each class in this list must implement a non-static Generate(cmdbDataGenerator $oGenerator) method - */ -//$aClassesToGenerate = array('bizOrganization' /* This one is special and must go first */, 'bizService', 'bizContact', 'bizPC', 'bizNetworkDevice'); -$aClassesToGenerate = array('bizOrganization' /* This one is special and must go first */, 'bizLocation', 'bizPC', 'bizNetworkDevice', 'bizPerson', 'bizIncidentTicket', 'bizInfraGroup', 'bizInfraInfra'); - -///////////////////////////////////////////////////////////////////////////////////// -// Actual actions of the page -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Populate an organization with objects of each class listed in the (global) $aClassesToGenerate array - * - * @param WebPage $oPage The object used for the HTML output - * @param cmdbGenerator $oGenerator The object used for the generation of the objects - * @param string $sSize An enum specifying (roughly) how many objects of each class to create: one of 'small', 'medium', 'big', 'huge' or 'max' - */ -function PopulateOrganization(CMDBChange $oMyChange, WebPage $oPage, cmdbDataGenerator $oGenerator, $sSize = 'small') -{ - global $aClassesToGenerate; - - for ($i=1 /* skip the first one (i=0) which is the org itself */; $iGenerate($oGenerator); - // By rom - // $oObject->DBInsert(); - $oObject->DBInsertTracked($oMyChange); - - //$oObject->DisplayDetails($oPage); - } - } - $oPage->p("$nbObjects $sClass objects created."); - } -} - -/** - * Delete an organization and all the instances of 'Object' belonging to this organization - * - * @param WebPage $oPage The object used for the HTML output - * @param string $sOrganizationCode The code (pkey) of the organization to delete - */ -function DeleteOrganization($oMyChange, $oPage, $sOrganizationCode) -{ - $oOrg = MetaModel::GetObject('bizOrganization', $sOrganizationCode); - if ($oOrg == null) - { - $oPage->p("Organization '$sOrganizationCode' already deleted!!"); - } - else - { - // Delete all the objects linked to this organization - $oFilter = new CMDBSearchFilter('logRealObject'); - $oFilter->AddCondition('organization', $sOrganizationCode, '='); - $oSet = new CMDBObjectSet($oFilter); - $countDeleted = $oSet->Count(); - MetaModel::BulkDeleteTracked($oMyChange, $oFilter); // Should do a one by one delete to let the objects do their own cleanup ! - $oPage->p("$countDeleted object(s) deleted!!"); - - $oOrg->DBDeleteTracked($oMyChange); - $oPage->p("Ok, organization '$sOrganizationCode' deleted!"); - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////// -// -// M a i n P r o g r a m -// -///////////////////////////////////////////////////////////////////////////////////////////////// - -$operation = utils::ReadParam('operation', ''); - -$oPage->add("
            \n"); -$oPage->add("
            \n"); - -switch($operation) -{ - - case 'specify_generate': - // Display a form to specify what to generate - $oPage->p("iTop Data Generator\n"); - $oPage->add("
            \n"); - $oPage->add("Number of organizations to generate: \n"); - $oPage->add(" (max ".count($aCompanies).") \n"); - $oPage->add("Size of the organizations\n"); - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->add("
            \n"); - break; - - case 'generate': - // perform the actual generation - $iOrgCount = utils::ReadParam('org_count', 0); - $sOrgSize = utils::ReadParam('org_size', 'small'); - // By rom - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Made by data generator ($iOrgCount orgs of size '$sOrgSize')"); - $oMyChange->DBInsert(); - while($iOrgCount > 0) - { - set_time_limit(5*60); // let it run for 5 minutes for each organization - $oGenerator = new cmdbDataGenerator(); - // Create the new organization - $oOrg = MetaModel::NewObject('bizOrganization'); - $oOrg->Generate($oGenerator); - - // By rom - // $oOrg->DBInsert(); - $oOrg->DBInsertTracked($oMyChange); - $oGenerator->SetOrganizationId($oOrg->GetKey()); - - $oPage->p("Organization '".$oOrg->GetKey()."' created\n"); - $oOrg->DisplayDetails($oPage); - $oPage->add("
            \n"); - PopulateOrganization($oMyChange, $oPage, $oGenerator, $sOrgSize); - $oPage->add("
            \n"); - unset($oGenerator); - $iOrgCount--; - } - break; - - case 'specify_update': - // Specify which organization to update - $oPage->add("
            \n"); - $oPage->add("Select the organization to update: \n"); - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->p(""); - $oPage->add("Quantity of of objects to add in each class: \n"); - $oPage->add("\n"); - $oPage->p(""); - $oPage->add("  \n"); - $oPage->add("\n"); - $oPage->add("
            \n"); - break; - - case 'update': - // perform the actual update - set_time_limit(5*60); // let it run for 5 minutes - $sOrganizationCode = utils::ReadParam('org', ''); - $sOrgSize = utils::ReadParam('org_size', 'small'); - if ($sOrganizationCode == '') - { - $oPage->p("Error: please specify an organization (org)."); - } - else - { - // By rom - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Made by data generator (update org '$sOrganizationCode', size '$sOrgSize')"); - $oMyChange->DBInsert(); - - $oPage->p("Organization '$sOrganizationCode' updated."); - $oGenerator = new cmdbDataGenerator($sOrganizationCode); - PopulateOrganization($oMyChange, $oPage, $oGenerator, $sOrgSize); - } - break; - - case 'specify_delete': - // Select an organization to be deleted - $oPage->add("
            \n"); - $oPage->add("Select the organization to delete: \n"); - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->p(""); - $oPage->add("  \n"); - $oPage->add("\n"); - $oPage->add("
            \n"); - break; - - case 'confirm_delete': - // confirm the dangerous action - $sOrganizationCode = ReadParam('org', ''); - $oPage->p("iTop Data Generator\n"); - $oPage->p("Warning: you are about to delete the organization '$sOrganizationCode' and all its related objects\n"); - $oPage->add("
            \n"); - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->add("  \n"); - $oPage->add("\n"); - $oPage->add("
            \n"); - break; - - case 'delete': - // perform the actual deletion - $sOrganizationCode = ReadParam('org', ''); - if ($sOrganizationCode == '') - { - $oPage->p("Error: please specify an organization (org)."); - } - else - { - // By rom - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Made by data generator (delete org '$sOrganizationCode'"); - $oMyChange->DBInsert(); - - $oPage->p("Deleting '$sOrganizationCode'\n"); - DeleteOrganization($oMyChange, $oPage, $sOrganizationCode); - } - break; - - // display the menu of actions - case 'menu': - default: - $oPage->p("Data Generator Menu"); - $oPage->p("Generate one or more organizations"); - $oPage->p("Add more objects into an organization"); - $oPage->p("Delete an organization"); -} -$oPage->add("
            \n"); -$oPage->add("
            \n"); - -$oPage->p("Return to the data generator menu"); - -$oPage->output(); -?> diff --git a/pages/db_importer.php b/pages/db_importer.php deleted file mode 100644 index cb56bf4e78..0000000000 --- a/pages/db_importer.php +++ /dev/null @@ -1,171 +0,0 @@ -add('
            '); - $oP->add('

            Creation of an archive for the business model: '.$sBizModel.'

            '); - $oP->p('Title of the archive: '); - $oP->p('Description of this archive: '); - $oP->p('Select the packages you want to include into this archive (When restoring the archive you will prompted to pick a package):'); - $oP->p(' The full database (schema + data).'); - $oP->p(' Only the schema but of the complete database.'); - $oP->p(' The complete business model (all the tables used by the business model, schema + data).'); - $oP->p(' Only the schema of the business model.'); - $oP->add(''); - $oP->add(''); - $oP->add(''); - $oP->add($oAppContext->GetForForm()); - $oP->add('
            '); - $oP->p('Back to menu'); - break; - - case 'do_create': - $sTitle = utils::ReadParam('title', 'Unknown archive'); - $sDescription = utils::ReadParam('description', 'No description provided for this archive.'); - $bfullDump = utils::ReadParam('full', false); - $bfullSchemaDump = utils::ReadParam('full_schema', false); - $bBizDump = utils::ReadParam('biz', false); - $bBizSchemaDump = utils::ReadParam('biz_schema', false); - $sArchiveFile = '../tmp/archive1.zip'; - - $oArchive = new iTopArchive($sArchiveFile, iTopArchive::create); - $oArchive->SetTitle($sTitle); - $oArchive->SetDescription($sDescription); - if ($bfullDump) - { - $oArchive->AddDatabaseDump("Full Database Dump", "Choose this option to completely reload your database. All current data will be deleted and replaced by the backup", "full-db.sql", 'itop', array(), false); - } - - if ($bfullSchemaDump) - { - $oArchive->AddDatabaseDump("Full Schema Dump", "Choose this option to completely wipe out your database and start from an empty database", "full-schema.sql", 'itop', array(), true); - } - - if ($bBizDump || $bBizSchemaDump) - { - // compute the list of the tables involved in the business model - $aBizTables = array(); - foreach(MetaModel::GetClasses('bizmodel') as $sClass) - { - $sTable = MetaModel::DBGetTable($sClass); - $aBizTables[$sTable] = $sTable; - } - unset($aBizTables['']); - if ($bfullDump) - { - $oArchive->AddDatabaseDump("Full Business Model Dump", "Choose this option to completely reload the business model part of your database. All current business data will be deleted and replaced by the backup. Application data (like menus...) are preserved.", "biz-db.sql", 'itop', $aBizTables, false); - } - - if ($bfullSchemaDump) - { - $oArchive->AddDatabaseDump("Full Business Model Schema Dump", "Choose this option to wipe out the business data and start from an empty database. All current business data will be deleted. Application data (like menus...) are preserved.", "biz-schema.sql", 'itop', $aBizTables, true); - } - } - $oArchive->WriteCatalog(); - $oP->p("The archive '$sTitle' has been created in $sArchiveFile."); - $oP->p('Back to menu'); - break; - - case 'select_archive': - $sArchivesDir = '../tmp'; - $oP->add('
            '); - $oP->add('

            Importation of an archive

            '); - $oP->p('Select the archive you want to import:'); - $aArchives = array(); - if ($handle = opendir($sArchivesDir)) - { - while (false !== ($sFileName = readdir($handle))) - { - if (strtolower(substr($sFileName, -3, 3)) == 'zip') - { - $oArchive = new iTopArchive('../tmp/'.$sFileName, iTopArchive::read); - if ($oArchive->IsValid()) - { - $oArchive->ReadCatalog(); - $aArchives['../tmp/'.$sFileName] = $oArchive->GetTitle(); - } - } - } - closedir($handle); - } - foreach($aArchives as $sFileName => $sTitle) - { - $oP->p(''.$sTitle); - } - $oP->add(''); - $oP->add(''); - $oP->add(''); - $oP->add($oAppContext->GetForForm()); - $oP->add('
            '); - $oP->p("(Archives are searched into the directory: $sArchivesDir.)"); - $oP->p('Cancel'); - break; - - case 'select_package': - $sArchiveFile = utils::ReadParam('archive_file', ''); - $oArchive = new iTopArchive($sArchiveFile, iTopArchive::read); - $oArchive->ReadCatalog(); - $oP->add('
            '); - $oP->add('

            Selection of a package inside '.$oArchive->GetTitle().'

            '); - $oP->p('Select the package you want to apply:'); - $aPackages = $oArchive->GetPackages(); - foreach($aPackages as $sPackageName => $aPackage) - { - $oP->p(''.$aPackage['title']); - $oP->p($aPackage['description']); - } - $oP->add(''); - $oP->add(''); - $oP->add(''); - $oP->add(''); - $oP->add($oAppContext->GetForForm()); - $oP->add('
            '); - $oP->p('Cancel'); - break; - - case 'import_package': - $sArchiveFile = utils::ReadParam('archive_file', ''); - $sPackageName = utils::ReadParam('package_name', ''); - $oArchive = new iTopArchive($sArchiveFile, iTopArchive::read); - $oArchive->ReadCatalog(); - $oP->add('

            Applying the package '.$sPackageName.'

            '); - if($oArchive->ImportSQL($sPackageName)) - { - $oP->p('Done.'); - } - else - { - $oP->p('Sorry, an error occured while applying the package...'); - } - $oP->p('Apply another package from the same archive'); - $oP->p('Select another archive'); - $oP->p('Back to the menu'); - break; - - case 'menu': - default: - $oP->add('

            Database backup & restore

            '); - $oP->add('

            Select one of the actions below:

            '); - $oP->add('

            Export the database to an archive

            '); - $oP->add('

            Reload the database from an archive

            '); -} - -$oP->output(); -?> From df98e257432fb53caee1a062dcdf763b7e4065e8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 14:39:43 +0000 Subject: [PATCH 314/970] Preparing the integration of the Flash navigator SVN:trunk[397] --- pages/UI.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pages/UI.php b/pages/UI.php index 34c29d8363..245bca6429 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1273,6 +1273,23 @@ try $oBlock->Display($oP, 0); $oObj->DisplayDetails($oP); break; + + case 'swf_navigator': + $sClass = utils::ReadParam('class', ''); + $id = utils::ReadParam('id', 0); + $sRelation = utils::ReadParam('relation', 'neighbours'); + $width = 1000; + $height = 700; + $sParams = "pWidth=$width&pHeight=$height&drillUrl=".urlencode('../pages/UI.php?operation=details')."&displayController=false&xmlUrl=".urlencode("./xml.navigator.php")."&obj_class=$sClass&obj_id=$id&relation=$sRelation"; + + $oP->add(" + + + + + + \n"); + break; default: if (is_object($oActiveNode)) From 89dffd28453303f94bc8ca6dd1828aaa2b64a0c5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 14:42:36 +0000 Subject: [PATCH 315/970] - Implementation of the localization... on going... SVN:trunk[398] --- dictionaries/dictionary.itop.ui.php | 7 +++++++ dictionaries/fr.dictionary.itop.ui.php | 7 +++++++ pages/audit.php | 6 +++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index fb254ea58c..e330edcda9 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -431,6 +431,13 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:UniversalSearchTitle' => 'iTop - Universal Search', 'UI:UniversalSearch:Error' => 'Error: %1$s', 'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ', + + 'UI:Audit:Title' => 'iTop - CMDB Audit', + 'UI:Audit:InteractiveAudit' => 'Interactive Audit', + 'UI:Audit:HeaderAuditRule' => 'Audit Rule', + 'UI:Audit:HeaderNbObjects' => '# Objects', + 'UI:Audit:HeaderNbErrors' => '# Errors', + 'UI:Audit:PercentageOk' => '% Ok', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 02a44fbab7..69353b6410 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -432,6 +432,13 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:UniversalSearch:Error' => 'Erreur : %1$s', 'UI:UniversalSearch:LabelSelectTheClass' => 'Sélectionnez le type d\'objets à rechercher : ', + 'UI:Audit:Title' => 'iTop - Audit de la CMDB', + 'UI:Audit:InteractiveAudit' => 'Audit Interactif', + 'UI:Audit:HeaderAuditRule' => 'Règle d\'audit', + 'UI:Audit:HeaderNbObjects' => 'Nb d\'Objets', + 'UI:Audit:HeaderNbErrors' => 'Nb d\'Erreurs', + 'UI:Audit:PercentageOk' => '% Ok', + )); ?> diff --git a/pages/audit.php b/pages/audit.php index aadc4f8f3a..03aaf57109 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -10,7 +10,7 @@ $oAppContext = new ApplicationContext(); require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed -$oP = new iTopWebPage("iTop - CMDB Audit", $currentOrganization); +$oP = new iTopWebPage(Dict::S('UI:Audit:Title'), $currentOrganization); function GetRuleResultSet($iRuleId, $oDefinitionFilter) { @@ -83,14 +83,14 @@ switch($operation) case 'audit': default: - $oP->add(''); + $oP->add(''); $oAuditFilter = new CMDBSearchFilter('AuditCategory'); $oCategoriesSet = new DBObjectSet($oAuditFilter); $oP->add("\n"); $oP->add("\n"); - if (isset($aRow['label']) && isset($aRow['label']) && isset($aRow['help'])) + if (isset($aRow['label']) && isset($aRow['input']) && isset($aRow['help'])) { $this->add("\n"); $this->add("\n"); diff --git a/toolkit.php b/toolkit.php index d816778e33..d0b92b45a9 100644 --- a/toolkit.php +++ b/toolkit.php @@ -16,6 +16,7 @@ echo "Check m echo "Backup and restore (shortcut)
            \n"; echo "Objects schema (shortcut)
            \n"; echo "Setup the email
            \n"; +echo "Generate data for benchmarking purposes
            \n"; echo "

            Web services

            \n"; echo "Available functions
            \n"; echo "WSDL (dynamically generated)
            \n"; From d115022132c89146b455ee410beb278f7ff0203d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 4 Jun 2010 14:02:46 +0000 Subject: [PATCH 346/970] #132 Views on data model classes SVN:trunk[429] --- core/dbobjectsearch.class.php | 6 ++---- core/metamodel.class.php | 40 +++++++++++++++++++++++++++++++++++ pages/ITopConsultant.php | 20 +++++++++++++++--- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 09309b7715..eab4c172c0 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -34,15 +34,13 @@ class DBObjectSearch private $m_aReferencedBy; private $m_aRelatedTo; - public function __construct($sClass, $sClassAlias = '') + public function __construct($sClass, $sClassAlias = null) { - if (empty($sClassAlias)) $sClassAlias = $sClass; + if (is_null($sClassAlias)) $sClassAlias = $sClass; assert('is_string($sClass)'); assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference // => idee d'un assert avec call stack (autre utilisation = echec sur query SQL) - if (empty($sClassAlias)) $sClassAlias = $sClass; - $this->m_aSelectedClasses = array($sClassAlias => $sClass); $this->m_aClasses = array($sClassAlias => $sClass); $this->m_oSearchCondition = new TrueExpression; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a306a09e1a..44da9e13f9 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2146,6 +2146,7 @@ abstract class MetaModel CMDBSource::CreateDB(self::$m_sDBName); } self::DBCreateTables(); + self::DBCreateViews(); } protected static function DBCreateTables() @@ -2169,6 +2170,25 @@ abstract class MetaModel // $sDoCreateAll = implode(" ; ", $aSQL); } + protected static function DBCreateViews() + { + list($aErrors, $aSugFix) = self::DBCheckViews(); + + $aSQL = array(); + foreach ($aSugFix as $sClass => $aTarget) + { + foreach ($aTarget as $aQueries) + { + foreach ($aQueries as $sQuery) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } + } + } + } + public static function DBDump() { $aDataDump = array(); @@ -2411,6 +2431,26 @@ abstract class MetaModel return array($aErrors, $aSugFix); } + public static function DBCheckViews() + { + $aErrors = array(); + $aSugFix = array(); + + // Reporting views (must be created after any other table) + // + foreach (self::GetClasses('bizmodel') as $sClass) + { + $sView = "view_$sClass"; + if (!CMDBSource::IsTable($sView)) + { + $oFilter = new DBObjectSearch($sClass, ''); + $sSQL = self::MakeSelectQuery($oFilter); + $aErrors[$sClass]['*'][] = "Missing view for class: $sClass"; + $aSugFix[$sClass]['*'][] = "CREATE VIEW `$sView` AS $sSQL"; + } + } + return array($aErrors, $aSugFix); + } private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false) { diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index 7c918c7175..29e7d54712 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -287,10 +287,11 @@ function printMenu($sConfigFile) echo "
          • Browse business model
          • "; if ($bHasDB) { - echo "
          • Concordance between Biz model and DB format
          • "; + echo "
          • Concordance between Biz model and DB format
          • "; + echo "
          • Concordance between Biz model and Views
          • "; echo "
          • DB integrity check
          • "; + echo "
          • Any check at once (see list above)
          • "; echo "
          • Setup userrights (init DB)
          • "; - echo "
          • Check business model, DB format and data integrity
          • "; echo "
          • Show Tables
          • "; echo "
          • Test an OQL query (debug)
          • "; echo "
          • Dump database
          • "; @@ -371,7 +372,10 @@ function DisplayDBFormatIssues($aErrors, $aSugFix, $sRepairUrl = "", $sSQLStatem $i = 0; foreach ($aTarget as $sTarget => $aMessages) { - echo "

            Wrong declaration for attribute $sTarget

            \n"; + if ($sTarget != '*') + { + echo "

            Wrong declaration for attribute $sTarget

            \n"; + } $sMsg = implode(' AND ', $aMessages); if (!empty($sRepairUrl)) { @@ -518,6 +522,12 @@ else DisplayDBFormatIssues($aErrors, $aSugFix, $sBaseUrl, $sSQLStatementArgName = "sql"); echo "done...
            \n"; break; + case "checkmodeltoviews": + echo "Check Views...
            \n"; + list($aErrors, $aSugFix) = MetaModel::DBCheckViews(); + DisplayDBFormatIssues($aErrors, $aSugFix, $sBaseUrl, $sSQLStatementArgName = "sql"); + echo "done...
            \n"; + break; case "checkdb": echo "Check DB integrity...
            \n"; MetaModel::DBCheckIntegrity($sBaseUrl, "sql"); @@ -541,6 +551,10 @@ else list($aErrors, $aSugFix) = MetaModel::DBCheckFormat(); DisplayDBFormatIssues($aErrors, $aSugFix, $sBaseUrl, $sSQLStatementArgName = "sql"); echo "done...
            \n"; + echo "Check Views...
            \n"; + list($aErrors, $aSugFix) = MetaModel::DBCheckViews(); + DisplayDBFormatIssues($aErrors, $aSugFix, $sBaseUrl, $sSQLStatementArgName = "sql"); + echo "done...
            \n"; echo "Check DB integrity...
            \n"; MetaModel::DBCheckIntegrity($sBaseUrl, "sql"); echo "done...
            \n"; From 65eb567f63ef0bd1c3c15e98a1bc5bd0cafbfc14 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 4 Jun 2010 16:23:18 +0000 Subject: [PATCH 347/970] #132 Finalized: do check if an attribute is missing in a view SVN:trunk[430] --- core/metamodel.class.php | 66 ++++++++++++++++++++++++++++++++++------ pages/ITopConsultant.php | 21 +++++++++++-- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 44da9e13f9..2ed53dfeab 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2047,11 +2047,21 @@ abstract class MetaModel public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) { if (empty($sRepairUrl)) return; - if (count($aSQLFixes) == 0) return; + + // By design, some queries might be blank, we have to ignore them + $aCleanFixes = array(); + foreach($aSQLFixes as $sSQLFix) + { + if (!empty($sSQLFix)) + { + $aCleanFixes[] = $sSQLFix; + } + } + if (count($aCleanFixes) == 0) return; echo "
            \n"; - echo " \n"; - echo " \n"; + echo " \n"; + echo " \n"; echo "\n"; } @@ -2160,9 +2170,12 @@ abstract class MetaModel { foreach ($aQueries as $sQuery) { - //$aSQL[] = $sQuery; - // forces a refresh of cached information - CMDBSource::CreateTable($sQuery); + if (!empty($sQuery)) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } } } } @@ -2181,9 +2194,12 @@ abstract class MetaModel { foreach ($aQueries as $sQuery) { - //$aSQL[] = $sQuery; - // forces a refresh of cached information - CMDBSource::CreateTable($sQuery); + if (!empty($sQuery)) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } } } } @@ -2441,8 +2457,38 @@ abstract class MetaModel foreach (self::GetClasses('bizmodel') as $sClass) { $sView = "view_$sClass"; - if (!CMDBSource::IsTable($sView)) + if (CMDBSource::IsTable($sView)) { + // Check that the view is complete + // + $bIsComplete = true; + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + foreach($oAttDef->GetSQLExpressions() as $sSuffix => $sTrash) + { + $sCol = $sAttCode.$sSuffix; + if (!CMDBSource::IsField($sView, $sCol)) + { + $bIsComplete = false; + $aErrors[$sClass][$sAttCode][] = "field '$sCol' could not be found in view '$sView'"; + $aSugFix[$sClass][$sAttCode][] = ""; + } + } + } + if (!$bIsComplete) + { + // Rework the view + // + $oFilter = new DBObjectSearch($sClass, ''); + $sSQL = self::MakeSelectQuery($oFilter); + $aErrors[$sClass]['*'][] = "View '$sView' is currently not complete"; + $aSugFix[$sClass]['*'][] = "ALTER VIEW `$sView` AS $sSQL"; + } + } + else + { + // Create the view + // $oFilter = new DBObjectSearch($sClass, ''); $sSQL = self::MakeSelectQuery($oFilter); $aErrors[$sClass]['*'][] = "Missing view for class: $sClass"; diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index 29e7d54712..d2fd4aee2a 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -380,9 +380,24 @@ function DisplayDBFormatIssues($aErrors, $aSugFix, $sRepairUrl = "", $sSQLStatem if (!empty($sRepairUrl)) { $aSQLFixes = array_merge($aSQLFixes, $aSugFix[$sClass][$sTarget]); - $sSQLFixes = implode('; ', $aSugFix[$sClass][$sTarget]); - $sUrl = "$sRepairUrl&$sSQLStatementArgName=".urlencode($sSQLFixes); - echo "
          • $sMsg (fix it now!)
          • \n"; + $aCleanFixes = array(); + foreach($aSugFix[$sClass][$sTarget] as $sSQLFix) + { + if (!empty($sSQLFix)) + { + $aCleanFixes[] = $sSQLFix; + } + } + if (count($aCleanFixes) > 0) + { + $sSQLFixes = implode('; ', $aCleanFixes); + $sUrl = "$sRepairUrl&$sSQLStatementArgName=".urlencode($sSQLFixes); + echo "
          • $sMsg (fix it now!)
          • \n"; + } + else + { + echo "
          • $sMsg
          • \n"; + } } else { From dfb2303fc27edef4f39569c89f6ac5d30c8c01a9 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 6 Jun 2010 16:57:48 +0000 Subject: [PATCH 348/970] - New implementation preventing users to submit the same form twice. SVN:trunk[431] --- application/transaction.class.inc.php | 111 ++++++++++++++++++++++++++ application/utils.inc.php | 12 ++- 2 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 application/transaction.class.inc.php diff --git a/application/transaction.class.inc.php b/application/transaction.class.inc.php new file mode 100644 index 0000000000..ffc55c257f --- /dev/null +++ b/application/transaction.class.inc.php @@ -0,0 +1,111 @@ + "gui", + "key_type" => "autoincrement", + "key_label" => "", + "name_attcode" => "expiration_date", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_transaction", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeDateTime("expiration_date", array("allowed_values"=>null, "sql"=>"expiration_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_SetZListItems('details', array('expiration_date')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('expiration_date')); // Attributes to be displayed for a list + } + /** + * Create a new transaction, store it in the database and return its id + * @param void + * @return int The identifier of the new transaction + */ + public static function GetNewTransactionId() + { + // First remove all the expired transactions... + self::CleanupExpiredTransactions(); + $oTransaction = new privUITransaction(); + $sDate = date('Y-m-d H:i:s', time()+TRANSACTION_EXPIRATION_DELAY); + $oTransaction->Set('expiration_date', $sDate); // 4 h delay by default + $oTransaction->DBInsert(); + return sprintf("%d", $oTransaction->GetKey()); + } + + /** + * Check whether a transaction is valid or not and remove the valid transaction from + * the database so that another call to IsTransactionValid for the same transaction + * will return false + * @param int $id Identifier of the transaction, as returned by GetNewTransactionId + * @return bool True if the transaction is valid, false otherwise + */ + public static function IsTransactionValid($id) + { + // First remove all the expired transactions... + self::CleanupExpiredTransactions(); + // TO DO put a critical section around this part to be 100% safe... + // sem_acquire(...) + $bResult = false; + $oTransaction = MetaModel::GetObject('privUITransaction', $id, false /* MustBeFound */); + if ($oTransaction) + { + $bResult = true; + $oTransaction->DBDelete(); + } + // sem_release(...) + return $bResult; + } + + /** + * Remove from the database all transactions that have expired + */ + protected static function CleanupExpiredTransactions() + { + $sQuery = 'SELECT privUITransaction WHERE expiration_date < NOW()'; + $oSearch = DBObjectSearch::FromOQL($sQuery); + $oSet = new DBObjectSet($oSearch); + while($oTransaction = $oSet->Fetch()) + { + $oTransaction->DBDelete(); + } + } + +} +?> diff --git a/application/utils.inc.php b/application/utils.inc.php index 490db8459b..16de6f9294 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -24,6 +24,7 @@ */ require_once('../core/config.class.inc.php'); +require_once('../application/transaction.class.inc.php'); define('ITOP_CONFIG_FILE', '../config-itop.php'); @@ -31,6 +32,11 @@ class FileUploadException extends Exception { } + +/** + * Helper functions to interact with forms: read parameters, upload files... + * @package iTop + */ class utils { private static $m_oConfig = null; @@ -116,14 +122,12 @@ class utils public static function GetNewTransactionId() { - // TO DO implement the real mechanism here - return sprintf("%08x", rand(0,2000000000)); + return privUITransaction::GetNewTransactionId(); } public static function IsTransactionValid($sId) { - // TO DO implement the real mechanism here - return true; + return privUITransaction::IsTransactionValid($sId); } public static function ReadFromFile($sFileName) From fce9cbad1ba82bd325940e46670e62b8f6a92373 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 6 Jun 2010 16:58:38 +0000 Subject: [PATCH 349/970] - New implementation preventing users to submit the same form twice. SVN:trunk[432] --- core/config.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index ce23676d9c..cb69269a54 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -466,6 +466,7 @@ class Config fwrite($hFile, " */\n"); fwrite($hFile, "\$MyModules = array(\n"); fwrite($hFile, "\t'application' => array (\n"); + fwrite($hFile, "\t\t'../application/transaction.class.inc.php',\n"); fwrite($hFile, "\t\t'../application/menunode.class.inc.php',\n"); fwrite($hFile, "\t\t'../application/audit.rule.class.inc.php',\n"); // Romain - That's dirty, because those 3 classes are in fact part of the core From ef38dac045f5c4d23632896e76f4a15f1fc541a5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 6 Jun 2010 17:00:21 +0000 Subject: [PATCH 350/970] - New implementation preventing users to submit the same form twice. - Better check whether or not an object was modified (rely on the core's IsModified function) SVN:trunk[433] --- pages/UI.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 08de50806e..bfa1c18cf5 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -755,18 +755,17 @@ try { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } + $oObj = $oContext->GetObject($sClass, $id); if (!utils::IsTransactionValid($sTransactionId)) { $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); } else { - $oObj = $oContext->GetObject($sClass, $id); if ($oObj != null) { $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->add("

            ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

            \n"); - $bObjectModified = false; foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) { if ($oAttDef->IsLinkSet()) @@ -778,7 +777,6 @@ try $oLinkSet = WizardHelper::ParseJsonSet($oObj, $oAttDef->GetLinkedClass(), $oAttDef->GetExtKeyToMe(), $aAttributes[$sAttCode]); $oObj->Set($sAttCode, $oLinkSet); // TO DO: detect a real modification, for now always update !! - $bObjectModified = true; } } else if (!$oAttDef->IsExternalField()) @@ -791,7 +789,6 @@ try if ($previousValue !== $aAttributes[$sAttCode]) { $oObj->Set($sAttCode, $aAttributes[$sAttCode]); - $bObjectModified = true; } } } @@ -810,12 +807,11 @@ try { // A new file has been uploaded $oObj->Set($sAttCode, $oDocument); - $bObjectModified = true; } } } } - if (!$bObjectModified) + if (!$oObj->IsModified()) { $oP->p(Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); } @@ -1149,8 +1145,8 @@ EOF $oObj->DBUpdateTracked($oMyChange); $oP->p(Dict::Format('UI:Class_Object_Updated', get_class($oObj), $oObj->GetName())); } - $oObj->DisplayDetails($oP); } + $oObj->DisplayDetails($oP); } else { From 9c43da711ef8f2a89e66be3e5a43469145670c79 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 6 Jun 2010 17:05:08 +0000 Subject: [PATCH 351/970] - Non mandatory fields can be empty, even if a validation pattern was specified. SVN:trunk[434] --- js/forms-json-utils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index 3fe52d8265..8e3afe8d6c 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -155,6 +155,11 @@ function ValidateField(sFieldId, sPattern, bMandatory, sFormId) { bValid = false; } + else if ((currentVal == '') || (currentVal == 0)) + { + // An empty field is Ok... + bValid = true; + } else if (sPattern != '') { re = new RegExp(sPattern); From 0bb1be78a5f5b07cfb73d9718d41c71724872520 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 7 Jun 2010 05:44:25 +0000 Subject: [PATCH 352/970] SVN:trunk[435] From dc2430332a7871cf54b700b1411e6b462eb2a454 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 7 Jun 2010 12:19:18 +0000 Subject: [PATCH 353/970] - Fix bug #116 (initial value of the linkset was lost) SVN:trunk[436] --- application/ui.linkswidget.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 67ff3ea1cf..7c142a90a8 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -75,6 +75,7 @@ class UILinksWidget { // Serialize the link set into a JSon object $aCurrentValues = array(); + $oCurrentValuesSet->Rewind(); // Make sure we can iterate through this set... while($oLinkObj = $oCurrentValuesSet->Fetch()) { $sRow = '{'; From d3ffa21943754a0e891f89e687f5a2eca8472ad7 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 7 Jun 2010 14:28:42 +0000 Subject: [PATCH 354/970] Improved error handling and localization SVN:trunk[437] --- core/dict.class.inc.php | 13 ++++++++++++- core/metamodel.class.php | 15 +++++++++------ pages/UI.php | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index 8c78456725..9be9fb7f22 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -101,6 +101,11 @@ class Dict { // Attempt to find the string in the user language // + if (!array_key_exists(self::$m_sCurrentLanguage, self::$m_aData)) + { + // It may happen, when something happens before the dictionnaries get loaded + return $sStringCode; + } $aCurrentDictionary = self::$m_aData[self::$m_sCurrentLanguage]; if (array_key_exists($sStringCode, $aCurrentDictionary)) { @@ -140,8 +145,14 @@ class Dict public static function Format($sFormatCode /*, ... arguments ....*/) { $sLocalizedFormat = self::S($sFormatCode); - $aArguments = func_get_args(); + + if ($sLocalizedFormat == $sFormatCode) + { + // Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded) + return $sFormatCode.' - '.implode(', ', $aArguments); + } + array_shift($aArguments); return vsprintf($sLocalizedFormat, $aArguments); } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 2ed53dfeab..f8c775117e 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2935,6 +2935,15 @@ abstract class MetaModel self::$m_bLogWebService = false; } + // Note: load the dictionary as soon as possible, because it might be + // needed when some error occur + foreach ($oConfig->GetDictionaries() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'dictionaries', $sToInclude); + } + // Set the language... after the dictionaries have been loaded! + Dict::SetDefaultLanguage($oConfig->GetDefaultLanguage()); + foreach ($oConfig->GetAppModules() as $sModule => $sToInclude) { self::Plugin($sConfigFile, 'application', $sToInclude); @@ -2947,12 +2956,6 @@ abstract class MetaModel { self::Plugin($sConfigFile, 'addons', $sToInclude); } - foreach ($oConfig->GetDictionaries() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'dictionaries', $sToInclude); - } - // Set the language... after the dictionaries have been loaded! - Dict::SetDefaultLanguage($oConfig->GetDefaultLanguage()); $sServer = $oConfig->GetDBHost(); $sUser = $oConfig->GetDBUser(); diff --git a/pages/UI.php b/pages/UI.php index bfa1c18cf5..a714807f54 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1306,7 +1306,7 @@ catch(CoreException $e) if (MetaModel::IsLogEnabledIssue()) { - if (class_exists('EventIssue')) + if (MetaModel::IsValidClass('EventIssue')) { $oLog = new EventIssue(); @@ -1335,7 +1335,7 @@ catch(Exception $e) if (MetaModel::IsLogEnabledIssue()) { - if (class_exists('EventIssue')) + if (MetaModel::IsValidClass('EventIssue')) { $oLog = new EventIssue(); From 3776e09cdbea69181e9585b914b0b2e112da8c3f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 7 Jun 2010 16:16:07 +0000 Subject: [PATCH 355/970] Localized strings for relations (e.g. "impacts") SVN:trunk[438] --- business/itop.business.class.inc.php | 2 +- business/test_farm.class.inc.php | 2 +- core/metamodel.class.php | 36 +++++++++++--------------- dictionaries/dictionary.itop.model.php | 11 ++++++++ pages/schema.php | 19 ++++++-------- 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 9dcc749449..5d6c9b8ac8 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -35,7 +35,7 @@ define('STANDARD_STATUSES', 'production,implementation,obsolete'); /** * Relation graphs */ -MetaModel::RegisterRelation("impacts", array("description"=>"objects being functionaly impacted", "verb_down"=>"impacts", "verb_up"=>"is impacted by")); +MetaModel::RegisterRelation("impacts"); //////////////////////////////////////////////////////////////////////////////////// /** diff --git a/business/test_farm.class.inc.php b/business/test_farm.class.inc.php index 4b3cefd5f6..dbb9b70fe3 100644 --- a/business/test_farm.class.inc.php +++ b/business/test_farm.class.inc.php @@ -27,7 +27,7 @@ // Business implementation demo /////////////////////////////////////////////////////////////////////////////// -//todo MetaModel::RegisterRelation("Potes", array("description"=>"ceux dont l'email ressemble au mien", "verb_down"=>"est pote de", "verb_up"=>"est pote de")); +//todo MetaModel::RegisterRelation("Potes"); //todo MetaModel::RegisterZList("list1", array("description"=>"une premiere list, just for fun", "type"=>"attributes")); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index f8c775117e..dd7d829a5c 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -711,12 +711,19 @@ abstract class MetaModel return self::$m_aRelationInfos[$sRelCode]; } - final static public function GetRelationProperty($sRelCode, $sProperty) + final static public function GetRelationDescription($sRelCode) { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]); - - return self::$m_aRelationInfos[$sRelCode][$sProperty]; + return Dict::S("Relation:$sRelCode/Description"); + } + + final static public function GetRelationVerbUp($sRelCode) + { + return Dict::S("Relation:$sRelCode/VerbUp"); + } + + final static public function GetRelationVerbDown($sRelCode) + { + return Dict::S("Relation:$sRelCode/VerbDown"); } public static function EnumRelationQueries($sClass, $sRelCode) @@ -844,23 +851,10 @@ abstract class MetaModel self::$m_aListInfos[$sListCode] = $aListInfo; } - public static function RegisterRelation($sRelCode, $aRelationInfo) + public static function RegisterRelation($sRelCode) { - // Check mandatory params - $aMandatParams = array( - "description" => "detailed (though one line) description of the list", - "verb_down" => "e.g.: 'impacts'", - "verb_up" => "e.g.: 'is impacted by'", - ); - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aRelationInfo)) - { - throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName"); - } - } - - self::$m_aRelationInfos[$sRelCode] = $aRelationInfo; + // Each item used to be an array of properties... + self::$m_aRelationInfos[$sRelCode] = $sRelCode; } // Must be called once and only once... diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index 840286d031..96bdd79692 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -23,6 +23,17 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ +////////////////////////////////////////////////////////////////////// +// Relations +////////////////////////////////////////////////////////////////////// +// + +Dict::Add('EN US', 'English', 'English', array( + 'Relation:impacts/Description' => 'functionaly impacted by', + 'Relation:impacts/VerbUp' => 'impacts', + 'Relation:impacts/VerbDown' => 'is impacted by', +)); + // Dictionnay conventions // Class: diff --git a/pages/schema.php b/pages/schema.php index 3f92140ba5..5ff26f348b 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -42,10 +42,8 @@ function MakeClassHLink($sClass) */ function MakeRelationHLink($sRelCode) { - $sDec = MetaModel::GetRelationProperty($sRelCode, 'description'); - //$sVerbDown = MetaModel::GetRelationProperty($sRelCode, 'verb_down'); - //$sVerbUp = MetaModel::GetRelationProperty($sRelCode, 'verb_up'); - return "".$sRelCode.""; + $sDesc = MetaModel::GetRelationDescription($sRelCode); + return "".$sRelCode.""; } /** @@ -305,10 +303,9 @@ function DisplayClassesList($oPage) { $oPage->add("
          • ".MakeRelationHLink($sRelCode)."\n"); $oPage->add("
              \n"); - foreach (MetaModel::EnumRelationProperties($sRelCode) as $sProp => $sValue) - { - $oPage->add("
            • $sProp: ".htmlentities($sValue)."
            • \n"); - } + $oPage->add("
            • Description: ".htmlentities(MetaModel::GetRelationDescription($sRelCode))."
            • \n"); + $oPage->add("
            • Verb up: ".htmlentities(MetaModel::GetRelationVerbUp($sRelCode))."
            • \n"); + $oPage->add("
            • Verb down: ".htmlentities(MetaModel::GetRelationVerbDown($sRelCode))."
            • \n"); $oPage->add("
            \n"); $oPage->add("
          • \n"); } @@ -454,9 +451,9 @@ function DisplayClassDetails($oPage, $sClass) */ function DisplayRelationDetails($oPage, $sRelCode) { - $sDesc = MetaModel::GetRelationProperty($sRelCode, 'description'); - $sVerbDown = MetaModel::GetRelationProperty($sRelCode, 'verb_down'); - $sVerbUp = MetaModel::GetRelationProperty($sRelCode, 'verb_up'); + $sDesc = MetaModel::GetRelationDescription($sRelCode); + $sVerbDown = MetaModel::GetRelationVerbDown($sRelCode); + $sVerbUp = MetaModel::GetRelationVerbUp($sRelCode); $oPage->add("

            ".Dict::Format('UI:Schema:Relation_Code_Description', $sRelCode, $sDesc)."

            "); $oPage->p(Dict::Format('UI:Schema:RelationDown_Description', $sVerbDown)); $oPage->p(Dict::Format('UI:Schema:RelationUp_Description', $sVerbUp)); From a813a1e50a9b94d2b7324c21958405599b10a62f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 8 Jun 2010 09:19:44 +0000 Subject: [PATCH 356/970] Fixed regression in the setup, due to the latest change (rearranged the includes) SVN:trunk[439] --- setup/index.php | 1 + setup/xmldataloader.class.inc.php | 1 + 2 files changed, 2 insertions(+) diff --git a/setup/index.php b/setup/index.php index aa8a503a87..c63c7aaace 100644 --- a/setup/index.php +++ b/setup/index.php @@ -338,6 +338,7 @@ function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bAllowMissingDatabas { require_once('../core/log.class.inc.php'); require_once('../core/coreexception.class.inc.php'); + require_once('../core/dict.class.inc.php'); require_once('../core/attributedef.class.inc.php'); require_once('../core/filterdef.class.inc.php'); require_once('../core/stimulus.class.inc.php'); diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index e706d1a52a..a3f2cdf94d 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -89,6 +89,7 @@ class XMLDataLoader { require_once('../core/log.class.inc.php'); require_once('../core/coreexception.class.inc.php'); + require_once('../core/dict.class.inc.php'); require_once('../core/attributedef.class.inc.php'); require_once('../core/filterdef.class.inc.php'); require_once('../core/stimulus.class.inc.php'); From 4d3fbf1f79e05756343541c0592b8106e0c5b533 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 8 Jun 2010 09:33:14 +0000 Subject: [PATCH 357/970] Do not allow the user to modify/delete objects designed for logging (change tracking, error log, information log, etc.) SVN:trunk[440] --- application/displayblock.class.inc.php | 27 ++++++++++++++------------ core/cmdbchange.class.inc.php | 4 ++++ core/cmdbchangeop.class.inc.php | 6 ++++++ core/dbobject.class.php | 6 ++++++ core/event.class.inc.php | 5 +++++ core/metamodel.class.php | 6 ++++++ pages/UI.php | 10 +++++----- 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index a161d31b2d..0ba9486310 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -470,7 +470,8 @@ class DisplayBlock $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; if ($bDisplayMenu) { - if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) + if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) + && !MetaModel::IsReadOnlyClass($sClass)) { $oAppContext = new ApplicationContext(); $sParams = $oAppContext->GetForLink(); @@ -512,7 +513,8 @@ class DisplayBlock $bDisplayMenu = isset($this->m_aParams['menu']) ? $this->m_aParams['menu'] == true : true; if ($bDisplayMenu) { - if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) + if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) + && (!MetaModel::IsReadOnlyClass($sClass))) { $oAppContext = new ApplicationContext(); $sParams = $oAppContext->GetForLink(); @@ -552,7 +554,8 @@ class DisplayBlock break; case 'modify': - if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES) + if ((UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES) + && !MetaModel::IsReadOnlyClass($this->m_oSet->GetClass())) { while($oObj = $this->m_oSet->Fetch()) { @@ -884,17 +887,17 @@ class MenuBlock extends DisplayBlock { case 0: // No object in the set, the only possible action is "new" - $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY); + $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../page/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); } break; case 1: $oObj = $oSet->Fetch(); $id = $oObj->GetKey(); - $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); - $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); - $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); - $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); + $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); + $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet) && !MetaModel::IsReadOnlyClass($sClass); + $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet) && !MetaModel::IsReadOnlyClass($sClass); + $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet) && !MetaModel::IsReadOnlyClass($sClass); // Just one object in the set, possible actions are "new / clone / modify and delete" if (isset($aExtraParams['link_attr'])) { @@ -942,16 +945,16 @@ class MenuBlock extends DisplayBlock default: // Check rights // New / Modify - $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet); - $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet); - $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet); + $bIsModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) && !MetaModel::IsReadOnlyClass($sClass); + $bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet) && !MetaModel::IsReadOnlyClass($sClass); + $bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet) && !MetaModel::IsReadOnlyClass($sClass); if (isset($aExtraParams['link_attr'])) { $id = $aExtraParams['object_id']; $sTargetAttr = $aExtraParams['target_attr']; $oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr); $sTargetClass = $oAttDef->GetTargetClass(); - $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet); + $bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet) && !MetaModel::IsReadOnlyClass($sClass); if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true&$sContext"); } //if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Add...', 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&linkage=".$aExtraParams['linkage']."&id=$id&addObjects=true&$sContext"); } if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "../pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&sContext"); } diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index a4bc1d8c5d..81d617ed3a 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -51,6 +51,10 @@ class CMDBChange extends DBObject MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); } + static public function IsReadOnly() + { + return true; + } } ?> diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index a8e6f8c812..ffff1493ab 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -57,6 +57,12 @@ class CMDBChangeOp extends DBObject MetaModel::Init_SetZListItems('details', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('change', 'date', 'userinfo')); // Attributes to be displayed for the complete details } + + static public function IsReadOnly() + { + return true; + } + /** * Describe (as a text string) the modifications corresponding to this change */ diff --git a/core/dbobject.class.php b/core/dbobject.class.php index d892065c42..b874f9dbbc 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -76,6 +76,12 @@ abstract class DBObject } } + // Read-only <=> Written once (archive) + static public function IsReadOnly() + { + return false; + } + public function RegisterAsDirty() { // While the object may be written to the DB, it is NOT possible to reload it diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 0b4099a7b7..303f4d0f4f 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -55,6 +55,11 @@ class Event extends cmdbAbstractObject // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } + + static public function IsReadOnly() + { + return true; + } } class EventNotification extends Event diff --git a/core/metamodel.class.php b/core/metamodel.class.php index dd7d829a5c..bb4d6fcdd3 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -226,6 +226,12 @@ abstract class MetaModel return self::GetParentPersistentClass($sClass); } + static public function IsReadOnlyClass($sClass) + { + $bReadOnly = call_user_func(array($sClass, 'IsReadOnly')); + return $bReadOnly; + } + final static public function GetName($sClass) { self::_check_subclass($sClass); diff --git a/pages/UI.php b/pages/UI.php index a714807f54..ab91868251 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -48,7 +48,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed) foreach ($aDeletes as $iId => $aData) { $oToDelete = $aData['to_delete']; - $bDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oToDelete)); + $bDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oToDelete)) && !MetaModel::IsReadOnlyClass($sClass); $aTotalDeletedObjs[$sRemoteClass][$iId]['auto_delete'] = $aData['auto_delete']; if (!$bDeleteAllowed) { @@ -605,7 +605,7 @@ try $oObj = $oSet->Fetch(); } - $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES); + $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); if( ($oObj != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) { @@ -642,7 +642,7 @@ try $oObjToClone = $oSet->Fetch(); } - $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES); + $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); if( ($oObjToClone != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) { @@ -894,7 +894,7 @@ try { $aObjects[] = $oContext->GetObject($sClass, $iId); } - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) + if (MetaModel::IsReadOnlyClass($sClass) || !UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) { throw new SecurityException(Dict::S('UI:Error:BulkDeleteNotAllowedOn_Class'), $sClass); } @@ -909,7 +909,7 @@ try $id = utils::ReadParam('id', ''); $oObj = $oContext->GetObject($sClass, $id); - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj))) + if (MetaModel::IsReadOnlyClass($sClass) || !UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj))) { throw new SecurityException(Dict::S('UI:Error:DeleteNotAllowedOn_Class'), $sClass); } From fd8b3c835e24706cd270b7d580e747fc56435e8d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 8 Jun 2010 13:12:17 +0000 Subject: [PATCH 358/970] - Upgraded to the latest version of Open Flash Charts (Version 2 Lug Wyrm Charmer) - Localization of the menus 90% done. - Removed the old types of Flash charts, (no longer used anyway, now we rely on open-flash-charts) SVN:trunk[441] --- application/displayblock.class.inc.php | 102 +- core/metamodel.class.php | 1 + css/light-grey.css | 17 +- dictionaries/dictionary.itop.ui.php | 170 +- dictionaries/fr.dictionary.itop.ui.php | 170 ++ images/open-flash-chart.swf | Bin 263109 -> 276186 bytes js/swfobject.js | 6 +- pages/php-ofc-library/ofc_area_base.php | 62 +- pages/php-ofc-library/ofc_bar_3d.php | 9 - pages/php-ofc-library/ofc_bar_base.php | 56 + pages/php-ofc-library/ofc_bar_glass.php | 106 +- pages/php-ofc-library/ofc_bar_sketch.php | 6 + pages/php-ofc-library/ofc_bar_stack.php | 5 + pages/php-ofc-library/ofc_line.php | 150 +- pages/php-ofc-library/ofc_line_base.php | 26 +- pages/php-ofc-library/ofc_pie.php | 164 +- pages/php-ofc-library/ofc_scatter.php | 15 +- pages/php-ofc-library/ofc_scatter_line.php | 34 +- pages/php-ofc-library/ofc_title.php | 24 + pages/php-ofc-library/ofc_tooltip.php | 20 +- pages/php-ofc-library/ofc_upload_image.php | 11 +- pages/php-ofc-library/ofc_x_axis.php | 73 +- pages/php-ofc-library/ofc_x_axis_label.php | 3 + pages/php-ofc-library/ofc_x_axis_labels.php | 43 +- pages/php-ofc-library/ofc_y_axis.php | 8 +- pages/php-ofc-library/ofc_y_axis_base.php | 64 +- pages/php-ofc-library/open-flash-chart.php | 56 +- setup/data/structure/1.menus.xml | 1685 +++++++++---------- 28 files changed, 1944 insertions(+), 1142 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 0ba9486310..7f3a74fe51 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -575,105 +575,16 @@ class DisplayBlock $sHtml .= "
            ".Dict::S('UI:SearchToggle')."
            \n"; break; - case 'pie_chart': - $sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : ''; - $sFilter = $this->m_oFilter->ToOQL(); - $sHtml .= " - - - - - - - - - "; - break; - - case 'pie_chart_ajax': - if (isset($aExtraParams['group_by'])) - { - $sGroupByField = $aExtraParams['group_by']; - $aGroupBy = array(); - while($oObj = $this->m_oSet->Fetch()) - { - $sValue = $oObj->Get($sGroupByField); - $aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1; - } - $sFilter = urlencode($this->m_oFilter->serialize()); - $aData = array(); - $sHtml .= "\n"; - $sHtml .= "3d pie\n"; - $sHtml .= "\n"; - $sHtml .= "\n"; - $sHtml .= "\n"; - foreach($aGroupBy as $sValue => $void) - { - $sHtml .= "$sValue\n"; - } - $sHtml .= "\n"; - $sHtml .= "\n"; - $sHtml .= "\n"; - foreach($aGroupBy as $void => $iCount) - { - $sHtml .= "$iCount\n"; - } - $sHtml .= "\n"; - $sHtml .= "\n"; - $sHtml .= " - - - - ||||||||||||||||||||||||||||||||||||||||||||||| - - - - - - ddaa41 - 88dd11 - 4e62dd - ff8811 - 4d4d4d - 5a4b6e - 1188ff - - "; - $sHtml .= "\n"; - } - else - { - // Simply count the number of elements in the set - $iCount = $oSet->Count(); - $sHtml .= "\n\n"; - } - break; - case 'open_flash_chart': static $iChartCounter = 0; $sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie'; $sTitle = isset($aExtraParams['chart_title']) ? $aExtraParams['chart_title'] : ''; $sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : ''; $sFilter = $this->m_oFilter->ToOQL(); - $sHtml .= "\n"; $sHtml .= "
            If the chart does not display, install Flash
            \n"; + $oPage->add_script("function ofc_resize(left, width, top, height) { /* do nothing special */ }"); + $oPage->add_ready_script("swfobject.embedSWF(\"../images/open-flash-chart.swf\", \"my_chart_{$iChartCounter}\", \"100%\", \"300\",\"9.0.0\", \"expressInstall.swf\", + {\"data-file\":\"".urlencode("../pages/ajax.render.php?operation=open_flash_chart¶ms[group_by]=$sGroupBy¶ms[chart_type]=$sChartType¶ms[chart_title]=$sTitle&encoding=oql&filter=".urlencode($sFilter))."\"});\n"); $iChartCounter++; break; @@ -742,6 +653,7 @@ class DisplayBlock $oChartElement->set_start_angle( 35 ); $oChartElement->set_animate( true ); $oChartElement->set_tooltip( '#label# - #val# (#percent#)' ); + $oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') ); if (isset($aExtraParams['group_by'])) { $sGroupByField = $aExtraParams['group_by']; @@ -755,7 +667,7 @@ class DisplayBlock $aData = array(); foreach($aGroupBy as $sValue => $iValue) { - $aData[] = new pie_value($iValue, $sValue); + $aData[] = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!! } @@ -763,9 +675,9 @@ class DisplayBlock $oChart->x_axis = null; } } - if (isset($aExtraParams['chart_title'])) //@@ BUG: not passed via ajax !!! + if (isset($aExtraParams['chart_title'])) { - $oTitle = new title( $aExtraParams['chart_title'] ); + $oTitle = new title( Dict::S($aExtraParams['chart_title']) ); $oChart->set_title( $oTitle ); } $oChart->set_bg_colour('#FFFFFF'); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index bb4d6fcdd3..067bbfc8b3 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2937,6 +2937,7 @@ abstract class MetaModel // Note: load the dictionary as soon as possible, because it might be // needed when some error occur + require_once('../core/dict.class.inc.php'); foreach ($oConfig->GetDictionaries() as $sModule => $sToInclude) { self::Plugin($sConfigFile, 'dictionaries', $sToInclude); diff --git a/css/light-grey.css b/css/light-grey.css index c1b8e1b9aa..82796028fc 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -21,9 +21,8 @@ body { h1 { font-family: Tahoma, Verdana, Arial, Helvetica; - font-size: 10pt; color: #000; - font-weight:10px; + font-weight: bold; } .hilite { color: #d81515; @@ -685,4 +684,18 @@ td.cell_error { } table.transparent, table.transparent td { background: transparent; +} +p.page-header { + color:#83B217; + font-weight: bold; + font-size: 2em; + font-family: Verdana, Arial, Helvetica, Sans-Serif +} +td.dashboard { + vertical-align:top; + text-align: center; + border: 1px solid #ccc; + -moz-border-radius: 10px; + padding: 10px; + width: 50%; } \ No newline at end of file diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 47202e765c..66b6b4328d 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -682,8 +682,176 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', 'UI:UserManagement:NoLifeCycleApplicable+' => 'No lifecycle has been defined for this class', 'UI:UserManagement:GrantMatrix' => 'Grant Matrix', - 'UI:USerManagement:LinkBetween_User_And_Profile' => 'Link between %1$s and %2$s', + 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Link between %1$s and %2$s', + + 'UI:AdminToolsMenu' => 'Admin tools', + 'UI:AdminToolsMenu+' => 'Admin tools', + 'UI:AdminToolsMenu:Title' => 'Administration tools', + 'UI:AdminToolsMenu:Text' => 'Tools accessible only to users having the administrator profile', + 'UI:AuditMenu' => 'Audit', + 'UI:AuditMenu+' => 'Audit', + + 'UI:ChangeManagementMenu' => 'Change Management', + 'UI:ChangeManagementMenu+' => 'Change Management', + 'UI:ChangeManagementMenu:Title' => 'Changes Overview', + 'UI-ChangeManagementMenu-ChangesByType' => 'Changes by type', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Changes by status', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Changes by workgroup', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Changes not yet assigned', + + 'UI:ConfigurationItemsMenu'=> 'Configuration Items', + 'UI:ConfigurationItemsMenu+'=> 'All Devices', + 'UI:ConfigurationItemsMenu:Title' => 'Configuration Items Overview', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Servers by criticity', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PCs by criticity', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Network devices by criticity', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Applications by criticity', + + 'UI:ConfigurationManagementMenu' => 'Configuration Management', + 'UI:ConfigurationManagementMenu+' => 'Configuration Management', + 'UI:ConfigurationManagementMenu:Title' => 'Infrastructure Overview', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Infrastructure objects by type', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Infrastructure objects by status', + + 'UI:ContactsMenu' => 'Contacts', + 'UI:ContactsMenu+' => 'Contacts', + 'UI:ContactsMenu:Title' => 'Contacts Overview', + 'UI-ContactsMenu-ContactsByLocation' => 'Contacts by location', + 'UI-ContactsMenu-ContactsByType' => 'Contacts by type', + 'UI-ContactsMenu-ContactsByStatus' => 'Contacts by status', + + 'UI:CSVImportMenu' => 'CSV import', + 'UI:CSVImportMenu+' => 'Bulk creation or update', + + 'UI:DataModelMenu' => 'Data Model', + 'UI:DataModelMenu+' => 'Overview of the Data Model', + + 'UI:ExportMenu' => 'Export', + 'UI:ExportMenu+' => 'Export the results of any query in HTML, CSV or XML', + + 'UI:IncidentManagementMenu' => 'Incident Management', + 'UI:IncidentManagementMenu+' => 'Incident Management', + 'UI:IncidentManagementMenu:Title' => 'Incident Overview', + 'UI-IncidentManagementMenu-IncidentsByType' => 'Incidents by type', + 'UI-IncidentManagementMenu-IncidentsByStatus' => 'Incidents by status', + 'UI-IncidentManagementMenu-IncidentsByWorkgroup' => 'Incidents by workgroup', + 'UI-IncidentManagementMenu-IncidentsNotYetAssigned' => 'Incidents not yet assigned', + + 'UI:NotificationsMenu' => 'Notifications', + 'UI:NotificationsMenu+' => 'Configuration of the Notifications', + + 'UI:RunQueriesMenu' => 'Run Queries', + 'UI:RunQueriesMenu+' => 'Run any query', + + 'UI:ServiceDeskMenu' => 'Service Desk', + 'UI:ServiceDeskMenu+' => 'Service Desk', + 'UI:ServiceDeskMenu:Title' => 'Service Calls Overview', + 'UI-ServiceDeskMenu-CallsByType' => 'Calls by type', + 'UI-ServiceDeskMenu-CallsByStatus' => 'Calls by status', + 'UI-ServiceDeskMenu-CallsBySeverity' => 'Calls by severity', + 'UI-ServiceDeskMenu-CallsNotYetAssigned' => 'Calls not yet assigned', + + 'UI:ServiceManagementMenu' => 'Service Management', + 'UI:ServiceManagementMenu+' => 'Service Management', + 'UI:ServiceManagementMenu:Title' => 'Service Management Overview', + 'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contracts by service level', + 'UI-ServiceManagementMenu-ContractsByStatus' => 'Contracts by status', + 'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contracts ending in less then 30 days', + + 'UI:AdvancedToolsMenu' => 'Tools', + 'UI:AdvancedToolsMenu+' => 'Advanced tools', + + 'UI:UniversalSearchMenu' => 'Universal Search', + 'UI:UniversalSearchMenu+' => 'Search for anything...', + + 'UI:UserManagementMenu' => 'User Management', + 'UI:UserManagementMenu+' => 'User management', + + 'UI:ApplicationsMenu' => 'Applications', + 'UI:ApplicationsMenu+' => 'Applications', + 'UI:ApplicationsMenu:Title' => 'Applications', + 'UI:CircuitsMenu' => 'Circuits', + 'UI:CircuitsMenu+' => 'Circuits', + 'UI:CircuitsMenu:Title' => 'Circuits', + 'UI:ContractsMenu' => 'Contracts', + 'UI:ContractsMenu+' => 'Contracts', + 'UI:ContractsMenu:Title' => 'Contracts', + 'UI:InterfacesMenu' => 'Interfaces', + 'UI:InterfacesMenu+' => 'Interfaces', + 'UI:InterfacesMenu:Title' => 'Interfaces', + 'UI:NetworkDevicesMenu' => 'Network Devices', + 'UI:NetworkDevicesMenu+' => 'Network Devices', + 'UI:NetworkDevicesMenu:Title' => 'Network Devices', + 'UI:PatchesMenu' => 'Patches', + 'UI:PatchesMenu+' => 'Patches', + 'UI:PatchesMenu:Title' => 'Patches', + 'UI:PCsMenu' => 'PCs', + 'UI:PCsMenu+' => 'PCs', + 'UI:PCsMenu:Title' => 'PCs', + 'UI:ServersMenu' => 'Servers', + 'UI:ServersMenu+' => 'Servers', + 'UI:ServersMenu:Title' => 'Servers', + 'UI:ServicesMenu' => 'Services', + 'UI:ServicesMenu+' => 'Services', + 'UI:ServicesMenu:Title' => 'Services', + 'UI:SubnetsMenu' => 'Subnets', + 'UI:SubnetsMenu+' => 'Subnets', + 'UI:SubnetsMenu:Title' => 'Subnets', + 'UI:ApplicationLogMenu' => 'Application Log', + 'UI:ApplicationLogMenu+' => 'Application Log', + 'UI:ApplicationLogMenu:Title' => 'Application Log', + 'UI:ClosedChangesMenu' => 'Closed Changes', + 'UI:ClosedChangesMenu+' => 'Closed Changes', + 'UI:ClosedChangesMenu:Title' => 'Closed Changes', + 'UI:ClosedIncidentsMenu' => 'Closed Incidents', + 'UI:ClosedIncidentsMenu+' => 'Closed Incidents', + 'UI:ClosedIncidentsMenu:Title' => 'Closed Incidents', + 'UI:DocumentsMenu' => 'Documents', + 'UI:DocumentsMenu+' => 'Documents', + 'UI:DocumentsMenu:Title' => 'Documents', + 'UI:GroupingMenu' => 'Groups', + 'UI:GroupingMenu+' => 'Groups', + 'UI:GroupingMenu:Title' => 'Groups', + 'UI:KnownErrorsMenu' => 'Known Errors', + 'UI:KnownErrorsMenu+' => 'Known Errors', + 'UI:KnownErrorsMenu:Title' => 'Known Errors', + 'UI:LocationsMenu' => 'Locations', + 'UI:LocationsMenu+' => 'Locations', + 'UI:LocationsMenu:Title' => 'Locations', + 'UI:MyChangesMenu' => 'My Changes', + 'UI:MyChangesMenu+' => 'My Changes', + 'UI:MyChangesMenu:Title' => 'My Changes', + 'UI:MyIncidentsMenu' => 'My Incidents', + 'UI:MyIncidentsMenu+' => 'My Incidents', + 'UI:MyIncidentsMenu:Title' => 'My Incidents', + 'UI:MyServiceCallsMenu' => 'My Service Calls', + 'UI:MyServiceCallsMenu+' => 'My Service Calls', + 'UI:MyServiceCallsMenu:Title' => 'My Service Calls', + 'UI:OpenChangesMenu' => 'Open Changes', + 'UI:OpenChangesMenu+' => 'Open Changes', + 'UI:OpenChangesMenu:Title' => 'Open Changes', + 'UI:OpenIncidentsMenu' => 'Open Incidents', + 'UI:OpenIncidentsMenu+' => 'Open Incidents', + 'UI:OpenIncidentsMenu:Title' => 'Open Incidents', + 'UI:OpenServiceCallsMenu' => 'Open Service Calls', + 'UI:OpenServiceCallsMenu+' => 'Open Service Calls', + 'UI:OpenServiceCallsMenu:Title' => 'Open Service Calls', + 'UI:PersonsMenu' => 'Persons', + 'UI:PersonsMenu+' => 'Persons', + 'UI:PersonsMenu:Title' => 'Persons', + 'UI:ProfilesMenu' => 'Profiles', + 'UI:ProfilesMenu+' => 'Profiles', + 'UI:ProfilesMenu:Title' => 'Profiles', + 'UI:ScheduledOutagesMenu' => 'Scheduled Outages', + 'UI:ScheduledOutagesMenu+' => 'Scheduled Outages', + 'UI:ScheduledOutagesMenu:Title' => 'Scheduled Outages', + 'UI:TeamsMenu' => 'Teams', + 'UI:TeamsMenu+' => 'Teams', + 'UI:TeamsMenu:Title' => 'Teams', + 'UI:UserAccountsMenu' => 'User Accounts', + 'UI:UserAccountsMenu+' => 'User Accounts', + 'UI:UserAccountsMenu:Title' => 'User Accounts', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 872600b57a..df86e6d570 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -681,6 +681,176 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:UserManagement:GrantMatrix' => 'Matrice des droits', 'UI:USerManagement:LinkBetween_User_And_Profile' => 'Lien entre %1$s et %2$s', + 'UI:AdminToolsMenu' => 'Outils d\'admin', + 'UI:AdminToolsMenu+' => 'Outils d\'administration', + 'UI:AdminToolsMenu:Title' => 'Outils d\'administration', + 'UI:AdminToolsMenu:Text' => 'Ces outils sont accessibles uniquement aux utilisateur possédant le profil Administrateur.', + + 'UI:AuditMenu' => 'Audit', + 'UI:AuditMenu+' => 'Audit', + + 'UI:ChangeManagementMenu' => 'Gestion du Changement', + 'UI:ChangeManagementMenu+' => 'Gestion du Changement', + 'UI:ChangeManagementMenu:Title' => 'Résumé des changements', + 'UI-ChangeManagementMenu-ChangesByType' => 'Changements par type', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Changements par état', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Changements par workgroup', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Changements en attente d\'assignation', + + 'UI:ConfigurationItemsMenu'=> 'Actifs (CIs)', + 'UI:ConfigurationItemsMenu+'=> 'Tous les actifs', + 'UI:ConfigurationItemsMenu:Title' => 'Résumé des actifs (CIs)', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Serveurs par criticité', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PCs par criticité', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Equipements réseau par criticité', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Applications par criticité', + + 'UI:ConfigurationManagementMenu' => 'Gestion de Configuration', + 'UI:ConfigurationManagementMenu+' => 'Gestion de Configuration', + 'UI:ConfigurationManagementMenu:Title' => 'Résumé de l\'Infrastructure', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Nombre d\'éléments par type', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Nombre d\'éléments par état', + + 'UI:ContactsMenu' => 'Contacts', + 'UI:ContactsMenu+' => 'Contacts', + 'UI:ContactsMenu:Title' => 'Résumé des contacts', + 'UI-ContactsMenu-ContactsByLocation' => 'Contacts par emplacement', + 'UI-ContactsMenu-ContactsByType' => 'Contacts par type', + 'UI-ContactsMenu-ContactsByStatus' => 'Contacts par état', + + + 'UI:CSVImportMenu' => 'Import CSV', + 'UI:CSVImportMenu+' => 'Import ou mise à jour en masse', + + 'UI:DataModelMenu' => 'Modèle de Données', + 'UI:DataModelMenu+' => 'Résumé du Modèle de Données', + + 'UI:ExportMenu' => 'Exportation', + 'UI:ExportMenu+' => 'Exportation des résultats d\'une requête en HTML, CSV ou XML', + + 'UI:IncidentManagementMenu' => 'Gestion des Incidents', + 'UI:IncidentManagementMenu+' => 'Gestion des Incidents', + 'UI:IncidentManagementMenu:Title' => 'Résumé des incidents', + 'UI-IncidentManagementMenu-IncidentsByType' => 'Incidents par type', + 'UI-IncidentManagementMenu-IncidentsByStatus' => 'Incidents par état', + 'UI-IncidentManagementMenu-IncidentsByWorkgroup' => 'Incidents par workgroup', + 'UI-IncidentManagementMenu-IncidentsNotYetAssigned' => 'Incidents en attente d\'assignation', + + 'UI:NotificationsMenu' => 'Notifications', + 'UI:NotificationsMenu+' => 'Configuration des Notifications', + + 'UI:RunQueriesMenu' => 'Requêtes OQL', + 'UI:RunQueriesMenu+' => 'Executer une requête OQL', + + 'UI:ServiceDeskMenu' => 'Service Desk', + 'UI:ServiceDeskMenu+' => 'Service Desk', + 'UI:ServiceDeskMenu:Title' => 'Résumé des demandes utilisateur', + 'UI-ServiceDeskMenu-CallsByType' => 'Demandes par type', + 'UI-ServiceDeskMenu-CallsByStatus' => 'Demandes par état', + 'UI-ServiceDeskMenu-CallsBySeverity' => 'Demandes par sévérité', + 'UI-ServiceDeskMenu-CallsNotYetAssigned' => 'Demandes en attente d\'assignation', + + 'UI:ServiceManagementMenu' => 'Gestion des Services', + 'UI:ServiceManagementMenu+' => 'Gestion des Services', + 'UI:ServiceManagementMenu:Title' => 'Résumé des services & contrats', + 'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contrats par niveau de service', + 'UI-ServiceManagementMenu-ContractsByStatus' => 'Contrats par état', + 'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contrats se terminant dans moins de 30 jours', + + 'UI:AdvancedToolsMenu' => 'Outils', + 'UI:AdvancedToolsMenu+' => 'Outils Avancés', + + 'UI:UniversalSearchMenu' => 'Recherche Universelle', + 'UI:UniversalSearchMenu+' => 'Rechercher n\'importe quel objet...', + + 'UI:UserManagementMenu' => 'Gestion des Utilisateurs', + 'UI:UserManagementMenu+' => 'Gestion des Utilisateurs', + + 'UI:ApplicationsMenu' => 'Applications', + 'UI:ApplicationsMenu+' => 'Applications', + 'UI:ApplicationsMenu:Title' => 'Applications', + 'UI:CircuitsMenu' => 'Circuits', + 'UI:CircuitsMenu+' => 'Circuits', + 'UI:CircuitsMenu:Title' => 'Circuits', + 'UI:ContractsMenu' => 'Contrats', + 'UI:ContractsMenu+' => 'Contrats', + 'UI:ContractsMenu:Title' => 'Contrats', + 'UI:InterfacesMenu' => 'Interfaces', + 'UI:InterfacesMenu+' => 'Interfaces', + 'UI:InterfacesMenu:Title' => 'Interfaces', + 'UI:NetworkDevicesMenu' => 'Equipements Réseau', + 'UI:NetworkDevicesMenu+' => 'Equipements Réseau', + 'UI:NetworkDevicesMenu:Title' => 'Equipements Réseau', + 'UI:PatchesMenu' => 'Patches', + 'UI:PatchesMenu+' => 'Patches', + 'UI:PatchesMenu:Title' => 'Patches', + 'UI:PCsMenu' => 'PCs', + 'UI:PCsMenu+' => 'PCs', + 'UI:PCsMenu:Title' => 'PCs', + 'UI:ServersMenu' => 'Serveurs', + 'UI:ServersMenu+' => 'Serveurs', + 'UI:ServersMenu:Title' => 'Serveurs', + 'UI:ServicesMenu' => 'Services', + 'UI:ServicesMenu+' => 'Services', + 'UI:ServicesMenu:Title' => 'Services', + 'UI:SubnetsMenu' => 'Subnets', + 'UI:SubnetsMenu+' => 'Subnets', + 'UI:SubnetsMenu:Title' => 'Subnets', + 'UI:ApplicationLogMenu' => 'Log Applicatif', + 'UI:ApplicationLogMenu+' => 'Log Applicatif', + 'UI:ApplicationLogMenu:Title' => 'Log Applicatif', + 'UI:ClosedChangesMenu' => 'Changements Terminés', + 'UI:ClosedChangesMenu+' => 'Changements Terminés', + 'UI:ClosedChangesMenu:Title' => 'Changements Terminés', + 'UI:ClosedIncidentsMenu' => 'Incidents Clôts', + 'UI:ClosedIncidentsMenu+' => 'Incidents Clôts', + 'UI:ClosedIncidentsMenu:Title' => 'Incidents Clôts', + 'UI:DocumentsMenu' => 'Documents', + 'UI:DocumentsMenu+' => 'Documents', + 'UI:DocumentsMenu:Title' => 'Documents', + 'UI:GroupingMenu' => 'Groupes', + 'UI:GroupingMenu+' => 'Groupes', + 'UI:GroupingMenu:Title' => 'Groupes', + 'UI:KnownErrorsMenu' => 'Erreurs Connues', + 'UI:KnownErrorsMenu+' => 'Erreurs Connues', + 'UI:KnownErrorsMenu:Title' => 'Erreurs Connues', + 'UI:LocationsMenu' => 'Emplacements', + 'UI:LocationsMenu+' => 'Emplacements', + 'UI:LocationsMenu:Title' => 'Emplacements', + 'UI:MyChangesMenu' => 'Mes Changements', + 'UI:MyChangesMenu+' => 'Mes Changements', + 'UI:MyChangesMenu:Title' => 'Mes Changements', + 'UI:MyIncidentsMenu' => 'Mes Incidents', + 'UI:MyIncidentsMenu+' => 'Mes Incidents', + 'UI:MyIncidentsMenu:Title' => 'Mes Incidents', + 'UI:MyServiceCallsMenu' => 'Mes Demandes de Service', + 'UI:MyServiceCallsMenu+' => 'Mes Demandes de Service', + 'UI:MyServiceCallsMenu:Title' => 'Mes Demandes de Service', + 'UI:OpenChangesMenu' => 'Changements En Cours', + 'UI:OpenChangesMenu+' => 'Changements En Cours', + 'UI:OpenChangesMenu:Title' => 'Changements En Cours', + 'UI:OpenIncidentsMenu' => 'Incidents En Cours', + 'UI:OpenIncidentsMenu+' => 'Incidents En Cours', + 'UI:OpenIncidentsMenu:Title' => 'Incidents En Cours', + 'UI:OpenServiceCallsMenu' => 'Demandes de Service En Cours', + 'UI:OpenServiceCallsMenu+' => 'Demandes de Service En Cours', + 'UI:OpenServiceCallsMenu:Title' => 'Demandes de Service En Cours', + 'UI:PersonsMenu' => 'Personnes', + 'UI:PersonsMenu+' => 'Personnes', + 'UI:PersonsMenu:Title' => 'Personnes', + 'UI:ProfilesMenu' => 'Profils', + 'UI:ProfilesMenu+' => 'Profils', + 'UI:ProfilesMenu:Title' => 'Profils', + 'UI:ScheduledOutagesMenu' => 'Maintenance Plannifiée', + 'UI:ScheduledOutagesMenu+' => 'Maintenance Plannifiée', + 'UI:ScheduledOutagesMenu:Title' => 'Maintenance Plannifiée', + 'UI:TeamsMenu' => 'Equipes', + 'UI:TeamsMenu+' => 'Equipes', + 'UI:TeamsMenu:Title' => 'Equipes', + 'UI:UserAccountsMenu' => 'Comptes Utilisateur', + 'UI:UserAccountsMenu+' => 'Comptes Utilisateur', + 'UI:UserAccountsMenu:Title' => 'Comptes Utilisateur', + )); ?> diff --git a/images/open-flash-chart.swf b/images/open-flash-chart.swf index 6f5f46f456187fbe35ac63a0852c28df9e975fbd..3a01e52aeeaba86b1a932c09ad48bb447e8ad8f3 100644 GIT binary patch literal 276186 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 @|{ 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
            ub@Fg&;j(y2HR4= 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ß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\n"); break; case 'apply_modify': @@ -954,6 +902,91 @@ try break; + case 'apply_new': + $sClass = utils::ReadPostedParam('class', ''); + $sClassLabel = MetaModel::GetName($sClass); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if ( empty($sClass) ) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p("".Dict::S('UI:Error:ObjectAlreadyCreated')."\n"); + } + else + { + $oObj = MetaModel::NewObject($sClass); + foreach(MetaModel::GetZListItems($sClass, 'details') as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsLinkSet()) + { + // Link set, the data is a set of link objects, encoded in JSON + $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); + if (!empty($aAttributes[$sAttCode])) + { + $oLinkSet = WizardHelper::ParseJsonSet($oObj, $oAttDef->GetLinkedClass(), $oAttDef->GetExtKeyToMe(), $aAttributes[$sAttCode]); + $oObj->Set($sAttCode, $oLinkSet); + // TO DO: detect a real modification, for now always update !! + } + } + else if (!$oAttDef->IsExternalField()) + { + $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); + if (!is_null($rawValue)) + { + $aAttributes[$sAttCode] = trim($rawValue); + $previousValue = $oObj->Get($sAttCode); + if ($previousValue !== $aAttributes[$sAttCode]) + { + $oObj->Set($sAttCode, $aAttributes[$sAttCode]); + } + } + } + else if ($oAttDef->IsWritable()) + { + $iFlags = $oObj->GetAttributeFlags($sAttCode); + if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) + { + // Non-visible, or read-only attribute, do nothing + } + else if ($oAttDef->GetEditClass() == 'Document') + { + // There should be an uploaded file with the named attr_ + $oDocument = utils::ReadPostedDocument('file_'.$sAttCode); + if (!$oDocument->IsEmpty()) + { + // A new file has been uploaded + $oObj->Set($sAttCode, $oDocument); + } + } + } + } + } + if (is_object($oObj)) + { + $sClass = get_class($oObj); + $sClassLabel = MetaModel::GetName($sClass); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBInsertTracked($oMyChange); + $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); + $oP->add("

            ".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

            \n"); + $oObj->DisplayDetails($oP); + } + break; + case 'wizard_apply_new': $sJson = utils::ReadPostedParam('json_obj', ''); $oWizardHelper = WizardHelper::FromJSON($sJson); @@ -1020,14 +1053,14 @@ try $oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add("
            \n"); $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); + $oP->set_title($sActionLabel); $oP->add("
            \n"); $oObj->DisplayBareDetails($oP); $aTargetState = $aTargetStates[$sTargetState]; $aExpectedAttributes = $aTargetState['attribute_list']; - $oP->add("
            \n"); $oP->add("

            $sActionDetails

            \n"); $oP->add("
            \n"); - $oP->add("
            \n"); + $oP->add("\n"); $aDetails = array(); $iFieldIndex = 0; $aFieldsMap = array(); @@ -1049,17 +1082,17 @@ try } } $oP->details($aDetails); - $oP->add("\n"); + $oP->add("\n"); + $aFieldsMap['id'] = 'id'; $oP->add("\n"); $oP->add("\n"); $oP->add("\n"); $oP->add("\n"); $oP->add($oAppContext->GetForForm()); - $oP->add("    \n"); + $oP->add("    \n"); $oP->add("\n"); $oP->add("\n"); $oP->add("
            \n"); - $oP->add("
            \n"); $iFieldsCount = count($aFieldsMap); $sJsonFieldsMap = json_encode($aFieldsMap); @@ -1119,7 +1152,7 @@ EOF $aDetails = array(); foreach($aExpectedAttributes as $sAttCode => $iExpectCode) { - if (($iExpectCode & OPT_ATT_MUSTCHANGE) || ($oObj->Get($sAttCode) == '') ) + if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) { $paramValue = utils::ReadPostedParam("attr_$sAttCode", ''); $oObj->Set($sAttCode, $paramValue); From c61e375483607e1738b209c2ab0c41fe844ec6ed Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 14 Jun 2010 09:33:50 +0000 Subject: [PATCH 378/970] Nicer display of the form's validation icon SVN:trunk[461] --- application/cmdbabstract.class.inc.php | 16 ++++++++-------- application/ui.linkswidget.class.inc.php | 2 +- css/light-grey.css | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 8870a8d22b..43674138f9 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -878,19 +878,19 @@ abstract class cmdbAbstractObject extends CMDBObject case 'DateTime': $aEventsList[] ='keyup'; $aEventsList[] ='change'; - $sHTMLValue = ""; + $sHTMLValue = " {$sValidationField}"; break; case 'Password': $aEventsList[] ='keyup'; $aEventsList[] ='change'; - $sHTMLValue = ""; + $sHTMLValue = " {$sValidationField}"; break; case 'Text': $aEventsList[] ='keypress'; $aEventsList[] ='change'; - $sHTMLValue = ""; + $sHTMLValue = " {$sValidationField}"; break; case 'List': @@ -911,7 +911,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sHTMLValue = "\n"; $sHTMLValue .= "\n"; $sHTMLValue .= "$sFileName
            \n"; - $sHTMLValue .= "\n"; + $sHTMLValue .= " {$sValidationField}\n"; break; case 'String': @@ -930,7 +930,7 @@ abstract class cmdbAbstractObject extends CMDBObject { // too many choices, use an autocomplete // The input for the auto complete - $sHTMLValue = "$sValidationField"; + $sHTMLValue = " {$sValidationField}"; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); @@ -953,13 +953,13 @@ abstract class cmdbAbstractObject extends CMDBObject $sSelected = ($value == $key) ? ' selected' : ''; $sHTMLValue .= "\n"; } - $sHTMLValue .= "$sValidationField\n"; + $sHTMLValue .= " {$sValidationField}\n"; $aEventsList[] ='change'; } } else { - $sHTMLValue = ""; + $sHTMLValue = " {$sValidationField}"; $aEventsList[] ='keyup'; $aEventsList[] ='change'; } @@ -973,7 +973,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add_ready_script("$('#$iId').bind('change', function(evt, sFormId) { return UpdateDependentFields(['".implode("','", $aDependencies)."']) } );"); // Bind to a custom event: validate } } - return "
            {$sHTMLValue}{$sValidationField}
            "; + return "
            {$sHTMLValue}
            "; } public function DisplayModifyForm(WebPage $oPage, $aExtraParams = array()) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 3e1b22a18a..6da294fcdd 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -108,7 +108,7 @@ class UILinksWidget $oPage->add_at_the_end($this->GetLinkObjectDialog($oPage, $this->m_iInputId)); // Forms should not be inside forms $sHTMLValue .= "m_iInputId}\" size=\"35\" value=\"\" title=\"".Dict::S('UI:LinksWidget:Autocomplete+')."\"/>"; $sHTMLValue .= "m_iInputId}\" value=\"".Dict::S('UI:Button:AddObject')."\" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; - $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; + $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/> m_iInputId}\">\n"; // another hidden input to store & pass the object's Id $sHTMLValue .= "m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n"; $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n"; diff --git a/css/light-grey.css b/css/light-grey.css index 77ea1a54d9..5b59ee9145 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -34,6 +34,7 @@ table.listResults { border-bottom: 3px solid #e6e6e1; border-right: 3px solid #e6e6e1; width: 100%; + background-color: #fff; } table.listResults td { From c439ff2f46e684179b79db0fc8cdde3906d46340 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 14 Jun 2010 14:23:51 +0000 Subject: [PATCH 379/970] Class icon defined in the data model, use either the static method GetClassIcon, or GetIcon (this one could be overloaded for an icon depending on the object state) SVN:trunk[462] --- core/dbobject.class.php | 5 +++++ core/metamodel.class.php | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index b874f9dbbc..d56268d539 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -492,6 +492,11 @@ abstract class DBObject $this->m_iKey = $iNewKey; } + public function GetIcon() + { + return MetaModel::GetClassIcon(get_class($this)); + } + public function GetName() { $sNameAttCode = MetaModel::GetNameAttributeCode(get_class($this)); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index bb4d6fcdd3..5ebf60e57d 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -280,6 +280,25 @@ abstract class MetaModel return self::GetClassDescription($sClass); } } + final static public function GetClassIcon($sClass) + { + self::_check_subclass($sClass); + + $sIcon = ''; + if (array_key_exists('icon', self::$m_aClassParams[$sClass])) + { + $sIcon = self::$m_aClassParams[$sClass]['icon']; + } + if (strlen($sIcon) == 0) + { + $sParentClass = self::GetParentPersistentClass($sClass); + if (strlen($sParentClass) > 0) + { + return self::GetClassIcon($sParentClass); + } + } + return $sIcon; + } final static public function IsAutoIncrementKey($sClass) { self::_check_subclass($sClass); From 21bea2e7c39089f752f2e85e685c05a9092591c8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 14 Jun 2010 15:49:35 +0000 Subject: [PATCH 380/970] - Enhanced the display and modification/creation of objects in order to be able to get rid of templates... (Trac#138) SVN:trunk[463] --- application/cmdbabstract.class.inc.php | 216 ++++++++++++++++++++----- application/displayblock.class.inc.php | 2 +- core/metamodel.class.php | 2 +- pages/UI.php | 5 +- pages/ajax.render.php | 2 +- 5 files changed, 179 insertions(+), 48 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 43674138f9..88bb1c955b 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -119,11 +119,12 @@ abstract class cmdbAbstractObject extends CMDBObject $oBlock->Display($oPage, -1); $oPage->add("\n"); + $oPage->add("GetIcon()."\" style=\"margin-top:-30px; margin-right:10px; float:right\">\n"); } - function DisplayBareDetails(WebPage $oPage) + function DisplayBareProperties(WebPage $oPage) { - $oPage->add($this->GetBareDetails($oPage)); + $oPage->add($this->GetBareProperties($oPage)); } function DisplayBareRelations(WebPage $oPage) @@ -133,7 +134,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->SetCurrentTabContainer('Related Objects'); foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) { - if ((get_class($oAttDef) == 'AttributeLinkedSetIndirect') || (get_class($oAttDef) == 'AttributeLinkedSet')) + if ($oAttDef->IsLinkset()) { $oPage->SetCurrentTab($oAttDef->GetLabel()); $oPage->p($oAttDef->GetDescription()); @@ -142,7 +143,7 @@ abstract class cmdbAbstractObject extends CMDBObject { $sTargetClass = $oAttDef->GetLinkedClass(); $oFilter = new DBObjectSearch($sTargetClass); - $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey()); // @@@ condition has same name as field ?? + $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey()); $oBlock = new DisplayBlock($oFilter, 'list', false); $oBlock->Display($oPage, 0); @@ -178,19 +179,32 @@ abstract class cmdbAbstractObject extends CMDBObject return $sDisplayName; } - function GetBareDetails(WebPage $oPage) + function GetBareProperties(WebPage $oPage) { $sHtml = ''; $oAppContext = new ApplicationContext(); $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); $aDetails = array(); $sClass = get_class($this); - $aList = MetaModel::GetZListItems($sClass, 'details'); + $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); + $aFullList = MetaModel::ListAttributeDefs($sClass); + $aList = $aDetailsList; + // Compute the list of properties to display, first the attributes in the 'details' list, then + // all the remaining attributes that are not external fields + foreach($aFullList as $sAttCode => $void) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (!in_array($sAttCode, $aDetailsList) && (!$oAttDef->IsExternalField())) + { + $aList[] = $sAttCode; + } + } foreach($aList as $sAttCode) { $iFlags = $this->GetAttributeFlags($sAttCode); - if ( ($iFlags & OPT_ATT_HIDDEN) == 0) + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) { // The field is visible in the current state of the object if ($sStateAttCode == $sAttCode) @@ -240,7 +254,7 @@ abstract class cmdbAbstractObject extends CMDBObject // Object's details // template not found display the object using the *old style* $this->DisplayBareHeader($oPage); - $this->DisplayBareDetails($oPage); + $this->DisplayBareProperties($oPage); $this->DisplayBareRelations($oPage); } } @@ -987,46 +1001,104 @@ abstract class cmdbAbstractObject extends CMDBObject $aDetails = array(); $aFieldsMap = array(); $oPage->add("
            \n"); - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + + $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); + $aFullList = MetaModel::ListAttributeDefs($sClass); + $aList = $aDetailsList; + // Compute the list of properties to display, first the attributes in the 'details' list, then + // all the remaining attributes that are not external fields + foreach($aFullList as $sAttCode => $void) { - if ($oAttDef->IsWritable()) + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (!in_array($sAttCode, $aDetailsList) && (!$oAttDef->IsExternalField())) { - if ($sStateAttCode == $sAttCode) + $aList[] = $sAttCode; + } + } + + foreach($aList as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $iFlags = $this->GetAttributeFlags($sAttCode); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) + { + if ($oAttDef->IsWritable()) { - // State attribute is always read-only from the UI - $sHTMLValue = $this->GetStateLabel(); - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - } - else - { - $iFlags = $this->GetAttributeFlags($sAttCode); - if ($iFlags & OPT_ATT_HIDDEN) + if ($sStateAttCode == $sAttCode) { - // Attribute is hidden, do nothing + // State attribute is always read-only from the UI + $sHTMLValue = $this->GetStateLabel(); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } else { - if ($iFlags & OPT_ATT_READONLY) + $iFlags = $this->GetAttributeFlags($sAttCode); + if ($iFlags & OPT_ATT_HIDDEN) { - // Attribute is read-only - $sHTMLValue = $this->GetAsHTML($sAttCode); + // Attribute is hidden, do nothing } else { - $sValue = $this->Get($sAttCode); - $sDisplayValue = $this->GetEditValue($sAttCode); - $aArgs = array('this' => $this); - $sInputId = $iFormId.'_'.$sAttCode; - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs); - $aFieldsMap[$sAttCode] = $sInputId; - + if ($iFlags & OPT_ATT_READONLY) + { + // Attribute is read-only + $sHTMLValue = $this->GetAsHTML($sAttCode); + } + else + { + $sValue = $this->Get($sAttCode); + $sDisplayValue = $this->GetEditValue($sAttCode); + $aArgs = array('this' => $this); + $sInputId = $iFormId.'_'.$sAttCode; + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $aFieldsMap[$sAttCode] = $sInputId; + + } + $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue); } - $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue); } } + else + { + $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $this->GetAsHTML($sAttCode)); + } } } $oPage->details($aDetails); + // Now display the relations, one tab per relation + $oPage->AddTabContainer('Related Objects'); + $oPage->SetCurrentTabContainer('Related Objects'); + foreach($aList as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsLinkset()) + { + $oPage->SetCurrentTab($oAttDef->GetLabel()); + $oPage->p($oAttDef->GetDescription()); + + if (get_class($oAttDef) == 'AttributeLinkedSet') + { + $sTargetClass = $oAttDef->GetLinkedClass(); + $oFilter = new DBObjectSearch($sTargetClass); + $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey()); + + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oPage, 0); + } + else // get_class($oAttDef) == 'AttributeLinkedSetIndirect' + { + $sValue = $this->Get($sAttCode); + $sDisplayValue = $this->GetEditValue($sAttCode); + $aArgs = array('this' => $this); + $sInputId = $iFormId.'_'.$sAttCode; + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $aFieldsMap[$sAttCode] = $sInputId; + $oPage->add($sHTMLValue); + } + } + } + $oPage->SetCurrentTab(''); $oPage->add("\n"); $oPage->add("\n"); $oPage->add("\n"); @@ -1082,32 +1154,88 @@ EOF $sTargetState = $oObjectToClone->GetState(); } - //foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - foreach(MetaModel::GetZListItems($sClass, 'details') as $sAttCode) + $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); + $aFullList = MetaModel::ListAttributeDefs($sClass); + $aList = $aDetailsList; + // Compute the list of properties to display, first the attributes in the 'details' list, then + // all the remaining attributes that are not external fields + foreach($aFullList as $sAttCode => $void) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $iOptions = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; - if ($sStateAttCode == $sAttCode) + if (!in_array($sAttCode, $aDetailsList) && (!$oAttDef->IsExternalField())) { - // State attribute is always read-only from the UI - $sHTMLValue = MetaModel::GetStateLabel($sClass, $sTargetState); - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + $aList[] = $sAttCode; } - else if ((!$oAttDef->IsExternalField()) && ($oAttDef->IsWritable()) && ($iOptions != OPT_ATT_HIDDEN) && ($iOptions != OPT_ATT_READONLY) ) + } + foreach($aList as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $iFlags = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; + + if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) { + if ($oAttDef->IsWritable()) + { + if ($sStateAttCode == $sAttCode) + { + // State attribute is always read-only from the UI + $sHTMLValue = MetaModel::GetStateLabel($sClass, $sTargetState); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + else + { + if ($iFlags & OPT_ATT_HIDDEN) + { + // Attribute is hidden, do nothing + } + else + { + if ($iFlags & OPT_ATT_READONLY) + { + // Attribute is read-only + $sHTMLValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetAsHTML($sAttCode); + } + else + { + $sFieldId = 'att_'.$iFieldIndex; + $sValue = ($oObjectToClone == null) ? '' : $oObjectToClone->Get($sAttCode); + $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetEditValue($sAttCode); + $sHTMLValue = "
            ".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sFieldId, '', $iFlags, $aArgs)."
            "; + $aFieldsMap[$sFieldId] = $sAttCode; + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + $iFieldIndex++; + } + } + } + } + } + } + $oPage->details($aDetails); + // Now display the relations, one tab per relation + $oPage->AddTabContainer('Related Objects'); + $oPage->SetCurrentTabContainer('Related Objects'); + foreach($aList as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsLinkset()) + { + $oPage->SetCurrentTab($oAttDef->GetLabel()); + $oPage->p($oAttDef->GetDescription()); + + $iFlags = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; $sFieldId = 'att_'.$iFieldIndex; $sValue = ($oObjectToClone == null) ? '' : $oObjectToClone->Get($sAttCode); $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetEditValue($sAttCode); - $iOptions = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; - - $sHTMLValue = "
            ".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sFieldId, '', $iOptions, $aArgs)."
            "; + $iFlags = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; + $sHTMLValue = "
            ".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sFieldId, '', $iFlags, $aArgs)."
            "; $aFieldsMap[$sFieldId] = $sAttCode; $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); $iFieldIndex++; + $oPage->add($sHTMLValue); } } - - $oPage->details($aDetails); + $oPage->SetCurrentTab(''); + if ($oObjectToClone != null) { $oPage->add("GetKey()."\">\n"); diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index c30bda818f..72f79a75fa 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -543,7 +543,7 @@ class DisplayBlock { while($oObj = $this->m_oSet->Fetch()) { - $sHtml .= $oObj->GetBareDetails($oPage); + $sHtml .= $oObj->GetBareProperties($oPage); } } break; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 5ebf60e57d..2367d80d98 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -338,7 +338,7 @@ abstract class MetaModel final static public function GetDisplayTemplate($sClass) { self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["display_template"]; + return array_key_exists("display_template", self::$m_aClassParams[$sClass]) ? self::$m_aClassParams[$sClass]["display_template"]: ''; } final static public function GetAttributeOrigin($sClass, $sAttCode) { diff --git a/pages/UI.php b/pages/UI.php index 9f6ce5cc75..b31d7279a6 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -616,6 +616,7 @@ try $oP->add("
            \n"); $oP->add("

            ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

            \n"); $oP->add("
            \n"); + $oP->add("GetIcon()."\" style=\"margin-top:-30px; margin-right:10px; float:right\">\n"); $oP->add("
            \n"); $oObj->DisplayModifyForm($oP); @@ -661,6 +662,7 @@ try $oP->add("

            ".Dict::Format('UI:CloneTitle_Class_Object', $sClassLabel, $oObj->GetName())."

            \n"); $oP->add("
            \n"); + $oP->add("GetIcon()."\" style=\"margin-top:-30px; margin-right:10px; float:right\">\n"); $oP->add("
            \n"); cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObjToClone); $oP->add("
            \n"); @@ -693,6 +695,7 @@ try $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); $oP->add("

            ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

            \n"); $oP->add("
            \n"); + $oP->add("\n"); cmdbAbstractObject::DisplayCreationForm($oP, $sClass, null /* $oObjToClone */); $oP->add("
            \n"); break; @@ -1055,7 +1058,7 @@ try $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); $oP->set_title($sActionLabel); $oP->add("\n"); - $oObj->DisplayBareDetails($oP); + $oObj->DisplayBareProperties($oP); $aTargetState = $aTargetStates[$sTargetState]; $aExpectedAttributes = $aTargetState['attribute_list']; $oP->add("

            $sActionDetails

            \n"); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index fb71b37260..d993d9153d 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -94,7 +94,7 @@ switch($operation) $sJson = utils::ReadParam('json_obj', '', 'post'); $oWizardHelper = WizardHelper::FromJSON($sJson); $oObj = $oWizardHelper->GetTargetObject(); - $oObj->DisplayBareDetails($oPage); + $oObj->DisplayBareProperties($oPage); break; case 'wizard_helper': From b973ff838e98e7b39e5ab6fbc8320787fb8a71ba Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 15 Jun 2010 13:32:47 +0000 Subject: [PATCH 381/970] #141 a few transparent changes in the user management SVN:trunk[464] --- .../userrightsprofile.class.inc.php | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index ba23f04ae2..d0ac5eefb9 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -439,7 +439,7 @@ class URP_UserProfile extends UserRightsBaseClass public function GetName() { - return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile'); + return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); } } @@ -505,8 +505,7 @@ class URP_ProfileProjection extends UserRightsBaseClass $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); - $aValues = $oValueSetDef->GetValues(array('user' => $oUser), ''); - $aRes = array_keys($aValues); + $aRes = $oValueSetDef->GetValues(array('user' => $oUser), ''); } else { @@ -578,8 +577,7 @@ class URP_ClassProjection extends UserRightsBaseClass $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); - $aValues = $oValueSetDef->GetValues(array('this' => $oObject), ''); - $aRes = array_keys($aValues); + $aRes = $oValueSetDef->GetValues(array('this' => $oObject), ''); } elseif ($sExpr == '') { @@ -787,7 +785,6 @@ class UserRightsProfile extends UserRightsAddOnAPI SetupProfiles::ComputeITILProfiles(); //SetupProfiles::ComputeBasicProfiles(); - SetupProfiles::DoCreateDimensions(); SetupProfiles::DoCreateDimensions(); SetupProfiles::DoCreateProfiles(); return true; @@ -1217,7 +1214,15 @@ exit; $iPKey = $oObject->GetKey(); $iDimension = $oDimension->GetKey(); - $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); + if (array_key_exists($iDimension, $this->m_aClassProjs[$sClass])) + { + $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); + } + else + { + // No projection for a given class: default to 'any' + $aObjectProjection = null; + } $aRes = array(); if (array_key_exists($iUser, $this->m_aUserProfiles)) @@ -1231,7 +1236,15 @@ exit; else { // user projection to be cached on a given page ! - $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + if (array_key_exists($iDimension, $this->m_aProPros[$iProfile])) + { + $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + } + else + { + // No projection for a given profile: default to 'any' + $aUserProjection = null; + } if (is_null($aUserProjection)) { From 9b6590652c3ab74c1088fb1533b6466fc91716dc Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 15 Jun 2010 13:53:28 +0000 Subject: [PATCH 382/970] #142 User profiles without any positive permission SVN:trunk[465] --- addons/userrights/userrightsprofile.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index d0ac5eefb9..ac2fa16c3c 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -1400,7 +1400,7 @@ class SetupProfiles { $oNewObj = MetaModel::NewObject("URP_ActionGrant"); $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('permission', $bPermission); + $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); $oNewObj->Set('class', $sClass); $oNewObj->Set('action', self::$m_aActions[$iAction]); $iId = $oNewObj->DBInsertNoReload(); @@ -1411,7 +1411,7 @@ class SetupProfiles { $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('permission', true); + $oNewObj->Set('permission', 'yes'); $oNewObj->Set('class', $sClass); $oNewObj->Set('stimulus', $sStimulusCode); $iId = $oNewObj->DBInsertNoReload(); From cb6605a22f40833d41d7591e3badf73e4fe11f94 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 15 Jun 2010 14:07:42 +0000 Subject: [PATCH 383/970] Error management: truncate long messages (was generating a second error over an error report) SVN:trunk[466] --- core/event.class.inc.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 882a8d9aca..41a3777c7d 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -210,6 +210,24 @@ class EventIssue extends Event { $this->Set('arguments_post', array()); } + + $sLength = strlen($this->Get('issue')); + if ($sLength > 255) + { + $this->Set('issue', substr($this->Get('issue'), 0, 200)." -truncated ($sLength chars)"); + } + + $sLength = strlen($this->Get('impact')); + if ($sLength > 255) + { + $this->Set('impact', substr($this->Get('impact'), 0, 200)." -truncated ($sLength chars)"); + } + + $sLength = strlen($this->Get('page')); + if ($sLength > 255) + { + $this->Set('page', substr($this->Get('page'), 0, 200)." -truncated ($sLength chars)"); + } } } From 6013920a4550c1294ddde69d72fc9956fe42133f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 15 Jun 2010 14:10:19 +0000 Subject: [PATCH 384/970] #124 Helpers for testing profile/dimension/projection SVN:trunk[467] --- setup/benchmark.php | 89 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/setup/benchmark.php b/setup/benchmark.php index 5df8081298..cc8b69abe1 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -82,6 +82,30 @@ class BenchmarkDataCreation return $this->m_aCreated[$sClass][array_rand($this->m_aCreated[$sClass])]; } + static protected function FindId($sClass) + { + $oSet = new DBObjectSet(new DBObjectSearch($sClass)); + if ($oSet->Count() < 1) + { + return null; + } + + $oObj = $oSet->Fetch(); + return $oObj->GetKey(); + } + + static protected function FindIdFromOQL($sOQL) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); + if ($oSet->Count() < 1) + { + return null; + } + + $oObj = $oSet->Fetch(); + return $oObj->GetKey(); + } + protected function MakeFeedback($oP, $sClass) { $iSample = reset($this->m_aCreated[$sClass]); @@ -100,9 +124,69 @@ class BenchmarkDataCreation $oP->add(""); } - public function Go(WebPage $oP, $oChange) + public function GoProjections(WebPage $oP, $oChange) { + // User login + // + $aData = array( + 'userid' => self::FindId('bizPerson'), + 'login' => 'foo', + 'password' => 'foo', + 'language' => 'EN US', + ); + $iLogin = $this->CreateObject('URP_Users', $aData, $oChange); + // Assign profiles to the new login + // + $aData = array( + 'userid' => $iLogin, + 'profileid' => self::FindIdFromOQL("SELECT URP_Profiles WHERE name LIKE 'Configuration Manager'"), + 'reason' => '', + ); + $iFoo = $this->CreateObject('URP_UserProfile', $aData, $oChange); + + // Dimension + // + $aData = array( + 'name' => 'location', + 'description' => '', + 'type' => 'bizLocation', + ); + $iDimLocation = $this->CreateObject('URP_Dimensions', $aData, $oChange); + + // Project classes + // + $aMyClassesToProject = array('bizDevice', 'bizServer'); + foreach($aMyClassesToProject as $sClass) + { + $aData = array( + 'dimensionid' => $iDimLocation, + 'class' => $sClass, + 'value' => '', + 'attribute' => 'location_name', + ); + $iFoo = $this->CreateObject('URP_ClassProjection', $aData, $oChange); + } + + // Project profiles + // + $aProfilesToProject = array(1, 2, 3, 4, 5, 6, 7, 8, 9); + foreach($aProfilesToProject as $iProfileId) + { + $aData = array( + 'dimensionid' => $iDimLocation, + 'profileid' => $iProfileId, + 'value' => 'Grenoble', + 'attribute' => '', + ); + $iFoo = $this->CreateObject('URP_ProfileProjection', $aData, $oChange); + } + + $oP->p('Created projections (Cf. login "foo", pwd "foo")'); + } + + public function GoVolume(WebPage $oP, $oChange) + { // 1 - Organizations // $aData = array( @@ -382,7 +466,8 @@ function DisplayStep3(SetupWebPage $oP, $oDataCreation) $oMyChange->Set("userinfo", "Administrator"); $iChangeId = $oMyChange->DBInsertNoReload(); - $oDataCreation->Go($oP, $oMyChange); + $oDataCreation->GoProjections($oP, $oMyChange); + $oDataCreation->GoVolume($oP, $oMyChange); } /** From 8035f20b1c31175bd33df1271fd4708aa6952730 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 15 Jun 2010 16:58:01 +0000 Subject: [PATCH 385/970] Fix some bugs in the handling of creation/modification forms (Trac #131) SVN:trunk[468] --- pages/UI.php | 3 +-- pages/ajax.render.php | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index b31d7279a6..6ce1fd6720 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -920,9 +920,8 @@ try else { $oObj = MetaModel::NewObject($sClass); - foreach(MetaModel::GetZListItems($sClass, 'details') as $sAttCode) + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); if ($oAttDef->IsLinkSet()) { // Link set, the data is a set of link objects, encoded in JSON diff --git a/pages/ajax.render.php b/pages/ajax.render.php index d993d9153d..3ab4346ed3 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -121,7 +121,8 @@ switch($operation) $displayValue = $oObj->GetEditValue($sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', 0, array('this' => $oObj)); - + // Make sure that we immediatly validate the field when we reload it + $oPage->add_ready_script("$('#$sId').trigger('validate');"); $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } } From 5df44c2a22bd4edc9d58bea9a2885194ca2eb5ed Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 15 Jun 2010 17:05:34 +0000 Subject: [PATCH 386/970] Adding the Flash navigator... SVN:trunk[469] From 46cd29e60a3d60a3069d6f89ae5ca0171cfc3b9c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 15 Jun 2010 17:08:00 +0000 Subject: [PATCH 387/970] Adding the Flash navigator... SVN:trunk[470] --- application/displayblock.class.inc.php | 5 + dictionaries/dictionary.itop.model.php | 6 +- navigator/navigator.as2 | 701 +++++++++++++++++++++++++ navigator/navigator.fla | Bin 0 -> 4377600 bytes navigator/navigator.swf | Bin 0 -> 339562 bytes 5 files changed, 709 insertions(+), 3 deletions(-) create mode 100755 navigator/navigator.as2 create mode 100755 navigator/navigator.fla create mode 100755 navigator/navigator.swf diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 72f79a75fa..5f214e1d44 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -835,6 +835,11 @@ class MenuBlock extends DisplayBlock //if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); } if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); } if ($bIsDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); } + $aRelations = MetaModel::EnumRelations(); + foreach($aRelations as $sRelationCode) + { + $aActions[] = array ('label' => MetaModel::GetRelationVerbUp($sRelationCode), 'url' => "../pages/$sUIPage?operation=swf_navigator&relation=$sRelationCode&class=$sClass&id=$id&$sContext"); + } } $aTransitions = $oObj->EnumTransitions(); $aStimuli = Metamodel::EnumStimuli($sClass); diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index 0894f0d4d5..e8761cc5fc 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -29,9 +29,9 @@ // Dict::Add('EN US', 'English', 'English', array( - 'Relation:impacts/Description' => 'functionaly impacted by', - 'Relation:impacts/VerbUp' => 'impacts', - 'Relation:impacts/VerbDown' => 'is impacted by', + 'Relation:impacts/Description' => 'Elements impacted by', + 'Relation:impacts/VerbUp' => 'Impact...', + 'Relation:impacts/VerbDown' => 'Elements impacted by...', )); diff --git a/navigator/navigator.as2 b/navigator/navigator.as2 new file mode 100755 index 0000000000..2c797b7c51 --- /dev/null +++ b/navigator/navigator.as2 @@ -0,0 +1,701 @@ +var TEST_MODE = 0; + +if (TEST_MODE == 1) +{ + var root_object_id = 11; + var root_object_class = "bizPC"; + var root_relation = "neighboors"; + var xml_document_url = "http://localhost:81/pages/xml.navigator.php"; + var details_url = "http://localhost:81/pages/UI.php?operation=details"; +} +else +{ + var root_object_id = obj_id; // 11; + var root_object_class = obj_class; // "bizPC"; + var root_relation = relation; // "neighboors"; + var xml_document_url = unescape(xmlUrl); // "http://localhost:81/pages/xml.navigator.php"; + var details_url = unescape(drillUrl); // "http://localhost:81/pages/UI.php?operation=details"; +} +if (pWidth == undefined) +{ + pWidth = 800; +} +if (pHeight == undefined) +{ + pHeight = 800; +} +var K = 0; //0.02; // elasticity coeff +var Q = 0; // Normally each element may have a specific "charge" +var deltaT = 1; // delta time +var solidFriction = 10; +var radius = 25; +if (customRadius != undefined) +{ + radius = customRadius; +} +if (displayController != 'true') +{ + _level0.Controller._x = -1000; // Make it invisible +} +var M = 1; // Mass of the object +var frictionCoeff = 0.9; // Friction coefficient +var bDrawForces = false; +var aXPos = new Array; +var aYPos = new Array; +var aClips = new Array; +var aLinks = new Array; +var aWeights = new Array; + +var oItemsContextMenu = new ContextMenu(); +oItemsContextMenu.hideBuiltInItems(); +var menuItem = new ContextMenuItem ("Details...", DisplayObjectDetails); +var menuItem2 = new ContextMenuItem ("Expand", Expand); +var menuItem3 = new ContextMenuItem ("Drill down", DrillDown); + +function DisplayObjectDetails(oSelectedObject) +{ + // Called by the context menu + var fullUrl:String = details_url; + if (fullUrl.indexOf("?",0) > 0) + { + fullUrl = fullUrl+"&class="+oSelectedObject.obj_class+"&id="+oSelectedObject.id; + } + else + { + fullUrl = fullUrl+"?class="+oSelectedObject.obj_class+"&id="+oSelectedObject.id; + } + getURL(fullUrl, "_top"); +} + +function DrillDown(oSelectedObject) +{ + DoReload(oSelectedObject.id, oSelectedObject.obj_class, root_relation); +} + +function Expand(oSelectedObject) +{ + DoLoad(oSelectedObject.id, oSelectedObject.obj_class, root_relation); +} + +oItemsContextMenu.customItems.push(menuItem, menuItem2, menuItem3); + +function DrawLinks() +{ + oLinksMovie = _root.links; + oLinksMovie.clear(); + oLinksMovie.lineStyle (5, 0xcccccc, 100); + for(i=0; i 1) // if less than 1 pixel, don't draw it + { + // Draw the arrow: main line + oForcesMovie.lineTo(aClips[i]._x + Fx, aClips[i]._y + Fy); + // Head of the arrow + oForcesMovie.lineTo(aClips[i]._x + Fx + 5*(Fy-Fx)/strengh, aClips[i]._y + Fy - 5*(Fx+Fy)/strengh); + oForcesMovie.moveTo(aClips[i]._x + Fx, aClips[i]._y + Fy); + // Head of the arrow + oForcesMovie.lineTo(aClips[i]._x + Fx - 5*(Fx+Fy)/strengh, aClips[i]._y + Fy - 5*(Fy-Fx)/strengh); + } + } + } +} + +function DrawItems() +{ + aForces = ComputeForces(); + DrawLinks(true); + DrawForces(aForces); +} + +function DoStartDrag() +{ + this.bInDrag = true; + if (this.iTimeout != 0) + { + clearTimeout(this.iTimeout); + this.iTimeout = 0; + } + this.startDrag() +} + + +function DoReleaseDrag() +{ + this.stopDrag(); + this.bInDrag = false; + if (this.lastclick - (this.lastclick=getTimer()) + 250 > 0) + { + //do doubleclick action + trace("double"); + DoReload(this.id, this.obj_class); + } + else + { + //do singleclick action + trace("single"); + } +} + +function DoRollOver() +{ + trace("Roll Over: "+this.name); + var iTimeOut:Number; + iTimeout = setTimeout(ShowTooltip, 500, this); + this.iTimeout = iTimeout; +} + +function DoRollOut() +{ + trace("Roll Out"); + if (this.iTimeout != 0) + { + clearTimeout(this.iTimeout); + this.iTimeout = 0; + } + HideTooltip(); +} + +function UpdatePositions() +{ + var MIN_MOVE_LIMIT = 0.3; // Whatever moves of less than 0.3 px per cycle, remains frozen to spare some CPU + for(var i=0; i MIN_MOVE_LIMIT) || (Math.abs(aClips[i].Vy * deltaT) > MIN_MOVE_LIMIT) ) + { + aClips[i]._x += aClips[i].Vx * deltaT; + aClips[i]._y += aClips[i].Vy * deltaT; + } + } + } +} + +function CreateItem(id, obj_class, icon, name, x, y, parentId, oNodeDetails) +{ + trace("CreateItem(id:"+id+" , parentId:"+parentId+")"); + var i = aClips.length; // Add new elements at the end of the array + var sMovieName; + switch (icon) + { + case "application": + sMovieName = "Item_application"; + break; + + case "business_process": + sMovieName = "Item_business_process"; + break; + + case "chemicals": + sMovieName = "Item_chemicals"; + break; + + case "contact": + sMovieName = "Item_contact"; + break; + + case "contract": + sMovieName = "Item_contract"; + break; + + case "change": + sMovieName = "Item_change"; + break; + + case "database": + sMovieName = "Item_database"; + break; + + case "db_instance": + sMovieName = "Item_db_instance"; + break; + + case "desktop": + sMovieName = "Item_dekstop"; + break; + + case "incident": + sMovieName = "Item_incident"; + break; + + case "interface": + sMovieName = "Item_interface"; + break; + + case "laptop": + sMovieName = "Item_laptop"; + break; + + case "network_device": + sMovieName = "Item_nw_device"; + break; + + case "printer": + sMovieName = "Item_printer"; + break; + + case "server": + sMovieName = "Item_server"; + break; + + case "wireless_device": + sMovieName = "Item_wireless_device"; + break; + + default: + sMovieName = "Item"; + } + aClips[i]= _root.links.attachMovie(sMovieName, "r"+i, i+10); + aClips[i]._x= x; + aClips[i]._y= y; + aClips[i].parentId = parentId; + aClips[i].details = oNodeDetails; + aClips[i]._xscale = radius; + aClips[i]._yscale = radius; + aClips[i].onPress = DoStartDrag; + aClips[i].onRelease = DoReleaseDrag; + aClips[i].onRollOver = DoRollOver; + aClips[i].onRollOut = DoRollOut; + aClips[i].Vx = 0; // No inital speed + aClips[i].Vy = 0; // No inital speed + aClips[i].bInDrag = false; + aClips[i].id = id; + aClips[i].icon = icon; + aClips[i].name = name; + aClips[i].obj_class = obj_class; + aClips[i].menu = oItemsContextMenu; + aWeights[i] = 1; + trace("Element added: index="+i+", id="+id+", obj_class="+id+", obj_class="+name+", icon="+icon+newline+"details: "+aClips[i].details.values); + return i; +} + +function GetParentAngle(oClip:MovieClip) +{ + var angle:Number = 0; + + trace("GetParentAngle oClip.parentId: "+oClip.parentId); + if (oClip.parentId != undefined) + { + oParentClip = aClips[oClip.parentId]; + dx = oClip._x - oParentClip._x; + dy = oClip._y - oParentClip._y; + trace("GetParentAngle dx: "+dx+", dy:"+dy); + if ((dx == 0) && (dy == 0)) + { + angle = 0; + } + else + { + angle = Math.atan2(dy, dx); + } + } + trace("GetParentAngle returned: "+((angle*360)/(2*Math.PI))); + return angle; +} + +function FindItemByIdAndClass(id, obj_class) +{ + var result = -1; + var i = 0; + while ( (i < aClips.length) && (result == -1)) + { + if ((aClips[i].id == id) && (aClips[i].obj_class == obj_class)) + { + result = i; + } + i++; + } + return result; +} + +function AddLink(sourceIndex, destinationIndex, bArrow) +{ + trace("Adding link between srcIndex:"+sourceIndex+" and destIndex:"+destinationIndex+" (arrow:"+bArrow+" )"); + aLinks[aLinks.length] = { start: sourceIndex, end: destinationIndex, bArrow: bArrow }; +} + +oLinksMovie = _root.createEmptyMovieClip ("links", -1); +oForcesMovie = _root.createEmptyMovieClip ("forces", 9999); +oLinksMovie.onEnterFrame = DrawItems; +oForcesMovie.onEnterFrame = DrawForces; + +//////////////////////////////////////////////////////////////////////////// +// Experimenting with the load of an XML file +//////////////////////////////////////////////////////////////////////////// + +// Create a new XML object. +var myLoader:XML = new XML(); + +// Set the ignoreWhite property to true (default value is false). +myLoader.ignoreWhite = true; + +// After loading is complete, trace the XML object. +myLoader.onLoad = function(success) +{ + DumpClips("Beginning of (asynchronous) load"); + if (success) + { + myXML = new XML(); + myXML.parseXML(myLoader); + oRootNode = myXML.firstChild; + trace("root node: "+oRootNode.nodeName); + if (oRootNode.attributes.title != undefined) + { + StaticContainer.Schema_title = oRootNode.attributes.title; + } + else + { + StaticContainer.Schema_title = ""; + } + oFirstNode = oRootNode.firstChild; + oStartPoint = {x:0, y:0}; + var placement = ""; + switch (oRootNode.attributes.position) + { + case "top": + oStartPoint.x = pWidth/2; + oStartPoint.y = 2*radius; + break; + + case "left": + oStartPoint.x = 2*radius; + oStartPoint.y = pHeight/2; + break; + + case "center": + default: + oStartPoint.x = pWidth /2; + oStartPoint.y = pHeight /2; + placement = "surround"; + } + LoadNode(oFirstNode, undefined, oStartPoint.x, oStartPoint.y, placement); + } + else + { + trace("Failed to load XML data:"+newline+myLoader); + } + _level0.LoadingAnimation._alpha = 0; + _level0.LoadingAnimation.stop(); + DumpClips("End of (asynchronous) load"); +} + +function LoadNode(oXmlNode:Object, parentIndex:Number, x:Number, y:Number, placementMethod:String) +{ + trace("Loading Node: "+oXmlNode); + + var nodeId = oXmlNode.attributes.id; + var nodeClass = oXmlNode.attributes.obj_class; + var nodeName = oXmlNode.attributes.name; + var nodeIcon = oXmlNode.attributes.icon; + var nodeIndex = FindItemByIdAndClass(nodeId, nodeClass); + var nodeZlist = oXmlNode.attributes.zlist; + var aNodeZlist = nodeZlist.split(','); + var oNodeDetails = { names: new Array(), values: new Array() }; + for(var att_index=0; att_index\n"; $oPage->add_ready_script("\$('#label_$iId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); $oPage->add_ready_script("\$('#label_$iId').result( function(event, data, formatted) { if (data) { $('#{$iId}').val(data[1]); } } );"); - // Prepopulate with a default value -- but no display value... - //if (!empty($value)) - //{ - // $oPage->add_ready_script("\$('#label_$iInputId').search( 'domino.combodo.com' );"); - //} $aEventsList[] ='change'; } else @@ -1176,6 +1171,46 @@ EOF { if ($oAttDef->IsWritable()) { + if ($oObjectToClone != null) + { + $sValue = $oObjectToClone->GetEditValue($sAttCode); + $aArgs['this'] = $oObjectToClone; + } + else + { + if(isset($aArgs['default'][$sAttCode])) + { + $sValue = $aArgs['default'][$sAttCode]; + } + else + { + $sValue = $oAttDef->GetDefaultValue(); + } + } + // Prepopulate with a default value -- but no display value... + $sDisplayValue = ''; + if (!empty($sValue)) + { + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs, ''); + switch (count($aAllowedValues)) + { + case 1: + case 0: + $sDisplayValue = $sValue; + break; + + default: + $sDisplayValue = $sValue; + foreach($aAllowedValues as $key => $display) + { + if ($key == $sValue) + { + $sDisplayValue = $display; + break; + } + } + } + } if ($sStateAttCode == $sAttCode) { // State attribute is always read-only from the UI @@ -1193,13 +1228,11 @@ EOF if ($iFlags & OPT_ATT_READONLY) { // Attribute is read-only - $sHTMLValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetAsHTML($sAttCode); + $sHTMLValue = ($oObjectToClone == null) ? $sDisplayValue : $oObjectToClone->GetAsHTML($sAttCode); } else { $sFieldId = 'att_'.$iFieldIndex; - $sValue = ($oObjectToClone == null) ? '' : $oObjectToClone->Get($sAttCode); - $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetEditValue($sAttCode); $sHTMLValue = "
            ".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sFieldId, '', $iFlags, $aArgs)."
            "; $aFieldsMap[$sFieldId] = $sAttCode; $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); diff --git a/pages/UI.php b/pages/UI.php index 6ce1fd6720..0fc40a71a6 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -664,7 +664,13 @@ try $oP->add("GetIcon()."\" style=\"margin-top:-30px; margin-right:10px; float:right\">\n"); $oP->add("
            \n"); - cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObjToClone); + $aDefaults = utils::ReadParam('default', array()); + $aContext = $oAppContext->GetAsHash(); + foreach($aContext as $key => $value) + { + $aDefaults[$key] = $value; + } + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObjToClone, array( 'default' => $aDefaults)); $oP->add("
            \n"); } else @@ -696,7 +702,13 @@ try $oP->add("

            ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

            \n"); $oP->add("
            \n"); $oP->add("\n"); - cmdbAbstractObject::DisplayCreationForm($oP, $sClass, null /* $oObjToClone */); + $aDefaults = utils::ReadParam('default', array()); + $aContext = $oAppContext->GetAsHash(); + foreach($aContext as $key => $value) + { + $aDefaults[$key] = $value; + } + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, null /* $oObjToClone */, array('default' => $aDefaults)); $oP->add("
            \n"); break; From d58a82c6c230b78469daecb9205650ac8b822b3c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 23 Jun 2010 14:32:54 +0000 Subject: [PATCH 390/970] Cleanup: obsolescence of "key_label" property on persistent classes SVN:trunk[473] --- .../userrights/userrightsmatrix.class.inc.php | 4 -- .../userrightsprofile.class.inc.php | 9 --- application/audit.category.class.inc.php | 1 - application/audit.rule.class.inc.php | 1 - application/cmdbabstract.class.inc.php | 2 +- application/iotask.class.inc.php | 1 - application/menunode.class.inc.php | 1 - application/transaction.class.inc.php | 1 - business/ChangeMgmt.business.php | 3 - business/KEDB.business.php | 3 - business/ServiceDesk.business.php | 3 - business/ServiceMgmt.business.php | 5 -- business/ServiceRequest.business.php | 2 - business/incidentMgmt.business.php | 5 -- business/itop.business.class.inc.php | 67 ++++++++++++------- business/test_farm.class.inc.php | 8 --- core/action.class.inc.php | 3 - core/attributedef.class.inc.php | 1 + core/cmdbchange.class.inc.php | 1 - core/cmdbchangeop.class.inc.php | 7 -- core/dbobject.class.php | 7 +- core/event.class.inc.php | 5 -- core/metamodel.class.php | 8 +-- core/trigger.class.inc.php | 7 -- pages/ITopConsultant.php | 1 - 25 files changed, 51 insertions(+), 105 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index fa78641ef4..6329050314 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -31,7 +31,6 @@ class UserRightsMatrixUsers extends DBObject ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "login", "state_attcode" => "", "reconc_keys" => array(), @@ -55,7 +54,6 @@ class UserRightsMatrixClassGrant extends DBObject ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -82,7 +80,6 @@ class UserRightsMatrixClassStimulusGrant extends DBObject ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -109,7 +106,6 @@ class UserRightsMatrixAttributeGrant extends DBObject ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index ac2fa16c3c..05e2434e95 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -58,7 +58,6 @@ class URP_Users extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "login", "state_attcode" => "", "reconc_keys" => array(), @@ -189,7 +188,6 @@ class URP_Profiles extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), @@ -296,7 +294,6 @@ class URP_Dimensions extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), @@ -410,7 +407,6 @@ class URP_UserProfile extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "userid", "state_attcode" => "", "reconc_keys" => array(), @@ -452,7 +448,6 @@ class URP_ProfileProjection extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "profileid", "state_attcode" => "", "reconc_keys" => array(), @@ -525,7 +520,6 @@ class URP_ClassProjection extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "dimensionid", "state_attcode" => "", "reconc_keys" => array(), @@ -601,7 +595,6 @@ class URP_ActionGrant extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "profileid", "state_attcode" => "", "reconc_keys" => array(), @@ -639,7 +632,6 @@ class URP_StimulusGrant extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "profileid", "state_attcode" => "", "reconc_keys" => array(), @@ -677,7 +669,6 @@ class URP_AttributeGrant extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "actiongrantid", "state_attcode" => "", "reconc_keys" => array(), diff --git a/application/audit.category.class.inc.php b/application/audit.category.class.inc.php index f9aa2645d0..655fd52b61 100644 --- a/application/audit.category.class.inc.php +++ b/application/audit.category.class.inc.php @@ -35,7 +35,6 @@ class AuditCategory extends cmdbAbstractObject ( "category" => "application", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array('name'), diff --git a/application/audit.rule.class.inc.php b/application/audit.rule.class.inc.php index c0bfb55d77..9310989711 100644 --- a/application/audit.rule.class.inc.php +++ b/application/audit.rule.class.inc.php @@ -36,7 +36,6 @@ class AuditRule extends cmdbAbstractObject ( "category" => "application", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array('name'), diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 7dc1d5e27b..3120f43434 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -571,7 +571,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aList[$sClassName][$sAttCode] = $oAttDef; } } - $aHeader[] = MetaModel::GetKeyLabel($sClassName); + $aHeader[] = 'id'; foreach($aList[$sClassName] as $sAttCode => $oAttDef) { if ($oAttDef->IsExternalField()) diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index 6ab618f3ea..db0c567a57 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -37,7 +37,6 @@ class InputOutputTask extends cmdbAbstractObject ( "category" => "application", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index a08e212086..c4bdd6297d 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -48,7 +48,6 @@ class menuNode extends DBObject ( "category" => "gui", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), diff --git a/application/transaction.class.inc.php b/application/transaction.class.inc.php index ffc55c257f..39db22201a 100644 --- a/application/transaction.class.inc.php +++ b/application/transaction.class.inc.php @@ -39,7 +39,6 @@ class privUITransaction extends DBObject ( "category" => "gui", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "expiration_date", "state_attcode" => "", "reconc_keys" => array(), diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index 852c976d55..18b8c8f7fd 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -36,7 +36,6 @@ class bizChangeTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "ticket_status", "reconc_keys" => array("title"), @@ -213,7 +212,6 @@ class lnkInfraChangeTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "impact", // ???? "state_attcode" => "", "reconc_keys" => array("impact"), // ???? @@ -257,7 +255,6 @@ class lnkContactChange extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "role", // ???? "state_attcode" => "", "reconc_keys" => array("role"), // ???? diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 0b5860f2ea..9fcfbd93d6 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -37,7 +37,6 @@ class bizKnownError extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -93,7 +92,6 @@ class lnkInfraError extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "", // ???? "state_attcode" => "", "reconc_keys" => array("infra_id","error_id"), // ???? @@ -133,7 +131,6 @@ class lnkDocumentError extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "link_type", "state_attcode" => "", "reconc_keys" => array("doc_name", "error_name"), diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index efeff71f43..465009e1e1 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -36,7 +36,6 @@ class bizServiceCall extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "call_status", "reconc_keys" => array("title"), @@ -171,7 +170,6 @@ class lnkCallTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "impact", // ???? "state_attcode" => "", "reconc_keys" => array("impact"), // ???? @@ -217,7 +215,6 @@ class lnkInfraCall extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "impact", // ???? "state_attcode" => "", "reconc_keys" => array("impact"), // ???? diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index 9f4830d7b2..e25b6daef6 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -36,7 +36,6 @@ class bizService extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", //"state_attcode" => "status", "state_attcode" => "", @@ -107,7 +106,6 @@ class bizContract extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", //"state_attcode" => "status", "state_attcode" => "", @@ -203,7 +201,6 @@ class lnkInfraContract extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "coverage", // ???? "state_attcode" => "", "reconc_keys" => array("infra_id","contract_id"), // ???? @@ -244,7 +241,6 @@ class lnkContactContract extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "role", // ???? "state_attcode" => "", "reconc_keys" => array("role"), // ???? @@ -289,7 +285,6 @@ class lnkDocumentContract extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "link_type", "state_attcode" => "", "reconc_keys" => array("doc_name", "contract_name"), diff --git a/business/ServiceRequest.business.php b/business/ServiceRequest.business.php index a45d00ac6f..8f88c6c4af 100644 --- a/business/ServiceRequest.business.php +++ b/business/ServiceRequest.business.php @@ -37,7 +37,6 @@ class bizServiceRequest extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -114,7 +113,6 @@ class bizServiceItem extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("request_id", "name"), // inherited attributes diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index af55e1cc5b..8499b48707 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -36,7 +36,6 @@ class bizIncidentTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "ticket_status", "reconc_keys" => array("title"), @@ -213,7 +212,6 @@ class lnkRelatedTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "impact", // ???? "state_attcode" => "", "reconc_keys" => array("impact"), // ???? @@ -259,7 +257,6 @@ class lnkInfraTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "impact", // ???? "state_attcode" => "", "reconc_keys" => array("impact"), // ???? @@ -305,7 +302,6 @@ class lnkContactTicket extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "role", // ???? "state_attcode" => "", "reconc_keys" => array("role"), // ???? @@ -350,7 +346,6 @@ class bizWorkgroup extends logRealObject ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_name", "name"), // inherited attributes diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 5d6c9b8ac8..d901ecc0ea 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -37,6 +37,49 @@ define('STANDARD_STATUSES', 'production,implementation,obsolete'); */ MetaModel::RegisterRelation("impacts"); +class classetest extends cmdbObject +{ + + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "key_type" => "automincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("name"), + "db_table" => "myclasstable", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeString("aaaa", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("b", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEmailAddress("c", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeIPAddress("d", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributePassword("e", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("f", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("g", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBoolean("h", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("i", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDate("j", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBlob("k", array("depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributePropertySet("l", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTable("m", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("n", array("targetclass"=>"bizOrganization", "jointype"=>null, "allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("o", array("allowed_values"=>null, "extkey_attcode"=>"n", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("p", array("linked_class"=>"class_to_be_defined", "ext_key_to_me"=>"attribute_to_be_defined", "ext_key_to_remote"=>"attribute_to_be_defined", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOQL("q", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("r", array("class_category"=>"bizmodel", "more_values"=>"myvalue1,myvalue2,myvalue3", "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTemplateString("s", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeTemplateText("t", array("allowed_values"=>null, "sql"=>"mysqlcolumn_to_be_defined", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + } +} + //////////////////////////////////////////////////////////////////////////////////// /** * An organization that owns some objects @@ -57,7 +100,6 @@ class bizOrganization extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("name"), @@ -111,7 +153,6 @@ class logRealObject extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("name"), @@ -158,7 +199,6 @@ class bizContact extends logRealObject ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -207,7 +247,6 @@ class bizPerson extends bizContact ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "first_name", "name"), // comment en définir plusieurs @@ -254,7 +293,6 @@ class bizTeam extends bizContact ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -287,7 +325,6 @@ class lnkContactTeam extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "role", "state_attcode" => "", "reconc_keys" => array("contact_id", "team_name"), @@ -326,7 +363,6 @@ class bizDocument extends logRealObject ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -369,7 +405,6 @@ class lnkDocumentRealObject extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "link_type", "state_attcode" => "", "reconc_keys" => array("doc_id", "object_name"), @@ -407,7 +442,6 @@ class lnkContactRealObject extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "role", "state_attcode" => "", "reconc_keys" => array("contact_id", "object_name"), @@ -448,7 +482,6 @@ abstract class logInfra extends logRealObject ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -477,7 +510,6 @@ class lnkContactInfra extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "role", "state_attcode" => "", "reconc_keys" => array("contact_id", "infra_id"), @@ -516,7 +548,6 @@ class bizLocation extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -638,7 +669,6 @@ class bizCircuit extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "provider_id", "carrier_ref", "name"), // inherited attributes @@ -709,7 +739,6 @@ class bizInterface extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "device_id", "name"), @@ -793,7 +822,6 @@ class bizSubnet extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -877,7 +905,6 @@ class bizDevice extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -938,7 +965,6 @@ class bizPC extends bizDevice ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -1022,7 +1048,6 @@ class bizServer extends bizDevice ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", //"state_attcode" => "status", "state_attcode" => "", @@ -1181,7 +1206,6 @@ class bizNetworkDevice extends bizDevice ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -1239,7 +1263,6 @@ class bizInfraGroup extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("org_id", "name"), // inherited attributes @@ -1313,7 +1336,6 @@ class bizApplication extends logInfra ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("device_id", "name"), // inherited attributes @@ -1383,7 +1405,6 @@ class lnkInfraGrouping extends cmdbAbstractObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "impact", "state_attcode" => "", "reconc_keys" => array(""), @@ -1435,7 +1456,6 @@ class lnkClientServer extends logRealObject ( "category" => "bizmodel,searchable", "key_type" => "autoincrement", - "key_label" => "link_id", "name_attcode" => "relation", // ???? "state_attcode" => "", "reconc_keys" => array("relation"), // ???? @@ -1477,7 +1497,6 @@ class bizPatch extends logRealObject ( "category" => "bizmodel,searchable", "key_type" => "", - "key_label" => "id", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array("device_id", "name"), // inherited attributes diff --git a/business/test_farm.class.inc.php b/business/test_farm.class.inc.php index dbb9b70fe3..850b3f0ede 100644 --- a/business/test_farm.class.inc.php +++ b/business/test_farm.class.inc.php @@ -43,7 +43,6 @@ class Animal extends cmdbObject ( "category" => "blah", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(""), @@ -70,7 +69,6 @@ class Mammal extends Animal ( "category" => "blah", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), @@ -95,7 +93,6 @@ class Bird extends Animal ( "category" => "blah", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -117,7 +114,6 @@ class WalkingBird extends Bird ( "category" => "blah", "key_type" => "", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -139,7 +135,6 @@ class FlyingBird extends Bird ( "category" => "blah", "key_type" => "", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -162,7 +157,6 @@ class AnimalRelation extends cmdbObject ( "category" => "blah", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -188,7 +182,6 @@ class EaterToEaten extends AnimalRelation ( "category" => "blah", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -210,7 +203,6 @@ class Group extends cmdbObject ( "category" => "blah", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 429d100ded..abca87b835 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -39,7 +39,6 @@ abstract class Action extends cmdbAbstractObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), @@ -104,7 +103,6 @@ abstract class ActionNotification extends Action ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), @@ -138,7 +136,6 @@ class ActionEmail extends ActionNotification ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "name", "state_attcode" => "", "reconc_keys" => array(), diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 0666fb620b..540f800801 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -936,6 +936,7 @@ class AttributeEnum extends AttributeString public function GetAllowedValues($aArgs = array(), $sBeginsWith = '') { $aRawValues = parent::GetAllowedValues($aArgs, $sBeginsWith); + if (is_null($aRawValues)) return null; $aLocalizedValues = array(); foreach ($aRawValues as $sKey => $sValue) { diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index 81d617ed3a..73dcb79e29 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -37,7 +37,6 @@ class CMDBChange extends DBObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "date", "state_attcode" => "", "reconc_keys" => array(), diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index ffff1493ab..3e6f17be1e 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -38,7 +38,6 @@ class CMDBChangeOp extends DBObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), @@ -87,7 +86,6 @@ class CMDBChangeOpCreate extends CMDBChangeOp ( "category" => "core/cmdb", "key_type" => "", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), @@ -122,7 +120,6 @@ class CMDBChangeOpDelete extends CMDBChangeOp ( "category" => "core/cmdb", "key_type" => "", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), @@ -156,7 +153,6 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp ( "category" => "core/cmdb", "key_type" => "", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), @@ -187,7 +183,6 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute ( "category" => "core/cmdb", "key_type" => "", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), @@ -278,7 +273,6 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute ( "category" => "core/cmdb", "key_type" => "", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), @@ -339,7 +333,6 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute ( "category" => "core/cmdb", "key_type" => "", - "key_label" => "", "name_attcode" => "change", "state_attcode" => "", "reconc_keys" => array(), diff --git a/core/dbobject.class.php b/core/dbobject.class.php index d56268d539..a00072e118 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -320,8 +320,11 @@ abstract class DBObject return; } } - $this->m_aCurrValues[$sAttCode] = $oAttDef->MakeRealValue($value); - $this->RegisterAsDirty(); // Make sure we do not reload it anymore... before saving it + if ($oAttDef->CheckValue($value)) + { + $this->m_aCurrValues[$sAttCode] = $oAttDef->MakeRealValue($value); + $this->RegisterAsDirty(); // Make sure we do not reload it anymore... before saving it + } } public function Get($sAttCode) diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 41a3777c7d..0d5f8953ab 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -33,7 +33,6 @@ class Event extends cmdbAbstractObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -70,7 +69,6 @@ class EventNotification extends Event ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -103,7 +101,6 @@ class EventNotificationEmail extends EventNotification ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -140,7 +137,6 @@ class EventIssue extends Event ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), @@ -240,7 +236,6 @@ class EventWebService extends Event ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(), diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 2367d80d98..a0e95780d9 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -304,11 +304,6 @@ abstract class MetaModel self::_check_subclass($sClass); return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement"); } - final static public function GetKeyLabel($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["key_label"]; - } final static public function GetNameAttributeCode($sClass) { self::_check_subclass($sClass); @@ -725,7 +720,7 @@ abstract class MetaModel // private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes) - public static function EnumRelations() + public static function EnumRelations($sClass = '') { return array_keys(self::$m_aRelationInfos); } @@ -1046,7 +1041,6 @@ abstract class MetaModel $aMandatParams = array( "category" => "group classes by modules defining their visibility in the UI", "key_type" => "autoincrement | string", - "key_label" => "if set, then display the key as an attribute", "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", "state_attcode" => "define wich attribute is representing the state (object lifecycle)", "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 486e63658e..8cd7077984 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -39,7 +39,6 @@ class Trigger extends cmdbAbstractObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), @@ -86,7 +85,6 @@ class TriggerOnObject extends Trigger ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), @@ -116,7 +114,6 @@ class TriggerOnStateChange extends TriggerOnObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), @@ -146,7 +143,6 @@ class TriggerOnStateEnter extends TriggerOnStateChange ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), @@ -175,7 +171,6 @@ class TriggerOnStateLeave extends TriggerOnStateChange ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), @@ -204,7 +199,6 @@ class TriggerOnObjectCreate extends TriggerOnObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "", "name_attcode" => "description", "state_attcode" => "", "reconc_keys" => array(), @@ -233,7 +227,6 @@ class lnkTriggerAction extends cmdbAbstractObject ( "category" => "core/cmdb", "key_type" => "autoincrement", - "key_label" => "Link ID", "name_attcode" => "", "state_attcode" => "", "reconc_keys" => array(""), diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index d2fd4aee2a..2ae597a0bc 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -60,7 +60,6 @@ function ShowClass($sClass, $sBaseArgs) $aProps["Description"] = MetaModel::GetClassDescription($sClass); $aProps["Autoincrement id?"] = MetaModel::IsAutoIncrementKey($sClass); - $aProps["Key label"] = MetaModel::GetKeyLabel($sClass); $aProps["Name attribute"] = MetaModel::GetNameAttributeCode($sClass); $aProps["Reconciliation keys"] = implode(", ", MetaModel::GetReconcKeys($sClass)); $aProps["DB key column"] = MetaModel::DBGetKey($sClass); From d792d77d608db87e7127382e0b8394e3d0e90e1d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 23 Jun 2010 15:00:33 +0000 Subject: [PATCH 391/970] Core: new feature, enumerate relations starting from a given class SVN:trunk[474] --- core/metamodel.class.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a0e95780d9..f1b69c26a4 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -722,7 +722,12 @@ abstract class MetaModel public static function EnumRelations($sClass = '') { - return array_keys(self::$m_aRelationInfos); + if (empty($sClass)) + { + return array_keys(self::$m_aRelationInfos); + } + + return array_keys(self::EnumRelationQueries($sClass, $sRelCode)); } public static function EnumRelationProperties($sRelCode) From 4494b3ea4536206832fec701b8396f0fe7431e22 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 23 Jun 2010 15:24:33 +0000 Subject: [PATCH 392/970] - New images for the UI re styling SVN:trunk[475] --- images/banner-bkg.png | Bin 0 -> 375 bytes images/banner-search.png | Bin 0 -> 562 bytes images/calendar.png | Bin 1091 -> 689 bytes images/drawer-handle.gif | Bin 318 -> 380 bytes images/itop-logo.png | Bin 0 -> 2466 bytes images/left-bkg.png | Bin 0 -> 215 bytes images/logo-combodo.png | Bin 0 -> 3684 bytes images/mini-arrow-orange-open.gif | Bin 0 -> 53 bytes images/mini-arrow-orange.gif | Bin 0 -> 54 bytes images/onOffBtn.png | Bin 0 -> 727 bytes images/search-top-left-corner.png | Bin 0 -> 287 bytes images/searchBtn.png | Bin 0 -> 1260 bytes images/splitter-bkg.png | Bin 0 -> 245 bytes images/splitter-top-corner.png | Bin 0 -> 881 bytes images/splitter-top.png | Bin 0 -> 169 bytes images/top-left-bkg.png | Bin 0 -> 641 bytes 16 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 images/banner-bkg.png create mode 100755 images/banner-search.png create mode 100755 images/itop-logo.png create mode 100755 images/left-bkg.png create mode 100755 images/logo-combodo.png create mode 100644 images/mini-arrow-orange-open.gif create mode 100644 images/mini-arrow-orange.gif create mode 100755 images/onOffBtn.png create mode 100755 images/search-top-left-corner.png create mode 100755 images/searchBtn.png create mode 100755 images/splitter-bkg.png create mode 100755 images/splitter-top-corner.png create mode 100755 images/splitter-top.png create mode 100755 images/top-left-bkg.png diff --git a/images/banner-bkg.png b/images/banner-bkg.png new file mode 100755 index 0000000000000000000000000000000000000000..159c1643dcf4aeb002a9eeee66749a6b8f0f660a GIT binary patch literal 375 zcmV--0f_#IP)gwk1?&|dP@aE>?^YicU@ax{*+U@P><>lbv z;oR}@?CI&{^78KT^6lj0-{a)pE!Y8?c?L$;o;r(_VI`4HADaa0H8@kK~#9! z?90az!ypU<&`s|hM}U?5|7Tj189S3pW-qz$Yy=W4-VC%wNy*}H^o^puwr1# zz=nYf0}BT3zj{s1m^0v2HHZ&sc@17fd}_T)-t0ot(5Atv+B^tu3>+DlG2o)BVqITd zjBe7Tf*k{E2CfY388|cWDEiN0TR+xOZP9hl^yR;7G1ID?(XX9>gM6$-SP47@$u~P^6v8T?eX#L>FMR@=;Pz#-`(BW<>li2 z{r%zL-rnBY=H}w*>E!qK_V)Jm*wF!+w1G;;Nacz^6=u~-R9=u>FMU><>2%4@!Z_l_V)4r|NoROyBPog z0Ut?3K~#9!?3h<_!!Qg5k?PnWE!Xtkdm!HbaFN*}C6faH^H!z|fsYjT&tqyWK(K%X zEMNf(Sik}nuz&?DU;&Fi7W6)viDEVu(_2vvwGWNR{Pk=R6hrN)ERTY6=uuwof_jTa zzJh*>nJf%ghy+-`0v51<1uS3z3s}HH`7iTp0~Ra!Fko>ha--kk^F(BRtleV0zF&zf zubvMSL(`78C++{Ld4K4=zc_D3ilKcAkPUV$KzkOTT?^2-!{w3cQjKe;pK!hIufV?( zX}I_qZtn)G^BbHi=cse8CTiv4h#^N4?ygB3362deU%BFbB=$Bp{>gD!TsGsHD=WA^ z5ev?5OAY3-O+Hdi<>26{93wr`mIY|r0^b4*05$TnXmv^faR2}S07*qoM6N<$f=BK! APx#24YJ`L;(K){{a7> zy{D6rF&KXbbV*G`2igV{2_P>X&Vuy-00J>dL_t(I%cYacOVm*m$3MTvjGB|9F%K#g z2|*}bSP<2!+F1lat=rc>P|+#`7cJWKHw2ZMLD(juFw93pBq8*$Q9?%@38t)h+*nFnrMiMY6$e1;*i}#kXF(ho6V=rUm?~5Z7{l+IFDaF69pI!k z289D@5F3Ts9Y_UC1#A@-^8gs@8URjq03NIrKqXQl5-D-D3}mwaRCfTVs@{8+*X@$g z`%`}#s;c_n^}4F6KAevF(V3{9n2mCwSkfzZ->Rx=2wMe=)==@1?CHPn445omGJ!yeBXZ~ms=t510K(9(3$j6qybJ%tr_PUELdx!8TfE^AVnBl=S+W!S0A@H-JfE7w!qTrPm-;1(#d8-l)zo5 zuE4m+!a#R|{Erg7*%or^+jMrs`SiU=XA+W$IGOgANNM0v+rbc^JJrm{qb0U07GoX8 rI-C`Z6Rb1;+=U}ud!w(n+jag6-IGw2vo?k00004b3#c}2nYxWd>Mx1l$TjLV`1Q_y+)SLX|jh;KJX41hl0{s9X>N6_ut% zfQV2f(xyoSvGc1*oy7Kku6Jf0hw-npy;Qj}(&)|X?tI_(edoQmN<@Ssj1XX9HDjv<*oLX-eaRA8Ey=4`5wa27xCgz!^WqbU+eF6EJOmxLE@rHFf~Nx(K2IeOq}yfY}W#n3IOd603srveOHr%Rw|!g*$@$tFMeJZ5s@#i`u+K9et&7++pT&_ z-v0Dg5fKRiQtNOcgaKUmWs@M(T%6w|4B?v_n_OK#$CXPCagI#*5l=rkL_75ibTwwputN5!O)F8DEaS&?w_O~r=Y<qVsCV;c2C%ASQo|>NK&dLgBPfrl24|#EJ9A|}F zYjuu|W+`Md{Sb`vXn|II|K~OU%PXrW6hAC%lR9utxLvJ(Gd3Ji8qHztKnTVduYrbF zPL&9Q_jvhaiJ$)3=B1M*F3xYUw6;gF5HV5A;hYd?`V3O*LpXI}nA%Q@nM$7J^#i8H zBmVxU&QuYK!x81Nyk{CHx-}RAoG9h^_^Txj5<_Y%sj*n&NUg)z^r5xz%<&PQdPgm1 zeU!uh1Naet0_?RMjo4wF&wmjRI`+Q36aP&vqnR$}J-|WBQW*;$$@5~h(Pv;fEiI@n z__yBm;m#xuoL}47GrczWpxa#J4>1Vh`XKv>_b~`?C>i5+=P&~x^q*xTz-!TAD{^Ne z7Gs_Kdp8ylk+u75b;sLU$0n(j-JAkV<-&eX*64uixEoAGG53aA~a${6lwyk9uk6r zt-UsX0L^9#1#IuNah*(TCWcI?i8A_dN9t}AP#TR;YKGEC#NGWikBvmscH1;#Ll|hH zP$S}<^%!&o8O?{psilw&XeNeSMsv_g$z>o6G}(;qF+HR~J_@*1ZS;!CiFC49@c*m_ h$7tRs@sWGte-1r*5A?tZIsgCw07*qoM6N;tV1n{S;oATJ diff --git a/images/drawer-handle.gif b/images/drawer-handle.gif index 648df789ee80af082acd9173d2cb29ee07ecc3c5..6028b75d9201f7209751ddceb7d30cb2a8cf8d29 100644 GIT binary patch literal 380 zcmV-?0fYWWNk%w1VQv5@0OJn;8-%VLk-Qs(uN;xP9F)Eslf4{>vK)@M9EPwQjJ6z# zv>cGS8iTDFf~*{r#Q*>QA^8LW000jFEC2ui0B!&%000F3(8)=wy*TU5yZ>M)j$~<` zXsWJk>%MR-&vb3yc&_h!@BhG{a7Zi~kI1BQ$!t2G(5Q4uty-_xtai)gHNN1mcuX#v z&*-#z&2GEj@c5ZQuiNkVynfH``~QG<0)qemgNKENhKG!dg#wR=kdcXyj*XXumY9u- zgo>b%nxB>c2&bs2s;jK6uCK7Mva_lNwzs&sy1Tr+zQ4f1!nq2?#>dFX%FE2n&d<=% z$O+Wd*4NnC+S}aS-rwNY2jk@B=I7|?>g(+7?(gOc^7Hid_V@Vt`uqI-{`CO{1ROxH z;J|_e4+;zz;9$dn3L!Q`NHJl=gcSuE#F%j-LWL?C2|g%5vZTqAC{wCj$+D%(moQ5{ am`R|f&6_xL>fFh*r_Y~2gA$k-0RTHlXw8!V literal 318 zcmV-E0m1%9Nk%w1VQv5y0J9DNb)Oe@pBIC!7lW-AgRB>Mq8ERt7j&H!d!!eEsuy>l z7k#D|bDR`|vKRmV{{R30A^8LW000gEEC2ui0B!&m000E*u*pfQy*TU5yZ>M)j$~<` zXsWJk>%MR-&vb3yc&_h!?~jb2a7Zi~kI1BQ$!t2G(5Q5pJXo*Ttai)odcWYXI2$ON z&wz6|9bmK7>UUc{htK72eA^D_+t+k?W(tIbhKGoWii?bmj*pOs1(TGOmY0~Bnwy-R zo}ZQoqNAjxrl+W>s;jK6uB8jHva__cwzs&sy1Tr+vdFX%FE2d3D3~c z($mz{*4NnC+S}3u-rwNi;^XAy=I7|?>fsIp?(gvN^7Hid_V@Vt`tJ_?0{;L53LHqV QpuvL(6DnLNpkD+4J2jA~%>V!Z diff --git a/images/itop-logo.png b/images/itop-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..41797152f3a8053662c7b724c24656ce924814dc GIT binary patch literal 2466 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 literal 0 HcmV?d00001 diff --git a/images/left-bkg.png b/images/left-bkg.png new file mode 100755 index 0000000000000000000000000000000000000000..d6ad385e08ca1dc0c9b9b3c90e04f9ea9bc001eb GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^AAnelgBeJkoc7QjNU@|l`Z_W&tUqDS+Vv91mn?CO zC<)F_D=AMbN@Z|N$xljE@XSq2PYp^v o+S+sOwDVIpV1N%f{tb)_uBCyyU0w7Ifrc=6y85}Sb4q9e01H%C(EtDd literal 0 HcmV?d00001 diff --git a/images/logo-combodo.png b/images/logo-combodo.png new file mode 100755 index 0000000000000000000000000000000000000000..010285f094b5b6e0b0942b0fc01dde4b75a60869 GIT binary patch literal 3684 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 GgEatpoDI$Z literal 0 HcmV?d00001 diff --git a/images/onOffBtn.png b/images/onOffBtn.png new file mode 100755 index 0000000000000000000000000000000000000000..e846aa269cc0f1f20f4e865d7411c41b34559594 GIT binary patch literal 727 zcmV;|0x127P)X0{{R3`rOqk00001b5ch_0Itp) z=>Px$M^H>uMF0Q*gBm7;9Vvz%D~2H~i6JbBB`}I7Gmlb!=Hlq+FMR_>gMe1=k4w3 z?(XXK?e6pM>-h5W`SkVn_VN7s`v3p`+Dshtp?C&-EkKe1Y2AyC<^X~ z)CDbGyraVZ`=8BHYqT?-eGYG$_czlC>HHP>r~0%pX&a4inff-Xx2w*2=og0GsLhz| z;Y3W?vY}UU;Xo-Id71yE1%!%1T+@K3)C1BnEums7wJRw7)N{_CvBdd44mn@IE9WzY zo-x)%g0Z>HMaB~7GB#!Csb#g1vUDl5Eo($V_!{?nM z5R@y?uu>?#Rf9>Tv-8uZPtp_i^LZuRzkgpefAWbF zC;tBZ`}yce6M{L77Y?EWHq$9Fs5yA_}B{J)dv;?^>GSJ>v3rj)7qYdbG+ ih7`>&TKH8ofuZ6d$IJRZWru-=GkCiCxvX{Px%i%?8dMey+J^YicY^ziQP>h106?Cj^ZwzKi^?CR>~>FMR@=;P+*;^pPw^78KG z<>B1i*z)r2@bK-|*VM|&#=gG0{QUdmiqorNT|&}q|NsB^v~z;-{RuCySRqF)GM3E-rm}}-RtM??vKLM`ug|0 zyt&B8#wD1=zu@fg^zfp}*~-}2hQHJO{rx7H#>~vftk~&-ywU9J>qMr`S+LVNpT|a| z$<*rc*x1&4ywrQZ+nC7P>G1CL_4M!d_>jur#l^zM$Hmmt(pRg@i^JGtvCzT6zh17+ z%F4**>FK=G-Qn%+=jZ0?>ge9y+bx{PF`UNB*x9$YwDI@&e!J0!ztm;1&-3%~L8Hm9 z+3Mrt-!YuWCz!?H;M~ID?fU%wz`(tQ#oo``+(M(u^78PC!`IHv%Ose_eZ16|&f@Lu z>nfVY+}zpYEy%1z>L1n z!NI=K(a+r6+WGnT*52LU?Ci|h+vMco$l2eU$Jis6#X_UWiNV!Krp)2t-PhOEYq{6& z^!8D#(e(KFE}hA<*X2;D%-!AG@$v36oW_X2)K;m=JfqC)_4ik-%<%B;A)`Gjw;NafH;_b@X-_h9DL!-!CvebON)wk2$ zES$(TpvleV@A~@r-{0Q4y0_ik+3xP_`1ti4l*Ir4|MvFr#iffq00001VoOIv0Eh)0 zNB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RV1``z~C|(=VeE#` zyU8arnLPBXhUh>3JCvmPbCT(=7|WlKNz=aMWEM$MGyPEF^JKf-*&bgBN4@r$x~4}J z7%9>8IBMDtX-NPW*(O!a7*ntm2#gg_JA4gyQ{|VN_wDQ@%g0*c)1f#nJcSu~S4J($ zPYIUq-Y6fPi$H6Rjd3~e>4=~;)|C9gk2W1g)WoJA9k2KAO|cq%tB?y&*w4V(*dbR0 zt-SIf8EfD(usJ?_9n>h^{T1Ve!((6zjGoZ3q0qzeeeujdW#XM9XBr5t?eYW;6ASyV zt|zu-<|TCC&z{SXK};8PFTtFE^bac zTa0;c%=zA->(q$LY23531rEIUPUf^gw*k!VQk|+=7GkdyyxmYl+<=>{7>k4=S?t0< z2PREUOQX2yonQ4iH@CRYMW9F9a5zk&@g8*E-b9Z?n^t;z7I0!{=|S&0uh;JSBp+>`(r0}C0C1Vw#t}2wSS0ZkRuR-p7w0w{PG6`}gl9OO`x(^yvKg^MC*T`Tzg_w{IVTEaktaVzNoGgDlhLPBzCVp>v4LIZa};fI3F#aumL$g_dLp@ETM X(R*#!M*0@;+9yj!@utC>gwv|%*@Eo&&$=- z(A3n>*x1w2(arPo@0ggD^78J$z`el0yo`*Ar>CT|v#+|kwwjukx3{$K@9N;++rh!U zq@*VC$;o;q_t*Y(q>7k*X-rm{t^zfUTn5?X+j*g4uPh&-wZFudl4c#KD}Ln2d~yj*g6|sHL*9ubiBju&}MIt*Mifkix>hm6ei~mXoHY zqt@2Z@bK)syt$vBoTQ_mwY9P7>E+1C#m&vh*4EPR@9Vp}xUR0Nlar6n&&T>)o}AFo&B@Bfy}h~4&dR>NyQ-?E%ge{#-`bIp zkC>R1#Kgd$pq;q5wcOm-mX?&Uv8~9+#Gam;=;-6x+Sc;&?fw1y|NsB?_VH15Tx$RT z0VYXAK~#9!?8?PR3_(AG;E0SSAD4c0l94OJ`dewAOjezsw{=` zI2gG(tr`da+q)?I!!IDAr8REE!2mQYu4i10iU7Ov<_@1t3sCA$a&7Ak0F7td<2hpo zSm_^J*b_AXl}{&!)XOHoT0!b`L^%O)Z+sqhXlQ`R0oBDpdKo}_X7X($p#a#}8TT@@ z5}>oY&vKk20CXi@`V3|@z-!a>lwIiocsPoWx)Vr%m>ae_YbYO}YJZE$^|%32N?hSI znGI0fR@Y;ira@ocD)4hB}-f* zN`mv#O3D+9QW+dm@{>{(JaZG%Q-e|yQz{EjrrH1%@dWsUxPJQd_SdiP|NsC0_U&W$ zx7~+;;>w;bjv*Ddk`mn5dA0~2WO*|4a0j2nj0=ocU0z7q9d=?-IKVLTHtVa1nE1m$ Ora@ocD)4hB}-f* zN`mv#O3D+9QW+dm@{>{(JaZG%Q-e|yQz{EjrrH1%T@COFaeeXP@vmRs|NQy=@#Dwe zzkfe^bnoWPt8d;sfA;Lrt5?tN-o1J2*0no#Zrr)pFo4a{YwTBPye*XOK^5ydvFP^(`8cA53{yM1o%IZJp8by)IDfTfgsUl!{~l~TkiU4 z4`;ZzbuI3jVtL$XlfA{WfT9x{J4&W57FeWPr!7-^pT8-lJDG89mdyMM#~wsDZBE^B kZ1E1~&UWiK;diVO4D(cPR5n~@0C| Date: Wed, 23 Jun 2010 15:27:15 +0000 Subject: [PATCH 393/970] - REstyling and update of the UI SVN:trunk[476] --- js/jquery-1.4.2.js | 6240 ++++++++++++++++++++++++++++++ js/jquery-1.4.2.min.js | 154 + js/jquery-ui-1.8.2.custom.min.js | 1012 +++++ js/jquery.blockUI.js | 648 ++-- js/jquery.layout.js | 2507 ++++++++++++ js/jquery.layout.min.js | 80 + js/jquery.treeview.js | 162 +- js/jquery.treeview.min.js | 15 + js/jquery.treeview.pack.js | 16 + js/linkswidget.js | 15 +- js/wizard.utils.js | 6 +- 11 files changed, 10542 insertions(+), 313 deletions(-) create mode 100755 js/jquery-1.4.2.js create mode 100755 js/jquery-1.4.2.min.js create mode 100755 js/jquery-ui-1.8.2.custom.min.js create mode 100755 js/jquery.layout.js create mode 100755 js/jquery.layout.min.js create mode 100644 js/jquery.treeview.min.js create mode 100644 js/jquery.treeview.pack.js diff --git a/js/jquery-1.4.2.js b/js/jquery-1.4.2.js new file mode 100755 index 0000000000..fff6776433 --- /dev/null +++ b/js/jquery-1.4.2.js @@ -0,0 +1,6240 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function( window, undefined ) { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // 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 jQuery( context ).find( selector ); + } + + // 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 current version of jQuery being used + jquery: "1.4.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // 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.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // 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 ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: 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() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // 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 object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // 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({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // 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 toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor + && !hasOwnProperty.call(obj, "constructor") + && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwnProperty.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + trim: function( text ) { + return (text || "").replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( 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; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; 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 ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + if ( !inv !== !callback( elems[ i ], i ) ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + +// Mutifunctional method to get and set values to a collection +// The value/s can be optionally by executed if its a function +function access( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; +} + +function now() { + return (new Date).getTime(); +} +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + now(); + + div.style.display = "none"; + div.innerHTML = "

            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 diff --git a/js/swfobject.js b/js/swfobject.js index c3831232b4..08fb27000e 100644 --- a/js/swfobject.js +++ b/js/swfobject.js @@ -1,5 +1,5 @@ -/* SWFObject v2.0 - Copyright (c) 2007 Geoff Stearns, Michael Williams, and Bobby van der Sluis +/* SWFObject v2.1 + Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis This software is released under the MIT License */ -var swfobject=function(){var Z="undefined",P="object",B="Shockwave Flash",h="ShockwaveFlash.ShockwaveFlash",W="application/x-shockwave-flash",K="SWFObjectExprInst",G=window,g=document,N=navigator,f=[],H=[],Q=null,L=null,T=null,S=false,C=false;var a=function(){var l=typeof g.getElementById!=Z&&typeof g.getElementsByTagName!=Z&&typeof g.createElement!=Z&&typeof g.appendChild!=Z&&typeof g.replaceChild!=Z&&typeof g.removeChild!=Z&&typeof g.cloneNode!=Z,t=[0,0,0],n=null;if(typeof N.plugins!=Z&&typeof N.plugins[B]==P){n=N.plugins[B].description;if(n){n=n.replace(/^.*\s+(\S+\s+\S+$)/,"$1");t[0]=parseInt(n.replace(/^(.*)\..*$/,"$1"),10);t[1]=parseInt(n.replace(/^.*\.(.*)\s.*$/,"$1"),10);t[2]=/r/.test(n)?parseInt(n.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof G.ActiveXObject!=Z){var o=null,s=false;try{o=new ActiveXObject(h+".7")}catch(k){try{o=new ActiveXObject(h+".6");t=[6,0,21];o.AllowScriptAccess="always"}catch(k){if(t[0]==6){s=true}}if(!s){try{o=new ActiveXObject(h)}catch(k){}}}if(!s&&o){try{n=o.GetVariable("$version");if(n){n=n.split(" ")[1].split(",");t=[parseInt(n[0],10),parseInt(n[1],10),parseInt(n[2],10)]}}catch(k){}}}}var v=N.userAgent.toLowerCase(),j=N.platform.toLowerCase(),r=/webkit/.test(v)?parseFloat(v.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,i=false,q=j?/win/.test(j):/win/.test(v),m=j?/mac/.test(j):/mac/.test(v);/*@cc_on i=true;@if(@_win32)q=true;@elif(@_mac)m=true;@end@*/return{w3cdom:l,pv:t,webkit:r,ie:i,win:q,mac:m}}();var e=function(){if(!a.w3cdom){return }J(I);if(a.ie&&a.win){try{g.write("\n"); + $oP->add("

            \n"); $oP->add("\n"); $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("\n"); while($oAuditCategory = $oCategoriesSet->fetch()) { From cfc69316d1f6ddc2615a88b29d1e838b880292bc Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 15:14:22 +0000 Subject: [PATCH 316/970] - Implementation of the localization... on going... SVN:trunk[399] --- dictionaries/dictionary.itop.ui.php | 14 ++++++++++++++ dictionaries/fr.dictionary.itop.ui.php | 14 ++++++++++++++ pages/run_query.php | 26 ++++++++++++-------------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index e330edcda9..f67481bba4 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -320,6 +320,8 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Button:DoImport' => ' Run the Import ! ', 'UI:Button:Done' => ' Done ', 'UI:Button:SimulateImport' => ' Simulate the Import ', + 'UI:Button:Test' => 'Test!', + 'UI:Button:Evaluate' => ' Evaluate ', 'UI:SearchToggle' => 'Search', 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', 'UI:NoObjectToDisplay' => 'No object to display.', @@ -438,6 +440,18 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Audit:HeaderNbObjects' => '# Objects', 'UI:Audit:HeaderNbErrors' => '# Errors', 'UI:Audit:PercentageOk' => '% Ok', + + 'UI:RunQuery:Title' => 'iTop - OQL Query Evaluation', + 'UI:RunQuery:QueryExamples' => 'Query Examples', + 'UI:RunQuery:HeaderPurpose' => 'Purpose', + 'UI:RunQuery:HeaderPurpose+' => 'Explanation about the query', + 'UI:RunQuery:HeaderOQLExpression' => 'OQL Expression', + 'UI:RunQuery:HeaderOQLExpression+' => 'The query in OQL syntax', + 'UI:RunQuery:ExpressionToEvaluate' => 'Expression to evaluate: ', + 'UI:RunQuery:MoreInfo' => 'More information about the query: ', + 'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ', + 'UI:RunQuery:SerializedFilter' => 'Serialized filter: ', + 'UI:RunQuery:Error' => 'An error occured while running the query: %1$s', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 69353b6410..d6cd276fac 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -315,6 +315,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Button:DoImport' => ' Lancer l\'import ! ', 'UI:Button:Done' => ' Terminé ', 'UI:Button:SimulateImport' => ' Simuler l\'import ', + 'UI:Button:Test' => 'Tester !', + 'UI:Button:Evaluate' => ' Exécuter ', 'UI:SearchToggle' => 'Recherche', @@ -439,6 +441,18 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Audit:HeaderNbErrors' => 'Nb d\'Erreurs', 'UI:Audit:PercentageOk' => '% Ok', + 'UI:RunQuery:Title' => 'iTop - Evaluation de requêtes OQL', + 'UI:RunQuery:QueryExamples' => 'Exemples de requêtes', + 'UI:RunQuery:HeaderPurpose' => 'Objectif', + 'UI:RunQuery:HeaderPurpose+' => 'But de la requête', + 'UI:RunQuery:HeaderOQLExpression' => 'Requête OQL', + 'UI:RunQuery:HeaderOQLExpression+' => 'La requête en OQL', + 'UI:RunQuery:ExpressionToEvaluate' => 'Requête à exécuter : ', + 'UI:RunQuery:MoreInfo' => 'Plus d\'information sur le requête: ', + 'UI:RunQuery:DevelopedQuery' => 'Requête OQL décompilée : ', + 'UI:RunQuery:SerializedFilter' => 'Version sérialisée : ', + 'UI:RunQuery:Error' => 'Une erreur s\'est produite durant l\'exécution de la requête: %1$s', + )); ?> diff --git a/pages/run_query.php b/pages/run_query.php index 6c75ee4aed..65a221da78 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -45,16 +45,16 @@ function ShowExamples($oP, $sExpression) $bUsingExample = true; } //$aDisplayData[$sTopic][] = array( - $aDisplayData['Query examples'][] = array( + $aDisplayData[Dict::S('UI:RunQuery:QueryExamples')][] = array( 'desc' => "
            ".htmlentities($sDescription)."
            ", 'oql' => "
            ".htmlentities($sOql)."
            ", - 'go' => "
            \n", + 'go' => "
            \n", ); } } $aDisplayConfig = array(); - $aDisplayConfig['desc'] = array('label' => 'Target', 'description' => ''); - $aDisplayConfig['oql'] = array('label' => 'OQL Expression', 'description' => ''); + $aDisplayConfig['desc'] = array('label' => Dict::S('UI:RunQuery:HeaderPurpose'), 'description' => Dict::S('UI:RunQuery:HeaderPurpose+')); + $aDisplayConfig['oql'] = array('label' => Dict::S('UI:RunQuery:HeaderOQLExpression'), 'description' => Dict::S('UI:RunQuery:HeaderOQLExpression+')); $aDisplayConfig['go'] = array('label' => '', 'description' => ''); foreach ($aDisplayData as $sTopic => $aQueriesDisplayData) @@ -72,7 +72,7 @@ $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); -$oP = new iTopWebPage("iTop - Expression Evaluation", $currentOrganization); +$oP = new iTopWebPage(Dict::S('UI:RunQuery:Title'), $currentOrganization); // Main program $sExpression = utils::ReadParam('expression', ''); @@ -97,9 +97,9 @@ try } $oP->add("
            \n"); - $oP->add("Expression to evaluate:
            \n"); + $oP->add(Dict::S('UI:RunQuery:ExpressionToEvaluate')."
            \n"); $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("\n"); if (!empty($sExpression)) @@ -113,22 +113,20 @@ try $oResultBlock->Display($oP, 1); $oP->p(''); - $oP->StartCollapsibleSection('More info on the query', false); - $oP->p('Query expression redevelopped: '.$oFilter->ToOQL()); - $oP->p('Serialized filter: '.$oFilter->serialize()); + $oP->StartCollapsibleSection(Dict::S('UI:RunQuery:MoreInfo'), false); + $oP->p(Dict::S('UI:RunQuery:DevelopedQuery').$oFilter->ToOQL()); + $oP->p(Dict::S('UI:RunQuery:SerializedFilter').$oFilter->serialize()); $oP->EndCollapsibleSection(); } } } catch(CoreException $e) { - $oP->p('An error occured while running the query:'); - $oP->p($e->getHtmlDesc()); + $oP->p(''.Dict::Format('UI:RunQuery:Error', $e->getHtmlDesc()).''); } catch(Exception $e) { - $oP->p('An error occured while running the query:'); - $oP->p($e->getMessage()); + $oP->p(''.Dict::Format('UI:RunQuery:Error', $e->getMessage()).''); } $oP->output(); From a28823141d1c324e44cd8aaba57e7580aadbf3ae Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 May 2010 17:14:44 +0000 Subject: [PATCH 317/970] - Implementation of the localization... on going... SVN:trunk[400] --- dictionaries/dictionary.itop.ui.php | 59 +++++++++++++ dictionaries/fr.dictionary.itop.ui.php | 59 +++++++++++++ pages/schema.php | 109 ++++++++++++------------- 3 files changed, 172 insertions(+), 55 deletions(-) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index f67481bba4..ee48ae9cdc 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -452,6 +452,65 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ', 'UI:RunQuery:SerializedFilter' => 'Serialized filter: ', 'UI:RunQuery:Error' => 'An error occured while running the query: %1$s', + + 'UI:Schema:Title' => 'iTop objects schema', + 'UI:Schema:CategoryMenuItem' => 'Category %1$s', + 'UI:Schema:Relationships' => 'Relationships', + 'UI:Schema:AbstractClass' => 'Abstract class: no object from this class can be instantiated.', + 'UI:Schema:NonAbstractClass' => 'Non abstract class: objects from this class can be instantiated.', + 'UI:Schema:ClassHierarchyTitle' => 'Class hierarchy', + 'UI:Schema:AllClasses' => 'All classes', + 'UI:Schema:ExternalKey_To' => 'External key to %1$s', + 'UI:Schema:Columns_Description' => 'Columns: %1$s', + 'UI:Schema:Default_Description' => 'Default: "%1$s"', + 'UI:Schema:NullAllowed' => 'Null Allowed', + 'UI:Schema:NullNotAllowed' => 'Null NOT Allowed', + 'UI:Schema:Attributes' => 'Attributes', + 'UI:Schema:AttributeCode' => 'Attribute Code', + 'UI:Schema:AttributeCode+' => 'Internal code of the attribute', + 'UI:Schema:Label' => 'Label', + 'UI:Schema:Label+' => 'Label of the attribute', + 'UI:Schema:Type' => 'Type', + 'UI:Schema:Type+' => 'Data type of the attribute', + 'UI:Schema:Origin' => 'Origin', + 'UI:Schema:Origin+' => 'The base class in which this attribute is defined', + 'UI:Schema:Description' => 'Description', + 'UI:Schema:Description+' => 'Description of the attribute', + 'UI:Schema:AllowedValues' => 'Allowed values', + 'UI:Schema:AllowedValues+' => 'Restrictions on the possible values for this attribute', + 'UI:Schema:MoreInfo' => 'More info', + 'UI:Schema:MoreInfo+' => 'More information about the field defined in the database', + 'UI:Schema:SearchCriteria' => 'Search criteria', + 'UI:Schema:FilterCode' => 'Filter code', + 'UI:Schema:FilterCode+' => 'Code of this search criteria', + 'UI:Schema:FilterDescription' => 'Description', + 'UI:Schema:FilterDescription+' => 'Description of this search criteria', + 'UI:Schema:AvailOperators' => 'Available operators', + 'UI:Schema:AvailOperators+' => 'Possible operators for this search criteria', + 'UI:Schema:ChildClasses' => 'Child classes', + 'UI:Schema:ReferencingClasses' => 'Referencing classes', + 'UI:Schema:RelatedClasses' => 'Related classes', + 'UI:Schema:LifeCycle' => 'Life cycle', + 'UI:Schema:Triggers' => 'Triggers', + 'UI:Schema:Relation_Code_Description' => 'Relation %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Down: %1$s', + 'UI:Schema:RelationUp_Description' => 'Up: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: propagate to %2$d levels, query: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: does not propagates (%2$d levels), query: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s is referenced by the class %2$s via the field %3$s', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s is linked to %2$s via %3$s::%4$s', + 'UI:Schema:Links:1-n' => 'Classes pointing to %1$s (1:n links):', + 'UI:Schema:Links:n-n' => 'Classes linked to %1$s (n:n links):', + 'UI:Schema:Links:All' => 'Graph of all related classes', + 'UI:Schema:NoLifeCyle' => 'There is no life cycle defined for this class.', + 'UI:Schema:LifeCycleTransitions' => 'Transitions', + 'UI:Schema:LifeCyleAttributeOptions' => 'Attribute options', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Hidden', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Read-only', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Mandatory', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Must change', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'User will be prompted to change the value', + 'UI:Schema:LifeCycleEmptyList' => 'empty list', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index d6cd276fac..bea05ca4db 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -453,6 +453,65 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:RunQuery:SerializedFilter' => 'Version sérialisée : ', 'UI:RunQuery:Error' => 'Une erreur s\'est produite durant l\'exécution de la requête: %1$s', + 'UI:Schema:Title' => 'Modèle de données iTop', + 'UI:Schema:CategoryMenuItem' => 'Catégorie %1$s', + 'UI:Schema:Relationships' => 'Relations', + 'UI:Schema:AbstractClass' => 'Class abstraite: les objets de cette classe ne peuvent pas être instanciés.', + 'UI:Schema:NonAbstractClass' => 'Class concrète: les objets de cette classe peuvent être instanciés.', + 'UI:Schema:ClassHierarchyTitle' => 'Hiérachie des classes', + 'UI:Schema:AllClasses' => 'Toutes les classes', + 'UI:Schema:ExternalKey_To' => 'Clef externe vers %1$s', + 'UI:Schema:Columns_Description' => 'Colonnes: %1$s', + 'UI:Schema:Default_Description' => 'Valeur par défaut: "%1$s"', + 'UI:Schema:NullAllowed' => 'Null autorisé', + 'UI:Schema:NullNotAllowed' => 'Null interdit', + 'UI:Schema:Attributes' => 'Attributs', + 'UI:Schema:AttributeCode' => 'Code', + 'UI:Schema:AttributeCode+' => 'Code interne de l\'attribut', + 'UI:Schema:Label' => 'Label', + 'UI:Schema:Label+' => 'Label de l\'attribut', + 'UI:Schema:Type' => 'Type', + 'UI:Schema:Type+' => 'Type de données de l\'attribut', + 'UI:Schema:Origin' => 'Origine', + 'UI:Schema:Origin+' => 'La classe de base dans laquelle l\'attribut est défini', + 'UI:Schema:Description' => 'Description', + 'UI:Schema:Description+' => 'Description de l\'attribut', + 'UI:Schema:AllowedValues' => 'Valeurs possibles', + 'UI:Schema:AllowedValues+' => 'Restrictions des valeurs possibles pour cet attribut', + 'UI:Schema:MoreInfo' => 'Plus info', + 'UI:Schema:MoreInfo+' => 'Plus d\'information à propos de la définition de ce champ dans la base de données', + 'UI:Schema:SearchCriteria' => 'Critères de recherche', + 'UI:Schema:FilterCode' => 'Code', + 'UI:Schema:FilterCode+' => 'Code de ce critère de recherche', + 'UI:Schema:FilterDescription' => 'Description', + 'UI:Schema:FilterDescription+' => 'Description de ce critère de recherche', + 'UI:Schema:AvailOperators' => 'Opérateurs', + 'UI:Schema:AvailOperators+' => 'Opérateurs possibles pour ce critère de recherche', + 'UI:Schema:ChildClasses' => 'Classes dérivées', + 'UI:Schema:ReferencingClasses' => 'Classes faisant référence', + 'UI:Schema:RelatedClasses' => 'Classes reliées', + 'UI:Schema:LifeCycle' => 'Cycle de vie', + 'UI:Schema:Triggers' => 'Déclencheurs', + 'UI:Schema:Relation_Code_Description' => 'Relation %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Sens descendant: %1$s', + 'UI:Schema:RelationUp_Description' => 'Sens montant: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: se propage sur %2$d niveau(x), requête: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: ne se propage pas (%2$d niveaux), requête: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s est référencé par la classe %2$s via le champ %3$s', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s est lié à la classe %2$s via %3$s::%4$s', + 'UI:Schema:Links:1-n' => 'Classes pointant sur %1$s (liens 1:n):', + 'UI:Schema:Links:n-n' => 'Classes liées à %1$s (liens n:n):', + 'UI:Schema:Links:All' => 'Graphe de toutes les classes liées', + 'UI:Schema:NoLifeCyle' => 'Aucun cycle de vie n\'est défini pour cette classe.', + 'UI:Schema:LifeCycleTransitions' => 'Transitions', + 'UI:Schema:LifeCyleAttributeOptions' => 'Options des attributs', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Caché', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Lecture seule', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Obligatoire', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Doit changer', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'L\'utilisateur se verra proposer de changer la valeur', + 'UI:Schema:LifeCycleEmptyList' => 'liste vide', + )); ?> diff --git a/pages/schema.php b/pages/schema.php index 2682e1c5f4..b777038475 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -61,7 +61,7 @@ function DisplayReferencingClasses($oPage, $sClass) { foreach ($aRemoteKeys as $sExtKeyAttCode => $oExtKeyAttDef) { - $oPage->add("
          • ".MakeClassHLink($sRemoteClass)." by $sExtKeyAttCode
          • \n"); + $oPage->add("
          • ".Dict::Format('UI:Schema:Class_ReferencingClasses_From_By', $sClass, MakeClassHLink($sRemoteClass), $sExtKeyAttCode)."
          • \n"); } } $oPage->add("\n"); @@ -82,7 +82,7 @@ function DisplayLinkingClasses($oPage, $sClass) { foreach($aRemoteClasses as $sExtKeyAttCode => $sRemoteClass) { - $oPage->add("
          • ".MakeClassHLink($sRemoteClass)." by ".MakeClassHLink($sLinkClass)."::$sExtKeyAttCode
          • \n"); + $oPage->add("
          • ".Dict::Format('UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute', $sClass, MakeClassHLink($sRemoteClass), MakeClassHLink($sLinkClass), $sExtKeyAttCode)); } } $oPage->add("\n"); @@ -133,16 +133,13 @@ function DisplayRelatedClassesBestInClass($oPage, $sClass, $iLevels = 20, &$aVis */ function DisplayRelatedClasses($oPage, $sClass) { - $oPage->add("

            Childs

            \n"); - DisplaySubclasses($oPage, $sClass); - - $oPage->add("

            Pointed to by...

            \n"); + $oPage->add("

            ".Dict::Format('UI:Schema:Links:1-n', $sClass)."

            \n"); DisplayReferencingClasses($oPage, $sClass); - $oPage->add("

            Linked to ...

            \n"); + $oPage->add("

            ".Dict::Format('UI:Schema:Links:n-n', $sClass)."

            \n"); DisplayLinkingClasses($oPage, $sClass); - $oPage->add("

            ZE Graph ...

            \n"); + $oPage->add("

            ".Dict::S('UI:Schema:Links:All')."

            \n"); DisplayRelatedClassesBestInClass($oPage, $sClass, 4); } @@ -154,14 +151,14 @@ function DisplayLifecycle($oPage, $sClass) $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); if (empty($sStateAttCode)) { - $oPage->p("no lifecycle for this class"); + $oPage->p(Dict::S('UI:Schema:NoLifeCyle')); } else { $aStates = MetaModel::EnumStates($sClass); $aStimuli = MetaModel::EnumStimuli($sClass); $oPage->add("\n"); - $oPage->add("

            Transitions

            \n"); + $oPage->add("

            ".Dict::S('UI:Schema:LifeCycleTransitions')."

            \n"); $oPage->add("
              \n"); foreach ($aStates as $sStateCode => $aStateDef) { @@ -187,7 +184,7 @@ function DisplayLifecycle($oPage, $sClass) } $oPage->add("
            \n"); - $oPage->add("

            Attribute options

            \n"); + $oPage->add("

            ".Dict::S('UI:Schema:LifeCyleAttributeOptions')."

            \n"); $oPage->add("
              \n"); foreach ($aStates as $sStateCode => $aStateDef) { @@ -203,11 +200,11 @@ function DisplayLifecycle($oPage, $sClass) $sAttLabel = $oAttDef->GetLabel(); $aOptions = array(); - if ($iOptions & OPT_ATT_HIDDEN) $aOptions[] = 'Hidden'; - if ($iOptions & OPT_ATT_READONLY) $aOptions[] = 'Read-only'; - if ($iOptions & OPT_ATT_MANDATORY) $aOptions[] = 'Mandatory'; - if ($iOptions & OPT_ATT_MUSTCHANGE) $aOptions[] = 'Must change'; - if ($iOptions & OPT_ATT_MUSTPROMPT) $aOptions[] = 'Must be proposed for changing'; + if ($iOptions & OPT_ATT_HIDDEN) $aOptions[] = Dict::S('UI:Schema:LifeCycleHiddenAttribute'); + if ($iOptions & OPT_ATT_READONLY) $aOptions[] = Dict::S('UI:Schema:LifeCycleReadOnlyAttribute'); + if ($iOptions & OPT_ATT_MANDATORY) $aOptions[] = Dict::S('UI:Schema:LifeCycleMandatoryAttribute'); + if ($iOptions & OPT_ATT_MUSTCHANGE) $aOptions[] = Dict::S('UI:Schema:LifeCycleAttributeMustChange'); + if ($iOptions & OPT_ATT_MUSTPROMPT) $aOptions[] = Dict::S('UI:Schema:LifeCycleAttributeMustPrompt'); if (count($aOptions)) { $sOptions = implode(', ', $aOptions); @@ -223,7 +220,7 @@ function DisplayLifecycle($oPage, $sClass) } else { - $oPage->p("empty list"); + $oPage->p("".Dict::S('UI:Schema:LifeCycleEmptyList').""); } } $oPage->add("
            \n"); @@ -246,7 +243,7 @@ function DisplayTriggers($oPage, $sClass) */ function DisplayClassesList($oPage) { - $oPage->add("

            iTop objects schema

            \n"); + $oPage->add("

            ".Dict::S('UI:Schema:Title')."

            \n"); $oPage->add("
              \n"); foreach(MetaModel::EnumCategories() as $sCategory) @@ -254,7 +251,7 @@ function DisplayClassesList($oPage) if (empty($sCategory)) continue; // means 'all' -> skip $sClosed = ($sCategory == 'bizmodel') ? '' : ' class="closed"'; - $oPage->add("Category $sCategory\n"); + $oPage->add("".Dict::Format('UI:Schema:CategoryMenuItem', $sCategory)."\n"); $oPage->add("
                \n"); foreach(MetaModel::GetClasses($sCategory) as $sClassName) @@ -277,7 +274,7 @@ function DisplayClassesList($oPage) $oPage->add("
              \n"); - $oPage->add("

              Relationships

              \n"); + $oPage->add("

              ".Dict::S('UI:Schema:Relationships')."

              \n"); $oPage->add("
                \n"); foreach (MetaModel::EnumRelations() as $sRelCode) @@ -304,14 +301,14 @@ function DisplayClassDetails($oPage, $sClass) $oPage->p("

                $sClass


                \n".MetaModel::GetClassDescription($sClass)."
                \n"); if (MetaModel::IsAbstract($sClass)) { - $oPage->p("Abstract class: could not be instantiated"); + $oPage->p(Dict::S('UI:Schema:AbstractClass')); } else { - $oPage->p("Not abstract: could be instantiated"); + $oPage->p(Dict::S('UI:Schema:NonAbstractClass')); } - $oPage->p("

                Class Hierarchy

                "); - $oPage->p("[All classes]"); + $oPage->p("

                ".Dict::S('UI:Schema:ClassHierarchyTitle')."

                "); + $oPage->p("[".Dict::S('UI:Schema:AllClasses')."]"); // List the parent classes $sParent = MetaModel::GetParentPersistentClass($sClass); $aParents = array(); @@ -345,7 +342,7 @@ function DisplayClassDetails($oPage, $sClass) { if ($oAttDef->IsExternalKey()) { - $sValue = "External key to ".MakeClassHLink($oAttDef->GetTargetClass()); + $sValue = Dict::Format('UI:Schema:ExternalKey_To',MakeClassHLink($oAttDef->GetTargetClass())); } else { @@ -367,9 +364,9 @@ function DisplayClassDetails($oPage, $sClass) $sCols = implode(', ', $aCols); $aMoreInfo = array(); - $aMoreInfo[] = "Column(s): $sCols"; - $aMoreInfo[] = "Default: '".$oAttDef->GetDefaultValue()."'"; - $aMoreInfo[] = $oAttDef->IsNullAllowed() ? "Null allowed" : "Null NOT allowed"; + $aMoreInfo[] = Dict::Format('UI:Schema:Columns_Description', $sCols); + $aMoreInfo[] = Dict::Format('UI:Schema:Default_Description', $oAttDef->GetDefaultValue()); + $aMoreInfo[] = $oAttDef->IsNullAllowed() ? Dict::S('UI:Schema:NullAllowed') : Dict::S('UI:Schema:NullNotAllowed'); $sMoreInfo .= implode(', ', $aMoreInfo); } @@ -378,14 +375,14 @@ function DisplayClassDetails($oPage, $sClass) $aDetails[] = array('code' => $oAttDef->GetCode(), 'type' => $sType, 'origin' => $sOrigin, 'label' => $oAttDef->GetLabel(), 'description' => $sValue, 'values' => $sAllowedValues, 'moreinfo' => $sMoreInfo); } - $oPage->SetCurrentTab('Attributes'); - $aConfig = array( 'code' => array('label' => 'Attribute code', 'description' => 'Code of this attribute'), - 'label' => array('label' => 'Label', 'description' => 'Label of this attribute'), - 'type' => array('label' => 'Type', 'description' => 'Data type of this attribute'), - 'origin' => array('label' => 'Origin', 'description' => 'The base class for this attribute'), - 'description' => array('label' => 'Description', 'description' => 'Description of this attribute'), - 'values' => array('label' => 'Allowed Values', 'description' => 'Restrictions on the possible values for this attribute'), - 'moreinfo' => array('label' => 'More info', 'description' => 'More info for the fields related to a Database field'), + $oPage->SetCurrentTab(Dict::S('UI:Schema:Attributes')); + $aConfig = array( 'code' => array('label' => Dict::S('UI:Schema:AttributeCode'), 'description' => Dict::S('UI:Schema:AttributeCode+')), + 'label' => array('label' => Dict::S('UI:Schema:Label'), 'description' => Dict::S('UI:Schema:Label+')), + 'type' => array('label' => Dict::S('UI:Schema:Type'), 'description' => Dict::S('UI:Schema:Type+')), + 'origin' => array('label' => Dict::S('UI:Schema:Origin'), 'description' => Dict::S('UI:Schema:Origin+')), + 'description' => array('label' => Dict::S('UI:Schema:Description'), 'description' => Dict::S('UI:Schema:Description+')), + 'values' => array('label' => Dict::S('UI:Schema:AllowedValues'), 'description' => Dict::S('UI:Schema:AllowedValues+')), + 'moreinfo' => array('label' => Dict::S('UI:Schema:MoreInfo'), 'description' => Dict::S('UI:Schema:MoreInfo+')), ); $oPage->table($aConfig, $aDetails); @@ -401,26 +398,26 @@ function DisplayClassDetails($oPage, $sClass) } $aDetails[] = array( 'code' => $sFilterCode, 'description' => $oFilterDef->GetLabel(),'operators' => implode(" / ", $aOpDescs)); } - $oPage->SetCurrentTab('Search criteria'); - $aConfig = array( 'code' => array('label' => 'Filter code', 'description' => 'Code of this search criteria'), - 'description' => array('label' => 'Description', 'description' => 'Description of this search criteria'), - 'operators' => array('label' => 'Available operators', 'description' => 'Possible operators for this search criteria') + $oPage->SetCurrentTab(Dict::S('UI:Schema:SearchCriteria')); + $aConfig = array( 'code' => array('label' => Dict::S('UI:Schema:FilterCode'), 'description' => Dict::S('UI:Schema:FilterCode+')), + 'description' => array('label' => Dict::S('UI:Schema:FilterDescription'), 'description' => Dict::S('UI:Schema:FilterDescription+')), + 'operators' => array('label' => Dict::S('UI:Schema:AvailOperators'), 'description' => Dict::S('UI:Schema:AvailOperators+')) ); $oPage->table($aConfig, $aDetails); - $oPage->SetCurrentTab('Child classes'); + $oPage->SetCurrentTab(Dict::S('UI:Schema:ChildClasses')); DisplaySubclasses($oPage, $sClass); - $oPage->SetCurrentTab('Referencing classes'); + $oPage->SetCurrentTab(Dict::S('UI:Schema:ReferencingClasses')); DisplayReferencingClasses($oPage, $sClass); - $oPage->SetCurrentTab('Related classes'); + $oPage->SetCurrentTab(Dict::S('UI:Schema:RelatedClasses')); DisplayRelatedClasses($oPage, $sClass); - $oPage->SetCurrentTab('Lifecycle'); + $oPage->SetCurrentTab(Dict::S('UI:Schema:LifeCycle')); DisplayLifecycle($oPage, $sClass); - $oPage->SetCurrentTab('Triggers'); + $oPage->SetCurrentTab(Dict::S('UI:Schema:Triggers')); DisplayTriggers($oPage, $sClass); $oPage->SetCurrentTab(); @@ -436,9 +433,9 @@ function DisplayRelationDetails($oPage, $sRelCode) $sDesc = MetaModel::GetRelationProperty($sRelCode, 'description'); $sVerbDown = MetaModel::GetRelationProperty($sRelCode, 'verb_down'); $sVerbUp = MetaModel::GetRelationProperty($sRelCode, 'verb_up'); - $oPage->add("

                Relation $sRelCode ($sDesc)

                "); - $oPage->p("Down: $sVerbDown"); - $oPage->p("Up: $sVerbUp"); + $oPage->add("

                ".Dict::Format('UI:Schema:Relation_Code_Description', $sRelCode, $sDesc)."

                "); + $oPage->p(Dict::Format('UI:Schema:RelationDown_Description', $sVerbDown)); + $oPage->p(Dict::Format('UI:Schema:RelationUp_Description', $sVerbUp)); $oPage->add("
                  \n"); foreach(MetaModel::GetClasses() as $sClass) @@ -451,10 +448,15 @@ function DisplayRelationDetails($oPage, $sRelCode) foreach ($aRelQueries as $sRelKey => $aQuery) { $sQuery = $aQuery['sQuery']; - $bPropagate = $aQuery['bPropagate'] ? "Propagate" : "Do not propagate"; $iDistance = $aQuery['iDistance']; - - $oPage->add("
                • $sRelKey: $bPropagate ($iDistance) ".$sQuery."
                • \n"); + if ($aQuery['bPropagate']) + { + $oPage->add("
                • ".Dict::Format('UI:Schema:RelationPropagates', $sRelKey, $iDistance, $sQuery)."
                • \n"); + } + else + { + $oPage->add("
                • ".Dict::Format('UI:Schema:RelationDoesNotPropagate', $sRelKey, $iDistance, $sQuery)."
                • \n"); + } } $oPage->add("
                \n"); $oPage->add("\n"); @@ -474,7 +476,7 @@ $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); $operation = utils::ReadParam('operation', ''); -$oPage = new iTopWebPage("iTop objects schema", $currentOrganization); +$oPage = new iTopWebPage(Dict::S('UI:Schema:Title'), $currentOrganization); $oPage->no_cache(); $operation = utils::ReadParam('operation', ''); @@ -491,9 +493,6 @@ switch($operation) DisplayRelationDetails($oPage, $sRelCode); break; - case 'details': - $oPage->p('operation=details has been deprecated, please use details_class'); - break; case 'list': default: DisplayClassesList($oPage); From aff20677fbebde7b82a0b5dbc1884e754ed9fb01 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 6 May 2010 09:01:21 +0000 Subject: [PATCH 318/970] - bug fix, found thanks to Eclipse syntax checking ! SVN:trunk[401] --- application/utils.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index ecf2d11c00..f5cb6021dc 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -51,7 +51,7 @@ class utils $sMimeType = $sType; } } - @finfo_close($finfo); + @finfo_close($rInfo); } $oDocument = new ormDocument($doc_content, $sMimeType, $_FILES[$sName]['name']); break; From 4e09d304f84ef8c310059e1331065fb24bc05a18 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 6 May 2010 10:44:38 +0000 Subject: [PATCH 319/970] - Implementation of the localization... on going... SVN:trunk[402] --- application/ui.linkswidget.class.inc.php | 33 +++++++++++++--------- application/uilinkswizard.class.inc.php | 24 ++++++++-------- application/uiwizard.class.inc.php | 10 +++---- application/utils.inc.php | 12 ++++---- dictionaries/dictionary.itop.ui.php | 36 +++++++++++++++++++++++- dictionaries/fr.dictionary.itop.ui.php | 31 ++++++++++++++++++++ 6 files changed, 109 insertions(+), 37 deletions(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index c419785874..a841cd695d 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -80,9 +80,9 @@ class UILinksWidget $sHTMLValue .= "\n"; $oPage->add_at_the_end($this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk')); // Forms should not be inside forms $oPage->add_at_the_end($this->GetLinkObjectDialog($oPage, $this->m_iInputId)); // Forms should not be inside forms - $sHTMLValue .= "m_iInputId}\" size=\"35\" value=\"\" title=\"Type the first 3 characters\"/>"; - $sHTMLValue .= "m_iInputId}\" value=\" Add... \" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; - $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; + $sHTMLValue .= "m_iInputId}\" size=\"35\" value=\"\" title=\"".Dict::S('UI:LinksWidget:Autocomplete+')."\"/>"; + $sHTMLValue .= "m_iInputId}\" value=\"".Dict::S('UI:Button:AddObject')."\" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; + $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/>"; // another hidden input to store & pass the object's Id $sHTMLValue .= "m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n"; $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n"; @@ -94,7 +94,7 @@ class UILinksWidget { // Few choices, use a normal 'select' $sHTMLValue = "
          • Audit Rule# Objects# Errors% Ok".Dict::S('UI:Audit:HeaderAuditRule')."".Dict::S('UI:Audit:HeaderNbObjects')."".Dict::S('UI:Audit:HeaderNbErrors')."".Dict::S('UI:Audit:PercentageOk')."
            -

            Selected objects:

            - +

            $sLabelSelectedObjects

            +

            -

            -

            +

            +

            -

            Available objects:

            - +

            $sLabelAvailableObjects

            +

            -       +      
            @@ -277,7 +284,7 @@ EOF; $sHTML = "
            \n"; $sHTML .= "
            \n"; - $sHTML .= "

            ".MetaModel::GetName($sLinkedClass)." attributes

            \n"; + $sHTML .= "

            ".Dict::Format('UI:Link_Class_Attributes', MetaModel::GetName($sLinkedClass))."

            \n"; $sHTML .= "
            \n"; $index = 0; $aAttrsMap = array(); @@ -318,7 +325,7 @@ EOF; } } $sHTML .= $oPage->GetDetails($aDetails); - $sHTML .= "     \n"; + $sHTML .= "     \n"; $sHTML .= "
            \n"; $sHTML .= "
            \n"; $sHTML .= "
            \n"; diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index 6bdb83ecfa..b58dca947b 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -19,7 +19,7 @@ class UILinksWizard $this->m_aEditableFields = array(); $this->m_aTableConfig = array(); - $this->m_aTableConfig['form::checkbox'] = array( 'label' => "", 'description' => "Select / Deselect All"); + $this->m_aTableConfig['form::checkbox'] = array( 'label' => "", 'description' => Dict::S('UI:SelectAllToggle+')); foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); @@ -46,7 +46,7 @@ class UILinksWizard } if (empty($this->m_sLinkedClass)) { - throw( new Exception("Incorrect link definition: the class of objects to manage: '$sLinkedClass' was not found as an external key in the class '$sClass'")); + throw( new Exception(Dict::Format('UI:Error:IncorrectLinkDefinition_LinkedClass_Class', $sLinkedClass, $sClass))); } foreach(MetaModel::GetZListItems($this->m_sLinkedClass, 'list') as $sFieldCode) { @@ -71,7 +71,7 @@ class UILinksWizard $oP->add("m_sLinkageAttr}\">\n"); $oP->add("m_iObjectId}\">\n"); $oP->add("m_sLinkingAttCode}\">\n"); - $oP->add("

            Manage ".MetaModel::GetName($this->m_sLinkedClass)."s linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetHyperlink()."

            \n"); + $oP->add("

            ".Dict::Format('UI:ManageObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "".$oTargetObj->GetHyperlink()."")."

            \n"); $oP->add("\n"); $oP->add("\n"); + $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); } + $aWizardSteps = $oWizard->GetWizardStructure(); + + // Display the structure of the wizard + $iStepIndex = 1; + $iMaxInputId = 0; + $aFieldsMap = array(); + foreach($aWizardSteps['mandatory'] as $aSteps) + { + $oP->SetCurrentTab("Step $iStepIndex *"); + $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, false /* no finish button */, $aArgs); + $iStepIndex++; + } + foreach($aWizardSteps['optional'] as $aSteps) + { + $oP->SetCurrentTab("Step $iStepIndex"); + $oWizard->DisplayWizardStep($aSteps, $iStepIndex, $iMaxInputId, $aFieldsMap, true, $aArgs); // true means enable the finish button + $iStepIndex++; + } + $oWizard->DisplayFinalStep($iStepIndex, $aFieldsMap); + + $oObj = null; + if (!empty($id)) + { + $oObj = $oContext->GetObject($sClass, $id); + } + if (!is_object($oObj)) + { + // new object or that can't be retrieved (corrupted id or object not allowed to this user) + $id = ''; + $oObj = MetaModel::NewObject($sClass); + } + $oP->add("\n"); break; case 'apply_modify': @@ -751,19 +729,19 @@ try $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! { - $oP->add("

            'class' and 'id' parameters must be specifed for this operation.

            \n"); + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } - else if (!utils::IsTransactionValid($sTransactionId)) + if (!utils::IsTransactionValid($sTransactionId)) { - $oP->p("Error: object has already be updated!\n"); + $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); } else { $oObj = $oContext->GetObject($sClass, $id); if ($oObj != null) { - $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel modification"); - $oP->add("

            ".$oObj->GetName()." - $sClassLabel modification

            \n"); + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); + $oP->add("

            ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

            \n"); $bObjectModified = false; foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) { @@ -815,7 +793,7 @@ try } if (!$bObjectModified) { - $oP->p("No modification detected. ".MetaModel::GetName(get_class($oObj))." has not been updated.\n"); + $oP->p(Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); } else if ($oObj->CheckToUpdate()) { @@ -823,7 +801,7 @@ try $oMyChange->Set("date", time()); if (UserRights::GetUser() != UserRights::GetRealUser()) { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { @@ -833,18 +811,17 @@ try $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); - $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); + $oP->p(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); } else { - $oP->p("Error: object can not be updated!\n"); - //$oObj->Reload(); // restore default values! + $oP->p("".Dict::S('UI:Error:ObjectCannotBeUpdated')."\n"); } } else { - $oP->set_title("iTop - Error"); - $oP->add("

            Sorry this object does not exist (or you are not allowed to edit it).

            \n"); + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); } } $oObj->DisplayDetails($oP); @@ -856,30 +833,26 @@ try $bSearchForm = utils::ReadParam('search_form', true); if (empty($sFilter)) { - $oP->set_title("iTop - Error"); - $oP->add("

            'filter' must be specifed for this operation.

            \n"); + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); } - else + $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); + $oP->add("

            ".Dict::S('UI:BulkDeleteTitle')."

            \n"); + // TO DO: limit the search filter by the user context + $oFilter = CMDBSearchFilter::unserialize($sFilter); // TO DO : check that the filter is valid + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) { - $oP->set_title("iTop - mass delete"); - $oP->add("

            Select the objects you want to delete

            \n"); - // TO DO: limit the search filter by the user context - $oFilter = CMDBSearchFilter::unserialize($sFilter); // TO DO : check that the filter is valid - $oSet = new DBObjectSet($oFilter); - if ($bSearchForm) - { - $oBlock = new DisplayBlock($oFilter, 'search', false); - $oBlock->Display($oP, 0); - } - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("GetClass()."\">\n"); - $oP->add("\n"); - $oBlock->Display($oP, 1, array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false)); - $oP->add("  >\">\n"); - $oP->add("
            \n"); + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0); } + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("GetClass()."\">\n"); + $oP->add("\n"); + $oBlock->Display($oP, 1, array('selection_type' => 'multiple', 'selection_mode' => true, 'display_limit' => false)); + $oP->add("  \n"); + $oP->add("
            \n"); break; case 'bulk_delete': @@ -891,11 +864,11 @@ try $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if ( empty($sClass) || empty($aSelectObject)) // TO DO: check that the class name is valid ! { - throw new ApplicationException('Error: \'class\' and \'selectObject[]\' parameters must be specifed for this operation.'); + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObject[]')); } if (!utils::IsTransactionValid($sTransactionId)) { - throw new ApplicationException('Error: objects have already been deleted!'); + throw new ApplicationException(Dict::S('UI:Error:ObjectsAlreadyDeleted')); } foreach($aSelectObject as $iId) { @@ -903,8 +876,9 @@ try } if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) { - throw new SecurityException('You are not allowed to perform a bulk delete of objects of class '.$sClass); + throw new SecurityException(Dict::S('UI:Error:BulkDeleteNotAllowedOn_Class'), $sClass); } + $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); DeleteObjects($oP, $sClass, $aObjects, ($operation == 'bulk_delete_confirmed')); break; @@ -917,16 +891,11 @@ try if (!UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj))) { - throw new SecurityException('You are not allowed to do delete objects of class '.$sClass); + throw new SecurityException(Dict::S('UI:Error:DeleteNotAllowedOn_Class'), $sClass); } DeleteObjects($oP, $sClass, array($oObj), ($operation == 'delete_confirmed')); break; - case 'apply_new': - $oP->p('Creation of the object'); - $oP->p('Obsolete, should now go through the wizard...'); - break; - case 'apply_clone': $sClass = utils::ReadPostedParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); @@ -934,7 +903,7 @@ try $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if (!utils::IsTransactionValid($sTransactionId)) { - $oP->p("Error: object has already be cloned!\n"); + $oP->p(Dict::S('UI:Error:ObjectAlreadyCloned')); } else { @@ -943,7 +912,7 @@ try $oMyChange->Set("date", time()); if (UserRights::GetUser() != UserRights::GetRealUser()) { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { @@ -961,7 +930,8 @@ try } } $oObj->DBCloneTracked($oMyChange); - $oP->add("

            ".$oObj->GetName()." - $sClassLabel created

            \n"); + $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); + $oP->add("

            ".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

            \n"); $oObj->DisplayDetails($oP); } @@ -973,7 +943,7 @@ try $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if (!utils::IsTransactionValid($sTransactionId)) { - $oP->p("Error: object has already be created!\n"); + $oP->p(Dict::S('UI:Error:ObjectAlreadyCreated')); } else { @@ -986,7 +956,7 @@ try $oMyChange->Set("date", time()); if (UserRights::GetUser() != UserRights::GetRealUser()) { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { @@ -995,8 +965,8 @@ try $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); - $oP->set_title("iTop - ".$oObj->GetName()." - $sClassLabel created"); - $oP->add("

            ".$oObj->GetName()." - $sClassLabel created

            \n"); + $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); + $oP->add("

            ".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

            \n"); $oObj->DisplayDetails($oP); } } @@ -1008,75 +978,67 @@ try $sStimulus = utils::ReadParam('stimulus', ''); if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! { - $oP->add("

            'class', 'id' and 'stimulus' parameters must be specifed for this operation.

            \n"); + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); + } + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) + { + // Invalid stimulus + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); + } + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $aTransition = $aTransitions[$sStimulus]; + $sTargetState = $aTransition['target_state']; + $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add("
            \n"); + $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); + $oP->add("
            \n"); + $oObj->DisplayBareDetails($oP); + $aTargetState = $aTargetStates[$sTargetState]; + $aExpectedAttributes = $aTargetState['attribute_list']; + $oP->add("
            \n"); + $oP->add("

            $sActionDetails

            \n"); + $oP->add("
            \n"); + $oP->add("
            \n"); + $aDetails = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) + { + $aAttributesDef = MetaModel::ListAttributeDefs($sClass); + $oAttDef = $aAttributesDef[$sAttCode]; + $aArgs = array('this' => $oObj); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), '', '', $iExpectCode, $aArgs); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + } + } + $oP->details($aDetails); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add($oAppContext->GetForForm()); + $oP->add("    \n"); + $oP->add("\n"); + $oP->add("
            \n"); + $oP->add("
            \n"); + $oP->add("
            \n"); } else { - $oObj = $oContext->GetObject($sClass, $id); - if ($oObj != null) - { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - if (!isset($aTransitions[$sStimulus])) - { - $oP->add("

            Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()}.

            \n"); - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $aTransition = $aTransitions[$sStimulus]; - $sTargetState = $aTransition['target_state']; - $aTargetStates = MetaModel::EnumStates($sClass); - $oP->add("
            \n"); - $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); - //$oP->add("

            Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()} to target state: $sTargetState.

            \n"); - $oP->add("
            \n"); - $oObj->DisplayBareDetails($oP); - $aTargetState = $aTargetStates[$sTargetState]; - //print_r($aTransitions[$sStimulus]); - //print_r($aTargetState); - $aExpectedAttributes = $aTargetState['attribute_list']; - $oP->add("
            \n"); - $oP->add("

            $sActionDetails

            \n"); - $oP->add("
            \n"); - $oP->add("
            \n"); - $aDetails = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - // Prompt for an attribute if - // - the attribute must be changed or must be displayed to the user for confirmation - // - or the field is mandatory and currently empty - if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || - (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) - { - $aAttributesDef = MetaModel::ListAttributeDefs($sClass); - $oAttDef = $aAttributesDef[$sAttCode]; - $aArgs = array('this' => $oObj); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), '', '', $iExpectCode, $aArgs); - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - } - } - $oP->details($aDetails); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add($oAppContext->GetForForm()); - $oP->add("    \n"); - $oP->add("\n"); - $oP->add("
            \n"); - $oP->add("
            \n"); - $oP->add("
            \n"); - } - } - else - { - $oP->set_title("iTop - Error"); - $oP->add("

            Sorry this object does not exist (or you are not allowed to edit it).

            \n"); - } - } + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } break; case 'apply_stimulus': @@ -1086,75 +1048,69 @@ try $sStimulus = utils::ReadPostedParam('stimulus', ''); if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! { - $oP->add("

            'class', 'id' and 'stimulus' parameters must be specifed for this operation.

            \n"); + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); } - else + $oObj = $oContext->GetObject($sClass, $id); + if ($oObj != null) { - $oObj = $oContext->GetObject($sClass, $id); - if ($oObj != null) + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - if (!isset($aTransitions[$sStimulus])) - { - $oP->add("

            Error: Invalid stimulus: '$sStimulus' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()}.

            \n"); - } - else if (!utils::IsTransactionValid($sTransactionId)) - { - $oP->p("Error: object has already been updated!\n"); - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $aTransition = $aTransitions[$sStimulus]; - $sTargetState = $aTransition['target_state']; - $aTargetStates = MetaModel::EnumStates($sClass); - $oP->add("
            \n"); - $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); - $oP->add("

            $sActionDetails

            \n"); - $oP->add("

            Applying '$sActionLabel' on object: {$oObj->GetName()} in state {$oObj->GetStateLabel()} to target state: $sTargetState.

            \n"); - $oP->add("
            \n"); - $aTargetState = $aTargetStates[$sTargetState]; - //print_r($aTransitions[$sStimulus]); - //print_r($aTargetState); - $aExpectedAttributes = $aTargetState['attribute_list']; - $aDetails = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - if (($iExpectCode & OPT_ATT_MUSTCHANGE) || ($oObj->Get($sAttCode) == '') ) - { - $paramValue = utils::ReadPostedParam("attr_$sAttCode", ''); - $oObj->Set($sAttCode, $paramValue); - } - } - if ($oObj->ApplyStimulus($sStimulus) && $oObj->CheckToUpdate()) - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) - { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); - } - else - { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); - - $oP->p(MetaModel::GetName(get_class($oObj))." updated.\n"); - } - $oObj->DisplayDetails($oP); - } + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); + } + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->p(Dict::S('UI:Error:ObjectAlreadyUpdated')); } else { - $oP->set_title("iTop - Error"); - $oP->add("

            Sorry this object does not exist (or you are not allowed to edit it).

            \n"); - } + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $aTransition = $aTransitions[$sStimulus]; + $sTargetState = $aTransition['target_state']; + $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add("
            \n"); + $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); + $oP->add("

            $sActionDetails

            \n"); + $oP->p(Dict::Format('UI:Apply_Stimulus_On_Object_In_State_ToTarget_State', $sACtionLabel, $oObj->GetName(), $oObj->GetStateLabel(), $sTargetState)); + $oP->add("
            \n"); + $aTargetState = $aTargetStates[$sTargetState]; + $aExpectedAttributes = $aTargetState['attribute_list']; + $aDetails = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + if (($iExpectCode & OPT_ATT_MUSTCHANGE) || ($oObj->Get($sAttCode) == '') ) + { + $paramValue = utils::ReadPostedParam("attr_$sAttCode", ''); + $oObj->Set($sAttCode, $paramValue); + } + } + if ($oObj->ApplyStimulus($sStimulus) && $oObj->CheckToUpdate()) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::GetUser() != UserRights::GetRealUser()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + $oP->p(Dict::Format('UI:Class_Object_Updated'), get_class($oObj), $oObj->GetName()); + } + $oObj->DisplayDetails($oP); + } } + else + { + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } break; case 'modify_links': @@ -1165,15 +1121,11 @@ try $bAddObjects = utils::ReadParam('addObjects', false); if ( empty($sClass) || empty($id) || empty($sLinkAttr) || empty($sTargetClass)) // TO DO: check that the class name is valid ! { - $oP->set_title("iTop - Error"); - $oP->add("

            4 parameters are mandatory for this operation: class, id, target_class and link_attr.

            \n"); - } - else - { - require_once('../application/uilinkswizard.class.inc.php'); - $oWizard = new UILinksWizard($sClass, $sLinkAttr, $id, $sTargetClass); - $oWizard->Display($oP, $oContext, array('StartWithAdd' => $bAddObjects)); + throw new ApplicationException(Dict::Format('UI:Error:4ParametersMissing', 'class', 'id', 'target_class', 'link_attr')); } + require_once('../application/uilinkswizard.class.inc.php'); + $oWizard = new UILinksWizard($sClass, $sLinkAttr, $id, $sTargetClass); + $oWizard->Display($oP, $oContext, array('StartWithAdd' => $bAddObjects)); break; case 'do_modify_links': @@ -1192,7 +1144,7 @@ try $oMyChange->Set("date", time()); if (UserRights::GetUser() != UserRights::GetRealUser()) { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { @@ -1305,9 +1257,9 @@ try catch(CoreException $e) { require_once('../setup/setuppage.class.inc.php'); - $oP = new SetupWebPage('iTop - fatal error'); - $oP->add("

            Fatal Error, iTop cannot continue

            \n"); - $oP->error("Error: '".$e->getHtmlDesc()."'"); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

            ".Dict::S('UI:FatalErrorMessage')."

            \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); $oP->output(); if (MetaModel::IsLogEnabledIssue()) @@ -1334,9 +1286,9 @@ catch(CoreException $e) catch(Exception $e) { require_once('../setup/setuppage.class.inc.php'); - $oP = new SetupWebPage('iTop - fatal error'); - $oP->add("

            Fatal Error, iTop cannot continue

            \n"); - $oP->error("Error: '".$e->getMessage()."'"); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

            ".Dict::S('UI:FatalErrorMessage')."

            \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); $oP->output(); if (MetaModel::IsLogEnabledIssue()) diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php index e5ca283929..28a8e10368 100644 --- a/pages/usermanagement_classproj.php +++ b/pages/usermanagement_classproj.php @@ -32,8 +32,8 @@ function ComputeProjections($oPage, $sScope) // Setup display structure // $aDisplayConfig = array(); - $aDisplayConfig['class'] = array('label' => 'Class', 'description' => 'Class'); - $aDisplayConfig['object'] = array('label' => 'Object', 'description' => 'Projected object'); + $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); + $aDisplayConfig['object'] = array('label' => Dict::S('UI:UserManagement:ProjectedObject'), 'description' => Dict::S('UI:UserManagement:ProjectedObject+')); foreach ($aDimensions as $iDimension => $oDimension) { $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); @@ -57,7 +57,7 @@ function ComputeProjections($oPage, $sScope) $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); if (is_null($aValues)) { - $sValues = htmlentities(''); + $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); } else { @@ -88,7 +88,7 @@ $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); $sScope = utils::ReadParam('scope', 'SELECT bizDevice'); -$oPage = new iTopWebPage("iTop user management - class projections", $currentOrganization); +$oPage = new iTopWebPage(Dict::S('UI:PageTitle:ClassProjections'), $currentOrganization); $oPage->no_cache(); ComputeProjections($oPage, $sScope); diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index a5cfb2e94f..a9475330e4 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -37,8 +37,8 @@ function ComputeProjections($oPage) // Setup display structure // $aDisplayConfig = array(); - $aDisplayConfig['user'] = array('label' => 'User', 'description' => 'User concerned by the projection'); - $aDisplayConfig['profile'] = array('label' => 'Profile', 'description' => 'Profile in which the projection is specified'); + $aDisplayConfig['user'] = array('label' => Dict::S('UI:UserManagement:User'), 'description' => Dict::S('UI:UserManagement:User+')); + $aDisplayConfig['profile'] = array('label' => Dict::S('UI:UserManagement:Profile'), 'description' => Dict::S('UI:UserManagement:Profile+')); foreach ($aDimensions as $iDimension => $oDimension) { $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); @@ -67,7 +67,7 @@ function ComputeProjections($oPage) $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); if (is_null($aValues)) { - $sValues = htmlentities(''); + $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); } else { @@ -98,7 +98,7 @@ $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); -$oPage = new iTopWebPage("iTop user management - profile projections", $currentOrganization); +$oPage = new iTopWebPage(Dict::S('UI:PageTitle:ProfileProjections'), $currentOrganization); $oPage->no_cache(); ComputeProjections($oPage); From e22393404f03316358f1782015bbaef4094e4d13 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 15 May 2010 08:44:36 +0000 Subject: [PATCH 324/970] - Implementation of the localization... on going... SVN:trunk[407] --- addons/userrights/userrightsprofile.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 14c800db5e..fb827dd549 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -149,7 +149,7 @@ class URP_Users extends UserRightsBaseClass $oPage->SetCurrentTabContainer('Related Objects'); - $oPage->SetCurrentTab('Grants matrix'); + $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); $this->DoShowGrantSumary($oPage, 'bizmodel'); // debug From 01539af632c28b934126a72b468c6e0e4b713c7a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 15 May 2010 14:51:29 +0000 Subject: [PATCH 325/970] New implementation of the "Actions" Javascript context menu. Simpler, easier to customize and more robust. SVN:trunk[408] --- application/displayblock.class.inc.php | 6 +- application/itopwebpage.class.inc.php | 3 +- css/light-grey.css | 183 +++----- js/jquery.jdMenu.js | 556 +++++++------------------ js/jquery.popupmenu.js | 62 +++ js/jquery.positionBy.js | 306 ++++++++++++++ 6 files changed, 586 insertions(+), 530 deletions(-) create mode 100644 js/jquery.popupmenu.js create mode 100644 js/jquery.positionBy.js diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 388b679aba..7978791458 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -954,14 +954,14 @@ class MenuBlock extends DisplayBlock if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "../pages/$sUIPage?operation=select_for_deletion&filter=$sFilter&$sContext"); } } } - $sHtml .= "
              \n
            • ".Dict::S('UI:Menu:Actions')."\n
                \n"; + $sHtml .= "
                  \n
                • ".Dict::S('UI:Menu:Actions')."\n
                    \n"; foreach ($aActions as $aAction) { $sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : ""; - $sHtml .= "
                  • {$aAction['label']}
                  • \n
                  • \n"; + $sHtml .= "
                  • {$aAction['label']}
                  • \n"; } $sHtml .= "
                  \n
                • \n
                \n"; - $oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n"); + $oPage->add_ready_script("$(\"div.itop_popup>ul\").popupmenu();\n"); return $sHtml; } } diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index b5758307d1..a4e64148f8 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -31,7 +31,8 @@ class iTopWebPage extends NiceWebPage $this->add_linked_script("../js/jquery.treeview.js"); $this->add_linked_script("../js/jquery.autocomplete.js"); $this->add_linked_script("../js/jquery.bgiframe.js"); - $this->add_linked_script("../js/jquery.jdMenu.js"); + $this->add_linked_script("../js/jquery.positionBy.js"); + $this->add_linked_script("../js/jquery.popupmenu.js"); $this->add_linked_script("../js/date.js"); $this->add_linked_script("../js/jquery.date.picker.js"); $this->add_linked_script("../js/jquery.tablesorter.min.js"); diff --git a/css/light-grey.css b/css/light-grey.css index 5ac6a419f4..e48fa60fc8 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -354,131 +354,82 @@ div.iTopLogo span { padding:5px 0px 16px 20px; } -/* jdMenu popup menus */ -ul.jd_menu, -ul.jd_menu_vertical { - margin: 0px; - padding: 0px; - list-style-type: none; -} -ul.jd_menu ul, -ul.jd_menu_vertical ul { - display: none; -} -ul.jd_menu li { - float: left; -} - -/* -- Sub-Menus -- */ -ul.jd_menu ul, -ul.jd_menu_vertical ul { - position: absolute; - display: none; - list-style-type: none; - margin: 0px; - padding: 0px; - z-index: 10000; -} -ul.jd_menu ul li, -ul.jd_menu_vertical ul li { - float: none; - margin: 0px; -} - -/* jdMenu popup menus styling */ -div.jd_menu_itop { - height:19px; +/* popup menus */ +div.itop_popup { + margin: 0; + padding: 0; float:right; - display:inline; +} + +div.itop_popup > ul { + height:19px; + display:block; width:70px; /* Nasty work-around for IE... en attendant mieux */ padding-left: 5px; - background: url(../images/actions_left.png) no-repeat left; + background: url(../images/actions_left.png) no-repeat top left; + cursor: pointer; } -ul.jd_menu_itop { +div.itop_popup > ul > li { + float: left; + list-style: none; + font-size: 11px; + font-family: Tahoma,sans-serif; height: 19px; - padding-right:16px; - background: url(../images/actions_right.png) no-repeat right; + padding-right: 16px; + padding-left: 4px; + background: url(../images/actions_right.png) no-repeat top right transparent; + font-weight: bold; + color: #fff; + vertical-align: middle; +} + +.itop_popup li a { + display: block; + padding: 5px 12px; + text-decoration: none; + noborder: 1px solid white; + width: 70px; + color: #000; + font-weight: bold; + white-space: nowrap; + background: #fff; +} + +.itop_popup li a:hover { + background: #1A4473; +} + +.itop_popup ul > li > ul +{ + border: 1px solid black; + background: #fff; +} + +.itop_popup li > ul +{ margin: 0; + padding: 0; + position: absolute; + display: none; + border-top: 1px solid white; +} + +.itop_popup li ul li { + float: none; + display: inline; +} + +.itop_popup li ul li a { + width: auto; +} + +.itop_popup li ul li a:hover { + background: #D81515; + color: #fff; font-weight: bold; } -ul.jd_menu_vertical { - width: 200px; - height: auto; - clear: both; - background: url(gradient-vertical.png) repeat-x; - background-color: #A5AFB8; -} - - -ul.jd_menu_itop a, -ul.jd_menu_itop a:active, -ul.jd_menu_itop a:link, -ul.jd_menu_itop a:visited { - text-decoration: none; - color: #FFF; -} -ul.jd_menu_itop ul li a, -ul.jd_menu_itop ul li a:active, -ul.jd_menu_itop ul li a:link, -ul.jd_menu_itop ul li a:visited { - color: #000; -} -ul.jd_menu_itop li { - font-family: Tahoma, sans-serif; - font-size: 11px; - padding: 2px 6px 4px 6px; - cursor: pointer; - white-space: nowrap; - color: #FFF; -} -ul.jd_menu_itop li.jd_menu_active_menubar, -ul.jd_menu_itop li.jd_menu_hover_menubar { - color: #FFF; -} - -ul.jd_menu_vertical li.jd_menu_active_menubar, -ul.jd_menu_vertical li.jd_menu_hover_menubar { - padding-left: 6px; - padding-top: 1px; - border-top: 1px solid #70777D; - border-left: 0px; - border-right: 0px; -} - -ul.jd_menu_itop ul { - background: #d81515; - border: 1px solid #70777D; -} -ul.jd_menu_itop ul li { - padding: 3px 10px 3px 4px; - background: #FFF; - border: none; - color: #000; -} -ul.jd_menu_itop ul li.jd_menu_active, -ul.jd_menu_itop ul li.jd_menu_hover { - background: #d81515; - padding-top: 2px; - border-top: 1px solid #ABB5BC; - padding-bottom: 2px; - border-bottom: 1px solid #929AA1; - color: #FFF; -} - -ul.jd_menu_itop li ul li a { - padding: 0; - background: #fff; - padding-left:0; -} - -ul.jd_menu_itop ul li.jd_menu_active a.jd_menu_active, -ul.jd_menu_itop ul li.jd_menu_hover a.jd_menu_hover { - color: #FFF; - background: #d81515; - padding-left:0; -} - +/************************************/ .wizHeader { background: #83b217; padding: 15px; diff --git a/js/jquery.jdMenu.js b/js/jquery.jdMenu.js index 40467a1df0..3b59ceda87 100644 --- a/js/jquery.jdMenu.js +++ b/js/jquery.jdMenu.js @@ -1,5 +1,5 @@ /* - * jdMenu 1.3.beta2 (2007-03-06) + * jdMenu 1.4.1 (2008-03-31) * * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us) * Dual licensed under the MIT (MIT-LICENSE.txt) @@ -7,429 +7,165 @@ * * http://jdsharp.us/ * - * Built upon jQuery 1.1.1 (http://jquery.com) - * This also requires the jQuery dimensions plugin + * Built upon jQuery 1.2.1 (http://jquery.com) + * This also requires the jQuery dimensions >= 1.2 plugin */ + +// This initializes the menu +$(function() { + $('ul.jd_menu').jdMenu(); +}); + (function($){ - // This will store an element list of all our menu objects - var jdMenu = []; + function addEvents(ul) { + var settings = $.data( $(ul).parents().andSelf().filter('ul.jd_menu')[0], 'jdMenuSettings' ); + $('> li', ul) + .bind('mouseenter.jdmenu mouseleave.jdmenu', function(evt) { + $(this).toggleClass('jdm_hover'); + var ul = $('> ul', this); + if ( ul.length == 1 ) { + clearTimeout( this.$jdTimer ); + var enter = ( evt.type == 'mouseenter' ); + var fn = ( enter ? showMenu : hideMenu ); + this.$jdTimer = setTimeout(function() { + fn( ul[0], settings.onAnimate, settings.isVertical ); + }, enter ? settings.showDelay : settings.hideDelay ); + } + }) + .bind('click.jdmenu', function(evt) { + var ul = $('> ul', this); + if ( ul.length == 1 && + ( settings.disableLinks == true || $(this).hasClass('accessible') ) ) { + showMenu( ul, settings.onAnimate, settings.isVertical ); + return false; + } + + // The user clicked the li and we need to trigger a click for the a + if ( evt.target == this ) { + var link = $('> a', evt.target).not('.accessible'); + if ( link.length > 0 ) { + var a = link[0]; + if ( !a.onclick ) { + window.open( a.href, a.target || '_self' ); + } else { + $(a).trigger('click'); + } + } + } + if ( settings.disableLinks || + ( !settings.disableLinks && !$(this).parent().hasClass('jd_menu') ) ) { + $(this).parent().jdMenuHide(); + evt.stopPropagation(); + } + }) + .find('> a') + .bind('focus.jdmenu blur.jdmenu', function(evt) { + var p = $(this).parents('li:eq(0)'); + if ( evt.type == 'focus' ) { + p.addClass('jdm_hover'); + } else { + p.removeClass('jdm_hover'); + } + }) + .filter('.accessible') + .bind('click.jdmenu', function(evt) { + evt.preventDefault(); + }); + } + + function showMenu(ul, animate, vertical) { + var ul = $(ul); + if ( ul.is(':visible') ) { + return; + } + ul.bgiframe(); + var li = ul.parent(); + ul .trigger('jdMenuShow') + .positionBy({ target: li[0], + targetPos: ( vertical === true || !li.parent().hasClass('jd_menu') ? 1 : 3 ), + elementPos: 0, + hideAfterPosition: true + }); + if ( !ul.hasClass('jdm_events') ) { + ul.addClass('jdm_events'); + addEvents(ul); + } + li .addClass('jdm_active') + // Hide any adjacent menus + .siblings('li').find('> ul:eq(0):visible') + .each(function(){ + hideMenu( this ); + }); + if ( animate === undefined ) { + ul.show(); + } else { + animate.apply( ul[0], [true] ); + } + } + + function hideMenu(ul, animate) { + var ul = $(ul); + $('.bgiframe', ul).remove(); + ul .filter(':not(.jd_menu)') + .find('> li > ul:eq(0):visible') + .each(function() { + hideMenu( this ); + }) + .end(); + if ( animate === undefined ) { + ul.hide() + } else { + animate.apply( ul[0], [false] ); + } + + ul .trigger('jdMenuHide') + .parents('li:eq(0)') + .removeClass('jdm_active jdm_hover') + .end() + .find('> li') + .removeClass('jdm_active jdm_hover'); + } // Public methods - $.fn.jdMenu = function(inSettings) { - var settings = $.extend({}, arguments.callee.defaults, inSettings); - return this.each(function() { - jdMenu.push(this); - $(this).addClass('jd_menu_flag_root'); - this.$settings = $.extend({}, settings, {isVerticalMenu: $(this).is('.jd_menu_vertical')}); + $.fn.jdMenu = function(settings) { + // Future settings: activateDelay + var settings = $.extend({ // Time in ms before menu shows + showDelay: 200, + // Time in ms before menu hides + hideDelay: 500, + // Should items that contain submenus not + // respond to clicks + disableLinks: true + // This callback allows for you to animate menus + //onAnimate: null + }, settings); + if ( !$.isFunction( settings.onAnimate ) ) { + settings.onAnimate = undefined; + } + return this.filter('ul.jd_menu').each(function() { + $.data( this, + 'jdMenuSettings', + $.extend({ isVertical: $(this).hasClass('jd_menu_vertical') }, settings) + ); addEvents(this); }); }; - $.fn.jdMenuShow = function() { - return this.each(function() { - showMenuLI.apply(this); - }); + + $.fn.jdMenuUnbind = function() { + $('ul.jdm_events', this) + .unbind('.jdmenu') + .find('> a').unbind('.jdmenu'); }; $.fn.jdMenuHide = function() { - return this.each(function() { - hideMenuUL.apply(this); + return this.filter('ul').each(function(){ + hideMenu( this ); }); }; // Private methods and logic $(window) // Bind a click event to hide all visible menus when the document is clicked - .bind('click', function(){ - $(jdMenu).find('ul:visible').jdMenuHide(); - }) - // Cleanup after ourself by nulling the $settings object - .bind('unload', function() { - $(jdMenu).each(function() { - this.$settings = null; - }); + .bind('click.jdmenu', function(){ + $('ul.jd_menu ul:visible').jdMenuHide(); }); - - // These are our default settings for this plugin - $.fn.jdMenu.defaults = { - activateDelay: 750, - showDelay: 150, - hideDelay: 550, - onShow: null, - onHideCheck: null, - onHide: null, - onAnimate: null, - onClick: null, - offsetX: 4, - offsetY: 2, - iframe: $.browser.msie - }; - - // Our special parentsUntil method to get all parents up to and including the matched element - $.fn.parentsUntil = function(match) { - var a = []; - $(this[0]).parents().each(function() { - a.push(this); - return !$(this).is(match); - }); - return this.pushStack(a, arguments); - }; - - // Returns our settings object for this menu - function getSettings(el) { - return $(el).parents('ul.jd_menu_flag_root')[0].$settings; - } - - // Unbind any events and then rebind them - function addEvents(ul) { - removeEvents(ul); - $('> li', ul) - .hover(hoverOverLI, hoverOutLI) - .bind('click', itemClick) - .find('> a.accessible') - .bind('click', accessibleClick); - }; - - // Remove all events for this menu - function removeEvents(ul) { - $('> li', ul) - .unbind('mouseover').unbind('mouseout') - .unbind('click') - .find('> a.accessible') - .unbind('click'); - }; - - function hoverOverLI() { - var cls = 'jd_menu_hover' + ($(this).parent().is('.jd_menu_flag_root') ? '_menubar' : ''); - $(this).addClass(cls).find('> a').addClass(cls); - - if (this.$timer) { - clearTimeout(this.$timer); - } - - // Do we have a sub menu? - if ($('> ul', this).size() > 0) { - var settings = getSettings(this); - - // Which delay to use, the longer activate one or the shorter show delay if a menu is already visible - var delay = ($(this).parents('ul.jd_menu_flag_root').find('ul:visible').size() == 0) - ? settings.activateDelay : settings.showDelay; - var t = this; - this.$timer = setTimeout(function() { - showMenuLI.apply(t); - }, delay); - } - }; - - function hoverOutLI() { - // Remove both classes so we do not have to test which one we are - $(this) .removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar') - .find('> a') - .removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar'); - - if (this.$timer) { - clearTimeout(this.$timer); - } - - // TODO: Possible bug with our test for visibility in that parent menus are hidden child menus are not - - // If we have a visible menu, hide it - if ($(this).is(':visible') && $('> ul', this).size() > 0) { - var settings = getSettings(this); - var ul = $('> ul', this)[0]; - this.$timer = setTimeout(function() { - hideMenuUL.apply(ul); - }, settings.hideDelay); - } - }; - - // "this" is a reference to the LI element that contains - // the UL that will be shown - function showMenuLI() { - var ul = $('> ul', this).get(0); - // We are already visible, just return - if ($(ul).is(':visible')) { - return false; - } - - // Clear our timer if it exists - if (this.$timer) { - clearTimeout(this.$timer); - } - - // Get our settings object - var settings = getSettings(this); - - // Call our callback - if (settings.onShow != null && settings.onShow.apply(this) == false) { - return false; - } - - // Add hover classes, needed for accessible functionality - var isRoot = $(this).parent().is('.jd_menu_flag_root'); - var c = 'jd_menu_active' + (isRoot ? '_menubar' : ''); - $(this).addClass(c).find('> a').addClass(c); - - if (!isRoot) { - // Add the active class to the parent list item which maybe our menubar - var c = 'jd_menu_active' + ($(this).parent().parent().parent().is('.jd_menu_flag_root') ? '_menubar' : ''); - $(this).parent().parent().addClass(c).find('> a').addClass(c); - } - - // Hide any existing menues at the same level - $(this).parent().find('> li > ul:visible').not(ul).each(function() { - hideMenuUL.apply(this); - }); - - addEvents(ul); - - // Our range object is used in calculating menu positions - var Range = function(x1, x2, y1, y2) { - this.x1 = x1; - this.x2 = x2; - this.y1 = y1; - this.y2 = y2; - } - Range.prototype.contains = function(range) { - return (this.x1 <= range.x1 && range.x2 <= this.x2) - && - (this.y1 <= range.y1 && range.y2 <= this.y2); - } - Range.prototype.transform = function(x, y) { - return new Range(this.x1 + x, this.x2 + x, this.y1 + y, this.y2 + y); - } - Range.prototype.nudgeX = function(range) { - if (this.x1 < range.x1) { - return new Range(range.x1, range.x1 + (this.x2 - this.x1), this.y1, this.y2); - } else if (this.x2 > range.x2) { - return new Range(range.x2 - (this.x2 - this.x1), range.x2, this.y1, this.y2); - } - return this; - } - Range.prototype.nudgeY = function(range) { - if (this.y1 < range.y1) { - return new Range(this.x1, this.x2, range.y1, range.y1 + (this.y2 - this.y1)); - } else if (this.y2 > range.y2) { - return new Range(this.x1, this.x2, range.y2 - (this.y2 - this.y1), range.y2); - } - return this; - } - - // window width & scroll offset - var sx = $(window).scrollLeft() - var sy = $(window).scrollTop(); - var ww = $(window).innerWidth(); - var wh = $(window).innerHeight(); - - var viewport = new Range( sx, sx + ww, - sy, sy + wh); - - // "Show" our menu so we can calculate its width, set left and top so that it does not accidentally - // go offscreen and trigger browser scroll bars - $(ul).css({visibility: 'hidden', left: 0, top: 0}).show(); - - var menuWidth = $(ul).outerWidth(); - var menuHeight = $(ul).outerHeight(); - - // Get the LI parent UL outerwidth in case borders are applied to it - var tp = $(this).parent(); - var thisWidth = tp.outerWidth(); - var thisBorderWidth = parseInt(tp.css('borderLeftWidth')) + parseInt(tp.css('borderRightWidth')); - //var thisBorderTop = parseInt(tp.css('borderTopWidth')); - var thisHeight = $(this).outerHeight(); - var thisOffset = $(this).offset({border: false}); - - $(ul).hide().css({visibility: ''}); - - // We define a list of valid positions for our menu and then test against them to find one that works best - var position = []; - // Bottom Horizontal - // Menu is directly below and left edges aligned to parent item - position[0] = new Range(thisOffset.left, thisOffset.left + menuWidth, - thisOffset.top + thisHeight, thisOffset.top + thisHeight + menuHeight); - // Menu is directly below and right edges aligned to parent item - position[1] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth, - position[0].y1, position[0].y2); - // Menu is "nudged" horizontally below parent item - position[2] = position[0].nudgeX(viewport); - - // Right vertical - // Menu is directly right and top edge aligned to parent item - position[3] = new Range(thisOffset.left + thisWidth - thisBorderWidth, thisOffset.left + thisWidth - thisBorderWidth + menuWidth, - thisOffset.top, thisOffset.top + menuHeight); - // Menu is directly right and bottom edges aligned with parent item - position[4] = new Range(position[3].x1, position[3].x2, - position[0].y1 - menuHeight, position[0].y1); - // Menu is "nudged" vertically to right of parent item - position[5] = position[3].nudgeY(viewport); - - // Top Horizontal - // Menu is directly top and left edges aligned to parent item - position[6] = new Range(thisOffset.left, thisOffset.left + menuWidth, - thisOffset.top - menuHeight, thisOffset.top); - // Menu is directly top and right edges aligned to parent item - position[7] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth, - position[6].y1, position[6].y2); - // Menu is "nudged" horizontally to the top of parent item - position[8] = position[6].nudgeX(viewport); - - // Left vertical - // Menu is directly left and top edges aligned to parent item - position[9] = new Range(thisOffset.left - menuWidth, thisOffset.left, - position[3].y1, position[3].y2); - // Menu is directly left and bottom edges aligned to parent item - position[10]= new Range(position[9].x1, position[9].x2, - position[4].y1 + thisHeight - menuHeight, position[4].y1 + thisHeight); - // Menu is "nudged" vertically to left of parent item - position[11]= position[10].nudgeY(viewport); - - // This defines the order in which we test our positions - var order = []; - if ($(this).parent().is('.jd_menu_flag_root') && !settings.isVerticalMenu) { - order = [0, 1, 2, 6, 7, 8, 5, 11]; - } else { - order = [3, 4, 5, 9, 10, 11, 0, 1, 2, 6, 7, 8]; - } - - // Set our default position (first position) if no others can be found - var pos = order[0]; - for (var i = 0, j = order.length; i < j; i++) { - // If this position for our menu is within the viewport of the browser, use this position - if (viewport.contains(position[order[i]])) { - pos = order[i]; - break; - } - } - var menuPosition = position[pos]; - - // Find if we are absolutely positioned or have an absolutely positioned parent - $(this).add($(this).parents()).each(function() { - if ($(this).css('position') == 'absolute') { - var abs = $(this).offset(); - // Transform our coordinates to be relative to the absolute parent - menuPosition = menuPosition.transform(-abs.left, -abs.top); - return false; - } - }); - - switch (pos) { - case 3: - menuPosition.y1 += settings.offsetY; - case 4: - menuPosition.x1 -= settings.offsetX; - break; - - case 9: - menuPosition.y1 += settings.offsetY; - case 10: - menuPosition.x1 += settings.offsetX; - break; - } - - if (settings.iframe) { - $(ul).bgiframe(); - } - - if (settings.onAnimate) { - $(ul).css({left: menuPosition.x1, top: menuPosition.y1}); - // The onAnimate method is expected to "show" the element it is passed - settings.onAnimate.apply(ul, [true]); - } else { - $(ul).css({left: menuPosition.x1, top: menuPosition.y1}).show(); - } - - return true; - } - - // "this" is a reference to a UL menu to be hidden - function hideMenuUL(recurse) { - if (!$(this).is(':visible')) { - return false; - } - - var settings = getSettings(this); - - // Test if this menu should get hidden - if (settings.onHideCheck != null && settings.onHideCheck.apply(this) == false) { - return false; - } - - // Hide all of our child menus first - $('> li ul:visible', this).each(function() { - hideMenuUL.apply(this, [false]); - }); - - // If we are the root, do not hide ourself - if ($(this).is('.jd_menu_flag_root')) { - alert('We are root'); - return false; - } - - var elms = $('> li', this).add($(this).parent()); - elms.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar') - .removeClass('jd_menu_active').removeClass('jd_menu_active_menubar') - .find('> a') - .removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar') - .removeClass('jd_menu_active').removeClass('jd_menu_active_menubar'); - - removeEvents(this); - $(this).each(function() { - if (settings.onAnimate != null) { - settings.onAnimate.apply(this, [false]); - } else { - $(this).hide(); - } - }).find('> .bgiframe').remove(); - // Our callback for after our menu is hidden - if (settings.onHide != null) { - settings.onHide.apply(this); - } - - // Recursively hide our parent menus - if (recurse == true) { - $(this).parentsUntil('ul.jd_menu_flag_root') - .removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar') - .not('.jd_menu_flag_root').filter('ul') - .each(function() { - hideMenuUL.apply(this, [false]); - }); - } - - return true; - } - - // Prevent the default (usually following a link) - function accessibleClick(e) { - if ($(this).is('.accessible')) { - // Stop the browser from the default link action allowing the - // click event to propagate to propagate to our LI (itemClick function) - e.preventDefault(); - } - } - - // Trigger a menu click - function itemClick(e) { - e.stopPropagation(); - - var settings = getSettings(this); - if (settings.onClick != null && settings.onClick.apply(this) == false) { - return false; - } - - if ($('> ul', this).size() > 0) { - showMenuLI.apply(this); - } else { - if (e.target == this) { - var link = $('> a', e.target).not('.accessible'); - if (link.size() > 0) { - var a = link.get(0); - if (!a.onclick) { - window.open(a.href, a.target || '_self'); - } else { - $(a).click(); - } - } - } - - hideMenuUL.apply($(this).parent(), [true]); - } - } })(jQuery); diff --git a/js/jquery.popupmenu.js b/js/jquery.popupmenu.js new file mode 100644 index 0000000000..dbdabb5d15 --- /dev/null +++ b/js/jquery.popupmenu.js @@ -0,0 +1,62 @@ +/* + * Simple popup menu 1.0 (2010-05-15) + * + * Copyright (c) 2010 Combodo SARL (www.combodo.com) + * Licenced under the GPL licence. + * + * http://www.combodo.com/ + * + * Built upon jQuery jQuery 1.2.3a (http://jquery.com) + * Requires the jQuery positionBy plugin by Jonathan Sharp (http://jdsharp.us) + */ + +jQuery.fn.popupmenu = function () +{ + var popupmenu = null; + + return this.each(function() + { + $(this).bind('mouseenter.popup_menu click.popup_menu', function (evt) + { + console.log(evt.type); + var previous_popup = popupmenu; + var bMenuClosed = false; + popupmenu = $(this).find('ul'); + if ( previous_popup != null) + { + if ( ((evt.type == 'click') && ((previous_popup[0] == popupmenu[0])) || // Comparing the jQuery objects + (evt.type == 'mouseenter') && (previous_popup[0] != popupmenu[0])) ) + // The user clicked again in the menu or moved over another menu let's close it + previous_popup.css('display', 'none'); + bMenuClosed = true; + + } + if ( (previous_popup == null) || (previous_popup[0] != popupmenu[0])) // Comparing the jQuery objects + { + // We really clicked in a different menu, let's open it + popupmenu.bgiframe(); + popupmenu.positionBy({ target: $(this), + targetPos: 2, + elementPos: 0, + hideAfterPosition: true + }); + popupmenu.css('display', 'block'); + } + if (bMenuClosed) + { + popupmenu = null; + } + evt.stopPropagation(); + }); + + $(document).bind('click.popup_menu', function(evt) + { + if (popupmenu) + { + // The user clicked in the document's body, let's close the menu + popupmenu.css('display', 'none'); + popupmenu = null; + } + }); + }); +}; \ No newline at end of file diff --git a/js/jquery.positionBy.js b/js/jquery.positionBy.js new file mode 100644 index 0000000000..23dd1148eb --- /dev/null +++ b/js/jquery.positionBy.js @@ -0,0 +1,306 @@ +/* + * positionBy 1.0.7 (2008-01-29) + * + * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://jdsharp.us/ + * + * Built upon jQuery 1.2.2 (http://jquery.com) + * This also requires the jQuery dimensions plugin + */ +(function($){ + /** + * This function centers an absolutely positioned element + */ + /* + $.fn.positionCenter = function(offsetLeft, offsetTop) { + var offsetLeft = offsetLeft || 1; + var offsetTop = offsetTop || 1; + + var ww = $(window).width(); + var wh = $(window).height(); + var sl = $(window).scrollLeft(); + var st = $(window).scrollTop(); + + return this.each(function() { + var $t = $(this); + + // If we are not visible we have to display our element (with a negative position offscreen) + + var left = Math.round( ( ww - $t.outerWidth() ) / 2 ); + if ( left < 0 ) { + left = 0; + } else { + left *= offsetLeft; + } + left += sl; + var top = Math.round( ( wh - $t.outerHeight() ) / 2 ); + if ( top < 0 ) { + top = 0; + } else { + top *= offsetTop; + } + top += st; + + $(this).parents().each(function() { + var $this = $(this); + if ( $this.css('position') != 'static' ) { + var o = $this.offset(); + left += -o.left; + top += -o.top; + return false; + } + }); + + $t.css({left: left, top: top}); + }); + }; + */ + + // Our range object is used in calculating positions + var Range = function(x1, y1, x2, y2) { + this.x1 = x1; this.x2 = x2; + this.y1 = y1; this.y2 = y2; + }; + Range.prototype.contains = function(range) { + return (this.x1 <= range.x1 && range.x2 <= this.x2) + && + (this.y1 <= range.y1 && range.y2 <= this.y2); + }; + Range.prototype.transform = function(x, y) { + return new Range(this.x1 + x, this.y1 + y, this.x2 + x, this.y2 + y); + }; + + $.fn.positionBy = function(args) { + var date1 = new Date(); + if ( this.length == 0 ) { + return this; + } + + var args = $.extend({ // The target element to position us relative to + target: null, + // The target's corner, possible values 0-3 + targetPos: null, + // The element's corner, possible values 0-3 + elementPos: null, + + // A raw x,y coordinate + x: null, + y: null, + + // Pass in an array of positions that are valid 0-15 + positions: null, + + // Add the final position class to the element (eg. positionBy0 through positionBy3, positionBy15) + addClass: false, + + // Force our element to be at the location we specified (don't try to auto position it) + force: false, + + // The element that we will make sure our element doesn't go outside of + container: window, + + // Should the element be hidden after positioning? + hideAfterPosition: false + }, args); + + if ( args.x != null ) { + var tLeft = args.x; + var tTop = args.y; + var tWidth = 0; + var tHeight = 0; + + // Position in relation to an element + } else { + var $target = $( $( args.target )[0] ); + var tWidth = $target.outerWidth(); + var tHeight = $target.outerHeight(); + var tOffset = $target.offset(); + var tLeft = tOffset.left; + var tTop = tOffset.top; + } + + // Our target right, bottom coord + var tRight = tLeft + tWidth; + var tBottom = tTop + tHeight; + + return this.each(function() { + var $element = $( this ); + + // Position our element in the top left so we can grab its width without triggering scrollbars + if ( !$element.is(':visible') ) { + $element.css({ left: -3000, + top: -3000 + }) + .show(); + } + + var eWidth = $element.outerWidth(); + var eHeight = $element.outerHeight(); + + // Holds x1,y1,x2,y2 coordinates for a position in relation to our target element + var position = []; + // Holds a list of alternate positions to try if this one is not in the browser viewport + var next = []; + + // Our Positions via ASCII ART + /* + 8 9 10 11 + +------------+ + 7 | 15 12 | 0 + | | + 6 | 14 13 | 1 + +------------+ + 5 4 3 2 + + */ + + position[0] = new Range(tRight, tTop, tRight + eWidth, tTop + eHeight); + next[0] = [1,7,4]; + + position[1] = new Range(tRight, tBottom - eHeight, tRight + eWidth, tBottom); + next[1] = [0,6,4]; + + position[2] = new Range(tRight, tBottom, tRight + eWidth, tBottom + eHeight); + next[2] = [1,3,10]; + + position[3] = new Range(tRight - eWidth, tBottom, tRight, tBottom + eHeight); + next[3] = [1,6,10]; + + position[4] = new Range(tLeft, tBottom, tLeft + eWidth, tBottom + eHeight); + next[4] = [1,6,9]; + + position[5] = new Range(tLeft - eWidth, tBottom, tLeft, tBottom + eHeight); + next[5] = [6,4,9]; + + position[6] = new Range(tLeft - eWidth, tBottom - eHeight, tLeft, tBottom); + next[6] = [7,1,4]; + + position[7] = new Range(tLeft - eWidth, tTop, tLeft, tTop + eHeight); + next[7] = [6,0,4]; + + position[8] = new Range(tLeft - eWidth, tTop - eHeight, tLeft, tTop); + next[8] = [7,9,4]; + + position[9] = new Range(tLeft, tTop - eHeight, tLeft + eWidth, tTop); + next[9] = [0,7,4]; + + position[10]= new Range(tRight - eWidth, tTop - eHeight, tRight, tTop); + next[10] = [0,7,3]; + + position[11]= new Range(tRight, tTop - eHeight, tRight + eWidth, tTop); + next[11] = [0,10,3]; + + position[12]= new Range(tRight - eWidth, tTop, tRight, tTop + eHeight); + next[12] = [13,7,10]; + + position[13]= new Range(tRight - eWidth, tBottom - eHeight, tRight, tBottom); + next[13] = [12,6,3]; + + position[14]= new Range(tLeft, tBottom - eHeight, tLeft + eWidth, tBottom); + next[14] = [15,1,4]; + + position[15]= new Range(tLeft, tTop, tLeft + eWidth, tTop + eHeight); + next[15] = [14,0,9]; + + if ( args.positions !== null ) { + var pos = args.positions[0]; + } else if ( args.targetPos != null && args.elementPos != null ) { + var pos = []; + pos[0] = []; + pos[0][0] = 15; + pos[0][1] = 7; + pos[0][2] = 8; + pos[0][3] = 9; + pos[1] = []; + pos[1][0] = 0; + pos[1][1] = 12; + pos[1][2] = 10; + pos[1][3] = 11; + pos[2] = []; + pos[2][0] = 2; + pos[2][1] = 3; + pos[2][2] = 13; + pos[2][3] = 1; + pos[3] = []; + pos[3][0] = 4; + pos[3][1] = 5; + pos[3][2] = 6; + pos[3][3] = 14; + + var pos = pos[args.targetPos][args.elementPos]; + } + var ePos = position[pos]; + var fPos = pos; + + if ( !args.force ) { + // TODO: Do the args.container + // window width & scroll offset + $window = $( window ); + var sx = $window.scrollLeft(); + var sy = $window.scrollTop(); + + // TODO: Look at innerWidth & innerHeight + var container = new Range( sx, sy, sx + $window.width(), sy + $window.height() ); + + // If we are outside of our viewport, see if we are outside vertically or horizontally and push onto the stack + var stack; + if ( args.positions ) { + stack = args.positions; + } else { + stack = [pos]; + } + var test = []; // Keeps track of our positions we already tried + + while ( stack.length > 0 ) { + var p = stack.shift(); + if ( test[p] ) { + continue; + } + test[p] = true; + + // If our current position is not within the viewport (eg. window) + // add the next suggested position + if ( !container.contains(position[p]) ) { + if ( args.positions === null ) { + stack = jQuery.merge( stack, next[p] ); + } + } else { + ePos = position[p]; + break; + } + } + } + + // + TODO: Determine if we are going to use absolute left, top, bottom, right + // positions relative to our target + + // Take into account any absolute or fixed positioning + // to 'normalize' our coordinates + $element.parents().each(function() { + var $this = $(this); + if ( $this.css('position') != 'static' ) { + var abs = $this.offset(); + ePos = ePos.transform( -abs.left, -abs.top ); + return false; + } + }); + + // Finally position our element + var css = { left: ePos.x1, top: ePos.y1 }; + if ( args.hideAfterPosition ) { + css['display'] = 'none'; + } + $element.css( css ); + + if ( args.addClass ) { + $element.removeClass( 'positionBy0 positionBy1 positionBy2 positionBy3 positionBy4 positionBy5 ' + + 'positionBy6 positionBy7 positionBy8 positionBy9 positionBy10 positionBy11 ' + + 'positionBy12 positionBy13 positionBy14 positionBy15') + .addClass('positionBy' + p); + } + }); + }; +})(jQuery); From fd7555e96b2f8d400597099158062ca2ce8e3e55 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 15 May 2010 14:52:52 +0000 Subject: [PATCH 326/970] No longer needed, implementation replaced by jquery.popupmenu.js SVN:trunk[409] --- js/jquery.jdMenu.js | 171 -------------------------------------------- 1 file changed, 171 deletions(-) delete mode 100644 js/jquery.jdMenu.js diff --git a/js/jquery.jdMenu.js b/js/jquery.jdMenu.js deleted file mode 100644 index 3b59ceda87..0000000000 --- a/js/jquery.jdMenu.js +++ /dev/null @@ -1,171 +0,0 @@ -/* - * jdMenu 1.4.1 (2008-03-31) - * - * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * http://jdsharp.us/ - * - * Built upon jQuery 1.2.1 (http://jquery.com) - * This also requires the jQuery dimensions >= 1.2 plugin - */ - -// This initializes the menu -$(function() { - $('ul.jd_menu').jdMenu(); -}); - -(function($){ - function addEvents(ul) { - var settings = $.data( $(ul).parents().andSelf().filter('ul.jd_menu')[0], 'jdMenuSettings' ); - $('> li', ul) - .bind('mouseenter.jdmenu mouseleave.jdmenu', function(evt) { - $(this).toggleClass('jdm_hover'); - var ul = $('> ul', this); - if ( ul.length == 1 ) { - clearTimeout( this.$jdTimer ); - var enter = ( evt.type == 'mouseenter' ); - var fn = ( enter ? showMenu : hideMenu ); - this.$jdTimer = setTimeout(function() { - fn( ul[0], settings.onAnimate, settings.isVertical ); - }, enter ? settings.showDelay : settings.hideDelay ); - } - }) - .bind('click.jdmenu', function(evt) { - var ul = $('> ul', this); - if ( ul.length == 1 && - ( settings.disableLinks == true || $(this).hasClass('accessible') ) ) { - showMenu( ul, settings.onAnimate, settings.isVertical ); - return false; - } - - // The user clicked the li and we need to trigger a click for the a - if ( evt.target == this ) { - var link = $('> a', evt.target).not('.accessible'); - if ( link.length > 0 ) { - var a = link[0]; - if ( !a.onclick ) { - window.open( a.href, a.target || '_self' ); - } else { - $(a).trigger('click'); - } - } - } - if ( settings.disableLinks || - ( !settings.disableLinks && !$(this).parent().hasClass('jd_menu') ) ) { - $(this).parent().jdMenuHide(); - evt.stopPropagation(); - } - }) - .find('> a') - .bind('focus.jdmenu blur.jdmenu', function(evt) { - var p = $(this).parents('li:eq(0)'); - if ( evt.type == 'focus' ) { - p.addClass('jdm_hover'); - } else { - p.removeClass('jdm_hover'); - } - }) - .filter('.accessible') - .bind('click.jdmenu', function(evt) { - evt.preventDefault(); - }); - } - - function showMenu(ul, animate, vertical) { - var ul = $(ul); - if ( ul.is(':visible') ) { - return; - } - ul.bgiframe(); - var li = ul.parent(); - ul .trigger('jdMenuShow') - .positionBy({ target: li[0], - targetPos: ( vertical === true || !li.parent().hasClass('jd_menu') ? 1 : 3 ), - elementPos: 0, - hideAfterPosition: true - }); - if ( !ul.hasClass('jdm_events') ) { - ul.addClass('jdm_events'); - addEvents(ul); - } - li .addClass('jdm_active') - // Hide any adjacent menus - .siblings('li').find('> ul:eq(0):visible') - .each(function(){ - hideMenu( this ); - }); - if ( animate === undefined ) { - ul.show(); - } else { - animate.apply( ul[0], [true] ); - } - } - - function hideMenu(ul, animate) { - var ul = $(ul); - $('.bgiframe', ul).remove(); - ul .filter(':not(.jd_menu)') - .find('> li > ul:eq(0):visible') - .each(function() { - hideMenu( this ); - }) - .end(); - if ( animate === undefined ) { - ul.hide() - } else { - animate.apply( ul[0], [false] ); - } - - ul .trigger('jdMenuHide') - .parents('li:eq(0)') - .removeClass('jdm_active jdm_hover') - .end() - .find('> li') - .removeClass('jdm_active jdm_hover'); - } - - // Public methods - $.fn.jdMenu = function(settings) { - // Future settings: activateDelay - var settings = $.extend({ // Time in ms before menu shows - showDelay: 200, - // Time in ms before menu hides - hideDelay: 500, - // Should items that contain submenus not - // respond to clicks - disableLinks: true - // This callback allows for you to animate menus - //onAnimate: null - }, settings); - if ( !$.isFunction( settings.onAnimate ) ) { - settings.onAnimate = undefined; - } - return this.filter('ul.jd_menu').each(function() { - $.data( this, - 'jdMenuSettings', - $.extend({ isVertical: $(this).hasClass('jd_menu_vertical') }, settings) - ); - addEvents(this); - }); - }; - - $.fn.jdMenuUnbind = function() { - $('ul.jdm_events', this) - .unbind('.jdmenu') - .find('> a').unbind('.jdmenu'); - }; - $.fn.jdMenuHide = function() { - return this.filter('ul').each(function(){ - hideMenu( this ); - }); - }; - - // Private methods and logic - $(window) - // Bind a click event to hide all visible menus when the document is clicked - .bind('click.jdmenu', function(){ - $('ul.jd_menu ul:visible').jdMenuHide(); - }); -})(jQuery); From d696e38902e26a21eae90a0ca2dcfbc313fb2128 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 15 May 2010 15:04:45 +0000 Subject: [PATCH 327/970] Bug fix: (Consequence of the fix of Trac #112) Now all attributes are can be used for filtering, one cannot add them automatically to the filter... especially for history block where it makes absolutely no sense... SVN:trunk[410] --- application/displayblock.class.inc.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 7978791458..e6e8d3aea9 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -771,15 +771,6 @@ class HistoryBlock extends DisplayBlock public function GetRenderContent(WebPage $oPage, $aExtraParams = array()) { $sHtml = ''; - // Add the extra params into the filter if they make sense for such a filter - $aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass())); - foreach($aFilterCodes as $sFilterCode) - { - if (isset($aExtraParams[$sFilterCode])) - { - $this->m_oFilter->AddCondition($sFilterCode, $aExtraParams[$sFilterCode]); // Use the default 'loose' operator - } - } $oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false)); $sHtml .= "\n"; switch($this->m_sStyle) From 76a8b5bbc352e95f2bca04b69ce5785c025394c6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 16:38:48 +0000 Subject: [PATCH 328/970] - Added support for pattern-based field validation (anticipating HTML5 ?). SVN:trunk[411] --- business/ChangeMgmt.business.php | 14 +- business/KEDB.business.php | 5 +- business/ServiceDesk.business.php | 10 +- business/ServiceMgmt.business.php | 4 +- business/incidentMgmt.business.php | 20 ++- business/itop.business.class.inc.php | 4 +- core/attributedef.class.inc.php | 191 +++++++++++++++++++++++++-- core/cmdbchange.class.inc.php | 2 +- core/event.class.inc.php | 2 +- core/metamodel.class.php | 20 +++ 10 files changed, 233 insertions(+), 39 deletions(-) diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index 40708a2ebb..45914799c3 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -39,12 +39,12 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("allowed_values"=>new ValueSetEnum("New, Validated,Rejected,Assigned,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); - MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("close_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTIme("creation_date", array("allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + // d�finir une date de d�faut � maintenant, alias creation ou modification du ticket + MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("close_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name"))); @@ -70,7 +70,7 @@ class bizChangeTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("linked_class"=>"lnkInfraChangeTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); - // doit-on aussi ajouter un filtre sur les extfields li une extkey ? ici le name de l'agent? + // doit-on aussi ajouter un filtre sur les extfields li� � une extkey ? ici le name de l'agent? // Display lists MetaModel::Init_SetZListItems('details', array('name','title', 'org_id','type','domain','requestor_id','change_request','ticket_status', 'outage','impact', 'last_update', 'start_date','end_date', 'assignment_count', 'workgroup_id','agent_id','supervisorgroup_id','supervisor_id','managergroup_id','manager_id','change_log','fallback')); // Attributes to be displayed for a list diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 510a208fad..5edf8a823d 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -95,7 +95,8 @@ class lnkInfraError extends cmdbAbstractObject } - +if (false) +{ //////////////////////////////////////////////////////////////////////////////////// /** * n-n link between any Contract and a Document @@ -130,6 +131,6 @@ class lnkDocumentError extends cmdbAbstractObject MetaModel::Init_SetZListItems('list', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for a list } } - +} ?> diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index 7e06843943..23b94e0484 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -46,12 +46,12 @@ class bizServiceCall extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeEnum("call_status", array("allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress,Resolved,Closed"), "sql"=>"call_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); // SetPossibleValues("status",array("Open","Monitored","Closed")); MetaModel::Init_AddAttribute(new AttributeText("call_description", array("allowed_values"=>null, "sql"=>"call_description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("creation_date", array("allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + // d�finir une date de d�faut � maintenant, alias creation ou modification du ticket + MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("next_update", array("allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array('org_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index 3ab6099043..05fe1998b1 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -110,8 +110,8 @@ class bizContract extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("cost", array("allowed_values"=>null, "sql"=>"cost", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_prod", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("move2prod_date", array("allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("end_prod", array("allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production,Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("Hardware,Software,Support,Licence"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeInteger("version_number", array("allowed_values"=>null, "sql"=>"version_number", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array()))); diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index 60fe0d9003..54bbb6623f 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -47,12 +47,12 @@ class bizIncidentTicket extends cmdbAbstractObject // SetPossibleValues("status",array("Open","Monitored","Closed")); MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - // dfinir une date de dfaut maintenant, alias creation ou modification du ticket - MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + // d�finir une date de d�faut � maintenant, alias creation ou modification du ticket + MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("next_update", array("allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("end_date", array("allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "allowed_values"=>new ValueSetObjects('SELECT bizPerson AS p WHERE p.org_id = :this->org_id'), "sql"=>"caller_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email"))); @@ -71,7 +71,7 @@ class bizIncidentTicket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("linked_class"=>"lnkRelatedTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"rel_ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual')))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("contacts_a_notifier", array("linked_class"=>"lnkContactTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"contact_id", "allowed_values"=>null, "default_value"=>new ValueSetRelatedObjectsFromLinkSet('impacted_infra_manual'/*sLinkSetAttCode*/, 'infra_id'/*sExtKeyToRemote*/, 'impacts'/*sRelationCode*/, 3/*iMaxDepth*/, 'bizContact'/*sTargetClass*/, 'lnkContactTicket'/*sTargetLinkClass*/, 'contact_id'/*sTargetExtKey*/) /* ici plus tard... */, "count_min"=>0, "count_max"=>0, "depends_on"=>array('impacted_infra_manual')))); - // doit-on aussi ajouter un filtre sur les extfields li une extkey ? ici le name de l'agent? + // doit-on aussi ajouter un filtre sur les extfields li� � une extkey ? ici le name de l'agent? // Display lists MetaModel::Init_SetZListItems('details', array('name','title', 'org_id', 'type','ticket_status', 'severity','start_date', 'initial_situation', 'current_situation','caller_id', 'impact', 'last_update', 'next_update','end_date', 'assignment_count', 'workgroup_id','agent_id','action_log','resolution')); // Attributes to be displayed for a list @@ -86,7 +86,7 @@ class bizIncidentTicket extends cmdbAbstractObject "title"=>OPT_ATT_MANDATORY, "org_id"=>OPT_ATT_MANDATORY, "caller_id"=>OPT_ATT_MANDATORY, "initial_situation"=>OPT_ATT_MANDATORY, "start_date"=>OPT_ATT_MANDATORY, "workgroup_id"=>OPT_ATT_MANDATORY, "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT,"resolution"=>OPT_ATT_HIDDEN))); MetaModel::Init_DefineState("Assigned", array("attribute_inherit"=>null, - "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_MUSTCHANGE,"resolution"=>OPT_ATT_HIDDEN))); + "attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_MUSTPROMPT, "agent_id"=>OPT_ATT_MUSTCHANGE,"resolution"=>OPT_ATT_HIDDEN))); MetaModel::Init_DefineState("WorkInProgress", array("attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY,"action_log"=>OPT_ATT_MANDATORY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY))); MetaModel::Init_DefineState("Resolved", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_MUSTCHANGE))); MetaModel::Init_DefineState("Closed", array("attribute_inherit"=>null, "attribute_list"=>array('name' => OPT_ATT_READONLY,'type' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "org_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,"current_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_READONLY,'last_update' => OPT_ATT_READONLY,'next_update' => OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_READONLY, "agent_id"=>OPT_ATT_READONLY,"action_log"=>OPT_ATT_READONLY,"impact"=>OPT_ATT_READONLY,"severity"=>OPT_ATT_READONLY,"resolution"=>OPT_ATT_READONLY))); @@ -153,12 +153,10 @@ class bizIncidentTicket extends cmdbAbstractObject $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'impacted_infra_manual', 'infra_id'); - $aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 2); + $aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 10); if (array_key_exists('logRealObject', $aComputed)) { - // Romain: supprimer cette ligne - // $aLinksToCreate = array(); foreach($aComputed['logRealObject'] as $iKey => $oObject) { if (MetaModel::IsParentClass('bizContact', get_class($oObject))) @@ -166,7 +164,7 @@ class bizIncidentTicket extends cmdbAbstractObject $oNewLink = new lnkContactTicket(); $oNewLink->Set('contact_id', $iKey); //$oNewLink->Set('ticket_id', $this->GetKey()); // unkown at that time! - $oNewLink->Set('role', 'concerned by an impacted CI'); + $oNewLink->Set('role', 'contact automatically computed'); // Romain: transformer cette ligne $oToNotify->AddObject($oNewLink); diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index dffa02cb53..1eec578d32 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -1330,7 +1330,7 @@ class bizApplication extends logInfra MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("install_date", array("allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"undefined", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("function", array("allowed_values"=>null, "sql"=>"function", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -1495,7 +1495,7 @@ class bizPatch extends logRealObject MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,obsolete'), "sql"=>"status", "default_value"=>"production", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "allowed_values"=>new ValueSetObjects('SELECT bizDevice AS p WHERE p.org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("install_date", array("allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("patch_type", array("allowed_values"=>new ValueSetEnum("OS,Application"), "sql"=>"patch_type", "default_value"=>"OS", "is_null_allowed"=>false, "depends_on"=>array()))); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index a36964bcb2..44738b110e 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -177,16 +177,14 @@ abstract class AttributeDefinition public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) public function RequiresIndex() {return false;} - public function GetJSCheckFunc() + public function GetValidationPattern() { - $sRegExp = $this->Get("regexp"); - if (empty($sRegExp)) return 'return true;'; - - return "return regexp('$sRegExp', myvalue);"; - } + return ''; + } + public function CheckValue($value) { - $sRegExp = $this->Get("regexp"); + $sRegExp = $this->Get("regexp"); // ??? Does it exist ?? if (empty($sRegExp)) return true; return preg_match(preg_escape($this->Get("regexp")), $value); @@ -464,6 +462,11 @@ class AttributeInteger extends AttributeDBField public function GetEditClass() {return "String";} protected function GetSQLCol() {return "INT(11)";} + public function GetValidationPattern() + { + return "^[0-9]+$"; + } + public function GetBasicFilterOperators() { return array( @@ -799,6 +802,11 @@ class AttributeText extends AttributeString class AttributeEmailAddress extends AttributeString { public function GetTypeDesc() {return "Email address(es)";} + + public function GetValidationPattern() + { + return "^([0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\\w]*[0-9a-zA-Z]\\.)+[a-zA-Z]{2,9})$"; + } } /** @@ -947,6 +955,163 @@ class AttributeEnum extends AttributeString * @version $itopversion$ */ class AttributeDate extends AttributeDBField +{ + const MYDATEFORMAT = "Y-m-d"; + + static public function InitStatics() + { + // Nothing to do... + } + + static protected function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetType() {return "Date";} + public function GetTypeDesc() {return "Date";} + public function GetEditClass() {return "Date";} + protected function GetSQLCol() {return "DATE";} + + // #@# THIS HAS TO REVISED + // Having null not allowed was interpreted by mySQL + // which was creating the field with the flag 'ON UPDATE CURRENT_TIMESTAMP' + // Then, on each update of the record, the field was modified. + // We will have to specify the default value if we want to restore this option + // In fact, we could also have more verbs dedicated to the DB: + // GetDBDefaultValue()... or GetDBFieldCreationStatement().... + public function IsNullAllowed() {return true;} + public function GetDefaultValue() + { + $default = parent::GetDefaultValue(); + + if (!parent::IsNullAllowed()) + { + if (empty($default)) + { + $default = date("Y-m-d"); + } + } + + return $default; + } + // END OF THE WORKAROUND + /////////////////////////////////////////////////////////////// + + public function GetValidationPattern() + { + return "^[0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30)))$"; + } + + public function GetBasicFilterOperators() + { + return array( + "="=>"equals", + "!="=>"differs from", + "<"=>"before", + "<="=>"before", + ">"=>"after (strictly)", + ">="=>"after", + "SameMonth"=>"same year/month", + "SameYear"=>"same year", + "Today"=>"today", + ">|"=>"after today + N days", + "<|"=>"before today + N days", + "=|"=>"equals today + N days", + ); + } + public function GetBasicFilterLooseOperator() + { + // Unless we implement a "same xxx, depending on given precision" ! + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + + switch ($sOpCode) + { + case '=': + case '!=': + case '<': + case '<=': + case '>': + case '>=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'SameMonth': + return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')"; + case 'SameYear': + return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)"; + case 'Today': + return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()"; + case '>|': + return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '<|': + return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '=|': + return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + default: + return $this->GetSQLExpr()." = $sQValue"; + } + } + + public function MakeRealValue($proposedValue) + { + if (!is_numeric($proposedValue)) + { + return $proposedValue; + } + else + { + return date("Y-m-d", $proposedValue); + } + throw new CoreException("Invalid type for a date (found ".gettype($proposedValue)." and accepting string/int/DateTime)"); + return null; + } + public function ScalarToSQL($value) + { + if (empty($value)) + { + // Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null. + return '0000-00-00'; + } + return $value; + } + + public function GetAsHTML($value) + { + return Str::pure2html($value); + } + + public function GetAsXML($value) + { + return Str::pure2xml($value); + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return '"'.$sEscaped.'"'; + } +} + +// Init static constant once for all (remove when PHP allows real static const) +AttributeDate::InitStatics(); +/** + * Map a date+time column to an attribute + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeDateTime extends AttributeDBField { const MYDATEFORMAT = "Y-m-d H:i:s"; //const MYDATETIMEZONE = "UTC"; @@ -971,7 +1136,7 @@ class AttributeDate extends AttributeDBField public function GetType() {return "Date";} public function GetTypeDesc() {return "Date and time";} - public function GetEditClass() {return "Date";} + public function GetEditClass() {return "DateTime";} protected function GetSQLCol() {return "TIMESTAMP";} // #@# THIS HAS TO REVISED @@ -999,6 +1164,11 @@ class AttributeDate extends AttributeDBField // END OF THE WORKAROUND /////////////////////////////////////////////////////////////// + public function GetValidationPattern() + { + return "^([0-9]{4}-(((0[13578]|(10|12))-(0[1-9]|[1-2][0-9]|3[0-1]))|(02-(0[1-9]|[1-2][0-9]))|((0[469]|11)-(0[1-9]|[1-2][0-9]|30))))( (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])){0,1}|0000-00-00 00:00:00|0000-00-00$"; + } + public function GetBasicFilterOperators() { return array( @@ -1398,6 +1568,11 @@ class AttributeURL extends AttributeString } return "$sLabel"; } + + public function GetValidationPattern() + { + return "^(http|https|ftp)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*$"; + } } /** diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index 54cdfc94c5..dcfe6da959 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -29,7 +29,7 @@ class CMDBChange extends DBObject ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeDate("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); } diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 6065ab8d04..5a396be626 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -31,7 +31,7 @@ class Event extends cmdbAbstractObject MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDate("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); // Display lists diff --git a/core/metamodel.class.php b/core/metamodel.class.php index ff467348be..678fd643a7 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -318,6 +318,26 @@ abstract class MetaModel return array(); } } + /** + * Find all attributes that depend on the specified one (reverse of GetPrequisiteAttributes) + * @param string $sClass Name of the class + * @param string $sAttCode Code of the attributes + * @return Array List of attribute codes that depend on the given attribute, empty array if none. + */ + final static public function GetDependentAttributes($sClass, $sAttCode) + { + $aResults = array(); + self::_check_subclass($sClass); + foreach (self::ListAttributeDefs($sClass) as $sDependentAttCode=>$void) + { + $aPrerequisites = self::GetPrequisiteAttributes($sClass, $sDependentAttCode); + if (in_array($sAttCode, $aPrerequisites)) + { + $aResults[] = $sDependentAttCode; + } + } + return $aResults; + } // #@# restore to private ? final static public function DBGetTable($sClass, $sAttCode = null) { From 1de8ae4c3303d16b4a0ee1d11db4f27e00cb9d29 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 16:47:16 +0000 Subject: [PATCH 329/970] - Added validation of the fields based on a regexp pattern - Fields that depend on another field (in the same form) get refreshed when one of the fields they depend on is changed - Updated the wizard to group all the mandatory fields on the first page, since having inter-dependent fields on one page is now supported SVN:trunk[412] --- application/cmdbabstract.class.inc.php | 52 ++++++--- application/uiwizard.class.inc.php | 61 ++++++----- application/wizardhelper.class.inc.php | 11 +- css/light-grey.css | 3 + images/validation_error.png | Bin 0 -> 1469 bytes images/validation_ok.png | Bin 0 -> 623 bytes js/forms-json-utils.js | 141 ++++++++++++++++--------- js/wizardhelper.js | 54 ++++------ pages/UI.php | 32 +++++- pages/ajax.render.php | 2 +- 10 files changed, 227 insertions(+), 129 deletions(-) create mode 100644 images/validation_error.png create mode 100644 images/validation_ok.png diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 91abac45a3..a1bd816de4 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -828,25 +828,29 @@ abstract class cmdbAbstractObject extends CMDBObject if (!$oAttDef->IsExternalField()) { $aCSSClasses = array(); + $bMandatory = 0; if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { $aCSSClasses[] = 'mandatory'; + $bMandatory = 1; } $sCSSClasses = self::GetCSSClasses($aCSSClasses); + $sValidationField = ""; switch($oAttDef->GetEditClass()) { case 'Date': + case 'DateTime': $aCSSClasses[] = 'date-pick'; $sCSSClasses = self::GetCSSClasses($aCSSClasses); - $sHTMLValue = ""; + $sHTMLValue = "$sValidationField"; break; case 'Password': - $sHTMLValue = ""; + $sHTMLValue = "$sValidationField"; break; case 'Text': - $sHTMLValue = ""; + $sHTMLValue = "$sValidationField"; break; case 'List': @@ -884,7 +888,7 @@ abstract class cmdbAbstractObject extends CMDBObject { // too many choices, use an autocomplete // The input for the auto complete - $sHTMLValue = ""; + $sHTMLValue = "$sValidationField"; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iInputId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); @@ -906,15 +910,22 @@ abstract class cmdbAbstractObject extends CMDBObject $sSelected = ($value == $key) ? ' selected' : ''; $sHTMLValue .= "\n"; } - $sHTMLValue .= "\n"; + $sHTMLValue .= "$sValidationField\n"; } } else { - $sHTMLValue = ""; + $sHTMLValue = "$sValidationField"; } break; } + $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; + $oPage->add_ready_script("$('#$iInputId').bind('validate blur', function(evt, sFormId) { return ValidateField('$iInputId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate + $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one + if (count($aDependencies) > 0) + { + $oPage->add_ready_script("$('#$iInputId').bind('change', function(evt, sFormId) { return UpdateDependentFields(['".implode("','", $aDependencies)."']) } );"); // Bind to a custom event: validate + } } return $sHTMLValue; } @@ -923,12 +934,14 @@ abstract class cmdbAbstractObject extends CMDBObject { static $iFormId = 0; $iFormId++; + $sClass = get_class($this); $oAppContext = new ApplicationContext(); - $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); $iKey = $this->GetKey(); $aDetails = array(); - $oPage->add("
                \n"); - foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef) + $aFieldsMap = array(); + $oPage->add("\n"); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) { if ($oAttDef->IsWritable()) { @@ -957,7 +970,10 @@ abstract class cmdbAbstractObject extends CMDBObject $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this); - $sHTMLValue = self::GetFormElementForField($oPage, get_class($this), $sAttCode, $oAttDef, $sValue, $sDisplayValue, '', '', $iFlags, $aArgs); + $sInputId = $iFormId.'_'.$sAttCode; + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs); + $aFieldsMap[$sAttCode] = $sInputId; + } $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); } @@ -966,7 +982,7 @@ abstract class cmdbAbstractObject extends CMDBObject } $oPage->details($aDetails); $oPage->add("\n"); - $oPage->add("\n"); + $oPage->add("\n"); $oPage->add("\n"); $oPage->add("\n"); foreach($aExtraParams as $sName => $value) @@ -977,6 +993,18 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add("    \n"); $oPage->add("\n"); $oPage->add("\n"); + + $iFieldsCount = count($aFieldsMap); + $sJsonFieldsMap = json_encode($aFieldsMap); + + $oPage->add_script( +<<add("
                \n"); + $oPage->add("\n"); $aStates = MetaModel::EnumStates($sClass); if ($oObjectToClone == null) { diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index 7285373b49..f1bf81256e 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -61,8 +61,8 @@ class UIWizard $sFieldFlag = (($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) || (!$oAttDef->IsNullAllowed()) )? ' *' : ''; $oDefaultValuesSet = $oAttDef->GetDefaultValue(/* $oObject->ToArgs() */); // @@@ TO DO: get the object's current value if the object exists $sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId", '', $iOptions, $aArgs); - $aFieldsMap[$iMaxInputId] = $sAttCode; - $aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "
                $sHTMLValue
                "); + $aFieldsMap["att_$iMaxInputId"] = $sAttCode; + $aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "$sHTMLValue"); if ($oAttDef->GetValuesDef() != null) { $sJSHandlerCode .= "\toWizardHelper.RequestAllowedValues('$sAttCode');\n"; @@ -128,6 +128,7 @@ $sJSHandlerCode $this->m_oPage->add("
                \n"); $this->m_oPage->add("
                \n"); $this->m_oPage->add($oAppContext->GetForForm()); + $this->m_oPage->add(""); $this->m_oPage->add("m_sClass)."\" />\n"); $this->m_oPage->add("
            \n"); $this->m_oPage->add("\n"); @@ -201,35 +202,45 @@ $sJSHandlerCode } // Now use the dependencies between the fields to order them - while(count($aFields) > 0) + // Start from the order of the 'details' + $aList = MetaModel::GetZListItems($this->m_sClass, 'details'); + $index = 0; + $aOrder = array(); + foreach($aFields as $sAttCode => $void) { - $aCurrentStep = array(); - foreach($aFields as $sAttCode => $aDependencies) + $aOrder[$sAttCode] = 999; // At the end of the list... + } + foreach($aList as $sAttCode) + { + if (array_key_exists($sAttCode, $aFields)) { - // All fields with no remaining dependencies can be entered at this - // step of the wizard - if (count($aDependencies) == 0) + $aOrder[$sAttCode] = $index; + } + $index++; + } + foreach($aFields as $sAttCode => $aDependencies) + { + // All fields with no remaining dependencies can be entered at this + // step of the wizard + if (count($aDependencies) > 0) + { + $iMaxPos = 0; + // Remove this field from the dependencies of the other fields + foreach($aDependencies as $sDependentAttCode => $void) { - $aCurrentStep[] = $sAttCode; - $aFieldsDone[$sAttCode] = ''; - unset($aFields[$sAttCode]); - // Remove this field from the dependencies of the other fields - foreach($aFields as $sUpdatedCode => $aDummy) - { - // remove the dependency - unset($aFields[$sUpdatedCode][$sAttCode]); - } + // position the current field after the ones it depends on + $iMaxPos = max($iMaxPos, 1+$aOrder[$sDependentAttCode]); } } - if (count($aCurrentStep) == 0) - { - // This step of the wizard would contain NO field ! - echo "Error: Circular reference in the dependencies between the fields."; - print_r($aFields); - break; - } - $aWizardSteps['mandatory'][] = $aCurrentStep; } + asort($aOrder); + $aCurrentStep = array(); + foreach($aOrder as $sAttCode => $rank) + { + $aCurrentStep[] = $sAttCode; + $aFieldsDone[$sAttCode] = ''; + } + $aWizardSteps['mandatory'][] = $aCurrentStep; // Now computes the steps to fill the optional fields diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 656b745890..b6eb2927b5 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -16,9 +16,8 @@ class WizardHelper public function GetTargetObject($bReadUploadedFiles = false) { $oObj = MetaModel::NewObject($this->m_aData['m_sClass']); - foreach($this->m_aData['m_aCurrentValues'] as $iIndex => $value) + foreach($this->m_aData['m_oCurrentValues'] as $sAttCode => $value) { - $sAttCode = array_search($iIndex, $this->m_aData['m_oFieldsMap']); // Because this is stored in a Javascript array, unused indexes // are filled with null values if ( ($sAttCode !== false) && ($value !== null)) @@ -104,7 +103,6 @@ class WizardHelper // Protect against a request for a non existing field if (isset($this->m_aData['m_oFieldsMap'][$sAttCode])) { - $iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode]; $oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode); if ($oAttDef->GetEditClass() == 'List') { @@ -124,13 +122,13 @@ class WizardHelper } $aData[] = $aRow; } - $this->m_aData['m_aDefaultValue'][$iIndex] = json_encode($aData); + $this->m_aData['m_oDefaultValue'][$sAttCode] = json_encode($aData); } else { // Normal handling for all other scalar attributes - $this->m_aData['m_aDefaultValue'][$iIndex] = $value; + $this->m_aData['m_oDefaultValue'][$sAttCode] = $value; } } } @@ -145,8 +143,7 @@ class WizardHelper // Protect against a request for a non existing field if (isset($this->m_aData['m_oFieldsMap'][$sAttCode])) { - $iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode]; - $this->m_aData['m_aAllowedValues'][$iIndex] = $sHtml; + $this->m_aData['m_oAllowedValues'][$sAttCode] = $sHtml; } } diff --git a/css/light-grey.css b/css/light-grey.css index e48fa60fc8..c1b8e1b9aa 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -478,6 +478,9 @@ div.wizStep span { padding: 5px; } +.wizContainer table tr td { + background: transparent; +} .alignRight { text-align: right; padding: 3px; diff --git a/images/validation_error.png b/images/validation_error.png new file mode 100644 index 0000000000000000000000000000000000000000..3dd3b55dbe0ebec6e35cec8eaeba62038acfeb10 GIT binary patch literal 1469 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ za0`Jj+tIX_n~5oC^DMQ#CujeSKy zVsdtBi9%9pdS;%jl7fPQl0s&Rtx~wDuYqrYb81GWM^#a3aFt(3a#eP+Wr~u$9hXgo z6;N|-YDuC(MQ%=Bu~mhw64*>DAR8pCucQE0Qj%?}1aWkPZ-9bxeo?A|iJqZuvVpOQ zf{B@)k-3qjxtWeaaAJvqS7M%mk-37AfdP;(vNANZGBE@?1`L$!xPY`xQA(Oskc%7C zP9V=#DWjyMz)D}gyu4hm+*mKaC|%#s($Z4jz)0W7NEfI=x41H|B(Xv_uUHvk2+SOp z)Z*l#%mQ$5fy_-z$}cUkRZ;?31P4&hB^JOf$}5Hj9xxd7D-sLz4fPE4;U)t$+5iQu zz!8yO6q28xV}~WqY(P3u6d`Oy=udS?EJ?KkhKGf&fswAEd5D3Lm9d$XiD?v)euyG8 z?Y{XbnQ4_s+KqLMOhODTtqcsTOpKt~krY9-+vtM=0x4j?p$_sBnz#ai082@RhgU&q zQ4Tm-Qj+ykb5e6t^Gb?=VP=RLW+};5Y57IDi6wTKxryni`UQFEHu?xbyzYaz8kj7A z$xbP0l+XkK D*?Od} literal 0 HcmV?d00001 diff --git a/images/validation_ok.png b/images/validation_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..a674561c15e90081b90197db179f20beab470875 GIT binary patch literal 623 zcmV-#0+9WQP)%FK}+}fcW$p+nfK2to%YT-oi(#rwO+8G<_39y@Ed#$ zgSE2t)!CZUdlvi)EC!dDS(!Enxq&Ky8*DKsxwZo(CC4{sq!)JChxXFWmo-*%Ra17R z!t%^gWiF6nGAeZRzkSG~smJd_Nt(ngonX6cuDzafUaG$?_8Pr zMMFi6-XHshoJ^(Hd5>ZDm@5>afm?i4M=L4KH!L>hZOufNnu;hYyCwpj`^8PV6DJQH z?y0LN*GCdz{8EU%sV+um?W3zfj?lp}fSPKS(l iCurrentStep) { // Check the values when moving forward - if (CheckMandatoryFields('wizStep'+iCurrentStep)) + if (CheckFields('wizStep'+iCurrentStep)) { oCurrentStep.style.display = 'none'; ActivateStep(iNextStep); @@ -85,70 +85,115 @@ function ActivateStep(iTargetStep) } oNextStep.style.display = ''; G_iCurrentStep = iTargetStep; - $('#wizStep'+(iTargetStep)).block({ message: null }); + //$('#wizStep'+(iTargetStep)).block({ message: null }); } -function AjaxGetValuesDef(oObj, sClass, sAttCode, iFieldId) -{ - var oJSON = document.getElementById(sJsonFieldId); - $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode, - { operation: "allowed_values" }, - function(data){ - //$('#field_'+iFieldId).html(data); - } - ); -} +//function AjaxGetValuesDef(oObj, sClass, sAttCode, iFieldId) +//{ +// var oJSON = document.getElementById(sJsonFieldId); +// $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode, +// { operation: "allowed_values" }, +// function(data){ +// //$('#field_'+iFieldId).html(data); +// } +// ); +//} +// +//function AjaxGetDefaultValue(oObj, sClass, sAttCode, iFieldId) +//{ +// // Asynchronously call the server to provide a default value if the field is +// // empty +// if (oObj['m_aCurrValues'][sAttCode] == '') +// { +// var oJSON = document.getElementById(sJsonFieldId); +// $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode, +// { operation: "default_value" }, +// function(json_data){ +// var oObj = ReloadObjectFromServer(json_data); +// UpdateFieldFromObject(iFieldId, aFieldsMap, oObj) +// } +// ); +// } +//} -function AjaxGetDefaultValue(oObj, sClass, sAttCode, iFieldId) -{ - // Asynchronously call the server to provide a default value if the field is - // empty - if (oObj['m_aCurrValues'][sAttCode] == '') - { - var oJSON = document.getElementById(sJsonFieldId); - $.get('ajax.render.php?class=' + sClass + '&json_obj=' + oJSON.value + '&att_code=' + sAttCode, - { operation: "default_value" }, - function(json_data){ - var oObj = ReloadObjectFromServer(json_data); - UpdateFieldFromObject(iFieldId, aFieldsMap, oObj) - } - ); - } -} +// Store the result of the form validation... there may be several forms per page, beware +var oFormErrors = { err_form0: 0 }; -function CheckMandatoryFields(sFormId) +function CheckFields(sFormId) { $('#'+sFormId+' :submit').attr('disable', 'disabled'); $('#'+sFormId+' :button[type=submit]').attr('disable', 'disabled'); firstErrorId = ''; - var iErrorsCount = 0; - $('#'+sFormId+' :input.mandatory').each( function() { - if (( this.value == '') || (this.value == 0)) - { - this.style.backgroundColor = '#fcc'; - iErrorsCount++; - if (iErrorsCount == 1) - { - firstErrorId = this.id; - } - } - else - { - this.style.backgroundColor = '#fff'; - } + // The two 'fields' below will be updated when the 'validate' event is processed + oFormErrors['err_'+sFormId] = 0; // Number of errors encountered when validating the form + oFormErrors['input_'+sFormId] = null; // First 'input' with an error, to set the focus to it + $('#'+sFormId+' :input').each( function() + { + validateEventResult = $(this).trigger('validate', sFormId); } ); - if(iErrorsCount > 0) + if(oFormErrors['err_'+sFormId] > 0) { alert('Please fill-in all mandatory fields before continuing.'); $('#'+sFormId+' :submit').attr('disable', ''); $('#'+sFormId+' :button[type=submit]').attr('disable', ''); - if (firstErrorId != '') + if (oFormErrors['input_'+sFormId] != null) { - $('#'+firstErrorId).focus(); + $('#'+oFormErrors['input_'+sFormId]).focus(); } } - return(iErrorsCount == 0); + return (oFormErrors['err_'+sFormId] == 0); // If no error, submit the form +} + +function ValidateField(sFieldId, sPattern, bMandatory, sFormId) +{ + var bValid = true; + var currentVal = $('#'+sFieldId).val(); + if (bMandatory && ((currentVal == '') || (currentVal == 0))) + { + bValid = false; + } + else if (sPattern != '') + { + re = new RegExp(sPattern); + //console.log('Validating field: '+sFieldId + ' current value: '+currentVal + ' pattern: '+sPattern ); + bValid = re.test(currentVal); + } + if (bValid) + { + // Visual feedback + $('#v_'+sFieldId).html(''); + } + else + { + // Report the error... + oFormErrors['err_'+sFormId]++; + if (oFormErrors['input_'+sFormId] == null) + { + // Let's remember the first input with an error, so that we can put back the focus on it later + oFormErrors['input_'+sFormId] = sFieldId; + } + // Visual feedback + $('#v_'+sFieldId).html(''); + } + //console.log('Form: '+sFormId+' Validating field: '+sFieldId + ' current value: '+currentVal+' pattern: '+sPattern+' result: '+bValid ); + return bValid; +} + +function UpdateDependentFields(aFieldNames) +{ + //console.log('UpdateDependentFields:'); + //console.log(aFieldNames); + index = 0; + oWizardHelper.ResetQuery(); + oWizardHelper.UpdateWizard(); + while(index < aFieldNames.length ) + { + sAttCode = aFieldNames[index]; + oWizardHelper.RequestAllowedValues(sAttCode); + index++; + } + oWizardHelper.AjaxQueryServer(); } diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 9e8831ee94..94937816a7 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -3,11 +3,11 @@ function WizardHelper(sClass) { this.m_oData = { 'm_sClass' : '', 'm_oFieldsMap': {}, - 'm_aCurrentValues': [], + 'm_oCurrentValues': {}, 'm_aDefaultValueRequested': [], 'm_aAllowedValuesRequested': [], - 'm_aDefaultValue': [], - 'm_aAllowedValues': [], + 'm_oDefaultValue': {}, + 'm_oAllowedValues': {}, 'm_iFieldsCount' : 0 }; this.m_oData.m_sClass = sClass; @@ -16,7 +16,6 @@ function WizardHelper(sClass) this.SetFieldsMap = function (oFieldsMap) { this.m_oData.m_oFieldsMap = oFieldsMap; - } this.SetFieldsCount = function (count) @@ -28,7 +27,6 @@ function WizardHelper(sClass) this.RequestDefaultValue = function (sFieldName) { currentValue = this.UpdateCurrentValue(sFieldName); - sFieldId = this.m_oData.m_oFieldsMap[sFieldName]; if (currentValue == null) { this.m_oData.m_aDefaultValueRequested.push(sFieldName); @@ -40,7 +38,7 @@ function WizardHelper(sClass) } this.SetCurrentValue = function (sFieldName, currentValue) { - this.m_oData.m_aCurrentValues[this.m_oData.m_oFieldsMap[sFieldName]] = currentValue; + this.m_oData.m_oCurrentValues[sFieldName] = currentValue; } this.ToJSON = function () @@ -57,44 +55,40 @@ function WizardHelper(sClass) this.ResetQuery = function () { this.m_oData.m_aDefaultValueRequested = []; - this.m_oData.m_aDefaultValue = []; + this.m_oData.m_oDefaultValue = {}; this.m_oData.m_aAllowedValuesRequested = []; - this.m_oData.m_aAllowedValues = []; + this.m_oData.m_oAllowedValues = {}; } this.UpdateFields = function () { //console.log('** UpdateFields **'); - //console.log(this.m_oData); - for(i=0; i< this.m_oData.m_aAllowedValuesRequested.length; i++) + // Set the full HTML for the input field + for(i=0; iGetName(); $sClassLabel = MetaModel::GetName(get_class($oObj)); $oObj->DBDeleteTracked($oMyChange); - $oP->add("

            ".Dict::Format('UI:Delete:_Name_Class_Deleted')."

            \n"); + $oP->add("

            ".Dict::Format('UI:Delete:_Name_Class_Deleted', $sName, $sClassLabel)."

            \n"); } } else @@ -995,6 +995,12 @@ try $aTransition = $aTransitions[$sStimulus]; $sTargetState = $aTransition['target_state']; $aTargetStates = MetaModel::EnumStates($sClass); + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add("
            \n"); $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); $oP->add("
            \n"); @@ -1006,6 +1012,8 @@ try $oP->add("
            \n"); $oP->add("
            \n"); $aDetails = array(); + $iFieldIndex = 0; + $aFieldsMap = array(); foreach($aExpectedAttributes as $sAttCode => $iExpectCode) { // Prompt for an attribute if @@ -1017,8 +1025,10 @@ try $aAttributesDef = MetaModel::ListAttributeDefs($sClass); $oAttDef = $aAttributesDef[$sAttCode]; $aArgs = array('this' => $oObj); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), '', '', $iExpectCode, $aArgs); - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode, $aArgs); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => "$sHTMLValue"); + $aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex; + $iFieldIndex++; } } $oP->details($aDetails); @@ -1033,6 +1043,18 @@ try $oP->add("
            \n"); $oP->add("
            \n"); $oP->add("\n"); + + $iFieldsCount = count($aFieldsMap); + $sJsonFieldsMap = json_encode($aFieldsMap); + + $oP->add_script( +<<add("
            \n"); $oP->add("

            $sActionLabel - {$oObj->GetName()}

            \n"); $oP->add("

            $sActionDetails

            \n"); - $oP->p(Dict::Format('UI:Apply_Stimulus_On_Object_In_State_ToTarget_State', $sACtionLabel, $oObj->GetName(), $oObj->GetStateLabel(), $sTargetState)); + $oP->p(Dict::Format('UI:Apply_Stimulus_On_Object_In_State_ToTarget_State', $sActionLabel, $oObj->GetName(), $oObj->GetStateLabel(), $sTargetState)); $oP->add("
            \n"); $aTargetState = $aTargetStates[$sTargetState]; $aExpectedAttributes = $aTargetState['attribute_list']; @@ -1101,7 +1123,7 @@ try $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); - $oP->p(Dict::Format('UI:Class_Object_Updated'), get_class($oObj), $oObj->GetName()); + $oP->p(Dict::Format('UI:Class_Object_Updated', get_class($oObj), $oObj->GetName())); } $oObj->DisplayDetails($oP); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 351a326a47..a3d1e8e2dd 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -91,7 +91,7 @@ switch($operation) $value = $oObj->Get($sAttCode); $displayValue = $oObj->GetEditValue($sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, 'att_'.$sId, '', 0, array('this' => $oObj)); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', 0, array('this' => $oObj)); $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } From d42f2f18be607f1339c95d5d99161c0ad3ad7895 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 16:51:03 +0000 Subject: [PATCH 330/970] Minor fix SVN:trunk[413] --- core/dbobject.class.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index bb894cadc4..e726a47512 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -399,6 +399,10 @@ abstract class DBObject if (is_object($oTargetObj)) { $sEditValue = $oTargetObj->GetName(); + } + else + { + $sEditValue = 0; } } else From e8d4cccf8f72f8925502abf60eed1f6acef94f43 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 16:52:13 +0000 Subject: [PATCH 331/970] - French translation cleanup SVN:trunk[414] --- dictionaries/fr.dictionary.itop.ui.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index cc1b1a8813..287dc5fdcf 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -573,11 +573,11 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Delete:AutomaticallyDeleted' => 'supprimé automatiquement', 'UI:Delete:AutomaticResetOf_Fields' => 'mise à jour automatique des champ(s): %1$s', 'UI:Delete:CleaningUpRefencesTo_Object' => 'Suppression de toutes les références vers %1$s...', - 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Supression de toutes les références vers les %1$d objets de type %2$s...', + 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Suppression de toutes les références vers les %1$d objets de type %2$s...', 'UI:Delete:Done+' => 'Ce qui a été effectué...', 'UI:Delete:_Name_Class_Deleted' => ' %2$s %1$s supprimé.', 'UI:Delete:ConfirmDeletionOf_Name' => 'Suppression de %1$s', - 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Supression de %1$d objets de type %2$s', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Suppression de %1$d objets de type %2$s', 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => 'Devrait être supprimé automatiquement, mais vous n\'avez pas le droit d\'effectuer cette opération.', 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => 'Doit être supprimé manuellement - mais vous n\'avez pas le droit de supprimer cet objet, contactez votre administrateur pour régler ce problème', 'UI:Delete:WillBeDeletedAutomatically' => 'Sera supprimé automatiquement', @@ -609,11 +609,11 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:CreationTitle_Class' => 'Création d\'un objet de type %1$s', 'UI:Class_Object_NotUpdated' => 'Aucun changement détecté, %2$s (type : %2$s) n\'a pas été modifié.', 'UI:Class_Object_Updated' => '%1$s (%2$s) mise à jour.', - 'UI:BulkDeletePageTitle' => 'iTop - Supression massive', + 'UI:BulkDeletePageTitle' => 'iTop - Suppression massive', 'UI:BulkDeleteTitle' => 'Sélectionnez les objets à supprimer:', 'UI:PageTitle:ObjectCreated' => 'iTop objet créé.', 'UI:Title:Object_Of_Class_Created' => '%2$s - %1$s créé(e).', - 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '%1$s de %2$s de l\'état %3$s vers l\'état %4$s.', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '%1$s pour %2$s de l\'état %3$s vers l\'état %4$s.', 'UI:PageTitle:FatalError' => 'iTop - Erreur Fatale', 'UI:FatalErrorMessage' => 'Erreur fatale, iTop ne peut pas continuer.', 'UI:Error_Details' => 'Erreur: %1$s.', @@ -634,7 +634,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:UserManagement:Action:Modify' => 'Modification', 'UI:UserManagement:Action:Modify+' => 'Création et modification d\'un objet', 'UI:UserManagement:Action:Delete' => 'Suppression', - 'UI:UserManagement:Action:Delete+' => 'Supression d\'un objet', + 'UI:UserManagement:Action:Delete+' => 'Suppression d\'un objet', 'UI:UserManagement:Action:BulkRead' => 'Lecture en masse (export)', 'UI:UserManagement:Action:BulkRead+' => 'Export de liste d\'objets', 'UI:UserManagement:Action:BulkModify' => 'Modification en masse', From 15352ad018cd18ddc2389f818aa584414cae2a8a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 16:54:08 +0000 Subject: [PATCH 332/970] - Checking memory_limit during the setup SVN:trunk[415] --- setup/index.php | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/setup/index.php b/setup/index.php index b1ca70ac9a..8f2065320d 100644 --- a/setup/index.php +++ b/setup/index.php @@ -13,6 +13,8 @@ define('SETUP_STRUCTURE_DATA_DIR', './data/structure'); define('SETUP_SAMPLE_DATA_DIR', './data'); define('PHP_MIN_VERSION', '5.2.0'); define('MYSQL_MIN_VERSION', '5.0.0'); +define('MIN_MEMORY_LIMIT', 32*1024*1024); + $sOperation = Utils::ReadParam('operation', 'step1'); $oP = new SetupWebPage('iTop configuration wizard'); @@ -46,7 +48,41 @@ function GetTmpDir() } } - +/** + * Check the value of the PHP setting 'memory_limit' + * against the minimum recommended value + * @param SetpWebPage $oP The current web page + * @param integer $iMinMemoryRequired The minimum memory for the test to pass + * @return boolean Whether or not it's Ok to continue + */ +function CheckMemoryLimit(SetupWebPage $oP, $iMinMemoryRequired) +{ + $sMemoryLimit = trim(ini_get('memory_limit')); + $bResult = true; + if (empty($sMemoryLimit)) + { + // On some PHP installations, memory_limit does not exist as a PHP setting! + // (encountered on a 5.2.0 under Windows) + // In that case, ini_set will not work, let's keep track of this and proceed anyway + $oP->warning("No memory limit has been defined in this instance of PHP"); + } + else + { + // Check that the limit will allow us to load the data + // + $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); + if ($iMemoryLimit < $iMinMemoryRequired) + { + $oP->error("memory_limit ($iMemoryLimit) is too small, the minimum value to run iTop is $iMinMemoryRequired."); + $bResult = false; + } + else + { + $oP->log_info("memory_limit is $iMemoryLimit, ok."); + } + } + return $bResult; +} /** * Helper function to retrieve the directory where files are to be uploaded * @return string Path to the temp directory used for uploading files @@ -168,6 +204,8 @@ function CheckPHPVersion(SetupWebPage $oP) $bResult = false; } + $bResult = $bResult & CheckMemoryLimit($oP, MIN_MEMORY_LIMIT); + return $bResult; } From d95b941d5a69149bdc7f364ab444da87759d2fc6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 16:55:22 +0000 Subject: [PATCH 333/970] - Partial localization... is this page used at all ? SVN:trunk[416] --- pages/usermanagement_userstatus.php | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index 34cf804a89..d5a17a892b 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -50,7 +50,7 @@ function ComputeObjectProjections($oPage, $oObject) $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); if (is_null($aValues)) { - $sValues = htmlentities(''); + $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); } else { @@ -97,7 +97,7 @@ function ComputeUserProjections($oPage, $oUser) // Setup display structure // $aDisplayConfig = array(); - $aDisplayConfig['profile'] = array('label' => 'Profile', 'description' => 'Profile in which the projection is specified'); + $aDisplayConfig['profile'] = array('label' => Dict::S('UI:UserManagement:Profile'), 'description' => Dict::S('UI:UserManagement:Profile+')); foreach ($aDimensions as $iDimension => $oDimension) { $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); @@ -122,7 +122,7 @@ function ComputeUserProjections($oPage, $oUser) $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); if (is_null($aValues)) { - $sValues = htmlentities(''); + $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); } else { @@ -152,18 +152,18 @@ function ComputeUserRights($oPage, $oUser, $oObject) UR_ALLOWED_DEPENDS => 'UR_ALLOWED_DEPENDS', ); $aActions = array( - UR_ACTION_READ => 'Read', - UR_ACTION_MODIFY => 'Modify', - UR_ACTION_DELETE => 'Delete', - UR_ACTION_BULK_READ => 'Bulk Read', - UR_ACTION_BULK_MODIFY => 'Bulk Modify', - UR_ACTION_BULK_DELETE => 'Bulk Delete', + UR_ACTION_READ => Dict::S('UI:UserManagement:Action:Read'), + UR_ACTION_MODIFY => Dict::S('UI:UserManagement:Action:Modify'), + UR_ACTION_DELETE => Dict::S('UI:UserManagement:Action:Delete'), + UR_ACTION_BULK_READ => Dict::S('UI:UserManagement:Action:BulkRead'), + UR_ACTION_BULK_MODIFY => Dict::S('UI:UserManagement:Action:BulkModify'), + UR_ACTION_BULK_DELETE => Dict::S('UI:UserManagement:Action:BulkDelete'), ); $aAttributeActions = array( - UR_ACTION_READ => 'Read', - UR_ACTION_MODIFY => 'Modify', - UR_ACTION_BULK_READ => 'Bulk Read', - UR_ACTION_BULK_MODIFY => 'Bulk Modify', + UR_ACTION_READ => Dict::S('UI:UserManagement:Action:Read'), + UR_ACTION_MODIFY => Dict::S('UI:UserManagement:Action:Modify'), + UR_ACTION_BULK_READ => Dict::S('UI:UserManagement:Action:BulkRead'), + UR_ACTION_BULK_MODIFY => Dict::S('UI:UserManagement:Action:BulkModify'), ); // Determine allowed actions for the object @@ -178,9 +178,9 @@ function ComputeUserRights($oPage, $oUser, $oObject) ); } $aDisplayConfig = array(); - $aDisplayConfig['action'] = array('label' => 'Action', 'description' => ''); - $aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => ''); - $oPage->p('

            Actions

            '); + $aDisplayConfig['action'] = array('label' => Dict::S('UI:UserManagement:Action'), 'description' => Dict::S('UI:UserManagement:Action+')); + $aDisplayConfig['permission'] = array('label' => Dict::S('UI:UserManagement:Permission'), 'description' => Dict::S('UI:UserManagement:Permission+')); + $oPage->p('

            '.Dict::S('UI:UserManagement:Actions').'

            '); $oPage->table($aDisplayConfig, $aDisplayData); @@ -201,7 +201,7 @@ function ComputeUserRights($oPage, $oUser, $oObject) ); } } - $oPage->p('

            Attributes

            '); + $oPage->p('

            '.Dict::S('UI:UserManagement:Attributes').'

            '); if (count($aDisplayData) > 0) { $aDisplayConfig = array(); @@ -263,7 +263,7 @@ if ($iUser == -1) } else { - $oPage->p('

            How is it computing the user rights?

            '); + $oPage->p('

            How are the user rights computed?

            '); $oPage->p('

            1st, find the profiles that apply

            '); $oPage->p('

            Project the current object in every existing dimension

            '); From 280c8579beaebd9f7e3bb7f0d7853f508ba0cef7 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 16 May 2010 18:46:19 +0000 Subject: [PATCH 334/970] - Small bug fixes after a quick test by performing a clean installation on Windows/IE 8 SVN:trunk[417] --- application/wizardhelper.class.inc.php | 10 +++++++++- business/KEDB.business.php | 4 +--- core/attributedef.class.inc.php | 2 +- js/jquery.popupmenu.js | 1 - pages/ajax.render.php | 18 ++++++++++++------ 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index b6eb2927b5..14e61ea5cb 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -198,7 +198,15 @@ class WizardHelper public function GetIdForField($sFieldName) { - return $this->m_aData['m_oFieldsMap'][$sFieldName]; + $sResult = ''; + // It may happen that the field we'd like to update does not + // exist in the form. For example, if the field should be hidden/read-only + // in the current state of the object + if (isset($this->m_aData['m_oFieldsMap'][$sFieldName])) + { + $sResult = $this->m_aData['m_oFieldsMap'][$sFieldName]; + } + return $sResult; } static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet) diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 5edf8a823d..8a04bc432e 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -95,8 +95,7 @@ class lnkInfraError extends cmdbAbstractObject } -if (false) -{ + //////////////////////////////////////////////////////////////////////////////////// /** * n-n link between any Contract and a Document @@ -131,6 +130,5 @@ class lnkDocumentError extends cmdbAbstractObject MetaModel::Init_SetZListItems('list', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for a list } } -} ?> diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 44738b110e..2ea114bfd0 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1268,7 +1268,7 @@ class AttributeDateTime extends AttributeDBField } // Init static constant once for all (remove when PHP allows real static const) -AttributeDate::InitStatics(); +AttributeDateTime::InitStatics(); /** diff --git a/js/jquery.popupmenu.js b/js/jquery.popupmenu.js index dbdabb5d15..fe27e70f7f 100644 --- a/js/jquery.popupmenu.js +++ b/js/jquery.popupmenu.js @@ -18,7 +18,6 @@ jQuery.fn.popupmenu = function () { $(this).bind('mouseenter.popup_menu click.popup_menu', function (evt) { - console.log(evt.type); var previous_popup = popupmenu; var bMenuClosed = false; popupmenu = $(this).find('ul'); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index a3d1e8e2dd..58380c3ada 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -88,12 +88,18 @@ switch($operation) foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) { $sId = $oWizardHelper->GetIdForField($sAttCode); - $value = $oObj->Get($sAttCode); - $displayValue = $oObj->GetEditValue($sAttCode); - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', 0, array('this' => $oObj)); - - $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); + if ($sId != '') + { + // It may happen that the field we'd like to update does not + // exist in the form. For example, if the field should be hidden/read-only + // in the current state of the object + $value = $oObj->Get($sAttCode); + $displayValue = $oObj->GetEditValue($sAttCode); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', 0, array('this' => $oObj)); + + $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); + } } $oPage->add("\n"); break; From d65693598b28c9ac2c23617f1976949490abcf72 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 18 May 2010 14:34:36 +0000 Subject: [PATCH 335/970] New type of attribute: IP address SVN:trunk[418] --- business/itop.business.class.inc.php | 6 ++-- core/attributedef.class.inc.php | 47 +++++++++++++++++++++------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 1eec578d32..eb0cd1966d 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -815,8 +815,8 @@ class bizSubnet extends logInfra ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("ip", array("allowed_values"=>null, "sql"=>"ip", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mask", array("allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeIPAddress("ip", array("allowed_values"=>null, "sql"=>"ip", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeIPAddress("mask", array("allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); // Display lists MetaModel::Init_SetZListItems('details', array('name', 'ip','mask')); // Attributes to be displayed for the complete details @@ -898,7 +898,7 @@ class bizDevice extends logInfra MetaModel::Init_AddAttribute(new AttributeString("brand", array("allowed_values"=>null, "sql"=>"brand", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("model", array("allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("serial_number", array("allowed_values"=>null, "sql"=>"serial_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeIPAddress("mgmt_ip", array("allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); } public static function GetRelationQueries($sRelCode) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 2ea114bfd0..0e9f433b67 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -160,14 +160,6 @@ abstract class AttributeDefinition } public function GetValuesDef() {return null;} public function GetPrerequisiteAttributes() {return array();} - //public function IsSearchableStd() {return $this->Get("search_std");} - //public function IsSearchableGlobal() {return $this->Get("search_global");} - //public function IsMandatory() {return $this->Get("is_mandatory");} - //public function GetMinVal() {return $this->Get("min");} - //public function GetMaxVal() {return $this->Get("max");} - //public function GetSize() {return $this->Get("size");} - //public function GetCheckRegExp() {return $this->Get("regexp");} - //public function GetCheckFunc() {return $this->Get("checkfunc");} public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!) @@ -184,10 +176,7 @@ abstract class AttributeDefinition public function CheckValue($value) { - $sRegExp = $this->Get("regexp"); // ??? Does it exist ?? - if (empty($sRegExp)) return true; - - return preg_match(preg_escape($this->Get("regexp")), $value); + return true; } public function MakeValue() @@ -587,6 +576,19 @@ class AttributeString extends AttributeDBField public function GetEditClass() {return "String";} protected function GetSQLCol() {return "VARCHAR(255)";} + public function CheckValue($value) + { + $sRegExp = $this->GetValidationPattern(); + if (empty($sRegExp)) + { + return true; + } + else + { + return preg_match(preg_escape($sRegExp), $value); + } + } + public function GetBasicFilterOperators() { return array( @@ -809,6 +811,27 @@ class AttributeEmailAddress extends AttributeString } } +/** + * Specialization of a string: IP address + * + * @package iTopORM + * @author Romain Quetiez + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version $itopversion$ + */ +class AttributeIPAddress extends AttributeString +{ + public function GetTypeDesc() {return "IP address";} + + public function GetValidationPattern() + { + $sNum = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])'; + return "^($sNum\\.$sNum\\.$sNum\\.$sNum)$"; + } +} + /** * Specialization of a string: OQL expression * From 80425789a72806489bb7308736f2d4b14f9cdeac Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 18 May 2010 15:02:50 +0000 Subject: [PATCH 336/970] #98 Fixed bug in the computation of free IP addresses for a given subnet SVN:trunk[419] --- business/itop.business.class.inc.php | 31 +++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index eb0cd1966d..c7b559ad4a 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -836,8 +836,12 @@ class bizSubnet extends logInfra $bit_ip = ip2long($this->Get('ip')); $bit_mask = ip2long($this->Get('mask')); - $sIPMin = long2ip($bit_ip & $bit_mask); - $sIPMax = long2ip(($bit_ip | (~$bit_mask)) - 1); + + $iIPMin = $bit_ip & $bit_mask; + $iIPMax = ($bit_ip | (~$bit_mask)) - 1; + + $sIPMin = long2ip($iIPMin); + $sIPMax = long2ip($iIPMax); $oPage->p("Interfaces having an IP in the range: $sIPMin to $sIPMax"); @@ -845,7 +849,7 @@ class bizSubnet extends logInfra self::DisplaySet($oPage, $oIfSet); $iCountUsed = $oIfSet->Count(); - $iCountRange = ip2long($sIPMax) - ip2long($sIPMin); + $iCountRange = $iIPMax - $iIPMin; $iFreeCount = $iCountRange - $iCountUsed; $oPage->SetCurrentTab('Free IPs'); @@ -853,18 +857,21 @@ class bizSubnet extends logInfra $oPage->p("Here is an extract of 10 free IP addresses"); $aUsedIPs = $oIfSet->GetColumnAsArray('ip_address', false); - $i = 0; - while ($i < min($iFreeCount, 10)) + $iAnIP = $iIPMin; + $iFound = 0; + while (($iFound < min($iFreeCount, 10)) && ($iAnIP <= $iIPMax)) { - $i++; - - $iAnIP = ip2long($sIPMin) + $i; - if (in_array($iAnIP, $aUsedIPs)) continue; - $sAnIP = long2ip($iAnIP); - $oPage->p($sAnIP); + if (!in_array($sAnIP, $aUsedIPs)) + { + $iFound++; + $oPage->p($sAnIP); + } + else + { + } + $iAnIP++; } - } } From 08a8835fa53c544d90587e9ad403d6e9b57710a5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 21 May 2010 07:43:51 +0000 Subject: [PATCH 337/970] Removed the indexes for the attributes of type Enum SVN:trunk[420] --- core/attributedef.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 0e9f433b67..1e97abdb15 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -925,7 +925,7 @@ class AttributeEnum extends AttributeString public function RequiresIndex() { - return true; + return false; } public function GetBasicFilterOperators() From 2b365dca75b509d3b8f00715bd4c1fa47ecb0d93 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 24 May 2010 09:19:42 +0000 Subject: [PATCH 338/970] Deleted old files SVN:trunk[421] --- application/dialogstack.class.inc.php | 252 --------------- business/Changes-04-Sep-2007.php | 32 -- business/business_itopbegins.class.inc.php | 230 -------------- business/business_test.class.inc.php | 337 --------------------- config-test-mymodel.php | 36 --- 5 files changed, 887 deletions(-) delete mode 100644 application/dialogstack.class.inc.php delete mode 100644 business/Changes-04-Sep-2007.php delete mode 100644 business/business_itopbegins.class.inc.php delete mode 100644 business/business_test.class.inc.php delete mode 100644 config-test-mymodel.php diff --git a/application/dialogstack.class.inc.php b/application/dialogstack.class.inc.php deleted file mode 100644 index f3e54bbf32..0000000000 --- a/application/dialogstack.class.inc.php +++ /dev/null @@ -1,252 +0,0 @@ -$sArgValue) - { - if (strstr($sArgName, "dlgstack_go,")) - { - $aTokens = explode(",", $sArgName); - return self::ArgNameDecode($aTokens[1]); - } - } - return ""; - } - - /** - * Protect against weird effects of PHP interpreting brackets... - */ - static private function ArgNameEncode($sArgName) - { - return str_replace(array('[', ']'), array('_bracket_open_', '_bracket_close_'), $sArgName); - } - static private function ArgNameDecode($sCodedArgName) - { - return str_replace(array('_bracket_open_', '_bracket_close_'), array('[', ']'), $sCodedArgName); - } - - /** - * True if the current page has been loaded from an "dialog startup button" - */ - static public function IsDialogStartup() - { - return (strlen(self::GetRetArgName()) > 0); - } - - - /** - * Helper to - */ - static private function RemoveArg(&$aValues, $sKey, &$retval = null) - { - if (isset($aValues[$sKey])) - { - if (empty($retval)) - { - $retval = $aValues[$sKey]; - } - unset($aValues[$sKey]); - } - } - - - /** - * Record current page args, and returns the initial value for the dialog - */ - static public function StartDialog() - { - if (!isset($_SESSION['dialogstack_currdlg'])) - { - // Init stack - $_SESSION['dialogstack_currdlg'] = array(); - } - - $sRetArgName = self::GetRetArgName(); - $sCodedArgName = self::ArgNameEncode($sRetArgName); - - $sArgForRetArgName = "dlgstack_init_".$sCodedArgName; - $sButtonName = "dlgstack_go,".$sCodedArgName; - - // Do not record utility arguments, neither the current value (stored separately) - // - $initValue = null; - $aPost = $_POST; - self::RemoveArg($aPost, $sArgForRetArgName, $initValue); - self::RemoveArg($aPost, $sButtonName); - self::RemoveArg($aPost, 'dlgstack_onok_page', $sOnOKPage); - self::RemoveArg($aPost, 'dlgstack_onok_args', $aOnOKArgs); - $aGet = $_GET; - self::RemoveArg($aGet, $sArgForRetArgName, $initValue); - self::RemoveArg($aGet, $sButtonName); - self::RemoveArg($aGet, 'dlgstack_onok_page', $sOnOKPage); - self::RemoveArg($aGet, 'dlgstack_onok_args', $aOnOKArgs); - - if (self::$m_bCurrPageDeclared) - { - throw new Exception("DeclareCaller() must not be called before StartDialog()"); - } - - $aCall = array( - "title"=>$_SESSION['dialogstack_callertitle'], - "uri"=>$_SESSION['dialogstack_calleruri'], - "post"=>$aPost, - "get"=>$aGet, - "retarg"=>$sRetArgName, - "initval"=>$initValue, - ); - if (isset($sOnOKPage)) $aCall["onok_page"] = $sOnOKPage; - if (isset($aOnOKArgs)) $aCall["onok_args"] = $aOnOKArgs; - - array_push($_SESSION['dialogstack_currdlg'], $aCall); - return $initValue; - } - /** - * Render a button to launch a new dialog - */ - static public function RenderEditableField($sTitle, $sArgName, $sCurrValue, $bAddFieldValue, $sOnOKPage = "", $aOnOKArgs = array()) - { - $sRet = ""; - $sCodedArgName = self::ArgNameEncode($sArgName); - if ($bAddFieldValue) - { - $sRet .= "\n"; - } - $sRet .= "\n"; - $sRet .= "\n"; - if (!empty($sOnOKPage)) - { - $sRet .= "\n"; - } - foreach($aOnOKArgs as $sArgName=>$value) - { - $sRet .= "\n"; - } - return $sRet; - } - /** - * Render a [set of] hidden field, from a value that may be an array - */ - static private function RenderHiddenField($sName, $value) - { - $sRet = ""; - if (is_array($value)) - { - foreach($value as $sKey=>$subvalue) - { - $sRet .= self::RenderHiddenField($sName.'['.$sKey.']', $subvalue); - } - } - else - { - $sRet .= "\n"; - } - return $sRet; - } - /** - * Render a form to end the current dialog and return to the caller - */ - static public function RenderEndDialogForm($iButtonStyle, $sTitle, $sRetValue = null) - { - $aCall = end($_SESSION['dialogstack_currdlg']); - if (!$aCall) return; - return self::privRenderEndDialogForm($aCall, $iButtonStyle, $sTitle, $sRetValue); - } - - /** - * Returns an array of buttons to get back to upper dialog levels - */ - static public function GetCurrentStack() - { - $aRet = array(); - if (isset($_SESSION['dialogstack_currdlg'])) - { - foreach ($_SESSION['dialogstack_currdlg'] as $aCall) - { - $aRet[] = self::privRenderEndDialogForm($aCall, DLGSTACK_CANCEL, $aCall["title"]); - } - } - return $aRet; - } - - /** - * Render a form to end the current dialog and return to the caller - */ - static private function privRenderEndDialogForm($aCall, $iButtonStyle, $sTitle, $sRetValue = null) - { - if (($iButtonStyle == DLGSTACK_OK) && isset($aCall["onok_page"])) $sFormAction = $aCall["onok_page"]; - else $sFormAction = $aCall["uri"]; - - $sRet = "
            \n"; - foreach ($aCall["post"] as $sName=>$value) - { - $sRet .= self::RenderHiddenField($sName, $value); - } - if ($iButtonStyle == DLGSTACK_OK) - { - if (isset($aCall["onok_args"])) - { - foreach($aCall["onok_args"] as $sArgName=>$value) - { - $sRet .= "\n"; - } - } - $sRet .= "\n"; - $sRet .= "\n"; - } - elseif ($iButtonStyle == DLGSTACK_CANCEL) - { - if (!is_null($aCall["initval"])) - { - $sRet .= "\n"; - } - $sRet .= "\n"; - } - else - { - throw new Exception("Wrong value for button style ($iButtonStyle)"); - } - $sRet .= "\n"; - $sRet .= "
            \n"; - return $sRet; - } -} -?> diff --git a/business/Changes-04-Sep-2007.php b/business/Changes-04-Sep-2007.php deleted file mode 100644 index e8097d75e0..0000000000 --- a/business/Changes-04-Sep-2007.php +++ /dev/null @@ -1,32 +0,0 @@ -Changements principaux: -- la classe AbstractObject est sortie du biz model -- join_type remplac par is_null_allowed (plac la fin pour tre + facile retrouver) -- j'ai enlev toute la classe logLocatedObject qui tait en commentaire -- Enlev 'address' de l'advanced search sur une location car ce n'est plus un critre de recherche possible (remplac par country) -- Ajout des critres de recherche sur bizCircuit -- Ajout les ZList sur bizCircuit -- Ajout les Zlist pour bizInterface -- Ajout les Zlist pour lnkInfraInfra -- Ajout les Zlist pour lnkInfraTicket - -Dans AbstractObject: dsactiv l'affichage des contacts lis qui ne marche pas pour les tickets. - -Bug fix ? -- J'ai rajout un blindage if (is_object($proposedValue) &&... dans AttributeDate::MakeRealValue mais je ne comprends pas d'o sort la classe DateTime... et pourtant il y en a... - -Amliorations: -- Ajouter une vrification des ZList (les attributs/critresde recherche dclars dans la liste existent-ils pour cet objet) - -Ne marche pas: -- Objets avec des clefs externes vides -- Enums !!!! - -Data Generator: -Organization '1' updated. -5 Location objects created. -19 PC objects created. -19 Network Device objects created. -42 Person objects created. -6 Incident objects created. -17 Infra Group objects created. -34 Infra Infra objects created. diff --git a/business/business_itopbegins.class.inc.php b/business/business_itopbegins.class.inc.php deleted file mode 100644 index c63e5c3db1..0000000000 --- a/business/business_itopbegins.class.inc.php +++ /dev/null @@ -1,230 +0,0 @@ - - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ - - -/////////////////////////////////////////////////////////////////////////////// -// Business implementation demo -/////////////////////////////////////////////////////////////////////////////// - - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbContact extends CMDBObject -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "att_contact_name", - "state_attcode" => "", - "reconc_keys" => array("att_contact_name"), - "db_table" => "contact", - "db_key_field" => "contactid", - "db_finalclass_field" => "actualclass", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_contact_name", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeInteger("att_contact_availability", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"availability"))); - MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbPerson extends cmdbContact -{ - public static function Init() - { - $oValsDunsNumber = new ValueSetObjects("cmdbCompany: att_company_dunsnumber Begins with '$[duns_prm::]'", "att_company_dunsnumber", array("att_company_dunsnumber"=>true)); - - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "att_contact_name", - "state_attcode" => "", - "reconc_keys" => array("att_contact_name"), - "db_table" => "person", - "db_key_field" => "personid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_person_email", array("allowed_values"=>$oValsDunsNumber, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"email"))); - MetaModel::Init_AddAttribute(new AttributeString("att_person_name", array("allowed_values"=>new ValueSetEnum(array("nom1", "nom2", "nom10", "no", "noms", "")), "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name"))); - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbSubcontractor extends cmdbPerson -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "att_contact_name", - "state_attcode" => "", - "reconc_keys" => array("att_contact_name"), - "db_table" => "subcontractor", - "db_key_field" => "subcontractorid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_contractinfo", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"contractinfo"))); - - MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_provider", array("allowed_values"=>null, "sql"=>"provider", "targetclass"=>"cmdbProvider", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_provider_ref", array("allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_provider", "target_attcode"=>"att_provider_ref"))); - - MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_tutor", array("allowed_values"=>null, "sql"=>"tutor", "targetclass"=>"cmdbPerson", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_email", array("allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_email"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_secondname", array("allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_name"))); - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbCrowd extends cmdbObject -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "att_crowd_peoplecount", - "state_attcode" => "", - "reconc_keys" => array("att_crowd_peoplecount"), - "db_table" => "crowd", - "db_key_field" => "crowdid", - "db_finalclass_field" => "crowdclass", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeInteger("att_crowd_peoplecount", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"peoplecount"))); - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbCompany extends cmdbCrowd -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "att_company_dunsnumber", - "state_attcode" => "", - "reconc_keys" => array("att_company_dunsnumber"), - "db_table" => "company", - "db_key_field" => "companyid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("att_company_dunsnumber", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"dunsnumber"))); - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbProvider extends cmdbCompany -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "att_provider_ref", - "state_attcode" => "", - "reconc_keys" => array("att_provider_ref"), - "db_table" => "provider", - "db_key_field" => "providerid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeInteger("att_provider_ref", array("allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"providerref"))); - } -} - - -?> diff --git a/business/business_test.class.inc.php b/business/business_test.class.inc.php deleted file mode 100644 index e065026f9a..0000000000 --- a/business/business_test.class.inc.php +++ /dev/null @@ -1,337 +0,0 @@ - - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ - -/////////////////////////////////////////////////////////////////////////////// -// Business implementation demo -/////////////////////////////////////////////////////////////////////////////// - -MetaModel::RegisterRelation("Potes", array("description"=>"ceux dont l'email ressemble au mien", "verb_down"=>"est pote de", "verb_up"=>"est pote de")); - - -MetaModel::RegisterZList("list1", array("description"=>"une premiere list, just for fun", "type"=>"attributes")); -MetaModel::RegisterZList("list2", array("description"=>"la secunda e meliora", "type"=>"attributes")); -MetaModel::RegisterZList("list3", array("description"=>"la variante qui tue", "type"=>"filters")); - - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbObjectHomeMade extends cmdbObject -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "", - "state_attcode" => "", - "reconc_keys" => array(""), - "db_table" => "", - "db_key_field" => "", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - } - - public static function GetRelationQueries($sRelCode) - { - //trigger_error("GetRelationQueries: cmdbObjectHomeMade"); - switch ($sRelCode) - { - case "Potes": - $aRels = array("xxxx" => array("sQuery"=>"SELECT cmdbContact AS c WHERE c.id = 40", "bPropagate"=>true, "iDistance"=>3)); - return $aRels; - } - } -} - - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbContact extends cmdbObjectHomeMade -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "name", - "state_attcode" => "etat", - "reconc_keys" => array("name"), - "db_table" => "contact", - "db_key_field" => "contactid", - "db_finalclass_field" => "actualclass", - ); - MetaModel::Init_Params($aParams); - //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("etat", array("allowed_values"=>new ValueSetEnum('justborn, 15, 21'), "sql"=>"etat", "default_value"=>"justborn", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("email", array("allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("owner", array("allowed_values"=>null, "sql"=>"ownerorg", "targetclass"=>"cmdbOrga", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ownername", array("allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_name_"))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ownertnut", array("allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_dunsnumber_"))); - - MetaModel::Init_AddAttribute(new AttributeLinkedSet("myworkshops", array("depends_on"=>array(), "linked_class"=>"cmdbLiens", "ext_key_to_me"=>"tocontact", "count_min"=>1, "count_max"=>10, "allowed_values"=>null))); - - MetaModel::Init_SetZListItems("list1", array("name", "email")); - MetaModel::Init_SetZListItems("list2", array()); - MetaModel::Init_SetZListItems("list3", array("ownername")); - - MetaModel::Init_DefineState("justborn", array("attribute_inherit"=>null, "attribute_list"=>array("owner"=>OPT_ATT_MANDATORY))); - MetaModel::Init_DefineState("15", array("attribute_inherit"=>"justborn", "attribute_list"=>array("owner"=>OPT_ATT_MUSTPROMPT, "email"=>OPT_ATT_MUSTPROMPT))); - MetaModel::Init_DefineState("21", array("attribute_inherit"=>"15", "attribute_list"=>array("email"=>OPT_ATT_READONLY|OPT_ATT_MUSTCHANGE))); - - MetaModel::Init_DefineStimulus(new StimulusUserAction("toschool", array())); - MetaModel::Init_DefineStimulus(new StimulusUserAction("raise", array())); - - MetaModel::Init_DefineTransition("justborn", "toschool", array("target_state"=>"15", "actions"=>array('MyLifecycleHandler', 'MyLifecycleHandler2'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("15", "raise", array("target_state"=>"21", "actions"=>null, "user_restriction"=>null)); - } - - public static function GetRelationQueries($sRelCode) - { - //trigger_error("GetRelationQueries: cmdbContact"); - switch ($sRelCode) - { - case "Potes": - $aRels = array( - "zz1" => array("sQuery"=>"SELECT cmdbContact AS c WHERE c.name = '\$[this.name::]'", "bPropagate"=>false, "iDistance"=>3), - "zz2" => array("sQuery"=>"SELECT cmdbContact AS c WHERE c.owner = \$[this.owner::] AND c.owner != 2", "bPropagate"=>false, "iDistance"=>3), - ); - return array_merge($aRels, parent::GetRelationQueries($sRelCode)); - } - } - - public function MyLifecycleHandler($sStimulusCode) - { - echo "

            youhou!

            "; - return true; - } - public function MyLifecycleHandler2($sStimulusCode) - { - echo "

            ... les papous...

            "; - return true; - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbTeam extends cmdbContact -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "email", - "state_attcode" => "", - "reconc_keys" => array("email"), - "db_table" => "team", - "db_key_field" => "teamid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - MetaModel::Init_OverloadAttributeParams("email", array()); - MetaModel::Init_AddAttribute(new AttributeInteger("headcount", array("allowed_values"=>null, "sql"=>"headcount", "default_value"=>654321, "is_null_allowed"=>false, "depends_on"=>array()))); - - MetaModel::Init_SetZListItems("noneditable", array("name")); - } - - public function ComputeValues() - { - //echo "Set(), function ComputeValues has been found for ".get_class($this)."
            \n"; - $this->Set("name", $this->Get("email")." and ".$this->Get("headcount")); - } - - public static function GetRelationQueries($sRelCode) - { - //trigger_error("GetRelationQueries: cmdbTeam"); - switch ($sRelCode) - { - case "Potes": - //$aRels = array("Relies on" => array("sQuery"=>"cmdbContact: name Begins with 'Louis'", "bPropagate"=>false, "iDistance"=>3)); - return array_merge(array(), parent::GetRelationQueries($sRelCode)); - } - } -} - - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbOrga extends cmdbObjectHomeMade -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "", - "key_label" => "", - "name_attcode" => "_name_", - "state_attcode" => "", - "reconc_keys" => array("_name_"), - "db_table" => "organization", - "db_key_field" => "orgid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("_name_", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("_status_", array("allowed_values"=>null, "sql"=>"status", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumber_", array("allowed_values"=>null, "sql"=>"dunsnumber", "default_value"=>99007, "is_null_allowed"=>false, "depends_on"=>array()))); -// not yet allowed MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumberBY2_", array("allowed_values"=>null, "sql"=>"dunsnumber * 3.141592654"))); - - MetaModel::Init_SetZListItems("list1", array("_status_")); - MetaModel::Init_SetZListItems("list2", array()); - MetaModel::Init_SetZListItems("list3", array("_name_")); - } - -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbLiens extends cmdbObjectHomeMade -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "function", - "state_attcode" => "", - "reconc_keys" => array("function"), - "db_table" => "role_ws", - "db_key_field" => "linkid", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("function", array("allowed_values"=>null, "sql"=>"function", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("a1", array("allowed_values"=>null, "sql"=>"a1", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("a2", array("allowed_values"=>null, "sql"=>"a2", "default_value"=>"XXXX", "is_null_allowed"=>true, "depends_on"=>array()))); - - // What makes it being a link... - MetaModel::Init_AddAttribute(new AttributeExternalKey("toworkshop", array("allowed_values"=>null, "sql"=>"ws_id", "targetclass"=>"cmdbWorkshop", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("ws_info", array("allowed_values"=>null, "extkey_attcode"=>"toworkshop", "target_attcode"=>"namitus"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("tocontact", array("allowed_values"=>null, "sql"=>"contactid", "targetclass"=>"cmdbContact", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("contact_info", array("allowed_values"=>null, "extkey_attcode"=>"tocontact", "target_attcode"=>"name"))); - - MetaModel::Init_SetZListItems("list1", array("toworkshop", "contact_info")); - MetaModel::Init_SetZListItems("list2", array("function")); - MetaModel::Init_SetZListItems("list3", array("function")); - } - - public static function GetRelationQueries($sRelCode) - { - throw new CoreException("GetRelationQueries: cmdbLiens"); - return array("Relies on" => array("sQuery"=>"", "bPropagate"=>true, "iDistance"=>3)); - } -} - -/** - * blah blah - * - * @package iTopUnitTests - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -class cmdbWorkshop extends cmdbObjectHomeMade -{ - public static function Init() - { - $aParams = array - ( - "category" => "blah", - "key_type" => "autoincrement", - "key_label" => "", - "name_attcode" => "namitus", - "state_attcode" => "", - "reconc_keys" => array("namitus"), - "db_table" => "workshop", - "db_key_field" => "ws_id", - "db_finalclass_field" => "", - ); - MetaModel::Init_Params($aParams); - //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("namitus", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array()))); - - MetaModel::Init_SetZListItems("list1", array("namitus")); - MetaModel::Init_SetZListItems("list2", array()); - MetaModel::Init_SetZListItems("list3", array("namitus")); - } - - public static function GetRelationQueries($sRelCode) - { - throw new CoreException("GetRelationQueries: cmdbWorkshop"); - return array("Relies on" => array("sQuery"=>"", "bPropagate"=>true, "iDistance"=>3)); - } -} - - -?> diff --git a/config-test-mymodel.php b/config-test-mymodel.php deleted file mode 100644 index e3176169d9..0000000000 --- a/config-test-mymodel.php +++ /dev/null @@ -1,36 +0,0 @@ - 'localhost', - 'db_user' => 'itop', - 'db_pwd' => '1T0p', - 'db_name' => 'TestBizModelGenericItop', - 'db_subname' => 'tribute2itop', // use it to differentiate two applications instances running on the same DB -); - -// Modules: file names should be specified as a absolute paths - -$MyModules = array( - 'application' => array ( - // to be continued... - ), - 'business' => array ( - '../business/business_test.class.inc.php' - // to be continued... - ), - 'addons' => array ( - // other modules to come later - ) -); - - -?> From a0e03718129693f6f694ca57c8eb34eada05e4e0 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 24 May 2010 09:24:35 +0000 Subject: [PATCH 339/970] #19 - Reviewed the licensing information and started to align the comments in the code SVN:trunk[422] --- .../userrights/userrightsmatrix.class.inc.php | 29 ++-- .../userrights/userrightsnull.class.inc.php | 26 +++- .../userrightsprofile.class.inc.php | 26 +++- application/ajaxwebpage.class.inc.php | 27 +++- application/application.inc.php | 25 ++- application/applicationcontext.class.inc.php | 24 +++ application/audit.category.class.inc.php | 23 ++- application/audit.rule.class.inc.php | 23 ++- application/cmdbabstract.class.inc.php | 32 +++- application/csvpage.class.inc.php | 26 +++- application/displayblock.class.inc.php | 24 +++ application/iotask.class.inc.php | 24 +++ application/itopwebpage.class.inc.php | 24 +++ application/itopwizardwebpage.class.inc.php | 24 +++ application/loginwebpage.class.inc.php | 24 +++ application/menunode.class.inc.php | 24 +++ application/nicewebpage.class.inc.php | 24 +++ application/startup.inc.php | 24 +++ application/template.class.inc.php | 24 +++ application/ui.linkswidget.class.inc.php | 24 +++ application/uilinkswizard.class.inc.php | 24 +++ application/uiwizard.class.inc.php | 26 +++- application/usercontext.class.inc.php | 24 +++ application/utils.inc.php | 40 ++++- application/webpage.class.inc.php | 24 +++ application/wizardhelper.class.inc.php | 24 +++ application/xmlpage.class.inc.php | 24 +++ business/ChangeMgmt.business.php | 24 +++ business/KEDB.business.php | 24 +++ business/ServiceDesk.business.php | 28 ++-- business/ServiceMgmt.business.php | 23 +++ business/ServiceRequest.business.php | 23 ++- business/data.samples.inc.php | 28 ++-- business/incidentMgmt.business.php | 28 ++-- business/itop.business.class.inc.php | 61 +++----- business/test_farm.class.inc.php | 29 ++-- core/MyHelpers.class.inc.php | 33 +++- core/action.class.inc.php | 44 +++--- core/archive.class.inc.php | 29 ++-- core/attributedef.class.inc.php | 145 +++--------------- core/bulkchange.class.inc.php | 51 +++--- core/cmdbchange.class.inc.php | 30 +++- core/cmdbchangeop.class.inc.php | 60 +++----- core/cmdbobject.class.inc.php | 48 +++--- core/cmdbsource.class.inc.php | 34 ++-- core/config.class.inc.php | 39 +++-- core/coreexception.class.inc.php | 24 +++ core/csvparser.class.inc.php | 37 +++-- core/data.generator.class.inc.php | 25 ++- core/dbobject.class.php | 34 ++-- core/dbobjectsearch.class.php | 38 +++-- core/dbobjectset.class.php | 31 +++- core/dict.class.inc.php | 35 +++-- core/email.class.inc.php | 26 +++- core/event.class.inc.php | 30 +++- core/expression.class.inc.php | 25 ++- core/filterdef.class.inc.php | 41 +++-- core/log.class.inc.php | 29 ++-- core/metamodel.class.php | 30 +++- core/oql/oql-lexer.plex | 24 +++ core/oql/oqlexception.class.inc.php | 24 +++ core/oql/oqlinterpreter.class.inc.php | 24 +++ core/oql/oqlquery.class.inc.php | 23 +++ core/ormdocument.class.inc.php | 32 +++- core/sqlquery.class.inc.php | 31 +++- core/stimulus.class.inc.php | 30 +++- core/test.class.inc.php | 81 +++------- core/trigger.class.inc.php | 31 +++- core/userrights.class.inc.php | 39 ++--- core/valuesetdef.class.inc.php | 54 +++---- dictionaries/dictionary.itop.core.php | 24 +++ dictionaries/dictionary.itop.model.php | 24 +++ dictionaries/dictionary.itop.ui.php | 24 +++ dictionaries/fr.dictionary.itop.ui.php | 24 +++ pages/ITopConsultant.php | 29 +++- pages/UI.php | 24 +++ pages/UniversalSearch.php | 24 +++ pages/ajax.csvimport.php | 24 +++ pages/ajax.render.php | 24 +++ pages/audit.php | 24 +++ pages/csvimport.php | 21 ++- pages/graphviz.php | 24 +++ pages/opensearch.xml.php | 24 +++ pages/run_query.php | 24 +++ pages/schema.php | 24 +++ pages/test.php | 24 +++ pages/testlist.inc.php | 24 +++ pages/usermanagement_classproj.php | 25 +++ pages/usermanagement_profileproj.php | 25 +++ pages/usermanagement_userstatus.php | 25 +++ setup/ajax.dataloader.php | 24 +++ setup/email.test.php | 24 +++ setup/index.php | 21 +++ setup/setuppage.class.inc.php | 25 ++- setup/xmldataloader.class.inc.php | 24 +++ webservices/export.php | 24 +++ webservices/import.php | 26 +++- webservices/itop.wsdl.php | 24 +++ webservices/itopsoap.examples.php | 24 +++ webservices/itopsoaptypes.class.inc.php | 24 +++ webservices/soapserver.php | 27 +++- webservices/webservices.class.inc.php | 44 ++++-- 102 files changed, 2375 insertions(+), 687 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 09d78cb00f..fa78641ef4 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -1,19 +1,28 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ - class UserRightsMatrixUsers extends DBObject { public static function Init() diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index bf43fdebe2..82d6a00d8c 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -1,19 +1,29 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ - class UserRightsNull extends UserRightsAddOnAPI { // Installation: create the very first user diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index fb827dd549..21434c26b7 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -1,19 +1,29 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ - define('ADMIN_PROFILE_ID', 1); class UserRightsBaseClass extends cmdbAbstractObject diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 18f00f72eb..5866c5673d 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -1,15 +1,30 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +require_once("../application/webpage.class.inc.php"); class ajax_page extends WebPage { diff --git a/application/application.inc.php b/application/application.inc.php index f613ff1e68..962b88e710 100644 --- a/application/application.inc.php +++ b/application/application.inc.php @@ -1,5 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/applicationcontext.class.inc.php'); require_once('../application/usercontext.class.inc.php'); require_once('../application/cmdbabstract.class.inc.php'); diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index e23ddd390b..85f9daab95 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once("../application/utils.inc.php"); /** * Helper class to store and manipulate the parameters that make the application's context diff --git a/application/audit.category.class.inc.php b/application/audit.category.class.inc.php index 14e04b5907..83b6bce900 100644 --- a/application/audit.category.class.inc.php +++ b/application/audit.category.class.inc.php @@ -1,11 +1,32 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +require_once('../application/cmdbabstract.class.inc.php'); + class AuditCategory extends cmdbAbstractObject { public static function Init() diff --git a/application/audit.rule.class.inc.php b/application/audit.rule.class.inc.php index d343451775..614a5e1545 100644 --- a/application/audit.rule.class.inc.php +++ b/application/audit.rule.class.inc.php @@ -1,12 +1,33 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +require_once('../application/audit.category.class.inc.php'); + class AuditRule extends cmdbAbstractObject { public static function Init() diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index a1bd816de4..9e7c8b9fea 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1,14 +1,34 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../core/cmdbobject.class.inc.php'); require_once('../application/utils.inc.php'); require_once('../application/applicationcontext.class.inc.php'); require_once('../application/ui.linkswidget.class.inc.php'); -//////////////////////////////////////////////////////////////////////////////////// -/** -* Abstract class that implements some common and useful methods for displaying -* the objects -*/ -//////////////////////////////////////////////////////////////////////////////////// + abstract class cmdbAbstractObject extends CMDBObject { diff --git a/application/csvpage.class.inc.php b/application/csvpage.class.inc.php index 2912f62a81..f4746d56f9 100644 --- a/application/csvpage.class.inc.php +++ b/application/csvpage.class.inc.php @@ -1,9 +1,31 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +require_once("../application/webpage.class.inc.php"); + class CSVPage extends WebPage { function __construct($s_title) @@ -32,4 +54,4 @@ class CSVPage extends WebPage } } -?> \ No newline at end of file +?> diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index e6e8d3aea9..a161d31b2d 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/webpage.class.inc.php'); require_once('../application/utils.inc.php'); require_once('../core/userrights.class.inc.php'); diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index 06a9c0022f..33310ee0bd 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/cmdbabstract.class.inc.php'); /** diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index a4e64148f8..9d933fbc58 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once("../application/nicewebpage.class.inc.php"); require_once("../application/usercontext.class.inc.php"); require_once("../application/applicationcontext.class.inc.php"); diff --git a/application/itopwizardwebpage.class.inc.php b/application/itopwizardwebpage.class.inc.php index ba5655ae2d..3b03af1b6b 100644 --- a/application/itopwizardwebpage.class.inc.php +++ b/application/itopwizardwebpage.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('itopwebpage.class.inc.php'); /** * Web page to display a wizard in the iTop framework diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 9f5b68d37a..1fdfb13294 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once("../application/nicewebpage.class.inc.php"); /** * Web page used for displaying the login form diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 71a9d85b7c..a29f8b8173 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../core/attributedef.class.inc.php'); require_once('../core/filterdef.class.inc.php'); require_once('../core/stimulus.class.inc.php'); diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index 26e729e93f..a9dfbc5f69 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once("../application/webpage.class.inc.php"); /** * Web page with some associated CSS and scripts (jquery) for a fancier display diff --git a/application/startup.inc.php b/application/startup.inc.php index d7a3854cb8..0ccf697e99 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + define('ITOP_VERSION', '0.9'); define('ITOP_REVISION', '$WCREV$'); define('ITOP_BUILD_DATE', '$WCNOW$'); diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 5fb74476fc..3b09ee3331 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/displayblock.class.inc.php'); /** * This class manages the special template format used internally to build the iTop web pages diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index a841cd695d..67ff3ea1cf 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/webpage.class.inc.php'); require_once('../application/displayblock.class.inc.php'); diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index b58dca947b..0006641caf 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + class UILinksWizard { protected $m_sClass; diff --git a/application/uiwizard.class.inc.php b/application/uiwizard.class.inc.php index f1bf81256e..257cbcc57f 100644 --- a/application/uiwizard.class.inc.php +++ b/application/uiwizard.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + class UIWizard { protected $m_oPage; @@ -153,7 +177,7 @@ $sJSHandlerCode foreach(MetaModel::GetAttributesList($this->m_sClass) as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); - if (!$oAttDef->IsExternalField() && !$oAttDef->IsNullAllowed() && + if (!$oAttDef->IsExternalField() && !$oAttDef->IsNullAllowed() && $oAttDef->IsWritable() && ($sAttCode != $sStateAttCode) ) { $aMandatoryAttributes[$sAttCode] = OPT_ATT_MANDATORY; diff --git a/application/usercontext.class.inc.php b/application/usercontext.class.inc.php index 89b89b93dc..e9ea9d8e40 100644 --- a/application/usercontext.class.inc.php +++ b/application/usercontext.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../core/cmdbobject.class.inc.php'); require_once('../core/userrights.class.inc.php'); /** diff --git a/application/utils.inc.php b/application/utils.inc.php index 21833f5f56..490db8459b 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../core/config.class.inc.php'); define('ITOP_CONFIG_FILE', '../config-itop.php'); @@ -156,15 +180,15 @@ class utils static public function GetAbsoluteUrl($bQueryString = true, $bForceHTTPS = false) { // Build an absolute URL to this page on this server/port - $sServerName = $_SERVER['SERVER_NAME']; - if ($bForceHTTPS) - { - $sProtocol = 'https'; - $sPort = ''; + $sServerName = $_SERVER['SERVER_NAME']; + if ($bForceHTTPS) + { + $sProtocol = 'https'; + $sPort = ''; } - else - { - $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + else + { + $sProtocol = isset($_SERVER['HTTPS']) ? 'https' : 'http'; if ($sProtocol == 'http') { $sPort = ($_SERVER['SERVER_PORT'] == 80) ? '' : ':'.$_SERVER['SERVER_PORT']; diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index 0ffc9584ff..6d0cec3756 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * Simple helper class to ease the production of HTML pages * diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 14e61ea5cb..9dccd4e80e 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/uiwizard.class.inc.php'); class WizardHelper diff --git a/application/xmlpage.class.inc.php b/application/xmlpage.class.inc.php index 24a81c5627..2d0d925539 100644 --- a/application/xmlpage.class.inc.php +++ b/application/xmlpage.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once("../application/webpage.class.inc.php"); /** * Simple web page with no includes or fancy formatting, useful to generateXML documents diff --git a/business/ChangeMgmt.business.php b/business/ChangeMgmt.business.php index 45914799c3..852c976d55 100644 --- a/business/ChangeMgmt.business.php +++ b/business/ChangeMgmt.business.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + //////////////////////////////////////////////////////////////////////////////////// /** * A Change Ticket diff --git a/business/KEDB.business.php b/business/KEDB.business.php index 8a04bc432e..0b5860f2ea 100644 --- a/business/KEDB.business.php +++ b/business/KEDB.business.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + //////////////////////////////////////////////////////////////////////////////////// /** diff --git a/business/ServiceDesk.business.php b/business/ServiceDesk.business.php index 23b94e0484..efeff71f43 100644 --- a/business/ServiceDesk.business.php +++ b/business/ServiceDesk.business.php @@ -1,16 +1,26 @@ - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ //////////////////////////////////////////////////////////////////////////////////// diff --git a/business/ServiceMgmt.business.php b/business/ServiceMgmt.business.php index 05fe1998b1..9f4830d7b2 100644 --- a/business/ServiceMgmt.business.php +++ b/business/ServiceMgmt.business.php @@ -1,4 +1,27 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ //////////////////////////////////////////////////////////////////////////////////// /** diff --git a/business/ServiceRequest.business.php b/business/ServiceRequest.business.php index cd9ae5512c..a45d00ac6f 100644 --- a/business/ServiceRequest.business.php +++ b/business/ServiceRequest.business.php @@ -1,6 +1,27 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ //////////////////////////////////////////////////////////////////////////////////// /** diff --git a/business/data.samples.inc.php b/business/data.samples.inc.php index 8f76a3bd17..f3c63056b2 100644 --- a/business/data.samples.inc.php +++ b/business/data.samples.inc.php @@ -1,16 +1,26 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ $aCompanies = array(); diff --git a/business/incidentMgmt.business.php b/business/incidentMgmt.business.php index 54bbb6623f..af55e1cc5b 100644 --- a/business/incidentMgmt.business.php +++ b/business/incidentMgmt.business.php @@ -1,16 +1,26 @@ - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ //////////////////////////////////////////////////////////////////////////////////// diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index c7b559ad4a..497a49aae2 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -1,21 +1,32 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/cmdbabstract.class.inc.php'); require_once('../application/template.class.inc.php'); -/** - * itop.business.class.inc.php - * User defined objects, implements the business need - * - * @package iTopBizModelSamples - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ - /** * Possible values for the statuses of objects */ @@ -37,18 +48,6 @@ MetaModel::RegisterRelation("impacts", array("description"=>"objects being funct * Organization ownership might be used to manage the R/W access to the object */ //////////////////////////////////////////////////////////////////////////////////// -/** - * itop.business.class.inc.php - * User defined objects, implements the business need - * - * @package iTopBizModelSamples - * @author Erwan Taloc - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ class bizOrganization extends cmdbAbstractObject { public static function Init() @@ -104,18 +103,6 @@ class bizOrganization extends cmdbAbstractObject * can be documented by Documents */ //////////////////////////////////////////////////////////////////////////////////// -/** - * itop.business.class.inc.php - * User defined objects, implements the business need - * - * @package iTopBizModelSamples - * @author Erwan Taloc - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ class logRealObject extends cmdbAbstractObject { public static function Init() diff --git a/business/test_farm.class.inc.php b/business/test_farm.class.inc.php index dcc524fa63..4b3cefd5f6 100644 --- a/business/test_farm.class.inc.php +++ b/business/test_farm.class.inc.php @@ -1,17 +1,26 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ /////////////////////////////////////////////////////////////////////////////// diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php index fded6913ad..606be35470 100644 --- a/core/MyHelpers.class.inc.php +++ b/core/MyHelpers.class.inc.php @@ -1,18 +1,35 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * MyHelpers - * various dev/debug helpers, to cleanup or at least re-organize * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ - class MyHelpers { public static function CheckValueInArray($sDescription, $value, $aData) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 399c10405b..429d100ded 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../core/email.class.inc.php'); @@ -6,12 +30,6 @@ require_once('../core/email.class.inc.php'); * A user defined action, to customize the application * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class Action extends cmdbAbstractObject { @@ -77,12 +95,6 @@ abstract class Action extends cmdbAbstractObject * A notification * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class ActionNotification extends Action { @@ -117,12 +129,6 @@ abstract class ActionNotification extends Action * An email notification * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class ActionEmail extends ActionNotification { @@ -233,7 +239,7 @@ class ActionEmail extends ActionNotification $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs); $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs); - $oEmail = new Email(); + $oEmail = new EMail(); if ($this->IsBeingTested()) { diff --git a/core/archive.class.inc.php b/core/archive.class.inc.php index 6403fffca9..082cd989a8 100644 --- a/core/archive.class.inc.php +++ b/core/archive.class.inc.php @@ -1,18 +1,29 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + /** * iTopArchive a class to manipulate (read/write) iTop archives with their catalog * Each iTop archive is a zip file that contains (at the root of the archive) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 1e97abdb15..1d6bf52690 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('MyHelpers.class.inc.php'); require_once('ormdocument.class.inc.php'); @@ -44,12 +68,6 @@ define('DEL_AUTO', 2); * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.) * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class AttributeDefinition { @@ -235,11 +253,6 @@ abstract class AttributeDefinition * Set of objects directly linked to an object, and being part of its definition * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeLinkedSet extends AttributeDefinition { @@ -301,11 +314,6 @@ class AttributeLinkedSet extends AttributeDefinition * Set of objects linked to an object (n-n), and being part of its definition * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeLinkedSetIndirect extends AttributeLinkedSet { @@ -320,11 +328,6 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet * Abstract class implementing default filters for a DB column * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeDBFieldVoid extends AttributeDefinition { @@ -412,11 +415,6 @@ class AttributeDBFieldVoid extends AttributeDefinition * Base class for all kind of DB attributes, with the exception of external keys * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeDBField extends AttributeDBFieldVoid { @@ -432,11 +430,6 @@ class AttributeDBField extends AttributeDBFieldVoid * Map an integer column to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeInteger extends AttributeDBField { @@ -521,11 +514,6 @@ class AttributeInteger extends AttributeDBField * Map a boolean column to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeBoolean extends AttributeInteger { @@ -557,11 +545,6 @@ class AttributeBoolean extends AttributeInteger * Map a varchar column (size < ?) to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeString extends AttributeDBField { @@ -658,11 +641,6 @@ class AttributeString extends AttributeDBField * An attibute that matches an object class * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeClass extends AttributeString { @@ -693,11 +671,6 @@ class AttributeClass extends AttributeString * The attribute dedicated to the finalclass automatic attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeFinalClass extends AttributeString { @@ -739,11 +712,6 @@ class AttributeFinalClass extends AttributeString * Map a varchar column (size < ?) to an attribute that must never be shown to the user * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributePassword extends AttributeString { @@ -767,11 +735,6 @@ class AttributePassword extends AttributeString * Map a text column (size > ?) to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeText extends AttributeString { @@ -795,11 +758,6 @@ class AttributeText extends AttributeString * Specialization of a string: email * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeEmailAddress extends AttributeString { @@ -815,11 +773,6 @@ class AttributeEmailAddress extends AttributeString * Specialization of a string: IP address * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeIPAddress extends AttributeString { @@ -836,11 +789,6 @@ class AttributeIPAddress extends AttributeString * Specialization of a string: OQL expression * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeOQL extends AttributeString { @@ -851,11 +799,6 @@ class AttributeOQL extends AttributeString * Specialization of a string: template * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeTemplateString extends AttributeString { @@ -866,11 +809,6 @@ class AttributeTemplateString extends AttributeString * Specialization of a text: template * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeTemplateText extends AttributeText { @@ -881,11 +819,6 @@ class AttributeTemplateText extends AttributeText * Map a enum column to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeEnum extends AttributeString { @@ -971,11 +904,6 @@ class AttributeEnum extends AttributeString * Map a date+time column to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeDate extends AttributeDBField { @@ -1128,11 +1056,6 @@ AttributeDate::InitStatics(); * Map a date+time column to an attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeDateTime extends AttributeDBField { @@ -1301,11 +1224,6 @@ AttributeDateTime::InitStatics(); * where an AttributeExternalField corresponds to a column into another table (class) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeExternalKey extends AttributeDBFieldVoid { @@ -1389,11 +1307,6 @@ class AttributeExternalKey extends AttributeDBFieldVoid * An attribute which corresponds to an external key (direct or indirect) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeExternalField extends AttributeDefinition { @@ -1561,11 +1474,6 @@ class AttributeExternalField extends AttributeDefinition * Map a varchar column to an URL (formats the ouput in HMTL) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeURL extends AttributeString { @@ -1602,11 +1510,6 @@ class AttributeURL extends AttributeString * A blob is an ormDocument, it is stored as several columns in the database * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class AttributeBlob extends AttributeDefinition { diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 7b001124a2..16b0cb18f9 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -1,16 +1,34 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * BulkChange * Interpret a given data set and update the DB accordingly (fake mode avail.) * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class BulkChangeException extends CoreException @@ -22,11 +40,6 @@ class BulkChangeException extends CoreException * A series of classes, keeping the information about a given cell: could it be changed or not (and why)? * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class CellChangeSpec { @@ -138,11 +151,6 @@ class CellStatus_Ambiguous extends CellStatus_Issue * A series of classes, keeping the information about a given row: could it be changed or not (and why)? * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class RowStatus { @@ -201,13 +209,10 @@ class RowStatus_Issue extends RowStatus /** - ** BulkChange * - ** @package iTopORM - ** @author Romain Quetiez - ** @license http://www.opensource.org/licenses/lgpl-license.php LGPL - ** @link www.itop.com - ** @since 1.0 - ** @version $itopversion$ */ + * BulkChange + * + * @package iTopORM + */ class BulkChange { protected $m_sClass; diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index dcfe6da959..a4bc1d8c5d 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -1,15 +1,33 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * A change as requested/validated at once by user, may groups many atomic changes * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class CMDBChange extends DBObject { diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index f347c2f17a..a8e6f8c812 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -1,15 +1,33 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * Various atomic change operations, to be tracked * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class CMDBChangeOp extends DBObject @@ -54,11 +72,6 @@ class CMDBChangeOp extends DBObject * Record the creation of an object * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CMDBChangeOpCreate extends CMDBChangeOp { @@ -94,11 +107,6 @@ class CMDBChangeOpCreate extends CMDBChangeOp * Record the deletion of an object * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CMDBChangeOpDelete extends CMDBChangeOp { @@ -133,11 +141,6 @@ class CMDBChangeOpDelete extends CMDBChangeOp * Record the modification of an attribute (abstract) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CMDBChangeOpSetAttribute extends CMDBChangeOp { @@ -169,11 +172,6 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp * Record the modification of a scalar attribute * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute { @@ -265,11 +263,6 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute * Record the modification of a blob * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute { @@ -331,11 +324,6 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute * Record the modification of a multiline string (text) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute { diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index c82c536c41..6b36dd0092 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -1,16 +1,34 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * cmdbObjectClass * the file to include, then the core is yours * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ require_once('coreexception.class.inc.php'); @@ -137,12 +155,6 @@ error_reporting(E_ALL | E_STRICT); * A persistent object, which changes are accurately recorded * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class CMDBObject extends DBObject { @@ -453,12 +465,6 @@ abstract class CMDBObject extends DBObject * TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design! * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class CMDBObjectSet extends DBObjectSet { @@ -513,12 +519,6 @@ class CMDBObjectSet extends DBObjectSet * TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design! * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class CMDBSearchFilter extends DBObjectSearch { diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index e21368fe20..f50f021b7f 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -1,16 +1,26 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ require_once('MyHelpers.class.inc.php'); @@ -26,6 +36,12 @@ class MySQLException extends CoreException } +/** + * CMDBSource + * database access wrapper + * + * @package iTopORM + */ class CMDBSource { protected static $m_sDBHost; diff --git a/core/config.class.inc.php b/core/config.class.inc.php index be34387778..bf9b6934d4 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -1,17 +1,30 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +require_once('coreexception.class.inc.php'); + class ConfigException extends CoreException { } @@ -27,6 +40,12 @@ define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60); define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60); define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); +/** + * Config + * configuration data (this class cannot not be localized, because it is responsible for loading the dictionaries) + * + * @package iTopORM + */ class Config { //protected $m_bIsLoaded = false; diff --git a/core/coreexception.class.inc.php b/core/coreexception.class.inc.php index 7d1b6a3ded..da1094b818 100644 --- a/core/coreexception.class.inc.php +++ b/core/coreexception.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + class SecurityException extends CoreException diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php index fe0662b61a..9c64cd3f43 100644 --- a/core/csvparser.class.inc.php +++ b/core/csvparser.class.inc.php @@ -1,23 +1,33 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + class CSVParserException extends CoreException { } - - define('stSTARTING', 1); //grey zone: the type is undetermined define('stRAW', 2); //building a non-qualified string define('stQUALIFIED', 3); //building qualified string @@ -33,11 +43,6 @@ define('evOTHERCHAR', 4); * CSVParser * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class CSVParser { diff --git a/core/data.generator.class.inc.php b/core/data.generator.class.inc.php index f113a89e38..417ba58aa6 100644 --- a/core/data.generator.class.inc.php +++ b/core/data.generator.class.inc.php @@ -1,16 +1,27 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ /** diff --git a/core/dbobject.class.php b/core/dbobject.class.php index e726a47512..d892065c42 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1,30 +1,34 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ require_once('metamodel.class.php'); - /** * A persistent object, as defined by the metamodel * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class DBObject { diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 4d7c3ed21c..09309b7715 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1,30 +1,28 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ - -/** - * Define filters for a given class of objects (formerly named "filter") - * - * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @mytagrom youpi - * @since 1.0 - * @version 1.1.1.1 $ - */ class DBObjectSearch { private $m_aClasses; // queried classes (alias => class name), the first item is the class corresponding to this filter (the rest is coming from subfilters) diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 14ac3fbff4..5b9c0c4813 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -1,17 +1,34 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * A set of persistent objects, could be heterogeneous * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ - class DBObjectSet { private $m_oFilter; diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index 83f187e3b2..8c78456725 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -1,17 +1,28 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ +// Copyright (C) 2010 Combodo SARL +// +// 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; version 3 of the License. +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +/** + * Class Dict + * Management of localizable strings + * + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ class DictException extends CoreException { diff --git a/core/email.class.inc.php b/core/email.class.inc.php index 136c6e5851..110301163d 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -1,17 +1,29 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + class EMail { protected $m_sBody; diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 5a396be626..0b4099a7b7 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -1,16 +1,30 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + class Event extends cmdbAbstractObject { public static function Init() diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index 82b3e61b3c..2cb0f25132 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -1,15 +1,26 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ class MissingQueryArgument extends CoreException diff --git a/core/filterdef.class.inc.php b/core/filterdef.class.inc.php index 25e8160890..fc3c03eb92 100644 --- a/core/filterdef.class.inc.php +++ b/core/filterdef.class.inc.php @@ -1,4 +1,29 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('MyHelpers.class.inc.php'); @@ -8,12 +33,6 @@ require_once('MyHelpers.class.inc.php'); * Definition of a filter (could be made out of an existing attribute, or from an expression) * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class FilterDefinition { @@ -96,11 +115,6 @@ abstract class FilterDefinition * Match against the object unique identifier * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class FilterPrivateKey extends FilterDefinition { @@ -148,11 +162,6 @@ class FilterPrivateKey extends FilterDefinition * Match against an existing attribute (the attribute type will determine the available operators) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class FilterFromAttribute extends FilterDefinition { diff --git a/core/log.class.inc.php b/core/log.class.inc.php index 0f5838d6a9..80e2635c04 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -1,15 +1,26 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ class FileLog diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 678fd643a7..a306a09e1a 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + // #@# todo: change into class const (see Doctrine) @@ -62,12 +86,6 @@ define('OPT_ATT_MUSTPROMPT', 16); * (API) The objects definitions as well as their mapping to the database * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class MetaModel { diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index 6814886db4..1191b6c376 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -1,5 +1,29 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + // Notes (from the source file: oql-lexer.plex) - Romain // // The strval rule is a little bit cryptic. diff --git a/core/oql/oqlexception.class.inc.php b/core/oql/oqlexception.class.inc.php index 512a3077fd..952a15bf2d 100644 --- a/core/oql/oqlexception.class.inc.php +++ b/core/oql/oqlexception.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + class OQLException extends CoreException { diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index 90fd60c1f8..bd249307d1 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + class OqlNormalizeException extends OQLException { diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index 1979cdac57..159636f38d 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -1,4 +1,27 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ // Position a string within an OQL query // This is a must if we want to be able to pinpoint an error at any stage of the query interpretation diff --git a/core/ormdocument.class.inc.php b/core/ormdocument.class.inc.php index 4ffb3ace49..e5c9ef204e 100644 --- a/core/ormdocument.class.inc.php +++ b/core/ormdocument.class.inc.php @@ -1,15 +1,35 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +/** + * ormDocument + * encapsulate the behavior of a binary data set that will be stored an attribute of class AttributeBlob + * + * @package itopORM */ class ormDocument diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php index 700d0b33b6..a4a1a994ed 100644 --- a/core/sqlquery.class.inc.php +++ b/core/sqlquery.class.inc.php @@ -1,16 +1,35 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * SQLQuery * build an mySQL compatible SQL query * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ require_once('cmdbsource.class.inc.php'); diff --git a/core/stimulus.class.inc.php b/core/stimulus.class.inc.php index 1d27bd0805..e1595e641b 100644 --- a/core/stimulus.class.inc.php +++ b/core/stimulus.class.inc.php @@ -1,15 +1,33 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * A stimulus is the trigger that makes the lifecycle go ahead (state machine) * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ // #@# Really dirty !!! diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 1c0f2b0e26..b6a654c43d 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('coreexception.class.inc.php'); require_once('attributedef.class.inc.php'); @@ -30,12 +54,6 @@ class UnitTestException extends Exception * Improved display of the backtrace * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class ExceptionFromError extends Exception { @@ -52,12 +70,6 @@ class ExceptionFromError extends Exception * Test handler API and basic helpers * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ abstract class TestHandler { @@ -177,11 +189,6 @@ abstract class TestHandler * Test to execute a piece of code (checks if an error occurs) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestFunction extends TestHandler { @@ -193,11 +200,6 @@ abstract class TestFunction extends TestHandler * Test to execute a piece of code (checks if an error occurs) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestWebServices extends TestHandler { @@ -255,11 +257,6 @@ abstract class TestWebServices extends TestHandler * Test to execute a piece of code (checks if an error occurs) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestSoapWebService extends TestHandler { @@ -275,11 +272,6 @@ abstract class TestSoapWebService extends TestHandler * Test to check that a function outputs some values depending on its input * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestFunctionInOut extends TestFunction { @@ -318,11 +310,6 @@ abstract class TestFunctionInOut extends TestFunction * Test to check an URL (Searches for Error/Warning/Etc keywords) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestUrl extends TestHandler { @@ -341,11 +328,6 @@ abstract class TestUrl extends TestHandler * Test to check a user management module * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestUserRights extends TestHandler { @@ -360,11 +342,6 @@ abstract class TestUserRights extends TestHandler * Test to execute a scenario on a given DB * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestScenarioOnDB extends TestHandler { @@ -399,11 +376,6 @@ abstract class TestScenarioOnDB extends TestHandler * Test to use a business model on a given DB * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestBizModel extends TestHandler { @@ -507,11 +479,6 @@ abstract class TestBizModel extends TestHandler * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class TestBizModelGeneric extends TestBizModel { diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 608ccafdc1..03d61c18f2 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -1,16 +1,35 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * A user defined trigger, to customize the application * A trigger will activate an action * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ class Trigger extends cmdbAbstractObject { diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 6078fa61b8..d19a338fe0 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -1,18 +1,29 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + class UserRightException extends CoreException { } @@ -36,11 +47,6 @@ define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions * User management module API * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ abstract class UserRightsAddOnAPI { @@ -66,11 +72,6 @@ abstract class UserRightsAddOnAPI * User management core API * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class UserRights { diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 1d7868763f..223e345029 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -1,20 +1,36 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once('MyHelpers.class.inc.php'); /** * ValueSetDefinition * value sets API and implementations * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ - -require_once('MyHelpers.class.inc.php'); - abstract class ValueSetDefinition { protected $m_bIsLoaded = false; @@ -70,11 +86,6 @@ abstract class ValueSetDefinition * Set of existing values for an attribute, given a search filter * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class ValueSetObjects extends ValueSetDefinition { @@ -122,11 +133,6 @@ class ValueSetObjects extends ValueSetDefinition * Set of existing values for a link set attribute, given a relation code * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition { @@ -201,11 +207,6 @@ class ValueSetRelatedObjectsFromLinkSet extends ValueSetDefinition * Fixed set values (could be hardcoded in the business model) * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class ValueSetEnum extends ValueSetDefinition { @@ -242,11 +243,6 @@ class ValueSetEnum extends ValueSetDefinition * Data model classes * * @package iTopORM - * @author Romain Quetiez - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version $itopversion$ */ class ValueSetEnumClasses extends ValueSetEnum { diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 4ee8ac7981..6f57a300e7 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + ////////////////////////////////////////////////////////////////////// // Classes in 'core/cmdb' diff --git a/dictionaries/dictionary.itop.model.php b/dictionaries/dictionary.itop.model.php index ca4594ca9c..840286d031 100644 --- a/dictionaries/dictionary.itop.model.php +++ b/dictionaries/dictionary.itop.model.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + // Dictionnay conventions // Class: diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index d7c71c4cf7..47202e765c 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + ////////////////////////////////////////////////////////////////////// // Classes in 'gui' diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 287dc5fdcf..872600b57a 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + ////////////////////////////////////////////////////////////////////// // Classes in 'gui' diff --git a/pages/ITopConsultant.php b/pages/ITopConsultant.php index a14a058477..7c918c7175 100644 --- a/pages/ITopConsultant.php +++ b/pages/ITopConsultant.php @@ -1,13 +1,30 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ function sexyclass($sClass, $sBaseArgs) { diff --git a/pages/UI.php b/pages/UI.php index 65f5487501..08de50806e 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * Perform all the needed checks to delete one (or more) objects */ diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index 5561571564..5b19d3107f 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); require_once('../application/applicationcontext.class.inc.php'); diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index bc69ee94b1..491e3f8dff 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/webpage.class.inc.php'); require_once('../application/ajaxwebpage.class.inc.php'); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 58380c3ada..fb71b37260 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/webpage.class.inc.php'); require_once('../application/ajaxwebpage.class.inc.php'); diff --git a/pages/audit.php b/pages/audit.php index 03aaf57109..a7fd54ff0b 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/csvimport.php b/pages/csvimport.php index 8ed3600b05..e77793edfc 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -1,14 +1,29 @@ * @author Romain Quetiez * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-3.0.html LGPL - * @link http://www.combodo.com/itop iTop + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + ini_set('memory_limit', '256M'); require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/graphviz.php b/pages/graphviz.php index b3d5874dbb..89e067b58f 100644 --- a/pages/graphviz.php +++ b/pages/graphviz.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/opensearch.xml.php b/pages/opensearch.xml.php index 6d57a98db0..68c7393db4 100644 --- a/pages/opensearch.xml.php +++ b/pages/opensearch.xml.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + $sFullUrl = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/UI.php'; $sICOFullUrl = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../images/iTop-icon.ico'; $sPNGFullUrl = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../images/iTop-icon.png'; diff --git a/pages/run_query.php b/pages/run_query.php index 65a221da78..74e2497a13 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/schema.php b/pages/schema.php index b777038475..3f92140ba5 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/test.php b/pages/test.php index e9e9bbea40..cf6f31042f 100644 --- a/pages/test.php +++ b/pages/test.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /////////////////////////////////////////////////////////////////////////////// diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 1298a5213e..82e31b7c6f 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + class TestSQLQuery extends TestScenarioOnDB { diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php index 28a8e10368..22db2525a5 100644 --- a/pages/usermanagement_classproj.php +++ b/pages/usermanagement_classproj.php @@ -1,4 +1,29 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index a9475330e4..ef42dd1cc8 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -1,4 +1,29 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index d5a17a892b..5a175aca39 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -1,4 +1,29 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index b01beb76e5..1ddd98a820 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * This page is called to load "asynchronously" some xml file into the database * parameters diff --git a/setup/email.test.php b/setup/email.test.php index bcf7935d8e..66fd3b8d68 100644 --- a/setup/email.test.php +++ b/setup/email.test.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + /** * Wizard to configure and initialize the iTop application */ diff --git a/setup/index.php b/setup/index.php index 8f2065320d..2c66c5f4c2 100644 --- a/setup/index.php +++ b/setup/index.php @@ -1,7 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + require_once('../application/utils.inc.php'); require_once('../core/config.class.inc.php'); require_once('../core/log.class.inc.php'); diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index 37785327f9..8d6533f768 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -1,10 +1,31 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +require_once("../application/nicewebpage.class.inc.php"); +define('INSTALL_LOG_FILE', '../setup.log'); + class SetupWebPage extends NiceWebPage { public function __construct($sTitle) diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 6879efb457..e706d1a52a 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + define ('KEYS_CACHE_FILE', '../keyscache.tmp'); /** * Class to load sets of objects from XML files into the database diff --git a/webservices/export.php b/webservices/export.php index e4a003bdb6..94aa77ef33 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../application/application.inc.php'); require_once('../application/nicewebpage.class.inc.php'); require_once('../application/csvpage.class.inc.php'); diff --git a/webservices/import.php b/webservices/import.php index ddf4b0deb9..b78d2a92c4 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -1,21 +1,31 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ // // Known limitations // - output still in html, because the errors are not displayed in xml -// - only external fields attributes could be used as reconciliation keys for external keys // - reconciliation is made on the first column // - no option to force 'always create' or 'never create' // - text qualifier hardcoded to " diff --git a/webservices/itop.wsdl.php b/webservices/itop.wsdl.php index db107fae2a..5590e0d2d7 100644 --- a/webservices/itop.wsdl.php +++ b/webservices/itop.wsdl.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + // This is to make sure that the client will accept it.... // header('Content-Type: application/xml; charset=UTF-8'); diff --git a/webservices/itopsoap.examples.php b/webservices/itopsoap.examples.php index c93be4333d..755d05e926 100644 --- a/webservices/itopsoap.examples.php +++ b/webservices/itopsoap.examples.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('itopsoaptypes.class.inc.php'); diff --git a/webservices/itopsoaptypes.class.inc.php b/webservices/itopsoaptypes.class.inc.php index 3e8f6f7ba6..c024e0133b 100644 --- a/webservices/itopsoaptypes.class.inc.php +++ b/webservices/itopsoaptypes.class.inc.php @@ -1,4 +1,28 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + // Note: the attributes must have the same names (case sensitive) as in the WSDL specification // diff --git a/webservices/soapserver.php b/webservices/soapserver.php index bd263bc21f..077fc25ccc 100644 --- a/webservices/soapserver.php +++ b/webservices/soapserver.php @@ -1,15 +1,26 @@ - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ // Important note: if some required includes are missing, this might result diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index c4c27b4adb..ccbb8da771 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -1,20 +1,36 @@ + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + require_once('../webservices/itopsoaptypes.class.inc.php'); /** - * Create Ticket web service - * Web Service API wrapper + * Generic response of iTop SOAP services * * @package iTopORM - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ */ - class WebServiceResult { @@ -201,6 +217,11 @@ class WebServiceResult } +/** + * Generic response of iTop SOAP services - failed login + * + * @package iTopORM + */ class WebServiceResultFailedLogin extends WebServiceResult { public function __construct($sLogin) @@ -210,6 +231,11 @@ class WebServiceResultFailedLogin extends WebServiceResult } } +/** + * Implementation of the Services + * + * @package iTopORM + */ class WebServices { /** From 1bc76fe5bd041d4a56538261f47f429fd85b4a2b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 27 May 2010 15:08:50 +0000 Subject: [PATCH 340/970] Fixed a typo in an error message (Tack #125). SVN:trunk[423] --- setup/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/index.php b/setup/index.php index 2c66c5f4c2..88082764de 100644 --- a/setup/index.php +++ b/setup/index.php @@ -221,7 +221,7 @@ function CheckPHPVersion(SetupWebPage $oP) // Check some more ini settings here, needed for file upload if (get_magic_quotes_gpc()) { - $oP->error("'magic_quotes_gpc' is set to On in '$sPhpIniFile', please turn if Off before continuing."); + $oP->error("'magic_quotes_gpc' is set to On in '$sPhpIniFile', please turn it Off before continuing."); $bResult = false; } From 1a5604f184f7ba02d735a29da408c0e9aa9e54f8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 27 May 2010 15:36:06 +0000 Subject: [PATCH 341/970] Fixed Trac #126: misleading message during the setup when magic_quotes_gpc in On. Now we provide a more accurate message. SVN:trunk[424] --- setup/index.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/setup/index.php b/setup/index.php index 88082764de..5924f1957a 100644 --- a/setup/index.php +++ b/setup/index.php @@ -166,7 +166,20 @@ function CheckPHPVersion(SetupWebPage $oP) if (function_exists('php_ini_loaded_file')) // PHP >= 5.2.4 { $sPhpIniFile = php_ini_loaded_file(); - $oP->log("Info - php.ini path: '$sPhpIniFile'"); + // Other included/scanned files + if ($sFileList = php_ini_scanned_files()) + { + if (strlen($sFileList) > 0) + { + $aFiles = explode(',', $sFileList); + + foreach ($aFiles as $sFile) + { + $sPhpIniFile .= ', '.trim($sFile); + } + } + } + $oP->log("Info - php.ini file(s): '$sPhpIniFile'"); } else { @@ -221,7 +234,7 @@ function CheckPHPVersion(SetupWebPage $oP) // Check some more ini settings here, needed for file upload if (get_magic_quotes_gpc()) { - $oP->error("'magic_quotes_gpc' is set to On in '$sPhpIniFile', please turn it Off before continuing."); + $oP->error("'magic_quotes_gpc' is set to On. Please turn it Off before continuing. You may want to check the PHP configuration file(s): '$sPhpIniFile'. Be aware that this setting can also be overridden in the apache configuration."); $bResult = false; } From d0c4546ec5290b05fa3ed72fad3a79ff602943cb Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 27 May 2010 15:56:53 +0000 Subject: [PATCH 342/970] - Fized bug Trac#107: "select first line as header" is now checked when uploading data from a file. SVN:trunk[425] --- application/itopwebpage.class.inc.php | 36 +++++++++++++++++++++++++-- pages/csvimport.php | 27 +++++++++++++------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 9d933fbc58..2f666bc768 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -113,7 +113,7 @@ class iTopWebPage extends NiceWebPage $("#RightPane").trigger("resize"); } - $("#tabbedContent > ul").tabs( 1, { fxFade: true, fxSpeed: 'fast' } ); // tabs + $("div[id^=tabbedContent] > ul").tabs( 1, { fxFade: true, fxSpeed: 'fast' } ); // tabs $("table.listResults").tableHover(); // hover tables $(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables $(".date-pick").datePicker( {clickInput: false, createButton: true, startDate: '2000-01-01'} ); // Date picker @@ -352,9 +352,10 @@ EOF foreach($this->m_aTabs as $sTabContainerName => $m_aTabs) { $sTabs = ''; + $container_index = 0; if (count($m_aTabs) > 0) { - $sTabs = "\n
            \n"; + $sTabs = "\n
            \n"; $sTabs .= "
              \n"; // Display the unordered list that will be rendered as the tabs $i = 0; @@ -374,6 +375,7 @@ EOF $sTabs .= "
            \n\n"; } $this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content); + $container_index++; } // Display the page's content @@ -427,6 +429,36 @@ EOF return $sPreviousTab; } + /** + * Make the given tab the active one, as if it were clicked + * DOES NOT WORK: apparently in the *old* version of jquery + * that we are using this is not supported... TO DO upgrade + * the whole jquery bundle... + */ + public function SelectTab($sTabContainer, $sTabLabel) + { + $container_index = 0; + $tab_index = 0; + foreach($this->m_aTabs as $sCurrentTabContainerName => $aTabs) + { + if ($sTabContainer == $sCurrentTabContainerName) + { + foreach($aTabs as $sCurrentTabLabel => $void) + { + if ($sCurrentTabLabel == $sTabLabel) + { + break; + } + $tab_index++; + } + break; + } + $container_index++; + } + $sSelector = '#tabbedContent_'.$container_index.' > ul'; + $this->add_ready_script("$('$sSelector').tabs('select', $tab_index);"); + } + public function StartCollapsibleSection($sSectionLabel, $bOpen = false) { $this->add($this->GetStartCollapsibleSection($sSectionLabel, $bOpen)); diff --git a/pages/csvimport.php b/pages/csvimport.php index e77793edfc..26f5a70731 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -964,21 +964,25 @@ function Welcome(iTopWebPage $oPage) $oPage->add("

            ".Dict::S('UI:Title:BulkImport+')."

            \n"); $oPage->AddTabContainer('tabs1'); - $sFileLoadHtml = '

            '.Dict::S('UI:CSVImport:SelectFile').'

            '. - '

            '. - '

            '. - '

            '. - '

            '. - '
            '; - - $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:LoadFromFile'), $sFileLoadHtml); - $sCSVData = utils::ReadParam('csvdata', ''); $sSeparator = utils::ReadParam('separator', ''); $sTextQualifier = utils::ReadParam('text_qualifier', ''); $bHeaderLine = utils::ReadParam('header_line', true); $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', 0); + + $sFileLoadHtml = '

            '.Dict::S('UI:CSVImport:SelectFile').'

            '. + '

            '. + '

            '. + '

            '. + '

            '. + ''. + ''. + ''. + ''. + '
            '; + + $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:LoadFromFile'), $sFileLoadHtml); $sCSVData = utils::ReadParam('csvdata', ''); $sPasteDataHtml = '

            '.Dict::S('UI:CSVImport:PasteData').'

            '. '

            '. @@ -994,6 +998,11 @@ function Welcome(iTopWebPage $oPage) '
            '; $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste'), $sPasteDataHtml); + if (!empty($sCSVData)) + { + // When there are some data, activate the 'copy & paste' tab by default + $oPage->SelectTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste')); + } $sTemplateHtml = '

            '.Dict::S('UI:CSVImport:PickClassForTemplate').' '; $sTemplateHtml .= GetClassesSelect('template_class', '', 300, UR_ACTION_BULK_MODIFY); $sTemplateHtml .= '

            '; From 8c2d8199208bc25199e3af5006e40a1fcdcb6c3c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 31 May 2010 14:41:05 +0000 Subject: [PATCH 343/970] - Moved the iTop version number to the Config class to be able to access it from the setup. SVN:trunk[426] --- application/startup.inc.php | 4 ---- core/config.class.inc.php | 4 ++++ setup/index.php | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/application/startup.inc.php b/application/startup.inc.php index 0ccf697e99..5a416ea947 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -23,10 +23,6 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -define('ITOP_VERSION', '0.9'); -define('ITOP_REVISION', '$WCREV$'); -define('ITOP_BUILD_DATE', '$WCNOW$'); - require_once('../application/utils.inc.php'); MetaModel::Startup(ITOP_CONFIG_FILE); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index bf9b6934d4..ce23676d9c 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -14,6 +14,10 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +define('ITOP_VERSION', '0.9'); +define('ITOP_REVISION', '$WCREV$'); +define('ITOP_BUILD_DATE', '$WCNOW$'); + /** * Configuration read/write * diff --git a/setup/index.php b/setup/index.php index 5924f1957a..aa8a503a87 100644 --- a/setup/index.php +++ b/setup/index.php @@ -28,6 +28,7 @@ require_once('../core/config.class.inc.php'); require_once('../core/log.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); require_once('./setuppage.class.inc.php'); + define('TMP_CONFIG_FILE', '../tmp-config-itop.php'); define('FINAL_CONFIG_FILE', '../config-itop.php'); define('SETUP_STRUCTURE_DATA_DIR', './data/structure'); @@ -40,6 +41,25 @@ define('MIN_MEMORY_LIMIT', 32*1024*1024); $sOperation = Utils::ReadParam('operation', 'step1'); $oP = new SetupWebPage('iTop configuration wizard'); +/** + * Get a nicely formatted version string + */ +function GetITopVersion() +{ + $sVersionString = ''; + if (ITOP_REVISION == '$WCREV$') + { + // This is NOT a version built using the buil system, just display the main version + $sVersionString = "iTop Version ".ITOP_VERSION; + } + else + { + // This is a build made from SVN, let display the full information + $sVersionString = "iTop Version ".ITOP_VERSION." revision ".ITOP_REVISION.", built on: ".ITOP_BUILD_DATE; + } + return $sVersionString; +} + /** * Helper function to retrieve the system's temporary directory * Emulates sys_get_temp_dir if neeed (PHP < 5.2.1) @@ -460,7 +480,10 @@ function PopulateDataFilesList(SetupWebPage $oP) function DisplayStep1(SetupWebPage $oP) { $sNextOperation = 'step2'; + $sVersionString = GetITopVersion(); $oP->add("

            iTop configuration wizard

            \n"); + $oP->p($sVersionString); + $oP->log($sVersionString); $oP->add("

            Checking prerequisites

            \n"); if (CheckPHPVersion($oP)) { From 024f67b1b8db139d9a1406b860ac4ba928111c1a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 31 May 2010 14:51:37 +0000 Subject: [PATCH 344/970] #128 Allow a blob to be undefined (optional property) SVN:trunk[427] --- business/itop.business.class.inc.php | 4 +++- core/attributedef.class.inc.php | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/business/itop.business.class.inc.php b/business/itop.business.class.inc.php index 497a49aae2..9dcc749449 100644 --- a/business/itop.business.class.inc.php +++ b/business/itop.business.class.inc.php @@ -342,7 +342,9 @@ class bizDocument extends logRealObject MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum("documentation,contract,working instructions,network map,white paper,presentation,training"), "sql"=>"type", "default_value"=>"documentation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("depends_on"=>array()))); +// MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'type', 'description', 'contents')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'type', 'contents')); // Attributes to be displayed for a list diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 1d6bf52690..2ef6955b8c 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -80,6 +80,18 @@ abstract class AttributeDefinition private $m_sHostClass = '!undefined!'; protected function Get($sParamName) {return $this->m_aParams[$sParamName];} protected function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} + + protected function GetOptional($sParamName, $default) + { + if (array_key_exists($sParamName, $this->m_aParams)) + { + return $this->m_aParams[$sParamName]; + } + else + { + return $default; + } + } public function __construct($sCode, $aParams) { @@ -353,6 +365,7 @@ class AttributeDBFieldVoid extends AttributeDefinition public function GetDefaultValue() {return "";} public function IsNullAllowed() {return false;} + // protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) public function GetSQLExpressions() @@ -423,7 +436,7 @@ class AttributeDBField extends AttributeDBFieldVoid return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); } public function GetDefaultValue() {return $this->Get("default_value");} - public function IsNullAllowed() {return strtolower($this->Get("is_null_allowed"));} + public function IsNullAllowed() {return $this->Get("is_null_allowed");} } /** @@ -1526,7 +1539,8 @@ class AttributeBlob extends AttributeDefinition public function IsScalar() {return true;} public function IsWritable() {return true;} public function GetDefaultValue() {return "";} - public function IsNullAllowed() {return false;} + public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + // Facilitate things: allow the user to Set the value from a string public function MakeRealValue($proposedValue) From 0a38eab3419c9e2eb2ba6e0ae7565143ee892a37 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 31 May 2010 15:27:40 +0000 Subject: [PATCH 345/970] #124 New page to create huge data sets and verify the scalability SVN:trunk[428] --- setup/benchmark.php | 439 ++++++++++++++++++++++++++++++++++ setup/setuppage.class.inc.php | 2 +- toolkit.php | 1 + 3 files changed, 441 insertions(+), 1 deletion(-) create mode 100644 setup/benchmark.php diff --git a/setup/benchmark.php b/setup/benchmark.php new file mode 100644 index 0000000000..5df8081298 --- /dev/null +++ b/setup/benchmark.php @@ -0,0 +1,439 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once('../application/application.inc.php'); +require_once('../application/itopwebpage.class.inc.php'); +require_once('../application/wizardhelper.class.inc.php'); +require_once('../application/startup.inc.php'); +require_once('../application/loginwebpage.class.inc.php'); +require_once('../application/utils.inc.php'); +require_once('./setuppage.class.inc.php'); + +class BenchmarkDataCreation +{ + var $m_aPlanned = array(); + var $m_aCreated = array(); + + var $m_aStatsByClass = array(); + + public function __construct($iPlannedCIs, $iPlannedContacts, $iPlannedContracts) + { + $this->m_aPlanned = array( + 'CIs' => $iPlannedCIs, + 'Contacts' => $iPlannedContacts, + 'Contracts' => $iPlannedContracts, + 'SubCIs' => 10 * $iPlannedCIs, + 'Incidents' => 2 * 12 * $iPlannedCIs, + 'ServiceCalls' => 1 * 12 * $iPlannedContacts, + 'Changes' => 1 * 12 * $iPlannedCIs, + 'Documents' => 12 * $iPlannedContracts + $iPlannedCIs, + ); + } + + public function GetPlan() + { + return $this->m_aPlanned; + } + + protected function CreateObject($sClass, $aData, $oChange) + { + $mu_t1 = MyHelpers::getmicrotime(); + + $oMyObject = MetaModel::NewObject($sClass); + foreach($aData as $sProp => $value) + { + $oMyObject->Set($sProp, $value); + } + + $iId = $oMyObject->DBInsertTrackedNoReload($oChange); + + $this->m_aCreated[$sClass][] = $iId; + + $mu_t2 = MyHelpers::getmicrotime(); + $this->m_aStatsByClass[$sClass][] = $mu_t2 - $mu_t1; + + return $iId; + } + + protected function RandomId($sClass) + { + return $this->m_aCreated[$sClass][array_rand($this->m_aCreated[$sClass])]; + } + + protected function MakeFeedback($oP, $sClass) + { + $iSample = reset($this->m_aCreated[$sClass]); + $sSample = "sample"; + + $iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3); + $fDurationMin = number_format(min($this->m_aStatsByClass[$sClass]), 3); + $fDurationMax = number_format(max($this->m_aStatsByClass[$sClass]), 3); + $fDurationAverage = number_format(array_sum($this->m_aStatsByClass[$sClass]) / count($this->m_aStatsByClass[$sClass]), 3); + + $oP->add("
              "); + $oP->add("
            • "); + $oP->add("$sClass: ".count($this->m_aCreated[$sClass])." - $sSample
              "); + $oP->add("Duration: $fDurationMin => $fDurationMax; Avg:$fDurationAverage; Total: $iDuration"); + $oP->add("
            • "); + $oP->add("
            "); + } + + public function Go(WebPage $oP, $oChange) + { + + // 1 - Organizations + // + $aData = array( + 'name' => 'benchmark', + ); + $iOrg = $this->CreateObject('bizOrganization', $aData, $oChange); + $this->MakeFeedback($oP, 'bizOrganization'); + + // 2 - Locations + // + $aData = array( + 'org_id' => $iOrg, + 'name' => 'rio', + ); + $iLoc = $this->CreateObject('bizLocation', $aData, $oChange); + $this->MakeFeedback($oP, 'bizLocation'); + + // 3 - Teams + // + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'fluminense', + ); + $iTeam = $this->CreateObject('bizTeam', $aData, $oChange); + $this->MakeFeedback($oP, 'bizTeam'); + + // 3' - Workgroups + // + $iAnyTeam = $this->RandomId('bizTeam'); + $aData = array( + 'org_id' => $iOrg, + 'team_id' => $iAnyTeam, + 'name' => 'trabolhogrupo'.$iAnyTeam, + ); + $iTeam = $this->CreateObject('bizWorkgroup', $aData, $oChange); + $this->MakeFeedback($oP, 'bizWorkgroup'); + + // 4 - Persons + // + for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'ningem'.$i, + ); + $this->CreateObject('bizPerson', $aData, $oChange); + } + $this->MakeFeedback($oP, 'bizPerson'); + + // 5 - Servers + // + for($i = 0 ; $i < $this->m_aPlanned['CIs'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'server'.$i, + ); + $this->CreateObject('bizServer', $aData, $oChange); + } + $this->MakeFeedback($oP, 'bizServer'); + + // 6 - Incident Tickets + // + for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('bizPerson'), + 'workgroup_id' => $this->RandomId('bizWorkgroup'), + 'agent_id' => $this->RandomId('bizPerson'), + 'title' => 'someevent'.$i, + ); + $iTicket = $this->CreateObject('bizIncidentTicket', $aData, $oChange); + + // Incident/Infra + // + $iInfraCount = rand(0, 6); + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'infra_id' => $this->RandomId('bizServer'), + 'ticket_id' => $iTicket, + 'impact' => 'info on impact '.$iLinked, + ); + $this->CreateObject('lnkInfraTicket', $aData, $oChange); + } + + // Incident/Contact + // + $iInfraCount = rand(0, 6); + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'contact_id' => $this->RandomId('bizPerson'), + 'ticket_id' => $iTicket, + 'role' => 'role '.$iLinked, + ); + $this->CreateObject('lnkContactTicket', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'bizIncidentTicket'); + + + // 7 - Change Tickets + // + for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'requestor_id' => $this->RandomId('bizPerson'), + 'workgroup_id' => $this->RandomId('bizWorkgroup'), + 'agent_id' => $this->RandomId('bizPerson'), + 'supervisorgroup_id' => $this->RandomId('bizWorkgroup'), + 'supervisor_id' => $this->RandomId('bizPerson'), + 'managergroup_id' => $this->RandomId('bizWorkgroup'), + 'manager_id' => $this->RandomId('bizPerson'), + 'title' => "change$i", + ); + $iTicket = $this->CreateObject('bizChangeTicket', $aData, $oChange); + + // Change/Infra + // + $iInfraCount = rand(0, 6); + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'infra_id' => $this->RandomId('bizServer'), + 'ticket_id' => $iTicket, + 'impact' => 'info on impact '.$iLinked, + ); + $this->CreateObject('lnkInfraChangeTicket', $aData, $oChange); + } + + // Change/Contact + // + $iInfraCount = rand(0, 6); + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'contact_id' => $this->RandomId('bizPerson'), + 'change_id' => $iTicket, + 'role' => 'role '.$iLinked, + ); + $this->CreateObject('lnkContactChange', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'bizChangeTicket'); + + // 8 - Service calls + // + for($i = 0 ; $i < $this->m_aPlanned['ServiceCalls'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('bizPerson'), + 'workgroup_id' => $this->RandomId('bizWorkgroup'), + 'agent_id' => $this->RandomId('bizPerson'), + 'title' => "call$i", + ); + $iTicket = $this->CreateObject('bizServiceCall', $aData, $oChange); + + // Call/Infra + // + $iInfraCount = rand(0, 6); + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'infra_id' => $this->RandomId('bizServer'), + 'call_id' => $iTicket, + 'impact' => 'info on impact '.$iLinked, + ); + $this->CreateObject('lnkInfraCall', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'bizServiceCall'); + + // 8 - Documents + // + $sMyDoc = ''; + for($i = 0 ; $i < 1000 ; $i++) + { + // 100 chars + $sMyDoc .= "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"; + } + $oRefDoc = new ormDocument($sMyDoc, 'text/plain'); + + for($i = 0 ; $i < $this->m_aPlanned['Documents'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => "document$i", + 'contents' => $oRefDoc, + ); + $this->CreateObject('bizDocument', $aData, $oChange); + } + $this->MakeFeedback($oP, 'bizDocument'); + } +} + +/** + * Ask the user what are the settings for the data load + */ +function DisplayStep1(SetupWebPage $oP) +{ + $sNextOperation = 'step2'; + $oP->add("

            iTop benchmarking

            \n"); + $oP->add("

            Please specify the requested volumes

            \n"); + $oP->add("
            \n"); + $oP->add("
            Data load configuration\n"); + $aForm = array(); + $aForm[] = array( + 'label' => "Main CIs:", + 'input' => "", + 'help' => ' exclude interfaces, subnets or any other type of secondary CI', + ); + $aForm[] = array( + 'label' => "Contacts:", + 'input' => "", + 'help' => '', + ); + $aForm[] = array( + 'label' => "Contracts:", + 'input' => "", + 'help' => '', + ); + $oP->form($aForm); + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); +} + + +/** + * Inform the user how many items will be created + */ +function DisplayStep2(SetupWebPage $oP, $oDataCreation) +{ + $sNextOperation = 'step3'; + $oP->add("

            iTop benchmarking

            \n"); + $oP->add("

            Step 2: review planned volumes

            \n"); + + $aPlanned = $oDataCreation->GetPlan(); + + $aForm = array(); + foreach ($aPlanned as $sKey => $iCount) + { + $aForm[] = array( + 'label' => $sKey, + 'input' => $iCount, + ); + } + $oP->form($aForm); + + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); +} + + +/** + * Do create the data set... could take some time to execute + */ +function DisplayStep3(SetupWebPage $oP, $oDataCreation) +{ +// $sNextOperation = 'step3'; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Administrator"); + $iChangeId = $oMyChange->DBInsertNoReload(); + + $oDataCreation->Go($oP, $oMyChange); +} + +/** + * Main program + */ + +LoginWebPage::DoLogin(); // Check user rights and prompt if needed + +$sOperation = Utils::ReadParam('operation', 'step1'); +$oP = new SetupWebPage('iTop benchmark utility'); + +try +{ + switch($sOperation) + { + case 'step1': + DisplayStep1($oP); + break; + + case 'step2': + $oP->no_cache(); + $iPlannedCIs = Utils::ReadParam('plannedcis'); + $iPlannedContacts = Utils::ReadParam('plannedcontacts'); + $iPlannedContracts = Utils::ReadParam('plannedcontracts'); + + $oDataCreation = new BenchmarkDataCreation($iPlannedCIs, $iPlannedContacts, $iPlannedContracts); + DisplayStep2($oP, $oDataCreation); + break; + + case 'step3': + $oP->no_cache(); + $iPlannedCIs = Utils::ReadParam('plannedcis'); + $iPlannedContacts = Utils::ReadParam('plannedcontacts'); + $iPlannedContracts = Utils::ReadParam('plannedcontracts'); + + $oDataCreation = new BenchmarkDataCreation($iPlannedCIs, $iPlannedContacts, $iPlannedContracts); + DisplayStep3($oP, $oDataCreation); + break; + + default: + $oP->error("Error: unsupported operation '$sOperation'"); + + } +} +catch(ZZException $e) +{ + $oP->error("Error: '".$e->getMessage()."'"); +} +catch(ZZCoreException $e) +{ + $oP->error("Error: '".$e->getHtmlDesc()."'"); +} +$oP->output(); +?> diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index 8d6533f768..a637fbd968 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -149,7 +149,7 @@ table.formTable { foreach($aData as $aRow) { $this->add("
            {$aRow['label']}{$aRow['input']}
            a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, + + // Will be defined later + deleteExpando: true, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var expando = "jQuery" + now(), uuid = 0, windowData = {}; + +jQuery.extend({ + cache: {}, + + expando:expando, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache; + + if ( !id && typeof name === "string" && data === undefined ) { + return null; + } + + // Compute a unique ID for the element + if ( !id ) { + id = ++uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + elem[ expando ] = id; + thisCache = cache[ id ] = jQuery.extend(true, {}, name); + + } else if ( !cache[ id ] ) { + elem[ expando ] = id; + cache[ id ] = {}; + } + + thisCache = cache[ id ]; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + // Completely remove the data cache + delete cache[ id ]; + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + 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() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc 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; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style instead. + return jQuery.style( elem, name, value ); + } +}); +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle, eventHandle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + handleObj.guid = handler.guid; + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( var j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( var j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, + isClick = jQuery.nodeName(target, "a") && type === "click", + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ type ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + type ]; + + if ( old ) { + target[ "on" + type ] = null; + } + + jQuery.event.triggered = true; + target[ type ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( old ) { + target[ "on" + type ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace, events; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + var events = jQuery.data(this, "events"), handlers = events[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, arguments ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); + }, + + remove: function( handleObj ) { + var remove = true, + type = handleObj.origType.replace(rnamespaces, ""); + + jQuery.each( jQuery.data(this, "events").live || [], function() { + if ( type === this.origType.replace(rnamespaces, "") ) { + remove = false; + return false; + } + }); + + if ( remove ) { + jQuery.event.remove( this, handleObj.origType, liveHandler ); + } + } + + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = eventHandle; + } + + return false; + }, + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + elem.removeEventListener( type, handle, false ); + } : + function( elem, type, handle ) { + elem.detachEvent( "on" + type, handle ); + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[ expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return 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 = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var formElems = /textarea|input|select/i, + + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return formElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + context.each(function(){ + jQuery.event.add( this, liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + }); + + } else { + // unbind live handler + context.unbind( liveConvert( type, selector ), fn ); + } + } + + return this; + } +}); + +function liveHandler( event ) { + var stop, elems = [], selectors = [], args = arguments, + related, match, handleObj, elem, j, i, l, data, + events = jQuery.data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( match[i].selector === handleObj.selector ) { + elem = match[i].elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +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").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

            "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
            "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = getText; +jQuery.isXMLDoc = isXML; +jQuery.contains = contains; + +return; + +window.Sizzle = Sizzle; + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + slice = Array.prototype.slice; + +// Implement the identical functionality for filter and not +var winnow = function( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur }); + delete matches[selector]; + } + } + cur = cur.parentNode; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +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 jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "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 jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /"; + }, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
            ", "
            " ], + thead: [ 1, "", "
            " ], + tr: [ 2, "", "
            " ], + td: [ 3, "", "
            " ], + col: [ 2, "", "
            " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and \n"; - $oPage->add_at_the_end($this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk')); // Forms should not be inside forms - $oPage->add_at_the_end($this->GetLinkObjectDialog($oPage, $this->m_iInputId)); // Forms should not be inside forms - $sHTMLValue .= "m_iInputId}\" size=\"35\" value=\"\" title=\"".Dict::S('UI:LinksWidget:Autocomplete+')."\"/>"; - $sHTMLValue .= "m_iInputId}\" value=\"".Dict::S('UI:Button:AddObject')."\" class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>"; - $sHTMLValue .= " m_iInputId', '$sExtKeyToRemote');\"/> m_iInputId}\">\n"; - // another hidden input to store & pass the object's Id - $sHTMLValue .= "m_iInputId}\" onChange=\"EnableAddButton('{$this->m_iInputId}');\"/>\n"; - $sHTMLValue .= "m_iInputId}\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\"/>\n"; - $oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\noLinkWidget{$this->m_iInputId}.Init();\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});"); - $oPage->add_ready_script("\$('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled');"); - $oPage->add_ready_script("\$('#ac_{$this->m_iInputId}').result( function(event, data, formatted) { if (data) { $('#id_ac_{$this->m_iInputId}').val(data[1]); $('#ac_add_{$this->m_iInputId}').attr('disabled', ''); } else { $('#ac_add_{$this->m_iInputId}').attr('disabled', 'disabled'); } } );"); - } - else - { - // Few choices, use a normal 'select' - $sHTMLValue = "\n"; - } - $sHTMLValue .= "
            m_iInputId}_values\">\n"; - if ($oCurrentValuesSet != null) - { - // transform the DBObjectSet into a CMDBObjectSet !!! - $aLinkedObjects = $oCurrentValuesSet->ToArray(false); - // Actual values will be displayed asynchronously, no need to display them here - //if (count($aLinkedObjects) > 0) - //{ - // $oSet = CMDBObjectSet::FromArray($sLinkedClass, $aLinkedObjects); - // $oDisplayBlock = DisplayBlock::FromObjectSet($oSet, 'list'); - // $sHTMLValue .= $oDisplayBlock->GetDisplay($oPage, $this->m_iInputId.'_current', array('linkage' => $sExtKeyToMe, 'menu' => false)); - //} - } - $sHTMLValue .= "
            \n"; - return $sHTMLValue; } + + /** + * A one-row form for editing a link record + * @param WebPage $oP Web page used for the ouput + * @param DBObject $oLinkedObj The object to which all the elements of the linked set refer to + * @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add + * @param Hash $aArgs Extra context arguments + * @return string The HTML fragment of the one-row form + */ + protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array() ) + { + $sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}"; + $aRow = array(); + if(is_object($linkObjOrId)) + { + $key = $linkObjOrId->GetKey(); + $sPrefix .= "[$key]["; + $sNameSuffix = "]"; // To make a tabular form + $aArgs['prefix'] = $sPrefix; + $aRow['form::checkbox'] = ""; + foreach($this->m_aEditableFields as $sFieldCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); + $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $key, $sNameSuffix, 0, $aArgs); + } + } + else + { + // form for creating a new record + $sPrefix .= "[$linkObjOrId]["; + $sNameSuffix = "]"; // To make a tabular form + $aArgs['prefix'] = $sPrefix; + $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] .= ""; + foreach($this->m_aEditableFields as $sFieldCode) + { + $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); + $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, '' /* id */, $sNameSuffix, 0, $aArgs); + } + } + + foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) + { + $aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode); + } + return $aRow; + } + + /** + * Display one row of the whole form + * @return none + */ + protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId) + { + $sHtml = ''; + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}_row_$iRowId\">\n"; + foreach($aConfig as $sName=>$void) + { + $sHtml .= "".$aRow[$sName]."\n"; + } + $sHtml .= "\n"; + + return $sHtml; + } + + /** + * Display the table with the form for editing all the links at once + * @param WebPage $oP The web page used for the output + * @param Hash $aConfig The table's header configuration + * @param Hash $aData The tabular data to be displayed + * @return string Html fragment representing the form table + */ + protected function DisplayFormTable(WebPage $oP, $aConfig, $aData) + { + $sHtml = ''; + $sHtml .= "\n"; + // Header + $sHtml .= "\n"; + $sHtml .= "\n"; + foreach($aConfig as $sName=>$aDef) + { + $sHtml .= "\n"; + } + $sHtml .= "\n"; + $sHtml .= "\n"; + + // Content + $sHtml .= "\n"; + if (count($aData) == 0) + { + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}_empty_row\">"; + } + else + { + foreach($aData as $iRowId => $aRow) + { + $sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId); + } + } + $sHtml .= "\n"; + + // Footer + $sHtml .= "
            ".$aDef['label']."
            ".Dict::S('UI:Message:EmptyList:UseAdd')."m_sAttCode}{$this->m_sNameSuffix}\" value=\"\">
            \n"; + + return $sHtml; + } + + + /** + * Get the HTML fragment corresponding to the linkset editing widget + * @param WebPage $oP The web page used for all the output + * @param DBObjectSet The initial value of the linked set + * @param Hash $aArgs Extra context arguments + * @return string The HTML fragment to be inserted into the page + */ + public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array()) + { + $iWidgetIndex = self::$iWidgetIndex; + $sHtmlValue = ''; + $sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode); + $sHtmlValue .= "
            m_sAttCode}{$this->m_sNameSuffix}\">\n"; + $oValue->Rewind(); + $aForm = array(); + $oContext = new UserContext(); + while($oCurrentLink = $oValue->Fetch()) + { + $aRow = array(); + $key = $oCurrentLink->GetKey(); + $oLinkedObj = $oContext->GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote)); + + $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs); + } + $sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm); + $oPage->add_ready_script(<<m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}'); + oWidget$iWidgetIndex.Init(); +EOF +); + $sHtmlValue .= "     m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget$iWidgetIndex.RemoveSelected();\" >"; + $sHtmlValue .= "   m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget$iWidgetIndex.AddObjects();\">\n"; + $sHtmlValue .= "

             

            \n"; + $sHtmlValue .= "
            \n"; + $oPage->add_at_the_end($this->GetObjectPickerDialog($oPage)); // To prevent adding forms inside the main form + return $sHtmlValue; + } + /** * This static function is called by the Ajax Page when there is a need to fill an autocomplete combo - * @param $oPage WebPage The ajax page used for the put^put (sent back to the browser + * @param $oPage WebPage The ajax page used for the output (sent back to the browser) * @param $oContext UserContext The context of the user (for limiting the search) * @param $sClass string The name of the class of the current object being edited * @param $sAttCode string The name of the attribute being edited @@ -257,111 +339,106 @@ class UILinksWidget return $sTargetClass; } - protected function GetObjectPickerDialog($oPage, $sTargetClass, $sOkFunction) + protected function GetObjectPickerDialog($oPage) { - $sDialogTitle = Dict::S('UI:ManageObjectsDlg'); - $sOkBtnLabel = Dict::S('UI:Button:Ok'); - $sCancelBtnLabel = Dict::S('UI:Button:Cancel'); - $sAddBtnLabel = Dict::S('UI:Button:AddToList'); - $sRemoveBtnLabel = Dict::S('UI:Button:RemoveFromList'); - $sFilterBtnLabel = Dict::S('UI:Button:FilterList'); - $sLabelSelectedObjects = Dict::S('UI:Label:SelectedObjects'); - $sLabelAvailableObjects = Dict::S('UI:Label:AvailableObjects'); - $sHTML = <<< EOF - -EOF; - $oPage->add_ready_script("$('#ManageObjectsDlg_$this->m_iInputId').dialog( {autoOpen: false, modal: true, width: 750, height: 350} );"); // JQuery UI dialog - //$oPage->add_ready_script("UpdateObjectList('$sClass');"); - return $sHTML; + $sHtml = "
            m_sAttCode}{$this->m_sNameSuffix}\">"; + //$oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); + $sHtml .= "
            \n"; + //$sHtml .= "
            \n"); + //$sHtml .= "

            ".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "".$oTargetObj->GetHyperlink()."")."

            \n"); + //$sHtml .= "
            \n"); + + $oContext = new UserContext(); + $iWidgetIndex = self::$iWidgetIndex; + $oFilter = $oContext->NewFilter($this->m_sRemoteClass); + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'search', false); + $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true)); + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget$iWidgetIndex.DoAddObjects(this.id);\">\n"; + $sHtml .= "
            m_sAttCode}{$this->m_sNameSuffix}\">\n"; + $sHtml .= "

            ".Dict::S('UI:Message:EmptyList:UseSearchForm')."

            \n"; + $sHtml .= "
            \n"; + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">  "; + $sHtml .= "
            \n"; + $sHtml .= "\n"; + $sHtml .= "
            \n"; + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ autoOpen: false, modal: true });"); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass), " ZZZZ ")."'});"); + $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget$iWidgetIndex.SearchObjectsToAdd);"); + return $sHtml; } - - protected function GetLinkObjectDialog($oPage, $sId) + + /** + * Search for objects to be linked to the current object (i.e "remote" objects) + * @param WebPage $oP The page used for the output (usually an AjaxWebPage) + * @param UserContext $oContext User context to limit the search... + * @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass + * @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search + */ + public function SearchObjectsToAdd(WebPage $oP, UserContext $oContext, $sRemoteClass = '', $aAlreadyLinkedIds = array()) { - $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); - $sLinkedClass = $oAttDef->GetLinkedClass(); - $sStateAttCode = MetaModel::GetStateAttributeCode($sLinkedClass); - $sDefaultState = MetaModel::GetDefaultState($sLinkedClass); - $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); - $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); - $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); - - $sHTML = "
            \n"; - $sHTML .= "
            \n"; - $sHTML .= "
            \n"; - $index = 0; - $aAttrsMap = array(); - $aDetails = array(); - foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef) + if ($sRemoteClass != '') { - if ($sStateAttCode == $sAttCode) + // assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass)); + $oFilter = $oContext->NewFilter($sRemoteClass); + } + else + { + // No remote class specified use the one defined in the linkedset + $oFilter = $oContext->NewFilter($this->m_sRemoteClass); + } + if (count($aAlreadyLinkedIds) > 0) + { + // Positive IDs correspond to existing link records + // negative IDs correspond to "remote" objects to be linked + $aLinkIds = array(); + $aRemoteObjIds = array(); + foreach($aAlreadyLinkedIds as $iId) { - // State attribute is always hidden from the UI - //$sHTMLValue = $this->GetStateLabel(); - //$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - } - else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote)) - { - $iFlags = MetaModel::GetAttributeFlags($sLinkedClass, $sDefaultState, $sAttCode); - if ($iFlags & OPT_ATT_HIDDEN) + if ($iId > 0) { - // Attribute is hidden, do nothing + $aLinkIds[] = $iId; } else { - if ($iFlags & OPT_ATT_READONLY) - { - // Attribute is read-only - $sHTMLValue = $this->GetAsHTML($sAttCode); - } - else - { - $sValue = ""; //$this->Get($sAttCode); - $sDisplayValue = ""; //$this->GetEditValue($sAttCode); - $sSubId = $sId.'_'.$index; - $aAttrsMap[$sAttCode] = $sSubId; - $index++; - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sLinkedClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sSubId, $this->m_sAttCode); - } - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); + $aRemoteObjIds[] = -$iId; } } + + if (count($aLinkIds) >0) + { + // Search for the links to find to which "remote" object they are linked + $oLinkFilter = $oContext->NewFilter($this->m_sLinkedClass); + $oLinkFilter->AddCondition('id', $aLinkIds, 'IN'); + $oLinkSet = new CMDBObjectSet($oLinkFilter); + while($oLink = $oLinkSet->Fetch()) + { + $aRemoteObjIds[] = $oLink->Get($this->m_sExtKeyToRemote); + } + } + $oFilter->AddCondition('id', $aRemoteObjIds, 'NOTIN'); + } + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results + } + + public function DoAddObjects(WebPage $oP, UserContext $oContext, $aLinkedObjectIds = array()) + { + $aTable = array(); + foreach($aLinkedObjectIds as $iObjectId) + { + $oLinkedObj = $oContext->GetObject($this->m_sRemoteClass, $iObjectId); + if (is_object($oLinkedObj)) + { + $aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids + $oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId)); + } + else + { + $oP->p(Dict::Format('UI:Error:Object_Class_Id_NotFound', $this->m_sLinkedClass, $iObjectId)); + } } - $sHTML .= $oPage->GetDetails($aDetails); - $sHTML .= "     \n"; - $sHTML .= "
            \n"; - $sHTML .= "
            \n"; - $sHTML .= "
            \n"; - $oPage->add_ready_script("$('#LinkDlg_$sId').dialog( {autoOpen: false, modal: true, width: 300 } );"); // jQuery UI dialog - //$oPage->add_ready_script("UpdateObjectList('$sClass');"); - return $sHTML; } } ?> From 835d54b22b732c1466c55570f257ca04dc06b9d5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 4 Aug 2010 19:27:23 +0000 Subject: [PATCH 570/970] Exclude createfrommail.php from the build, since it is more a sample than some production quality code. SVN:trunk[653] --- webservices/exclude.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 webservices/exclude.txt diff --git a/webservices/exclude.txt b/webservices/exclude.txt new file mode 100644 index 0000000000..17b21ce340 --- /dev/null +++ b/webservices/exclude.txt @@ -0,0 +1,5 @@ +# +# The following source files are not re-distributed with the "build" of the application +# since they are used solely for constructing other files during the build process +# +createfrommail.php \ No newline at end of file From b0b3330c13db2ae547cb530e27af11df1903b6e4 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 4 Aug 2010 19:30:36 +0000 Subject: [PATCH 571/970] - New implementation of the n-n link edition widget... in progress. SVN:trunk[654] --- core/dbobject.class.php | 32 +++++++++++++++++++++++++++++--- core/metamodel.class.php | 6 +++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index aeae906200..6d0ee602d7 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -350,6 +350,28 @@ abstract class DBObject return $this->m_aOrigValues[$sAttCode]; } + /** + * Updates the value of an external field by (re)loading the object + * corresponding to the external key and getting the value from it + * @param string $sAttCode Attribute code of the external field to update + * @return void + */ + protected function UpdateExternalField($sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); + if ($oAttDef->IsExternalField()) + { + $sTargetClass = $oAttDef->GetTargetClass(); + $objkey = $this->Get($oAttDef->GetKeyAttCode()); + $oObj = MetaModel::GetObject($sTargetClass, $objkey); + if (is_object($oObj)) + { + $value = $oObj->Get($oAttDef->GetExtAttCode()); + $this->Set($sAttCode, $value); + } + } + } + // Compute scalar attributes that depend on any other type of attribute public function DoComputeValues() { @@ -495,10 +517,14 @@ abstract class DBObject } $this->m_iKey = $iNewKey; } - - public function GetIcon() + /** + * Get the icon representing this object + * @param boolean $bImgTag If true the result is a full IMG tag (or an emtpy string if no icon is defined) + * @return string Either the full IMG tag ($bImgTag == true) or just the path to the icon file + */ + public function GetIcon($bImgTag = true) { - return MetaModel::GetClassIcon(get_class($this)); + return MetaModel::GetClassIcon(get_class($this), $bImgTag); } public function GetName() diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 81cba95072..1f893efd76 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -288,7 +288,7 @@ abstract class MetaModel return self::GetClassDescription($sClass); } } - final static public function GetClassIcon($sClass) + final static public function GetClassIcon($sClass, $bImgTag = true) { self::_check_subclass($sClass); @@ -305,6 +305,10 @@ abstract class MetaModel return self::GetClassIcon($sParentClass); } } + if ($bImgTag && ($sIcon != '')) + { + $sIcon = ""; + } return $sIcon; } final static public function IsAutoIncrementKey($sClass) From 2df78842896a9dc885f7814eef1d6bfb0c19d58e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 4 Aug 2010 19:55:11 +0000 Subject: [PATCH 572/970] Fixing Trac #209 (default_language setting not used properly) SVN:trunk[655] --- core/dict.class.inc.php | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index dd63184c93..2a1351a6a3 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -59,7 +59,7 @@ class Dict { protected static $m_iErrorMode = DICT_ERR_STRING; protected static $m_sDefaultLanguage = 'EN US'; - protected static $m_sCurrentLanguage = 'EN US'; + protected static $m_sCurrentLanguage = null; // No language selected by default protected static $m_aLanguages = array(); // array( code => array( 'description' => '...', 'localized_description' => '...') ...) protected static $m_aData = array(); @@ -84,6 +84,16 @@ class Dict } + public static function GetCurrentLanguage() + { + if (self::$m_sCurrentLanguage == null) // May happen when no user is logged in (i.e login screen, non authentifed page) + { + // In which case let's use the default language + return self::$m_sDefaultLanguage; + } + return self::$m_sCurrentLanguage; + } + //returns a hash array( code => array( 'description' => '...', 'localized_description' => '...') ...) public static function GetLanguages() { @@ -101,12 +111,12 @@ class Dict { // Attempt to find the string in the user language // - if (!array_key_exists(self::$m_sCurrentLanguage, self::$m_aData)) + if (!array_key_exists(self::GetCurrentLanguage(), self::$m_aData)) { // It may happen, when something happens before the dictionnaries get loaded return $sStringCode; } - $aCurrentDictionary = self::$m_aData[self::$m_sCurrentLanguage]; + $aCurrentDictionary = self::$m_aData[self::GetCurrentLanguage()]; if (array_key_exists($sStringCode, $aCurrentDictionary)) { return $aCurrentDictionary[$sStringCode]; @@ -215,11 +225,4 @@ class Dict MyHelpers::var_dump_html(self::$m_aData); } } - -/* -Dans les templates, les chaines localizables seront remplacées par un tag code_de_la_chaine -Modifier les profils utilisateurs pour stocker le langage de l'utilisateur. -*/ - - ?> From 37bc87e7057465d9b6912f911076d16079d1c550 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 4 Aug 2010 20:07:07 +0000 Subject: [PATCH 573/970] - New implementation of the n-n link edition widget... in progress. SVN:trunk[656] --- js/jquery.popupmenu.js | 14 +++ js/linkswidget.js | 274 +++++++++++++++++++++-------------------- js/utils.js | 29 +++-- 3 files changed, 177 insertions(+), 140 deletions(-) diff --git a/js/jquery.popupmenu.js b/js/jquery.popupmenu.js index 321a19ffcd..d56524e135 100644 --- a/js/jquery.popupmenu.js +++ b/js/jquery.popupmenu.js @@ -34,6 +34,20 @@ jQuery.fn.popupmenu = function () elementPos: 0, hideAfterPosition: true }); + // In links containing a hash, replace what's after the hash by the current hash + // In order to navigate to the same tab as the current one when editing an object + currentHash = ''; + aMatches = /#(.*)$/.exec(window.location.href); + if (aMatches != null) + { + currentHash = aMatches[1]; + popupmenu.find('a').each( function() { + if ( /#(.*)$/.test(this.href)) + { + this.href = this.href.replace(/#(.*)$/, '#'+currentHash); + } + }); + } popupmenu.css('display', 'block'); } else diff --git a/js/linkswidget.js b/js/linkswidget.js index 7747f2f1cc..2127053cec 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -1,153 +1,163 @@ // JavaScript Document - -function LinksWidget(id, sLinkedClass, sExtKeyToMe, sExtKeyToRemote, aAttributes) +function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix) { this.id = id; - this.sLinkedClass = sLinkedClass; - this.sExtKeyToMe = sExtKeyToMe; - this.sExtKeyToRemote = sExtKeyToRemote; - this.aAttributes = aAttributes; - this.aLinks = new Array(); - + this.iInputId = iInputId; + this.sClass = sClass; + this.sAttCode = sAttCode; + this.sSuffix = sSuffix; + var me = this; this.Init = function() { - sLinks = $('#'+this.id).val(); - if (sLinks.length > 0) - { - this.aLinks = JSON.parse(sLinks); - } - this.Refresh(); - } - - this.Refresh = function () - { - $('#v_'+this.id).html(''); - sLinks = JSON.stringify(this.aLinks); - if (this.aLinks.length == 0) - { - $('#'+this.id+'_values').empty(); - $('#'+this.id).val(sLinks); - $('#'+this.id).trigger('validate'); - } - else - { - $('#'+this.id).val(sLinks); - $('#'+this.id+'_values').load('ajax.render.php?operation=ui.linkswidget.linkedset&sclass='+this.sLinkedClass+'&sextkeytome='+this.sExtKeyToMe+'&sextkeytoremote='+this.sExtKeyToRemote+'&myid='+this.id, - {'sset' : sLinks}, function() - { - // Refresh the style of the loaded table - $('#'+this.id+' table.listResults').tableHover(); - $('#'+this.id+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra', 'truncatedList']} ); // sortable and zebra tables - } - ); - } + // make sure that the form is clean + $('#linkedset_'+this.id+' .selection').each( function() { this.checked = false; }); + $('#'+this.id+'_btnRemove').attr('disabled','disabled'); + $('#'+this.id+'_linksToRemove').val(''); } - this.OnOk = function() + this.RemoveSelected = function() { - this.aObjectBeingLinked = new Array(); - sSelected = 'selected_objects_'+this.id; - oSelected = document.getElementById(sSelected); - for(i=0; i 0) - { - $('#LinkDlg_'+this.id).dialog('open'); - } - else - { - this.Refresh(); - $('#ac_add_'+this.id).attr('disabled', 'disabled'); - } - } + ); + // Disable the button since all the selected items have been removed + $(my_id+'_btnRemove').attr('disabled','disabled'); + // Re-run the zebra plugin to properly highlight the remaining lines + $('#linkset_'+this.id+' .listResults').trigger('update'); + } - this.OnCancel = function() + this.OnSelectChange = function() { - // Restore the links to their previous value (just in case) - this.aLinks = this.aPreviousLinks; + var nbChecked = $('#linkedset_'+me.id+' .selection:checked').length; + if (nbChecked > 0) + { + $('#'+me.id+'_btnRemove').attr('disabled',''); + } + else + { + $('#'+me.id+'_btnRemove').attr('disabled','disabled'); + } } - this.OnLinkOk = function() + this.AddObjects = function() { - $('#LinkDlg_'+this.id).dialog('close'); - for(i=0; i 0) + { + for(index = 0; index < aSubmit.length; index++) + { + // Restore the previously bound submit handlers + if (aSubmit[index].data != undefined) + { + $('#'+divId+' form').bind('submit.'+aSubmit[index].namespace, aSubmit[index].data, aSubmit[index].handler) + } + else + { + $('#'+divId+' form').bind('submit.'+aSubmit[index].namespace, aSubmit[index].handler) + } + } + } $('#'+divId).unblock(); } ); From fd9e971ecb6da96d5bff2765ef72ab7987215a44 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 4 Aug 2010 20:08:02 +0000 Subject: [PATCH 574/970] - New implementation of the n-n link edition widget... in progress. SVN:trunk[657] --- js/jquery.history.js | 156 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 js/jquery.history.js diff --git a/js/jquery.history.js b/js/jquery.history.js new file mode 100644 index 0000000000..25eab9f0f9 --- /dev/null +++ b/js/jquery.history.js @@ -0,0 +1,156 @@ +/* + * jQuery history plugin + * + * Copyright (c) 2006 Taku Sano (Mikage Sawatari) + * Licensed under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + * Modified by Lincoln Cooper to add Safari support and only call the callback once during initialization + * for msie when no initial hash supplied. + * API rewrite by Lauris Bukis-Haberkorns + */ + +(function($) { + +function History() +{ + this._curHash = ''; + this._callback = function(hash){}; +}; + +$.extend(History.prototype, { + + init: function(callback) { + this._callback = callback; + this._curHash = location.hash; + + if($.browser.msie) { + // To stop the callback firing twice during initilization if no hash present + if (this._curHash == '') { + this._curHash = '#'; + } + + // add hidden iframe for IE + $("body").prepend(''); + var iframe = $("#jQuery_history")[0].contentWindow.document; + iframe.open(); + iframe.close(); + iframe.location.hash = this._curHash; + } + else if ($.browser.safari) { + // etablish back/forward stacks + this._historyBackStack = []; + this._historyBackStack.length = history.length; + this._historyForwardStack = []; + this._isFirst = true; + this._dontCheck = false; + } + this._callback(this._curHash.replace(/^#/, '')); + setInterval(this._check, 100); + }, + + add: function(hash) { + // This makes the looping function do something + this._historyBackStack.push(hash); + + this._historyForwardStack.length = 0; // clear forwardStack (true click occured) + this._isFirst = true; + }, + + _check: function() { + if($.browser.msie) { + // On IE, check for location.hash of iframe + var ihistory = $("#jQuery_history")[0]; + var iframe = ihistory.contentDocument || ihistory.contentWindow.document; + var current_hash = iframe.location.hash; + if(current_hash != $.history._curHash) { + + location.hash = current_hash; + $.history._curHash = current_hash; + $.history._callback(current_hash.replace(/^#/, '')); + + } + } else if ($.browser.safari) { + if (!$.history._dontCheck) { + var historyDelta = history.length - $.history._historyBackStack.length; + + if (historyDelta) { // back or forward button has been pushed + $.history._isFirst = false; + if (historyDelta < 0) { // back button has been pushed + // move items to forward stack + for (var i = 0; i < Math.abs(historyDelta); i++) $.history._historyForwardStack.unshift($.history._historyBackStack.pop()); + } else { // forward button has been pushed + // move items to back stack + for (var i = 0; i < historyDelta; i++) $.history._historyBackStack.push($.history._historyForwardStack.shift()); + } + var cachedHash = $.history._historyBackStack[$.history._historyBackStack.length - 1]; + if (cachedHash != undefined) { + $.history._curHash = location.hash; + $.history._callback(cachedHash); + } + } else if ($.history._historyBackStack[$.history._historyBackStack.length - 1] == undefined && !$.history._isFirst) { + // back button has been pushed to beginning and URL already pointed to hash (e.g. a bookmark) + // document.URL doesn't change in Safari + if (document.URL.indexOf('#') >= 0) { + $.history._callback(document.URL.split('#')[1]); + } else { + $.history._callback(''); + } + $.history._isFirst = true; + } + } + } else { + // otherwise, check for location.hash + var current_hash = location.hash; + if(current_hash != $.history._curHash) { + $.history._curHash = current_hash; + $.history._callback(current_hash.replace(/^#/, '')); + } + } + }, + + load: function(hash) { + var newhash; + + if ($.browser.safari) { + newhash = hash; + } else { + newhash = '#' + hash; + location.hash = newhash; + } + this._curHash = newhash; + + if ($.browser.msie) { + var ihistory = $("#jQuery_history")[0]; // TODO: need contentDocument? + var iframe = ihistory.contentWindow.document; + iframe.open(); + iframe.close(); + iframe.location.hash = newhash; + this._callback(hash); + } + else if ($.browser.safari) { + this._dontCheck = true; + // Manually keep track of the history values for Safari + this.add(hash); + + // Wait a while before allowing checking so that Safari has time to update the "history" object + // correctly (otherwise the check loop would detect a false change in hash). + var fn = function() {$.history._dontCheck = false;}; + window.setTimeout(fn, 200); + this._callback(hash); + // N.B. "location.hash=" must be the last line of code for Safari as execution stops afterwards. + // By explicitly using the "location.hash" command (instead of using a variable set to "location.hash") the + // URL in the browser and the "history" object are both updated correctly. + location.hash = newhash; + } + else { + this._callback(hash); + } + } +}); + +$(document).ready(function() { + $.history = new History(); // singleton instance +}); + +})(jQuery); From 422ac562605ef9d835b54f539fc5e231aa7926a0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 4 Aug 2010 20:10:20 +0000 Subject: [PATCH 575/970] - New implementation of the n-n link edition widget... in progress. SVN:trunk[658] --- pages/UI.php | 55 ++++++++++++++++++++++++++++++++--------- pages/ajax.render.php | 37 ++++++++++++--------------- pages/xml.navigator.php | 4 +-- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 9f49e5404e..758df0220d 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -357,16 +357,48 @@ function UpdateObject(&$oObj) { foreach(MetaModel::ListAttributeDefs(get_class($oObj)) as $sAttCode=>$oAttDef) { - if ($oAttDef->IsLinkSet()) + if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect()) { - // Link set, the data is a set of link objects, encoded in JSON - $aAttributes[$sAttCode] = trim(utils::ReadPostedParam("attr_$sAttCode", '')); - if (!empty($aAttributes[$sAttCode])) + $aLinks = utils::ReadPostedParam("attr_$sAttCode", ''); + $sLinkedClass = $oAttDef->GetLinkedClass(); + $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); + $sExtKeyToMe = $oAttDef->GetExtKeyToMe(); + $oLinkedSet = DBObjectSet::FromScratch($sLinkedClass); + if (is_array($aLinks)) { - $oLinkSet = WizardHelper::ParseJsonSet($oObj, $oAttDef->GetLinkedClass(), $oAttDef->GetExtKeyToMe(), $aAttributes[$sAttCode]); - $oObj->Set($sAttCode, $oLinkSet); - // TO DO: detect a real modification, for now always update !! + foreach($aLinks as $id => $aData) + { + if (is_numeric($id)) + { + if ($id < 0) + { + // New link to be created, the opposite of the id (-$id) is the ID of the remote object + $oLink = MetaModel::NewObject($sLinkedClass); + $oLink->Set($sExtKeyToRemote, -$id); + $oLink->Set($sExtKeyToMe, $oObj->GetKey()); + } + else + { + // Existing link, potentially to be updated... + $oLink = MetaModel::GetObject($sLinkedClass, $id); + } + // Now populate the attributes + foreach($aData as $sName => $value) + { + if (MetaModel::IsValidAttCode($sLinkedClass, $sName)) + { + $oLinkAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sName); + if ($oLinkAttDef->IsWritable()) + { + $oLink->Set($sName, $value); + } + } + } + $oLinkedSet->AddObject($oLink); + } + } } + $oObj->Set($sAttCode, $oLinkedSet); } else if ($oAttDef->IsWritable()) { @@ -426,6 +458,7 @@ try switch($operation) { case 'details': + $sClass = utils::ReadParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); @@ -604,7 +637,7 @@ try { $iCount += count($aLeafs); $oP->add("
            \n"); - $oP->add("

            ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."

            \n"); + $oP->add("

            ".MetaModel::GetClassIcon($sClassName)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."

            \n"); $oP->add("
            \n"); $oLeafsFilter->AddCondition('id', $aLeafs, 'IN'); $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); @@ -649,7 +682,7 @@ try { $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->add("
            \n"); - $oP->add("

            GetIcon()."\" style=\"vertical-align:middle\"> ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

            \n"); + $oP->add("

            ".$oObj->GetIcon()." ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

            \n"); $oP->add("
            \n"); $oP->add("
            \n"); @@ -762,7 +795,7 @@ try // Display the creation form $sClassLabel = MetaModel::GetName($sRealClass); $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); - $oP->add("

            ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

            \n"); + $oP->add("

            ".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

            \n"); $oP->add("
            \n"); $aDefaults = utils::ReadParam('default', array()); $aContext = $oAppContext->GetAsHash(); @@ -777,7 +810,7 @@ try { // Select the derived class to create $sClassLabel = MetaModel::GetName($sClass); - $oP->add("

            ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

            \n"); + $oP->add("

            ".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

            \n"); $oP->add("
            \n"); $oP->add('
            '); $oP->add('

            '.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 7c5fbceb40..10778b5993 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -53,14 +53,13 @@ $oContext = new UserContext(); $operation = utils::ReadParam('operation', ''); $sFilter = stripslashes(utils::ReadParam('filter', '')); $sEncoding = utils::ReadParam('encoding', 'serialize'); -$sClass = utils::ReadParam('class', 'bizContact'); +$sClass = utils::ReadParam('class', 'MissingAjaxParam'); $sStyle = utils::ReadParam('style', 'list'); switch($operation) { case 'addObjects': require_once('../application/uilinkswizard.class.inc.php'); - $sClass = utils::ReadParam('class', '', 'get'); $sLinkedClass = utils::ReadParam('linkedClass', '', 'get'); $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); @@ -70,26 +69,22 @@ switch($operation) break; case 'searchObjectsToAdd': - require_once('../application/uilinkswizard.class.inc.php'); - - $sClass = utils::ReadParam('class', '', 'get'); - $sLinkedClass = utils::ReadParam('linkedClass', '', 'get'); - $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); - $iObjectId = utils::ReadParam('objectId', '', 'get'); - $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); - $oLinksWizard->SearchObjectsToAdd($oPage, $oContext); + $sRemoteClass = utils::ReadParam('sRemoteClass', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix); + $oWidget->SearchObjectsToAdd($oPage, $oContext, $sRemoteClass, $aAlreadyLinked); break; case 'doAddObjects': - require_once('../application/uilinkswizard.class.inc.php'); - - $sClass = utils::ReadParam('class', '', 'get'); - $sLinkedClass = utils::ReadParam('linkedClass', '', 'get'); - $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); - $iObjectId = utils::ReadParam('objectId', '', 'get'); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); $aLinkedObjectIds = utils::ReadParam('selectObject', array(), 'get'); - $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); - $oLinksWizard->DoAddObjects($oPage, $oContext, $aLinkedObjectIds); + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix); + $oWidget->DoAddObjects($oPage, $oContext, $aLinkedObjectIds); break; case 'wizard_helper_preview': @@ -324,9 +319,9 @@ switch($operation) break; case 'search_form': - $sClass = utils::ReadParam('className', '', 'get'); - $sRootClass = utils::ReadParam('baseClass', '', 'get'); - $currentId = utils::ReadParam('currentId', '', 'get'); + $sClass = utils::ReadParam('className', ''); + $sRootClass = utils::ReadParam('baseClass', ''); + $currentId = utils::ReadParam('currentId', ''); $oFilter = $oContext->NewFilter($sClass); $oSet = new CMDBObjectSet($oFilter); $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass)); diff --git a/pages/xml.navigator.php b/pages/xml.navigator.php index 8a1a486eda..66188dc9c8 100755 --- a/pages/xml.navigator.php +++ b/pages/xml.navigator.php @@ -80,7 +80,7 @@ function GetRelatedObjects(DBObject $oObj, $sRelationName, &$oLinks, &$oXmlDoc, $oLinkedNode->SetAttribute('id', $oTargetObj->GetKey()); $oLinkedNode->SetAttribute('obj_class', get_class($oTargetObj)); $oLinkedNode->SetAttribute('name', $oTargetObj->GetName()); - $oLinkedNode->SetAttribute('icon', BuildIconPath($oTargetObj->GetIcon())); + $oLinkedNode->SetAttribute('icon', BuildIconPath($oTargetObj->GetIcon(false /* No IMG tag */))); AddNodeDetails($oLinkedNode, $oTargetObj); $oSubLinks = $oXmlDoc->CreateElement('links'); GetRelatedObjects($oTargetObj, $sRelationName, $oSubLinks, $oXmlDoc, $oLinkedNode); @@ -137,7 +137,7 @@ if ($id != 0) $oXmlNode->SetAttribute('id', $oObj->GetKey()); $oXmlNode->SetAttribute('obj_class', get_class($oObj)); $oXmlNode->SetAttribute('name', $oObj->GetName()); - $oXmlNode->SetAttribute('icon', BuildIconPath($oObj->GetIcon())); // Hard coded for the moment + $oXmlNode->SetAttribute('icon', BuildIconPath($oObj->GetIcon(false /* No IMG tag */))); // Hard coded for the moment AddNodeDetails($oXmlNode, $oObj); $oLinks = $oXmlDoc->CreateElement("links"); From 279e0238cbaaad1f19520162c972469e3fcd91c0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 5 Aug 2010 09:07:14 +0000 Subject: [PATCH 576/970] - New implementation of the n-n link edition widget... almost complete. SVN:trunk[659] --- application/cmdbabstract.class.inc.php | 10 +++++++- application/displayblock.class.inc.php | 11 ++++++--- application/ui.linkswidget.class.inc.php | 19 +++++++-------- css/light-grey.css | 30 ++++-------------------- dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + js/linkswidget.js | 22 +++++++++++++++-- js/utils.js | 30 +++++++++++++----------- pages/ajax.render.php | 6 +++++ 9 files changed, 74 insertions(+), 56 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3142b2e53d..18baced5f2 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1050,7 +1050,15 @@ abstract class cmdbAbstractObject extends CMDBObject $iKey = $this->GetKey(); $aDetails = array(); $aFieldsMap = array(); - $oPage->add("m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return CheckFields('form_{$this->m_iFormId}', true)\">\n"); + if (!isset($aExtraParams['action'])) + { + $sFormAction = $_SERVER['SCRIPT_NAME']; // No parameter in the URL, the only parameter will be the ones passed through the form + } + else + { + $sFormAction = $aExtraParams['action']; + } + $oPage->add("m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return CheckFields('form_{$this->m_iFormId}', true)\">\n"); $oPage->AddTabContainer(OBJECT_PROPERTIES_TAB); $oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB); diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 636acf1ab3..826741fc77 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -571,9 +571,14 @@ class DisplayBlock static $iSearchSectionId = 1; $sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed'; $sHtml .= "

            \n"; - $oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\n" . - " \$(\"#Search_$iSearchSectionId\").slideToggle('normal');\n" . - " $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});"); + $oPage->add_ready_script( +<<m_oSet, $aExtraParams); $sHtml .= "
            \n"; $sHtml .= "
            \n"; diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 6ee62d8e2c..9843a247c2 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -76,6 +76,8 @@ class UILinksWidget } } } + + $this->m_aTableConfig['static::key'] = array( 'label' => MetaModel::GetName($this->m_sRemoteClass), 'description' => MetaModel::GetClassDescription($this->m_sRemoteClass)); foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) { // TO DO: check the state of the attribute: hidden or visible ? @@ -127,6 +129,7 @@ class UILinksWidget } } + $aRow['static::key'] = $oLinkedObj->GetHyperLink(); foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode) { $aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode); @@ -342,12 +345,7 @@ EOF protected function GetObjectPickerDialog($oPage) { $sHtml = "
            m_sAttCode}{$this->m_sNameSuffix}\">"; - //$oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); - $sHtml .= "
            \n"; - //$sHtml .= "
            \n"); - //$sHtml .= "

            ".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "".$oTargetObj->GetHyperlink()."")."

            \n"); - //$sHtml .= "
            \n"); - + $sHtml .= "
            \n"; $oContext = new UserContext(); $iWidgetIndex = self::$iWidgetIndex; $oFilter = $oContext->NewFilter($this->m_sRemoteClass); @@ -355,16 +353,17 @@ EOF $oBlock = new DisplayBlock($oFilter, 'search', false); $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true)); $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget$iWidgetIndex.DoAddObjects(this.id);\">\n"; - $sHtml .= "
            m_sAttCode}{$this->m_sNameSuffix}\">\n"; - $sHtml .= "

            ".Dict::S('UI:Message:EmptyList:UseSearchForm')."

            \n"; + $sHtml .= "
            m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n"; + $sHtml .= "

            ".Dict::S('UI:Message:EmptyList:UseSearchForm')."

            \n"; $sHtml .= "
            \n"; $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">  "; $sHtml .= "
            \n"; $sHtml .= "\n"; $sHtml .= "
            \n"; - $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ autoOpen: false, modal: true });"); - $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass), " ZZZZ ")."'});"); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget$iWidgetIndex.UpdateSizes });"); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass))."'});"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget$iWidgetIndex.SearchObjectsToAdd);"); + $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget$iWidgetIndex.UpdateSizes);"); return $sHtml; } diff --git a/css/light-grey.css b/css/light-grey.css index 704f516f60..3590cd8dbe 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -129,7 +129,7 @@ tr.clicked td { td.label { font-family: Tahoma, Verdana, Arial, Helvetica; - font-size: 8pt; + font-size: 12px; color: #000000; background-color:#f6f6f6; padding: 0.25em; @@ -161,33 +161,15 @@ td a.no-arrow:hover { padding-left:0px; background: inherit; } - a.small_action { font-family: Tahoma, Verdana, Arial, Helvetica; font-size: 8pt; color: #000000; text-decoration:none; } - .display_block { - noborder: 1px dashed #CCC; padding:0.25em; } -div#TopPane .display_block { - background: #fff; - padding:0.25em; - text-align:center; -} -div#TopPane label { - color:#000; - background: #fff; -} - -div#TopPane td { - color:#000; - background: #fff; -} - .actions_details { float:right; margin-top:10px; @@ -197,7 +179,6 @@ div#TopPane td { padding-bottom: 2px; background: url(../images/actions_left.png) no-repeat left; } - .actions_details span{ background: url(../images/actions_right.png) no-repeat right; color: #fff; @@ -218,6 +199,7 @@ div#TopPane td { input.textSearch { border:1px solid #000; font-family:Tahoma,Verdana, Arial, Helvetica, sans-serif; + font-size: 12px; color:#000000; } @@ -581,16 +563,11 @@ input.dp-applied { color: #000; padding: 10px; margin: 0; -} -.SearchDrawer form table tbody tr td { - background: #d6e8ef; - color: #fff; - font-size: 10pt; + font-size: 12px; } .SearchDrawer label { background: #d6e8ef; color: #000; - padding-left: 10px; } .SearchDrawer h1 { color: #000; @@ -612,6 +589,7 @@ input.dp-applied { margin-top: 0; margin-bottom: 0; display: block; + font-size: 12px; } div.HRDrawer { diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index cf695da676..d023216f0d 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -560,6 +560,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Link_Class_Attributes' => '%1$s attributes', 'UI:SelectAllToggle+' => 'Select All / Deselect All', 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Add %1$s objects linked with %2$s: %3$s', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Add %1$s objects to link with the %2$s', 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Manage %1$s objects linked with %2$s: %3$s', 'UI:AddLinkedObjectsOf_Class' => 'Add %1$ss...', 'UI:RemoveLinkedObjectsOf_Class' => 'Remove selected objects', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index c51e188019..c709fd081f 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -563,6 +563,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Link_Class_Attributes' => 'Attributs du type %1$s', 'UI:SelectAllToggle+' => 'Tout sélectionner / Tout déselectionner', 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Ajouter des objets de type %1$s liés à %3$s (%2$s)', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Ajouter des objets de type %1$s à lier à cet objet de type %2$s', 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Gérer les objets de type %1$s liés à %3$s (%2$s)', 'UI:AddLinkedObjectsOf_Class' => 'Ajouter des objets de type %1$s...', 'UI:RemoveLinkedObjectsOf_Class' => 'Enlever les objets sélectionnés', diff --git a/js/linkswidget.js b/js/linkswidget.js index 2127053cec..d57352a3da 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -56,9 +56,8 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix) this.AddObjects = function() { - //$('#dlg_'+this.id).hide(); $('#dlg_'+me.id).dialog('open'); - //alert('Not Yet Implemented !'); + this.UpdateSizes(null, null); } this.SearchObjectsToAdd = function() @@ -153,6 +152,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix) $('#linkedset_'+me.id+' .listResults tbody').append(data); $('#linkedset_'+me.id+' .listResults').trigger('update'); $('#linkedset_'+me.id+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('#linkedset_'+me.id+' :input').each( function() { $(this).trigger('validate', ''); }); // Validate newly added form fields... } }, 'html' @@ -160,4 +160,22 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix) $('#dlg_'+me.id).dialog('close'); return false; } + + this.UpdateSizes = function(event, ui) + { + var dlg = $('#dlg_'+me.id); + var searchForm = $('#SearchFormToAdd_'+me.id); + var results = $('#SearchResultsToAdd_'+me.id); + padding_right = parseInt(dlg.css('padding-right').replace('px', '')); + padding_left = parseInt(dlg.css('padding-left').replace('px', '')); + padding_top = parseInt(dlg.css('padding-top').replace('px', '')); + padding_bottom = parseInt(dlg.css('padding-bottom').replace('px', '')); + width = dlg.innerWidth() - padding_right - padding_left - 22; // 5 (margin-left) + 5 (padding-left) + 5 (padding-right) + 5 (margin-right) + 2 for rounding ! + height = dlg.innerHeight() - padding_top - padding_bottom -22; + wizard = dlg.find('.wizContainer:first'); + wizard.width(width); + wizard.height(height); + form_height = searchForm.outerHeight(); + results.height(height - form_height - 40); // Leave some space for the buttons + } } diff --git a/js/utils.js b/js/utils.js index de6d078f1f..ccb8f07842 100644 --- a/js/utils.js +++ b/js/utils.js @@ -56,38 +56,40 @@ function UpdateFileName(id, sNewFileName) */ function ReloadSearchForm(divId, sClassName, sBaseClass) { - $('#'+divId).block(); - var formEvents = $('#'+divId+' form').data('events'); - var bSubmitHookIsUsed = false; + var oDiv = $('#'+divId); + oDiv.block(); + var oFormEvents = $('#'+divId+' form').data('events'); // Save the submit handlers aSubmit = new Array(); - if ( (formEvents != null) && (formEvents.submit != undefined)) + if ( (oFormEvents != null) && (oFormEvents.submit != undefined)) { - aSubmit = formEvents.submit; + aSubmit = oFormEvents.submit; } $.post('ajax.render.php', { operation: 'search_form', className: sClassName, baseClass: sBaseClass, currentId: divId }, - function(data){ - $('#'+divId).empty(); - $('#'+divId).append(data); - if (aSubmit.length > 0) - { + function(data) { + oDiv.empty(); + oDiv.append(data); + if (aSubmit.length > 0) + { + var oForm = $('#'+divId+' form'); // Form was reloaded, recompute it for(index = 0; index < aSubmit.length; index++) { // Restore the previously bound submit handlers if (aSubmit[index].data != undefined) { - $('#'+divId+' form').bind('submit.'+aSubmit[index].namespace, aSubmit[index].data, aSubmit[index].handler) + oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].data, aSubmit[index].handler) } else { - $('#'+divId+' form').bind('submit.'+aSubmit[index].namespace, aSubmit[index].handler) + oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].handler) } } - } - $('#'+divId).unblock(); + } + oDiv.unblock(); + oDiv.parent().resize(); // Inform the parent that the form has just been (potentially) resized } ); } diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 10778b5993..06d8da5190 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -68,6 +68,7 @@ switch($operation) $oLinksWizard->DisplayAddForm($oPage, $oContext); break; + // ui.linkswidget case 'searchObjectsToAdd': $sRemoteClass = utils::ReadParam('sRemoteClass', ''); $sAttCode = utils::ReadParam('sAttCode', ''); @@ -78,6 +79,7 @@ switch($operation) $oWidget->SearchObjectsToAdd($oPage, $oContext, $sRemoteClass, $aAlreadyLinked); break; + // ui.linkswidget case 'doAddObjects': $sAttCode = utils::ReadParam('sAttCode', ''); $iInputId = utils::ReadParam('iInputId', ''); @@ -223,15 +225,18 @@ switch($operation) break; case 'ui.linkswidget': + /* $sClass = utils::ReadParam('sclass', 'bizContact'); $sAttCode = utils::ReadParam('attCode', 'name'); $sOrg = utils::ReadParam('org_id', ''); $sName = utils::ReadParam('q', ''); $iMaxCount = utils::ReadParam('max', 30); UILinksWidget::Autocomplete($oPage, $oContext, $sClass, $sAttCode, $sName, $iMaxCount); + */ break; case 'ui.linkswidget.linkedset': + /* $sClass = utils::ReadParam('sclass', 'bizContact'); $sJSONSet = stripslashes(utils::ReadParam('sset', '')); $sExtKeyToMe = utils::ReadParam('sextkeytome', ''); @@ -240,6 +245,7 @@ switch($operation) UILinksWidget::RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe, $sExtKeyToRemote, $iObjectId); $iFieldId = utils::ReadParam('myid', '-1'); $oPage->add_ready_script("$('#{$iFieldId}').trigger('validate');"); + */ break; case 'autocomplete': From d6e1ccfee39018fa90dfab67c7b8c910e36cf3b3 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 5 Aug 2010 12:21:50 +0000 Subject: [PATCH 577/970] - Fixed bug Trac #210 (Incorrect error message in case of uploading a file too big) SVN:trunk[660] --- application/utils.inc.php | 2 +- dictionaries/dictionary.itop.ui.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 16de6f9294..635eaae4de 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -92,7 +92,7 @@ class utils case UPLOAD_ERR_FORM_SIZE: case UPLOAD_ERR_INI_SIZE: - throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig'), ini_get('upload_max_filesize')); + throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize'))); break; case UPLOAD_ERR_PARTIAL: diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index d023216f0d..760677e1b5 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -336,7 +336,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Incorrect link definition: the class of objects to manage: %1$s was not found as an external key in the class %2$s', 'UI:Error:Object_Class_Id_NotFound' => 'Object: %1$s:%2$d not found.', 'UI:Error:WizardCircularReferenceInDependencies' => 'Error: Circular reference in the dependencies between the fields, check the data model.', - 'UI:Error:UploadedFileTooBig' => 'Uploaded file is too big. (Max allowed size is %1$s. Check you PHP configuration for upload_max_filesize.', + 'UI:Error:UploadedFileTooBig' => 'Uploaded file is too big. (Max allowed size is %1$s). Check you PHP configuration for upload_max_filesize.', 'UI:Error:UploadedFileTruncated.' => 'Uploaded file has been truncated !', 'UI:Error:NoTmpDir' => 'The temporary directory is not defined.', 'UI:Error:CannotWriteToTmp_Dir' => 'Unable to write the temporary file to the disk. upload_tmp_dir = "%1$s".', From 9e618d466a371b57a9a4fb74aa5e15d64c740b24 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 5 Aug 2010 12:31:07 +0000 Subject: [PATCH 578/970] - Minor fixes... SVN:trunk[661] --- application/wizardhelper.class.inc.php | 2 +- modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 9fff9a0db9..780b98cd79 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -76,7 +76,7 @@ class WizardHelper if ( isset($aLinkedObject[$sLinkedAttCode]) && ($aLinkedObject[$sLinkedAttCode] !== null) ) { $sLinkedAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sLinkedAttCode); - if (($sLinkedAttDef->IsExternalKey()) && ($aLinkedObject[$sLinkedAttCode] != '') ) + if (($sLinkedAttDef->IsExternalKey()) && ($aLinkedObject[$sLinkedAttCode] != '') && ($aLinkedObject[$sLinkedAttCode] != 0) ) { // For external keys: load the target object so that external fields // get filled too diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index f3c2a6af6e..42b7a82a14 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -72,7 +72,7 @@ class Location extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array(""), + "reconc_keys" => array("name", "org_id"), "db_table" => "location", "db_key_field" => "id", "db_finalclass_field" => "", From 9a70c6f82dc85828e1a17bf7ffc70c1da9318014 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 5 Aug 2010 12:33:34 +0000 Subject: [PATCH 579/970] Sample for creating tickets by reading a incoming emails in a mailbox SVN:trunk[662] --- webservices/createfrommail.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/webservices/createfrommail.php b/webservices/createfrommail.php index 68698f0b3c..99be09a38a 100644 --- a/webservices/createfrommail.php +++ b/webservices/createfrommail.php @@ -141,7 +141,10 @@ for($index = 1; $index <= $iNbMessages; $index++) $iPartIndex = 0; $bFound = false; $sTextBody = ''; - if (count($oStructure->parts) == 0) + //echo "
            \n";
            +	//print_r($oStructure);
            +	//echo "
            \n"; + if (!isset($oStructure->parts) || count($oStructure->parts) == 0) { $sTextBody = $oStructure->body; } @@ -178,7 +181,7 @@ for($index = 1; $index <= $iNbMessages; $index++) } } - $oTicket = CreateTicket($aSender['name'], $aSender['email'], $sSubject, $sBody); + $oTicket = CreateTicket($aSender['email'], $sSubject, $sTextBody); if ($oTicket != null) { // Ticket created, delete the email From db8c287925cfbf2b50279453a23d200a58850269 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 5 Aug 2010 17:10:35 +0000 Subject: [PATCH 580/970] - Fixed the problem of initial (default) values (Trac #172) when creating an object by factorizing: creation / modification and cloning of objects ! SVN:trunk[663] --- application/cmdbabstract.class.inc.php | 232 ++++--------------------- application/displayblock.class.inc.php | 10 +- core/dbobject.class.php | 12 ++ pages/UI.php | 15 +- 4 files changed, 69 insertions(+), 200 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 18baced5f2..ce02e017e4 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -156,17 +156,25 @@ abstract class cmdbAbstractObject extends CMDBObject { $iFlags = $this->GetAttributeFlags($sAttCode); $sClass = get_class($this); + $sInputId = $this->m_iFormId.'_'.$sAttCode; if (get_class($oAttDef) == 'AttributeLinkedSet') { // 1:n links $sTargetClass = $oAttDef->GetLinkedClass(); - $oPage->p(" ".$oAttDef->GetDescription()); + $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); $oFilter = new DBObjectSearch($sTargetClass); $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey(),'='); + $aParams = array( + 'target_attr' => $oAttDef->GetExtKeyToMe(), + 'object_id' => $this->GetKey(), + 'menu' => true, + 'default' => array($oAttDef->GetExtKeyToMe() => $this->GetKey()), + ); + $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oPage, 0); + $oBlock->Display($oPage, $sInputId, $aParams); } else // get_class($oAttDef) == 'AttributeLinkedSetIndirect' { @@ -179,7 +187,6 @@ abstract class cmdbAbstractObject extends CMDBObject $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this); - $sInputId = $this->m_iFormId.'_'.$sAttCode; $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; $aFieldsMap[$sAttCode] = $sInputId; $oPage->add($sHTMLValue); @@ -197,6 +204,7 @@ abstract class cmdbAbstractObject extends CMDBObject 'target_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), 'menu' => true, + 'default' => array($oAttDef->GetExtKeyToMe() => $this->GetKey()), ); } else @@ -1081,7 +1089,7 @@ abstract class cmdbAbstractObject extends CMDBObject { $iFlags = $this->GetAttributeFlags($sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) + if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0)) { if ($oAttDef->IsWritable()) { @@ -1131,17 +1139,28 @@ abstract class cmdbAbstractObject extends CMDBObject $this->DisplayBareRelations($oPage, true); // Edit mode $oPage->SetCurrentTab(''); - $oPage->add("\n"); $oPage->add("\n"); - $oPage->add("\n"); $oPage->add("\n"); foreach($aExtraParams as $sName => $value) { $oPage->add("\n"); } $oPage->add($oAppContext->GetForForm()); - $oPage->add("    \n"); - $oPage->add("\n"); + if ($iKey > 0) + { + // The object already exists in the database, it's modification + $oPage->add("\n"); + $oPage->add("\n"); + $oPage->add("    \n"); + $oPage->add("\n"); + } + else + { + // The object does not exist in the database it's a creation + $oPage->add("\n"); + $oPage->add("    \n"); + $oPage->add("\n"); + } $oPage->add("\n"); $iFieldsCount = count($aFieldsMap); @@ -1149,7 +1168,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oPage->add_script( <<add_ready_script( <<m_iFormId}', false); EOF ); } - + public static function DisplayCreationForm(WebPage $oPage, $sClass, $oObjectToClone = null, $aArgs = array(), $aExtraParams = array()) { - static $iCreationFormId = 0; - - $iCreationFormId++; - $iFieldIndex = 0; $oAppContext = new ApplicationContext(); - $aDetails = array(); - $aFieldsMap = array(); - $sOperation = ($oObjectToClone == null) ? 'apply_new' : 'apply_clone'; $sClass = ($oObjectToClone == null) ? $sClass : get_class($oObjectToClone); $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); - $oPage->add("
            \n"); $aStates = MetaModel::EnumStates($sClass); - $oPage->AddTabContainer(OBJECT_PROPERTIES_TAB); - $oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB); - $oPage->SetCurrentTab(Dict::S('UI:PropertiesTab')); - $oObj = MetaModel::NewObject($sClass); - if (isset($aArgs['default'])) - { - // Pre-populated default values - $aDefaultValues = $aArgs['default']; - foreach($aDefaultValues as $sAttCode => $value) - { - if (MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - $oObj->Set($sAttCode, $value); - } - } - } - $aArgs['this'] = $oObj; - if ($oObjectToClone == null) { $sTargetState = MetaModel::GetDefaultState($sClass); + $oObj = MetaModel::NewObject($sClass); + $oObj->Set($sStateAttCode, $sTargetState); } else { - $sTargetState = $oObjectToClone->GetState(); + $oObj = clone $oObjectToClone; } - - $aDetailsList = self::FlattenZList(MetaModel::GetZListItems($sClass, 'details')); - //$aFullList = MetaModel::ListAttributeDefs($sClass); - $aList = array(); - // Compute the list of properties to display, first the attributes in the 'details' list, then - // all the remaining attributes that are not external fields - foreach($aDetailsList as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if (!$oAttDef->IsExternalField()) - { - $aList[] = $sAttCode; - } - } - foreach($aList as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $iFlags = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; - - if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) - { - if ($oAttDef->IsWritable()) - { - if ($oObjectToClone != null) - { - $sValue = $oObjectToClone->GetEditValue($sAttCode); - $aArgs['this'] = $oObjectToClone; - } - else - { - if(isset($aArgs['default'][$sAttCode])) - { - $sValue = $aArgs['default'][$sAttCode]; - } - else - { - $sValue = $oAttDef->GetDefaultValue(); - } - } - // Prepopulate with a default value -- but no display value... - $sDisplayValue = ''; - if (!empty($sValue)) - { - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs, ''); - switch (count($aAllowedValues)) - { - case 1: - case 0: - $sDisplayValue = $sValue; - break; - - default: - $sDisplayValue = $sValue; - foreach($aAllowedValues as $key => $display) - { - if ($key == $sValue) - { - $sDisplayValue = $display; - break; - } - } - } - } - if ($sStateAttCode == $sAttCode) - { - // State attribute is always read-only from the UI - $sHTMLValue = MetaModel::GetStateLabel($sClass, $sTargetState); - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - } - else - { - if ($iFlags & OPT_ATT_HIDDEN) - { - // Attribute is hidden, do nothing - } - else - { - if ($iFlags & OPT_ATT_READONLY) - { - // Attribute is read-only - $sHTMLValue = ($oObjectToClone == null) ? $sDisplayValue : $oObjectToClone->GetAsHTML($sAttCode); - } - else - { - $sFieldId = 'att_'.$iFieldIndex; - $sHTMLValue = "
            ".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sFieldId, '', $iFlags, $aArgs)."
            "; - $aFieldsMap[$sFieldId] = $sAttCode; - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - $iFieldIndex++; - } - } - } - } - } - } - $oPage->details($aDetails); - // Now display the relations, one tab per relation - if ($oObjectToClone != null) - { - $oObj = $oObjectToClone; // Hmm, likely to fail... - } - else - { - $oObj = new $sClass; - } - $oObj->m_iFormId = $iCreationFormId; - $oObj->DisplayBareRelations($oPage, true); - /* - foreach($aList as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef->IsLinkset()) - { - $oPage->SetCurrentTab($oAttDef->GetLabel()); - $oPage->p($oAttDef->GetDescription()); - - $iFlags = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; - $sFieldId = 'att_'.$iFieldIndex; - $sValue = ($oObjectToClone == null) ? '' : $oObjectToClone->Get($sAttCode); - $sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetEditValue($sAttCode); - $iFlags = isset($aStates[$sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$sTargetState]['attribute_list'][$sAttCode] : 0; - $sHTMLValue = "
            ".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sFieldId, '', $iFlags, $aArgs)."
            "; - $aFieldsMap[$sFieldId] = $sAttCode; - $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue); - $iFieldIndex++; - $oPage->add($sHTMLValue); - } - } - $oPage->SetCurrentTab(''); - */ - - if ($oObjectToClone != null) - { - $oPage->add("GetKey()."\">\n"); - } - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->add("\n"); - $oPage->add($oAppContext->GetForForm()); - foreach($aExtraParams as $sName => $value) - { - $oPage->add("\n"); - } - $oPage->add("    \n"); - $oPage->add("\n"); - $oPage->add("
            \n"); - $aNewFieldsMap = array(); - foreach($aFieldsMap as $id => $sFieldCode) - { - $aNewFieldsMap[$sFieldCode] = $id; - } - $iFieldsCount = count($aFieldsMap); - $sJsonFieldsMap = json_encode($aNewFieldsMap); - - $oPage->add_script(" - // Initializes the object once at the beginning of the page... - var oWizardHelper = new WizardHelper('$sClass'); - oWizardHelper.SetFieldsMap($sJsonFieldsMap); - oWizardHelper.SetFieldsCount($iFieldsCount);"); - $oPage->add_ready_script("CheckFields('creation_form_{$iCreationFormId}', false);"); + return $oObj->DisplayModifyForm( $oPage, $aExtraParams = array()); } protected static function GetCSSClasses($aCSSClasses) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 826741fc77..46c1d590d8 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -521,7 +521,15 @@ class DisplayBlock { $oAppContext = new ApplicationContext(); $sParams = $oAppContext->GetForLink(); - $sHtml .= $oPage->GetP("".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."\n"); + $sDefaults = ''; + if (isset($this->m_aParams['default'])) + { + foreach($this->m_aParams['default'] as $sName => $sValue) + { + $sDefaults .= '&'.urlencode($sName).'='.urlencode($sValue); + } + } + $sHtml .= $oPage->GetP("".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sClass))."\n"); } } } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 6d0ee602d7..d4b31bb4f8 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -915,6 +915,18 @@ abstract class DBObject $this->m_iKey = $iNewKey; return $this->DBInsert(); } + + /** + * This function is automatically called after cloning an object with the "clone" PHP language construct + * The purpose of this method is to reset the appropriate attributes of the object in + * order to make sure that the newly cloned object is really distinct from its clone + */ + public function __clone() + { + $this->m_bIsInDB = false; + $this->m_bDirty = true; + $this->m_iKey = self::GetNextTempId(get_class($this)); + } // To be optionaly overloaded protected function OnUpdate() diff --git a/pages/UI.php b/pages/UI.php index 758df0220d..fcfa11b379 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -803,7 +803,20 @@ try { $aDefaults[$key] = $value; } - cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, null /* $oObjToClone */, array('default' => $aDefaults)); + // Set all the default values in an object and clone this "default" object + $oObjToClone = MetaModel::NewObject($sRealClass); + foreach($aDefaults as $sName => $value) + { + if (MetaModel::IsValidAttCode($sRealClass, $sName)) + { + $oAttDef = MetaModel::GetAttributeDef($sRealClass, $sName); + if ($oAttDef->IsWritable()) + { + $oObjToClone->Set($sName, $value); + } + } + } + cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, $oObjToClone, array('default' => $aDefaults)); $oP->add("
            \n"); } else From cdf9812fbe0c58058ad309b6dd4b28058477e501 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 6 Aug 2010 07:50:11 +0000 Subject: [PATCH 581/970] Fix: actions were all recorded with 'user' on behalf of 'user' as if the user was impersonated... SVN:trunk[664] --- core/userrights.class.inc.php | 1 - 1 file changed, 1 deletion(-) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 65ac93eedf..b0e035e5e5 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -274,7 +274,6 @@ class UserRights return false; } self::$m_oUser = $oUser; - self::$m_oRealUser = $oUser; Dict::SetUserLanguage(self::GetUserLanguage()); return true; } From 272387d61ea56941037f8eabd7a8db33c7439e68 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 9 Aug 2010 10:19:14 +0000 Subject: [PATCH 582/970] - Integrated all the authentications methods and various logon methods... SVN:trunk[665] --- application/itopwebpage.class.inc.php | 7 +- application/loginwebpage.class.inc.php | 267 ++++++++++++------ application/utils.inc.php | 12 + core/config.class.inc.php | 22 +- core/userrights.class.inc.php | 116 +++++++- dictionaries/dictionary.itop.ui.php | 2 + dictionaries/fr.dictionary.itop.ui.php | 2 + modules/authent-ldap/model.authent-ldap.php | 8 +- modules/authent-local/model.authent-local.php | 8 +- pages/UI.php | 4 +- pages/ajax.csvimport.php | 17 +- pages/ajax.render.php | 15 +- pages/testlist.inc.php | 4 +- setup/index.php | 5 +- webservices/webservices.class.inc.php | 3 +- 15 files changed, 348 insertions(+), 144 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 30c5f19128..e04ed7ddb2 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -492,7 +492,12 @@ EOF } $sLogOffMenu = "
              • "; $sLogOffMenu .= "
              • $sLogonMessage
              • \n"; - $sLogOffMenu .= "
              • ".Dict::S('UI:LogOffMenu')."
              • \n"; + + if (utils::CanLogOff() && UserRights::CanLogOff()) + { + //$sLogOffMenu .= "
              • ".Dict::S('UI:LogOffMenu')."
              • \n"; + $sLogOffMenu .= "
              • ".Dict::S('UI:LogOffMenu')."
              • \n"; + } if (UserRights::CanChangePassword()) { $sLogOffMenu .= "
              • ".Dict::S('UI:ChangePwdMenu')."
              • \n"; diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index cd33217c85..349bb6d83d 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -84,32 +84,47 @@ EOF ); } - public function DisplayLoginForm($bFailedLogin = false) + public function DisplayLoginForm($sLoginType, $bFailedLogin = false) { - $sAuthUser = utils::ReadParam('auth_user', ''); - $sAuthPwd = utils::ReadParam('suggest_pwd', ''); - - $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); - $this->add("
                \n"); - $this->add("
                \n"); - $this->add("

                ".Dict::S('UI:Login:Welcome')."

                \n"); - if ($bFailedLogin) + switch($sLoginType) { - $this->add("

                ".Dict::S('UI:Login:IncorrectLoginPassword')."

                \n"); + case 'popup': + case 'url': + $this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION)); + $this->add_header('HTTP/1.0 401 Unauthorized'); + // Note: displayed when the user will click on Cancel + $this->add('

                '.Dict::S('UI:Login:Error:AccessRestricted').'

                '); + break; + + case 'remote': + case 'form': + default: // In case the settings get messed up... + $sAuthUser = utils::ReadParam('auth_user', ''); + $sAuthPwd = utils::ReadParam('suggest_pwd', ''); + + $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); + $this->add("
                \n"); + $this->add("
                \n"); + $this->add("

                ".Dict::S('UI:Login:Welcome')."

                \n"); + if ($bFailedLogin) + { + $this->add("

                ".Dict::S('UI:Login:IncorrectLoginPassword')."

                \n"); + } + else + { + $this->add("

                ".Dict::S('UI:Login:IdentifyYourself')."

                \n"); + } + $this->add("
                \n"); + $this->add("\n"); + $this->add("\n"); + $this->add("\n"); + $this->add("\n"); + $this->add("
                \n"); + $this->add("\n"); + $this->add("
                \n"); + $this->add("
                \n"); + break; } - else - { - $this->add("

                ".Dict::S('UI:Login:IdentifyYourself')."

                \n"); - } - $this->add("
                \n"); - $this->add("\n"); - $this->add("\n"); - $this->add("\n"); - $this->add("\n"); - $this->add("
                \n"); - $this->add("\n"); - $this->add("
                \n"); - $this->add("
                \n"); } public function DisplayChangePwdForm($bFailedLogin = false) @@ -155,8 +170,16 @@ EOF $this->add("
            \n"); } - static protected function ResetSession() + static function ResetSession() { + if (isset($_SESSION['login_mode'])) + { + $sPreviousLoginMode = $_SESSION['login_mode']; + } + else + { + $sPreviousLoginMode = ''; + } // Unset all of the session variables. $_SESSION = array(); // If it's desired to kill the session, also delete the session cookie. @@ -186,7 +209,7 @@ EOF return $bSecured; } - static function DoLogin() + protected static function Login() { if (self::SecureConnectionRequired() && !self::IsConnectionSecure()) { @@ -195,52 +218,147 @@ EOF header("Location: $sUrl"); exit; } - $bHTTPBasicAuthentication = (utils::ReadParam('auth', '', 'get') == 'http_basic'); - if ($bHTTPBasicAuthentication) + + $aAllowedLoginTypes = utils::GetConfig()->GetAllowedLoginTypes(); + + if (isset($_SESSION['auth_user'])) { - // Basic HTTP/PHP authentication mecanism - // - // meme avec ca c'est pourri - return; - if (!isset($_SERVER['PHP_AUTH_USER'])) + //echo "User: ".$_SESSION['auth_user']."\n"; + // Already authentified + UserRights::Login($_SESSION['auth_user']); // Login & set the user's language + return true; + } + else + { + $index = 0; + $sLoginMode = ''; + $sAuthentication = 'internal'; + while(($sLoginMode == '') && ($index < count($aAllowedLoginTypes))) { - header('WWW-Authenticate: Basic realm="iTop access is restricted"'); - header('HTTP/1.0 401 Unauthorized'); - // Note: accessed when the user will click on Cancel - echo '

            '.Dict::S('UI:Login:Error:AccessRestricted').'

            '; + $sLoginType = $aAllowedLoginTypes[$index]; + switch($sLoginType) + { + case 'form': + // iTop standard mode: form based authentication + $sAuthUser = utils::ReadParam('auth_user', '', 'post'); + $sAuthPwd = utils::ReadParam('auth_pwd', '', 'post'); + if ($sAuthUser != '') + { + $sLoginMode = 'form'; + } + break; + + case 'popup': + // Standard PHP authentication method, works with Apache... + // Case 1) Apache running in CGI mode + rewrite rules in .htaccess + if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) + { + list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); + $sLoginMode = 'popup'; + } + else if (isset($_SERVER['PHP_AUTH_USER'])) + { + $sAuthUser = $_SERVER['PHP_AUTH_USER']; + $sAuthPwd = $_SERVER['PHP_AUTH_PW']; + $sLoginMode = 'popup'; + } + break; + + case 'remote': + // Web server supplied authentication + if (isset($_SERVER['REMOTE_USER'])) + { + $sAuthUser = $_SERVER['REMOTE_USER']; + $sAuthPwd = ''; // No password in this case the web server already authentified the user... + $sLoginMode = 'remote'; + $sAuthentication = 'external'; + } + break; + + case 'url': + // Credentials passed directly in the url + $sAuthUser = utils::ReadParam('auth_user', '', 'get'); + if ($sAuthUser != '') + { + $sAuthPwd = utils::ReadParam('auth_pwd', '', 'post'); + $sLoginMode = 'url'; + } + break; + } + $index++; + } + //echo "\nsLoginMode: $sLoginMode (user: $sAuthUser / pwd: $sAuthPwd\n)"; + if ($sLoginMode == '') + { + // First connection + $sDesiredLoginMode = utils::ReadParam('login_mode'); + if (in_array($sDesiredLoginMode, $aAllowedLoginTypes)) + { + $sLoginMode = $sDesiredLoginMode; + } + else + { + $sLoginMode = $aAllowedLoginTypes[0]; // First in the list... + } + $oPage = new LoginWebPage(); + $oPage->DisplayLoginForm( $sLoginMode, false /* no previous failed attempt */); + $oPage->output(); exit; } else { - $sAuthUser = $_SERVER['PHP_AUTH_USER']; - $sAuthPwd = $_SERVER['PHP_AUTH_PW']; - if (!UserRights::Login($sAuthUser, $sAuthPwd)) + if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sAuthentication)) { - header('WWW-Authenticate: Basic realm="Unknown user \''.$sAuthUser.'\'"'); - header('HTTP/1.0 401 Unauthorized'); - // Note: accessed when the user will click on Cancel - // Todo: count the attempts - echo '

            '.Dict::S('UI:Login:Error:AccessRestricted').'

            '; + self::ResetSession(); + $oPage = new LoginWebPage(); + $oPage->DisplayLoginForm( $sLoginMode, true /* failed attempt */); + $oPage->output(); exit; } + else + { + // User is Ok, let's save it in the session and proceed with normal login + UserRights::Login($sAuthUser, $sAuthentication); // Login & set the user's language + $_SESSION['auth_user'] = $sAuthUser; + $_SESSION['login_mode'] = $sLoginMode; + } } - return; } - - // Home-made authentication mecanism - // + } + + static function DoLogin() + { $operation = utils::ReadParam('loginop', ''); session_start(); if ($operation == 'logoff') { + if (isset($_SESSION['login_mode'])) + { + $sLoginMode = $_SESSION['login_mode']; + } + else + { + $aAllowedLoginTypes = utils::GetConfig()->GetAllowedLoginTypes(); + if (count($aAllowedLoginTypes) > 0) + { + $sLoginMode = $aAllowedLoginTypes[0]; + } + else + { + $sLoginMode = 'form'; + } + } self::ResetSession(); - } - - if ($operation == 'change_pwd') + $oPage = new LoginWebPage(); + $oPage->DisplayLoginForm( $sLoginMode, false /* not a failed attempt */); + $oPage->output(); + exit; + } + else if ($operation == 'change_pwd') { $sAuthUser = $_SESSION['auth_user']; - $sAuthPwd = $_SESSION['auth_pwd']; - UserRights::Login($sAuthUser, $sAuthPwd); // Set the user's language + UserRights::Login($sAuthUser); // Set the user's language $oPage = new LoginWebPage(); $oPage->DisplayChangePwdForm(); $oPage->output(); @@ -249,11 +367,10 @@ EOF if ($operation == 'do_change_pwd') { $sAuthUser = $_SESSION['auth_user']; - $sAuthPwd = $_SESSION['auth_pwd']; - UserRights::Login($sAuthUser, $sAuthPwd); // Set the user's language + UserRights::Login($sAuthUser); // Set the user's language $sOldPwd = utils::ReadPostedParam('old_pwd'); $sNewPwd = utils::ReadPostedParam('new_pwd'); - if (UserRights::CanChangePassword() && ((!UserRights::Login($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd)))) + if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd)))) { $oPage = new LoginWebPage(); $oPage->DisplayChangePwdForm(true); // old pwd was wrong @@ -268,44 +385,8 @@ EOF } } - if (!isset($_SESSION['auth_user']) || !isset($_SESSION['auth_pwd'])) - { - if ($operation == 'loginurl') - { - $sAuthUser = utils::ReadParam('auth_user', '', 'get'); - $sAuthPwd = utils::ReadParam('auth_pwd', '', 'get'); - } - else if ($operation == 'login') - { - $sAuthUser = utils::ReadParam('auth_user', '', 'post'); - $sAuthPwd = utils::ReadParam('auth_pwd', '', 'post'); - } - else - { - $oPage = new LoginWebPage(); - $oPage->DisplayLoginForm(); - $oPage->output(); - exit; - } - } - else - { - $sAuthUser = $_SESSION['auth_user']; - $sAuthPwd = $_SESSION['auth_pwd']; - } - if (!UserRights::Login($sAuthUser, $sAuthPwd)) - { - self::ResetSession(); - $oPage = new LoginWebPage(); - $oPage->DisplayLoginForm( true /* failed attempt */); - $oPage->output(); - exit; - } - else - { - $_SESSION['auth_user'] = $sAuthUser ; - $_SESSION['auth_pwd'] = $sAuthPwd; - } + self::Login(); } + } // End of class ?> diff --git a/application/utils.inc.php b/application/utils.inc.php index 635eaae4de..fd8ca15044 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -231,5 +231,17 @@ class utils return $sUrl; } + + /** + * Tells whether or not log off operation is supported. + * Actually in only one case: + * 1) iTop is using an internal authentication + * 2) the user did not log-in using the "popup" mode (i.e basic authentication) or by passing credentials in the URL + * @return boolean True if logoff is supported, false otherwise + */ + static function CanLogOff() + { + return (isset($_SESSION['login_mode']) && $_SESSION['login_mode'] == 'form'); + } } ?> diff --git a/core/config.class.inc.php b/core/config.class.inc.php index ce96a60e60..7bcb184e89 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -43,6 +43,7 @@ define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60); define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60); define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); +define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|popup|remote|url'); /** * Config @@ -103,6 +104,11 @@ class Config * @var string Langage code, default if the user language is undefined */ protected $m_sDefaultLanguage; + + /** + * @var string Type of login process allowed: form|popup|url|remote + */ + protected $m_sAllowedLoginTypes; public function __construct($sConfigFile, $bLoadConfig = true) { @@ -149,7 +155,8 @@ class Config $this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL; $this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED; $this->m_sDefaultLanguage = 'EN US'; - + $this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES; + $this->m_aModuleSettings = array(); if ($bLoadConfig) @@ -247,6 +254,7 @@ class Config $this->m_aModuleSettings = isset($MyModuleSettings) ? $MyModuleSettings : array(); $this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US'; + $this->m_sAllowedLoginTypes = isset($MySettings['allowed_login_types']) ? trim($MySettings['allowed_login_types']) : DEFAULT_ALLOWED_LOGIN_TYPES; } protected function Verify() @@ -394,6 +402,12 @@ class Config return $this->m_sDefaultLanguage; } + + public function GetAllowedLoginTypes() + { + return explode('|', $this->m_sAllowedLoginTypes); + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -469,6 +483,11 @@ class Config $this->m_sDefaultLanguage = $sLanguageCode; } + public function SetAllowedLoginTypes($aAllowedLoginTypes) + { + $this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes); + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -516,6 +535,7 @@ class Config fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n"); fwrite($hFile, "\t'secure_connection_required' => ".($this->m_bSecureConnectionRequired ? 'true' : 'false').",\n"); fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n"); + fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n"); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index b0e035e5e5..fcb39e522d 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -200,7 +200,41 @@ abstract class User extends cmdbAbstractObject } } +/** + * Abstract class for all types of "internal" authentication i.e. users + * for which the application is supplied a login and a password opposed + * to "external" users for whom the authentication is performed outside + * of the application (by the web server for example). + * Note that "internal" users do not necessary correspond to a local authentication + * they may be authenticated by a remote system, like in authent-ldap. + */ +abstract class UserInternal extends User +{ + // Nothing special, just a base class to categorize this type of authenticated users + public static function Init() + { + $aParams = array + ( + "category" => "core", + "key_type" => "autoincrement", + "name_attcode" => "login", + "state_attcode" => "", + "reconc_keys" => array('login'), + "db_table" => "priv_internalUser", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + // Display lists + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('login', 'contactid')); // Criteria of the advanced search form + } +} /** * User management core API @@ -261,9 +295,21 @@ class UserRights } } - public static function Login($sName, $sPassword) + public static function Login($sName, $sAuthentication = 'any') { - $oUser = self::FindUser($sName); + $oUser = self::FindUser($sName, $sAuthentication); + if (is_null($oUser)) + { + return false; + } + self::$m_oUser = $oUser; + Dict::SetUserLanguage(self::GetUserLanguage()); + return true; + } + + public static function CheckCredentials($sName, $sPassword, $sAuthentication = 'any') + { + $oUser = self::FindUser($sName, $sAuthentication); if (is_null($oUser)) { return false; @@ -273,8 +319,6 @@ class UserRights { return false; } - self::$m_oUser = $oUser; - Dict::SetUserLanguage(self::GetUserLanguage()); return true; } @@ -302,6 +346,18 @@ class UserRights } } + public static function CanLogOff() + { + if (!is_null(self::$m_oUser)) + { + return self::$m_oUser->CanLogOff(); + } + else + { + return false; + } + } + public static function ChangePassword($sCurrentPassword, $sNewPassword) { if (!is_null(self::$m_oUser)) @@ -521,22 +577,54 @@ class UserRights } static $m_aCacheUsers; - protected static function FindUser($sLogin) + /** + * Find a user based on its login and its type of authentication + * @param string $sLogin Login/identifier of the user + * @param string $sAuthentication Type of authentication used: internal|external|any + * @return User The found user or null + */ + protected static function FindUser($sLogin, $sAuthentication = 'any') { - if (!isset(self::$m_aCacheUsers)) + if ($sAuthentication == 'any') { - self::$m_aCacheUsers = array(); + $oUser = self::FindUser($sLogin, 'internal'); + if ($oUser == null) + { + $oUser = self::FindUser($sLogin, 'external'); + } } - if (!isset(self::$m_aCacheUsers[$sLogin])) + else { - $oSearch = DBObjectSearch::FromOQL("SELECT User WHERE login = :login"); - $oSet = new DBObjectSet($oSearch, array(), array('login' => $sLogin)); - $oUser = $oSet->fetch(); - self::$m_aCacheUsers[$sLogin] = $oUser; + if (!isset(self::$m_aCacheUsers)) + { + self::$m_aCacheUsers = array('internal' => array(), 'external' => array()); + } + + if (!isset(self::$m_aCacheUsers[$sAuthentication][$sLogin])) + { + switch($sAuthentication) + { + case 'external': + $sBaseClass = 'UserExternal'; + break; + + case 'internal': + $sBaseClass = 'UserInternal'; + break; + + default: + echo "

            sAuthentication = $sAuthentication

            \n"; + assert(false); // should never happen + } + $oSearch = DBObjectSearch::FromOQL("SELECT $sBaseClass WHERE login = :login"); + $oSet = new DBObjectSet($oSearch, array(), array('login' => $sLogin)); + $oUser = $oSet->fetch(); + self::$m_aCacheUsers[$sAuthentication][$sLogin] = $oUser; + } + $oUser = self::$m_aCacheUsers[$sAuthentication][$sLogin]; } - return self::$m_aCacheUsers[$sLogin]; + return $oUser; } } - ?> diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 760677e1b5..1d558b4dd4 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -409,6 +409,8 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Login:RetypeNewPasswordPrompt' => 'Retype new password', 'UI:Login:IncorrectOldPassword' => 'Error: the old password is incorrect', 'UI:LogOffMenu' => 'Log off', + 'UI:LogOff:ThankYou' => 'Thank you for using iTop', + 'UI:LogOff:ClickHereToLoginAgain' => 'Click here to login again...', 'UI:ChangePwdMenu' => 'Change Password...', 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index c709fd081f..5e652a05ff 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -410,6 +410,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Login:RetypeNewPasswordPrompt' => 'Resaisir le nouveau mot de passe', 'UI:Login:IncorrectOldPassword' => 'Erreur: l\'ancien mot de passe est incorrect', 'UI:LogOffMenu' => 'Déconnexion', + 'UI:LogOff:ThankYou' => 'Merci d\'avoir utilisé iTop', + 'UI:LogOff:ClickHereToLoginAgain' => 'Cliquez ici pour vous reconnecter...', 'UI:ChangePwdMenu' => 'Changer de mot de passe...', 'UI:Login:RetypePwdDoesNotMatch' => 'Les deux saisies du nouveau mot de passe ne sont pas identiques !', 'UI:Button:Login' => 'Entrer dans iTop', diff --git a/modules/authent-ldap/model.authent-ldap.php b/modules/authent-ldap/model.authent-ldap.php index 8b46fcbb21..0f255a40f9 100644 --- a/modules/authent-ldap/model.authent-ldap.php +++ b/modules/authent-ldap/model.authent-ldap.php @@ -25,7 +25,7 @@ */ -class UserLDAP extends User +class UserLDAP extends UserInternal { public static function Init() { @@ -157,6 +157,12 @@ class UserLDAP extends User return false; } + public function CanLogOff() + { + // Internal authentication allows everybody to log off + return true; + } + public function ChangePassword($sOldPassword, $sNewPassword) { return false; diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index 675c646cf5..8602a66024 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -25,7 +25,7 @@ */ -class UserLocal extends User +class UserLocal extends UserInternal { public static function Init() { @@ -74,6 +74,12 @@ class UserLocal extends User return true; } + public function CanLogOff() + { + // Internal authentication allows everybody to log off + return true; + } + public function ChangePassword($sOldPassword, $sNewPassword) { if ($this->Get('password') == $sOldPassword) diff --git a/pages/UI.php b/pages/UI.php index fcfa11b379..456ad506a1 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1389,7 +1389,7 @@ EOF ////MetaModel::ShowQueryTrace(); $oP->output(); } -catch(CoreException $e) +catch(ZZCoreException $e) { require_once('../setup/setuppage.class.inc.php'); $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); @@ -1418,7 +1418,7 @@ catch(CoreException $e) // For debugging only //throw $e; } -catch(Exception $e) +catch(ZZException $e) { require_once('../setup/setuppage.class.inc.php'); $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index d3e06168d3..42c280c140 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -168,19 +168,10 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMo } require_once('../application/startup.inc.php'); -session_start(); -if (isset($_SESSION['auth_user'])) -{ - $sAuthUser = $_SESSION['auth_user']; - $sAuthPwd = $_SESSION['auth_pwd']; - // Attempt to login, fails silently - UserRights::Login($sAuthUser, $sAuthPwd); -} -else -{ - // No session information - echo "

            No session information

            \n"; -} + +require_once('../application/loginwebpage.class.inc.php'); +LoginWebPage::DoLogin(); // Check user rights and prompt if needed + $oContext = new UserContext(); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 06d8da5190..f5bee6eddc 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -32,19 +32,8 @@ require_once('../application/ui.linkswidget.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/user.preferences.class.inc.php'); -session_start(); -if (isset($_SESSION['auth_user'])) -{ - $sAuthUser = $_SESSION['auth_user']; - $sAuthPwd = $_SESSION['auth_pwd']; - // Attempt to login, fails silently - UserRights::Login($sAuthUser, $sAuthPwd); -} -else -{ - // No session information - echo "

            No session information

            \n"; -} +require_once('../application/loginwebpage.class.inc.php'); +LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oPage = new ajax_page(""); $oPage->no_cache(); diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index ad0cb5106d..ad2043fba8 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -347,8 +347,8 @@ class TestUserRightsMatrixItop extends TestUserRights protected function DoExecute() { $sUser = 'Romain'; - echo "

            Totor: ".(UserRights::Login('Totor', 'toto') ? 'ok' : 'NO')."

            \n"; - echo "

            Romain: ".(UserRights::Login('Romain', 'toto') ? 'ok' : 'NO')."

            \n"; + echo "

            Totor: ".(UserRights::CheckCredentials('Totor', 'toto') ? 'ok' : 'NO')."

            \n"; + echo "

            Romain: ".(UserRights::CheckCredentials('Romain', 'toto') ? 'ok' : 'NO')."

            \n"; echo "

            User: ".UserRights::GetUser()."

            \n"; echo "

            On behalf of...".UserRights::GetRealUser()."

            \n"; diff --git a/setup/index.php b/setup/index.php index 15c738bb6f..2f0ae1a9e5 100644 --- a/setup/index.php +++ b/setup/index.php @@ -959,10 +959,11 @@ function SetupFinished(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $o // Start the application InitDataModel($oP, FINAL_CONFIG_FILE, false); // Load model and startup DB - if (UserRights::Login($sAuthUser, $sAuthPwd)) + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) { + UserRights::Login($sAuthUser); $_SESSION['auth_user'] = $sAuthUser; - $_SESSION['auth_pwd'] = $sAuthPwd; + // remove the tmp config file @unlink(TMP_CONFIG_FILE); // try to make the final config file read-only diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index b9d07efd32..8af6eca102 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -556,13 +556,14 @@ class WebServices public function CreateIncidentTicket($sLogin, $sPassword, $sTitle, $sDescription, $oCallerDesc, $oCustomerDesc, $oServiceDesc, $oServiceSubcategoryDesc, $sProduct, $oWorkgroupDesc, $aSOAPImpactedCIs, $sImpact, $sUrgency) { - if (!UserRights::Login($sLogin, $sPassword)) + if (!UserRights::CheckCredentials($sLogin, $sPassword)) { $oRes = new WebServiceResultFailedLogin($sLogin); $this->LogUsage(__FUNCTION__, $oRes); return $oRes->ToSoapStructure(); } + UserRights::Login($sLogin); $aCallerDesc = self::SoapStructToExternalKeySearch($oCallerDesc); $aCustomerDesc = self::SoapStructToExternalKeySearch($oCustomerDesc); From 1c3e111c9d56b9d2f91675e95d067c8834a87a17 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 9 Aug 2010 11:44:14 +0000 Subject: [PATCH 583/970] Fixed bug #159: pattern matching too strict for URL fields. SVN:trunk[666] --- core/attributedef.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index ad7e93b874..d4c14dd5d7 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1691,7 +1691,7 @@ class AttributeURL extends AttributeString public function GetValidationPattern() { - return "^(http|https|ftp)\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*$"; + return "^(http|https|ftp)\://[a-zA-Z0-9\-\.]+(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9\-\._\?\,\'/\\\+&%\$#\=~])*$"; } } From ed7fdf45e2adedae2383045f1b56748ffe37704a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 9 Aug 2010 11:47:31 +0000 Subject: [PATCH 584/970] - New authentification module to support "external" authentication, i.e. when the authentication is done outside of iTop (for example by the web server itself using a .htaccess file) SVN:trunk[667] --- .../en.dict.authent-external.php | 65 +++++++++++++ .../fr.dict.authent-external.php | 63 ++++++++++++ .../model.authent-external.php | 95 +++++++++++++++++++ .../module.authent-external.php | 68 +++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 modules/authent-external/en.dict.authent-external.php create mode 100644 modules/authent-external/fr.dict.authent-external.php create mode 100644 modules/authent-external/model.authent-external.php create mode 100644 modules/authent-external/module.authent-external.php diff --git a/modules/authent-external/en.dict.authent-external.php b/modules/authent-external/en.dict.authent-external.php new file mode 100644 index 0000000000..8633c3b9aa --- /dev/null +++ b/modules/authent-external/en.dict.authent-external.php @@ -0,0 +1,65 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserExternal +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:UserExternal' => 'External user', + 'Class:UserExternal+' => 'User authentified outside of iTop', + 'Class:UserExternal/Attribute:contactid' => 'Contact (person)', + 'Class:UserExternal/Attribute:contactid+' => 'Personal details from the business data', + 'Class:UserExternal/Attribute:last_name' => 'Last name', + 'Class:UserExternal/Attribute:last_name+' => 'Name of the corresponding contact', + 'Class:UserExternal/Attribute:first_name' => 'First name', + 'Class:UserExternal/Attribute:first_name+' => 'First name of the corresponding contact', + 'Class:UserExternal/Attribute:email' => 'Email', + 'Class:UserExternal/Attribute:email+' => 'Email of the corresponding contact', + 'Class:UserExternal/Attribute:login' => 'Login', + 'Class:UserExternal/Attribute:login+' => 'user identification string', + 'Class:UserExternal/Attribute:language' => 'Language', + 'Class:UserExternal/Attribute:language+' => 'user language', + 'Class:UserExternal/Attribute:language/Value:EN US' => 'English', + 'Class:UserExternal/Attribute:language/Value:EN US+' => 'English (U.S.)', + 'Class:UserExternal/Attribute:language/Value:FR FR' => 'French', + 'Class:UserExternal/Attribute:language/Value:FR FR+' => 'French (France)', + 'Class:UserExternal/Attribute:profile_list' => 'Profiles', + 'Class:UserExternal/Attribute:profile_list+' => 'Roles, granting rights for that person', +)); + + + +?> diff --git a/modules/authent-external/fr.dict.authent-external.php b/modules/authent-external/fr.dict.authent-external.php new file mode 100644 index 0000000000..d7d87d41c7 --- /dev/null +++ b/modules/authent-external/fr.dict.authent-external.php @@ -0,0 +1,63 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserExternal +// + +Dict::Add('EN US', 'French', 'Français', array( + 'Class:UserExternal' => 'Utilisateur externe à iTop', + 'Class:UserExternal+' => 'Utilisateur authentifié à l\'extérieur d\'iTop', + 'Class:UserExternal/Attribute:contactid' => 'Contact (personne)', + 'Class:UserExternal/Attribute:contactid+' => '', + 'Class:UserExternal/Attribute:last_name' => 'Nom', + 'Class:UserExternal/Attribute:last_name+' => '', + 'Class:UserExternal/Attribute:first_name' => 'Prénom', + 'Class:UserExternal/Attribute:first_name+' => '', + 'Class:UserExternal/Attribute:email' => 'Adresse email', + 'Class:UserExternal/Attribute:email+' => '', + 'Class:UserExternal/Attribute:login' => 'Login', + 'Class:UserExternal/Attribute:login+' => '', + 'Class:UserExternal/Attribute:language' => 'Langage', + 'Class:UserExternal/Attribute:language+' => '', + 'Class:UserExternal/Attribute:language/Value:EN US' => 'Anglais', + 'Class:UserExternal/Attribute:language/Value:EN US+' => 'Anglais (Etats-unis)', + 'Class:UserExternal/Attribute:language/Value:FR FR' => 'Français', + 'Class:UserExternal/Attribute:language/Value:FR FR+' => 'Français (France)', + 'Class:UserExternal/Attribute:profile_list' => 'Profils', + 'Class:UserExternal/Attribute:profile_list+' => 'Rôles, ouvrants les droits d\'accès', +)); + +?> diff --git a/modules/authent-external/model.authent-external.php b/modules/authent-external/model.authent-external.php new file mode 100644 index 0000000000..f42b2e65f9 --- /dev/null +++ b/modules/authent-external/model.authent-external.php @@ -0,0 +1,95 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +class UserExternal extends User +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/authentication", + "key_type" => "autoincrement", + "name_attcode" => "login", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'login')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('login', 'contactid')); // Criteria of the advanced search form + } + + /** + * Check the user's password... always return true. Actually the password + * is not even passed to this function, we trust the web server for authentifiying + * the users + */ + public function CheckCredentials($sPassword) + { + // External authentication: for iTop it's always Ok + return true; + } + + public function TrustWebServerContext() + { + return true; + } + + public function CanChangePassword() + { + // External authentication: iTop has no way to change a user's password + return false; + } + + public function CanLogOff() + { + // External authentication: iTop has no way to force a log off + return false; + } + + public function ChangePassword($sOldPassword, $sNewPassword) + { + return false; + } +} + + +?> diff --git a/modules/authent-external/module.authent-external.php b/modules/authent-external/module.authent-external.php new file mode 100644 index 0000000000..7a261be105 --- /dev/null +++ b/modules/authent-external/module.authent-external.php @@ -0,0 +1,68 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +SetupWebPage::AddModule( + __FILE__, // Path to the current file, all other file names are relative to the directory containing this file + 'authent-external/1.0.0', + array( + // Identification + // + 'label' => 'External user authentication', + 'category' => 'authentication', + + // Setup + // + 'dependencies' => array( + ), + 'mandatory' => false, + 'visible' => true, + + // Components + // + 'datamodel' => array( + 'model.authent-external.php', + ), + 'dictionary' => array( + 'en.dict.authent-external.php', + ), + 'data.struct' => array( + //'data.struct.authent-ldap.xml', + ), + 'data.sample' => array( + //'data.sample.authent-ldap.xml', + ), + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => array(), + ) +); +?> From e3d021280278f3d1d04e30ca718e5eca9acaf46d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 9 Aug 2010 11:48:07 +0000 Subject: [PATCH 585/970] - Integrated all the authentications methods and various logon methods... SVN:trunk[668] --- pages/logoff.php | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pages/logoff.php diff --git a/pages/logoff.php b/pages/logoff.php new file mode 100644 index 0000000000..b77b36b746 --- /dev/null +++ b/pages/logoff.php @@ -0,0 +1,38 @@ +add("
            \n"); +$oPage->add("
            \n"); +$oPage->add("

            ".Dict::S('UI:LogOff:ThankYou')."

            \n"); +$oPage->add("

            ".Dict::S('UI:LogOff:ClickHereToLoginAgain')."

            "); +$oPage->add("
            \n"); +$oPage->output(); +?> From 5b0c1e30039485a1fb8a2a1ba29bc8503f5308c4 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 10 Aug 2010 16:38:55 +0000 Subject: [PATCH 586/970] - Integrated all the authentications methods and various logon methods... SVN:trunk[669] --- application/loginwebpage.class.inc.php | 24 ++++++++++++++---------- application/utils.inc.php | 2 +- core/config.class.inc.php | 22 ++++++++++++++++++++-- dictionaries/fr.dictionary.itop.ui.php | 2 +- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 349bb6d83d..90c6aa592f 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -88,7 +88,7 @@ EOF { switch($sLoginType) { - case 'popup': + case 'basic': case 'url': $this->add_header('WWW-Authenticate: Basic realm="'.Dict::Format('UI:iTopVersion:Short', ITOP_VERSION)); $this->add_header('HTTP/1.0 401 Unauthorized'); @@ -96,7 +96,7 @@ EOF $this->add('

            '.Dict::S('UI:Login:Error:AccessRestricted').'

            '); break; - case 'remote': + case 'external': case 'form': default: // In case the settings get messed up... $sAuthUser = utils::ReadParam('auth_user', ''); @@ -248,29 +248,33 @@ EOF } break; - case 'popup': + case 'basic': // Standard PHP authentication method, works with Apache... // Case 1) Apache running in CGI mode + rewrite rules in .htaccess if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { list($sAuthUser, $sAuthPwd) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); - $sLoginMode = 'popup'; + $sLoginMode = 'basic'; } else if (isset($_SERVER['PHP_AUTH_USER'])) { $sAuthUser = $_SERVER['PHP_AUTH_USER']; $sAuthPwd = $_SERVER['PHP_AUTH_PW']; - $sLoginMode = 'popup'; + $sLoginMode = 'basic'; } break; - case 'remote': + case 'external': // Web server supplied authentication - if (isset($_SERVER['REMOTE_USER'])) - { - $sAuthUser = $_SERVER['REMOTE_USER']; + $bExternalAuth = false; + $sExtAuthVar = utils::GetConfig()->GetExternalAuthenticationVariable(); // In which variable is the info passed ? + $sEval = '$bExternalAuth = isset('.$sExtAuthVar.');'; + eval($sEval); + if ($bExternalAuth) + { + eval('$sAuthUser = '.$sExtAuthVar.';'); // Retrieve the value $sAuthPwd = ''; // No password in this case the web server already authentified the user... - $sLoginMode = 'remote'; + $sLoginMode = 'external'; $sAuthentication = 'external'; } break; diff --git a/application/utils.inc.php b/application/utils.inc.php index fd8ca15044..e375572f48 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -236,7 +236,7 @@ class utils * Tells whether or not log off operation is supported. * Actually in only one case: * 1) iTop is using an internal authentication - * 2) the user did not log-in using the "popup" mode (i.e basic authentication) or by passing credentials in the URL + * 2) the user did not log-in using the "basic" mode (i.e basic authentication) or by passing credentials in the URL * @return boolean True if logoff is supported, false otherwise */ static function CanLogOff() diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 7bcb184e89..efc8674283 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -43,7 +43,8 @@ define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60); define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60); define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); -define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|popup|remote|url'); +define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external'); +define ('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']'); /** * Config @@ -106,9 +107,14 @@ class Config protected $m_sDefaultLanguage; /** - * @var string Type of login process allowed: form|popup|url|remote + * @var string Type of login process allowed: form|basic|url|external */ protected $m_sAllowedLoginTypes; + + /** + * @var string Name of the PHP variable in which external authentication information is passed by the web server + */ + protected $m_sExtAuthVariable; public function __construct($sConfigFile, $bLoadConfig = true) { @@ -156,6 +162,7 @@ class Config $this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED; $this->m_sDefaultLanguage = 'EN US'; $this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES; + $this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE; $this->m_aModuleSettings = array(); @@ -255,6 +262,7 @@ class Config $this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US'; $this->m_sAllowedLoginTypes = isset($MySettings['allowed_login_types']) ? trim($MySettings['allowed_login_types']) : DEFAULT_ALLOWED_LOGIN_TYPES; + $this->m_sExtAuthVariable = isset($MySettings['ext_auth_variable']) ? trim($MySettings['ext_auth_variable']) : DEFAULT_EXT_AUTH_VARIABLE; } protected function Verify() @@ -408,6 +416,11 @@ class Config return explode('|', $this->m_sAllowedLoginTypes); } + public function GetExternalAuthenticationVariable() + { + return $this->m_sExtAuthVariable; + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -488,6 +501,11 @@ class Config $this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes); } + public function SetExternalAuthenticationVariable($sExtAuthVariable) + { + $this->m_sExtAuthVariable = $sExtAuthVariable; + } + public function FileIsWritable() { return is_writable($this->m_sFile); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 5e652a05ff..dec3b9e167 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -83,7 +83,7 @@ Dict::Add('FR FR', 'French', 'Français', array( // Class: User // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:User' => 'Utilisateur', 'Class:User+' => 'Compte utilisateur', 'Class:User/Attribute:finalclass' => 'Type de compte', From 73458617bf84eff3287a1d73266e0ff24795fc15 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 10 Aug 2010 16:53:28 +0000 Subject: [PATCH 587/970] - Integrated fix for patch #188 - ForceHttps = SecureConnectionRequired SVN:trunk[670] --- application/loginwebpage.class.inc.php | 3 +-- application/utils.inc.php | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 90c6aa592f..eb485d8265 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -194,8 +194,7 @@ EOF static function SecureConnectionRequired() { - $oConfig = new Config(ITOP_CONFIG_FILE); - return $oConfig->GetSecureConnectionRequired(); + return utils::GetConfig()->GetSecureConnectionRequired(); } static function IsConnectionSecure() diff --git a/application/utils.inc.php b/application/utils.inc.php index e375572f48..3a376cdffd 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -185,6 +185,11 @@ class utils { // Build an absolute URL to this page on this server/port $sServerName = $_SERVER['SERVER_NAME']; + if (self::GetConfig()->GetSecureConnectionRequired()) + { + // If a secure connection is required, then any URL must start with https ! + $bForceHTTPS = true; + } if ($bForceHTTPS) { $sProtocol = 'https'; From 9eba34362c9e25e963fa4bf9cb8a825e680fbe45 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 10 Aug 2010 17:10:39 +0000 Subject: [PATCH 588/970] - Fixing Trac #188... new flag https_hyperlinks with a slightly different meaning: produce https hyperlinks even if the current page is NOT https. SVN:trunk[671] --- application/utils.inc.php | 5 +++-- core/config.class.inc.php | 25 ++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 3a376cdffd..5268e6c41e 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -185,9 +185,10 @@ class utils { // Build an absolute URL to this page on this server/port $sServerName = $_SERVER['SERVER_NAME']; - if (self::GetConfig()->GetSecureConnectionRequired()) + if (self::GetConfig()->GetSecureConnectionRequired() || self::GetConfig()->GetHttpsHyperlinks()) { - // If a secure connection is required, then any URL must start with https ! + // If a secure connection is required, or if the URL is requested to start with HTTPS + // then any URL must start with https ! $bForceHTTPS = true; } if ($bForceHTTPS) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index efc8674283..8d3cdef0f8 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -43,6 +43,7 @@ define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); define ('DEFAULT_STANDARD_RELOAD_INTERVAL', 5*60); define ('DEFAULT_FAST_RELOAD_INTERVAL', 1*60); define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); +define ('DEFAULT_HTTPS_HYPERLINKS', false); define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external'); define ('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']'); @@ -97,10 +98,19 @@ class Config protected $m_iFastReloadInterval; /** - * @var boolean Whether or not a secure connection is required for using the application + * @var boolean Whether or not a secure connection is required for using the application. + * If set, any attempt to connect to an iTop page with http:// will be redirected + * to https:// */ protected $m_bSecureConnectionRequired; + /** + * @var boolean Forces iTop to output hyperlinks starting with https:// even + * if the current page is not using https. This can be useful when + * the application runs behind a SSL gateway + */ + protected $m_bHttpsHyperlinks; + /** * @var string Langage code, default if the user language is undefined */ @@ -160,6 +170,7 @@ class Config $this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = DEFAULT_FAST_RELOAD_INTERVAL; $this->m_bSecureConnectionRequired = DEFAULT_SECURE_CONNECTION_REQUIRED; + $this->m_bHttpsHyperlinks = DEFAULT_HTTPS_HYPERLINKS; $this->m_sDefaultLanguage = 'EN US'; $this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE; @@ -257,6 +268,7 @@ class Config $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL; $this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED; + $this->m_bHttpsHyperlinks = isset($MySettings['https_hyperlinks']) ? trim($MySettings['https_hyperlinks']) : DEFAULT_HTTPS_HYPERLINKS; $this->m_aModuleSettings = isset($MyModuleSettings) ? $MyModuleSettings : array(); @@ -405,6 +417,11 @@ class Config return $this->m_bSecureConnectionRequired; } + public function GetHttpsHyperlinks() + { + return $this->m_bHttpsHyperlinks; + } + public function GetDefaultLanguage() { return $this->m_sDefaultLanguage; @@ -491,6 +508,11 @@ class Config $this->m_bSecureConnectionRequired = $bSecureConnectionRequired; } + public function SetHttpsHyperlinks($bHttpsHyperlinks) + { + $this->m_bHttpsHyperlinks = $bHttpsHyperlinks; + } + public function SetDefaultLanguage($sLanguageCode) { $this->m_sDefaultLanguage = $sLanguageCode; @@ -552,6 +574,7 @@ class Config fwrite($hFile, "\t'standard_reload_interval' => {$this->m_iStandardReloadInterval},\n"); fwrite($hFile, "\t'fast_reload_interval' => {$this->m_iFastReloadInterval},\n"); fwrite($hFile, "\t'secure_connection_required' => ".($this->m_bSecureConnectionRequired ? 'true' : 'false').",\n"); + fwrite($hFile, "\t'https_hyperlinks' => ".($this->m_bHttpsHyperlinks ? 'true' : 'false').",\n"); fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n"); fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n"); fwrite($hFile, ");\n"); From c08c12bb72589d50f175e32373721b438ee180ba Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 10 Aug 2010 18:38:01 +0000 Subject: [PATCH 589/970] - Fixed Trac #196: root menu nodes are no longer hyperlink, thus cannot be opened in a new window/tab. SVN:trunk[672] --- application/itopwebpage.class.inc.php | 2 +- application/menunode.class.inc.php | 2 +- css/light-grey.css | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index e04ed7ddb2..7df18238d1 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -113,7 +113,7 @@ class iTopWebPage extends NiceWebPage } // Accordion Menu - $("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false }); + $("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false, icons: false }); }); //add new widget called TruncatedList to properly display truncated lists when they are sorted $.tablesorter.addWidget({ diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 4897817be6..ab3b9e1f61 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -99,7 +99,7 @@ class ApplicationMenu { $oMenuNode = self::GetMenuNode($aMenu['index']); if (($oMenuNode->GetMenuId() == 'AdminTools') && (!UserRights::IsAdministrator())) continue; // Don't display the admin menu for non admin users - $oPage->AddToMenu('

            '.$oMenuNode->GetTitle().'

            '); + $oPage->AddToMenu('

            '.$oMenuNode->GetTitle().'

            '); $oPage->AddToMenu('
            '); $aChildren = self::GetChildren($aMenu['index']); if (count($aChildren) > 0) diff --git a/css/light-grey.css b/css/light-grey.css index 3590cd8dbe..5c6413a0f9 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -236,6 +236,10 @@ input.textSearch { background-color:#FFFFFF; } +#accordion h3 { + padding: 10px; +} + .ui-accordion-content ul { list-style:none; padding-left:16px; From 28cfa56c95fc0eda5cf4ad55a94872b766855a2a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 10 Aug 2010 18:45:24 +0000 Subject: [PATCH 590/970] - Fixed bug #197: incorrect validation of textarea fields SVN:trunk[673] --- application/cmdbabstract.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index ce02e017e4..73c4c877d3 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -951,7 +951,7 @@ abstract class cmdbAbstractObject extends CMDBObject break; case 'Text': - $aEventsList[] ='keypress'; + $aEventsList[] ='keyup'; $aEventsList[] ='change'; $sHTMLValue = " {$sValidationField}"; break; From 954b1a3779193bf2f18259cbbba878bc3b7c340d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 16 Aug 2010 14:46:25 +0000 Subject: [PATCH 591/970] Fixed issue with sample data for notifications: losing links when enabling an email notification SVN:trunk[674] --- core/action.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 3ef97aa9db..adbe7f564c 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -115,7 +115,7 @@ abstract class ActionNotification extends Action MetaModel::Init_InheritAttributes(); // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'description', 'status')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form @@ -159,7 +159,7 @@ class ActionEmail extends ActionNotification MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array()))); // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance', 'trigger_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form From ad1cf8f6582dcba4aa2686fafb1deaeb2b01575c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 16 Aug 2010 14:47:56 +0000 Subject: [PATCH 592/970] Cosmetic: configuration module rendering booleans anytime SVN:trunk[675] --- core/config.class.inc.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 8d3cdef0f8..07bfcc7a7b 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -259,16 +259,16 @@ class Config $this->m_sDBName = trim($MySettings['db_name']); $this->m_sDBSubname = trim($MySettings['db_subname']); - $this->m_bLogGlobal = isset($MySettings['log_global']) ? trim($MySettings['log_global']) : DEFAULT_LOG_GLOBAL; - $this->m_bLogNotification = isset($MySettings['log_notification']) ? trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION; - $this->m_bLogIssue = isset($MySettings['log_issue']) ? trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE; - $this->m_bLogWebService = isset($MySettings['log_web_service']) ? trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE; + $this->m_bLogGlobal = isset($MySettings['log_global']) ? (bool) trim($MySettings['log_global']) : DEFAULT_LOG_GLOBAL; + $this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION; + $this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE; + $this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE; $this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT; $this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT; $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; $this->m_iFastReloadInterval = isset($MySettings['fast_reload_interval']) ? trim($MySettings['fast_reload_interval']) : DEFAULT_FAST_RELOAD_INTERVAL; - $this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED; - $this->m_bHttpsHyperlinks = isset($MySettings['https_hyperlinks']) ? trim($MySettings['https_hyperlinks']) : DEFAULT_HTTPS_HYPERLINKS; + $this->m_bSecureConnectionRequired = isset($MySettings['secure_connection_required']) ? (bool) trim($MySettings['secure_connection_required']) : DEFAULT_SECURE_CONNECTION_REQUIRED; + $this->m_bHttpsHyperlinks = isset($MySettings['https_hyperlinks']) ? (bool) trim($MySettings['https_hyperlinks']) : DEFAULT_HTTPS_HYPERLINKS; $this->m_aModuleSettings = isset($MyModuleSettings) ? $MyModuleSettings : array(); From 02dd08587cd7e7674f7e8fdda44dff59dd505050 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 16 Aug 2010 14:49:36 +0000 Subject: [PATCH 593/970] #205 Notifications missing external fields (on object creation) SVN:trunk[676] --- core/dbobject.class.php | 5 +++-- core/metamodel.class.php | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index d4b31bb4f8..9f3a80b200 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -39,7 +39,8 @@ abstract class DBObject private $m_aCurrValues = array(); protected $m_aOrigValues = array(); - private $m_bDirty = false; // The object may have incorrect external keys, then any attempt of reload must be avoided + private $m_bDirty = false; // Means: "a modification is ongoing" + // The object may have incorrect external keys, then any attempt of reload must be avoided private $m_bFullyLoaded = false; // Compound objects can be partially loaded private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode @@ -884,6 +885,7 @@ abstract class DBObject $this->DBWriteLinks(); $this->m_bIsInDB = true; + $this->m_bDirty = false; // Activate any existing trigger $sClass = get_class($this); @@ -902,7 +904,6 @@ abstract class DBObject public function DBInsert() { $this->DBInsertNoReload(); - $this->m_bDirty = false; $this->Reload(); return $this->m_iKey; } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 1f893efd76..4bf3e7234c 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3476,10 +3476,13 @@ abstract class MetaModel { $aSearches = array(); $aReplacements = array(); - foreach($aParams as $sSearch => $sReplace) + foreach($aParams as $sSearch => $replace) { + // Some environment parameters are objects, we just need scalars + if (is_object($replace)) continue; + $aSearches[] = '$'.$sSearch.'$'; - $aReplacements[] = $sReplace; + $aReplacements[] = (string) $replace; } return str_replace($aSearches, $aReplacements, $aInput); } From b4807be4bf8fac51cbdee4922765b1f403599270 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 16 Aug 2010 14:51:55 +0000 Subject: [PATCH 594/970] Updated benchmark tool and sample data with new data model names and attributes SVN:trunk[677] --- application/template.class.inc.php | 8 +- .../data.struct.ta-actions.xml | 18 +-- .../data.struct.ta-links.xml | 4 +- .../itop-tickets-1.0.0/model.itop-tickets.php | 2 +- setup/benchmark.php | 137 ++++++++++-------- 5 files changed, 90 insertions(+), 79 deletions(-) diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 7f0113436e..964dc40e36 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -228,16 +228,16 @@ class DisplayTemplate SELECT CMDBChangeOp WHERE objkey = $id$ AND objclass = \'$class$\'
            - SELECT bizNetworkDevice AS d WHERE d.id = $id$ + SELECT NetworkDevice AS d WHERE d.id = $id$ - SELECT bizInterface AS i WHERE i.device_id = $id$ + SELECT Interface AS i WHERE i.device_id = $id$ - SELECT bizContact AS c JOIN ContactsLinks AS l ON l.contact_id = c.id WHERE l.object_id = $id$ + SELECT Contact AS c JOIN lnkContactToCI AS l ON l.contact_id = c.id WHERE l.ci_id = $id$ - SELECT bizDocument AS d JOIN lnkDocumentRealObject as l ON l.document_id = d.id WHERE l.object_id = $id$) + SELECT Document AS d JOIN lnkDocumentToCI as l ON l.document_id = d.id WHERE l.ci_id = $id$) '; diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml index 1b7af12f57..3d776a69f6 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml @@ -1,21 +1,21 @@ - + Incident Notification to a Workgroup This action informs a team that a ticket has been assigned their workgroup disabled -SELECT bizTeam AS t JOIN bizWorkgroup AS w ON w.team_id = t.id WHERE w.id=:this->workgroup_id +SELECT Team WHERE id=:this->workgroup_id -The ticket $this->name()$, severity $this->severity$ has been assigned to the workgroup $this->workgroup_name$ +The ticket $this->name()$, priority $this->priority$ has been assigned to the workgroup $this->workgroup_name$ <html> <body> <p>The incident ticket $this->name()$ has been assigned to the workgroup $this->workgroup_name$.</p> <p>Description: $this->title$</p> -<p>Initial Situation: $this->initial_situation$</p> +<p>Title: $this->title$</p> <hr/> <p>for more information on this ticket, click here: $this->hyperlink()$</p> </body> @@ -29,15 +29,15 @@ -SELECT bizPerson WHERE id=:this->agent_id +SELECT Person WHERE id=:this->agent_id -The ticket $this->name()$, severity $this->severity$ has been assigned to you +The ticket $this->name()$, priority $this->priority$ has been assigned to you <html> <body> <p>The incident ticket $this->name()$ has been assigned to you.</p> <p>Description: $this->title$</p> -<p>Initial Situation: $this->initial_situation$</p> +<p>Title: $this->title$</p> <hr/> <p>for more information on this ticket, click here: $this->hyperlink()$</p> </body> @@ -51,10 +51,10 @@ -SELECT bizPerson WHERE id=:this->caller_id +SELECT Person WHERE id=:this->caller_id -Ticket $this->name()$, severity $this->severity$ - $this->ticket_status$ +Ticket $this->name()$, priority $this->priority$ - $this->ticket_status$ <html> <body> <p>The incident ticket $this->name()$ has changed to status $this->ticket_status$</p> diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-links.xml b/modules/itop-tickets-1.0.0/data.struct.ta-links.xml index bd58a7d670..85649bcd4b 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-links.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-links.xml @@ -16,8 +16,8 @@ 1 -4 +1 2 1 - \ No newline at end of file + diff --git a/modules/itop-tickets-1.0.0/model.itop-tickets.php b/modules/itop-tickets-1.0.0/model.itop-tickets.php index 5d31585ae2..be6b3e3ff1 100644 --- a/modules/itop-tickets-1.0.0/model.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/model.itop-tickets.php @@ -566,7 +566,7 @@ abstract class ResponseTicket extends Ticket // $oNewLink->Set('role', 'created before'); // $oToNotify->AddObject($oNewLink); - $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'impacted_infra_manual', 'infra_id'); + $oImpactedInfras = DBObjectSet::FromLinkSet($this, 'impacted_infra_manual', 'ci_id'); $aComputed = $oImpactedInfras->GetRelatedObjects('impacts', 10); diff --git a/setup/benchmark.php b/setup/benchmark.php index e58b894229..ad78b27f38 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -129,12 +129,12 @@ class BenchmarkDataCreation // User login // $aData = array( - 'contactid' => self::FindId('bizPerson'), + 'contactid' => self::FindId('Person'), 'login' => 'foo', 'password' => 'foo', 'language' => 'EN US', ); - $iLogin = $this->CreateObject('User', $aData, $oChange); + $iLogin = $this->CreateObject('UserLocal', $aData, $oChange); // Assign profiles to the new login // @@ -150,13 +150,13 @@ class BenchmarkDataCreation $aData = array( 'name' => 'location', 'description' => '', - 'type' => 'bizLocation', + 'type' => 'Location', ); $iDimLocation = $this->CreateObject('URP_Dimensions', $aData, $oChange); // Project classes // - $aMyClassesToProject = array('bizDevice', 'bizServer'); + $aMyClassesToProject = array('NetworkDevice', 'Server'); foreach($aMyClassesToProject as $sClass) { $aData = array( @@ -192,17 +192,33 @@ class BenchmarkDataCreation $aData = array( 'name' => 'benchmark', ); - $iOrg = $this->CreateObject('bizOrganization', $aData, $oChange); - $this->MakeFeedback($oP, 'bizOrganization'); + $iOrg = $this->CreateObject('Organization', $aData, $oChange); + $this->MakeFeedback($oP, 'Organization'); + // 1' - Services + // + $aData = array( + 'name' => 'My Service', + ); + $iOrg = $this->CreateObject('Service', $aData, $oChange); + $this->MakeFeedback($oP, 'Service'); + + // 1'' - Service subcategories + // + $aData = array( + 'name' => 'My subcategory', + ); + $iOrg = $this->CreateObject('ServiceSubcategory', $aData, $oChange); + $this->MakeFeedback($oP, 'ServiceSubcategory'); + // 2 - Locations // $aData = array( 'org_id' => $iOrg, 'name' => 'rio', ); - $iLoc = $this->CreateObject('bizLocation', $aData, $oChange); - $this->MakeFeedback($oP, 'bizLocation'); + $iLoc = $this->CreateObject('Location', $aData, $oChange); + $this->MakeFeedback($oP, 'Location'); // 3 - Teams // @@ -210,21 +226,11 @@ class BenchmarkDataCreation 'org_id' => $iOrg, 'location_id' => $iLoc, 'name' => 'fluminense', + 'email' => 'fluminense@nowhere.fr', ); - $iTeam = $this->CreateObject('bizTeam', $aData, $oChange); - $this->MakeFeedback($oP, 'bizTeam'); + $iTeam = $this->CreateObject('Team', $aData, $oChange); + $this->MakeFeedback($oP, 'Team'); - // 3' - Workgroups - // - $iAnyTeam = $this->RandomId('bizTeam'); - $aData = array( - 'org_id' => $iOrg, - 'team_id' => $iAnyTeam, - 'name' => 'trabolhogrupo'.$iAnyTeam, - ); - $iTeam = $this->CreateObject('bizWorkgroup', $aData, $oChange); - $this->MakeFeedback($oP, 'bizWorkgroup'); - // 4 - Persons // for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++) @@ -233,10 +239,11 @@ class BenchmarkDataCreation 'org_id' => $iOrg, 'location_id' => $iLoc, 'name' => 'ningem'.$i, + 'email' => 'foo'.$i.'@nowhere.fr', ); - $this->CreateObject('bizPerson', $aData, $oChange); + $this->CreateObject('Person', $aData, $oChange); } - $this->MakeFeedback($oP, 'bizPerson'); + $this->MakeFeedback($oP, 'Person'); // 5 - Servers // @@ -247,9 +254,9 @@ class BenchmarkDataCreation 'location_id' => $iLoc, 'name' => 'server'.$i, ); - $this->CreateObject('bizServer', $aData, $oChange); + $this->CreateObject('Server', $aData, $oChange); } - $this->MakeFeedback($oP, 'bizServer'); + $this->MakeFeedback($oP, 'Server'); // 6 - Incident Tickets // @@ -257,12 +264,15 @@ class BenchmarkDataCreation { $aData = array( 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('bizPerson'), - 'workgroup_id' => $this->RandomId('bizWorkgroup'), - 'agent_id' => $this->RandomId('bizPerson'), + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), 'title' => 'someevent'.$i, + 'ticket_log' => 'Testing...', ); - $iTicket = $this->CreateObject('bizIncidentTicket', $aData, $oChange); + $iTicket = $this->CreateObject('Incident', $aData, $oChange); // Incident/Infra // @@ -270,11 +280,10 @@ class BenchmarkDataCreation for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) { $aData = array( - 'infra_id' => $this->RandomId('bizServer'), + 'ci_id' => $this->RandomId('Server'), 'ticket_id' => $iTicket, - 'impact' => 'info on impact '.$iLinked, ); - $this->CreateObject('lnkInfraTicket', $aData, $oChange); + $this->CreateObject('lnkTicketToCI', $aData, $oChange); } // Incident/Contact @@ -283,14 +292,14 @@ class BenchmarkDataCreation for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) { $aData = array( - 'contact_id' => $this->RandomId('bizPerson'), + 'contact_id' => $this->RandomId('Person'), 'ticket_id' => $iTicket, 'role' => 'role '.$iLinked, ); - $this->CreateObject('lnkContactTicket', $aData, $oChange); + $this->CreateObject('lnkTicketToContact', $aData, $oChange); } } - $this->MakeFeedback($oP, 'bizIncidentTicket'); + $this->MakeFeedback($oP, 'Incident'); // 7 - Change Tickets @@ -299,16 +308,17 @@ class BenchmarkDataCreation { $aData = array( 'org_id' => $iOrg, - 'requestor_id' => $this->RandomId('bizPerson'), - 'workgroup_id' => $this->RandomId('bizWorkgroup'), - 'agent_id' => $this->RandomId('bizPerson'), - 'supervisorgroup_id' => $this->RandomId('bizWorkgroup'), - 'supervisor_id' => $this->RandomId('bizPerson'), - 'managergroup_id' => $this->RandomId('bizWorkgroup'), - 'manager_id' => $this->RandomId('bizPerson'), + 'requestor_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'supervisor_group_id' => $this->RandomId('Team'), + 'supervisor_id' => $this->RandomId('Person'), + 'manager_group_id' => $this->RandomId('Team'), + 'manager_id' => $this->RandomId('Person'), 'title' => "change$i", + 'description' => "Let's do something there", ); - $iTicket = $this->CreateObject('bizChangeTicket', $aData, $oChange); + $iTicket = $this->CreateObject('NormalChange', $aData, $oChange); // Change/Infra // @@ -316,11 +326,10 @@ class BenchmarkDataCreation for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) { $aData = array( - 'infra_id' => $this->RandomId('bizServer'), + 'ci_id' => $this->RandomId('Server'), 'ticket_id' => $iTicket, - 'impact' => 'info on impact '.$iLinked, ); - $this->CreateObject('lnkInfraChangeTicket', $aData, $oChange); + $this->CreateObject('lnkTicketToCI', $aData, $oChange); } // Change/Contact @@ -329,14 +338,14 @@ class BenchmarkDataCreation for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) { $aData = array( - 'contact_id' => $this->RandomId('bizPerson'), - 'change_id' => $iTicket, + 'contact_id' => $this->RandomId('Person'), + 'ticket_id' => $iTicket, 'role' => 'role '.$iLinked, ); - $this->CreateObject('lnkContactChange', $aData, $oChange); + $this->CreateObject('lnkTicketToContact', $aData, $oChange); } } - $this->MakeFeedback($oP, 'bizChangeTicket'); + $this->MakeFeedback($oP, 'NormalChange'); // 8 - Service calls // @@ -344,12 +353,15 @@ class BenchmarkDataCreation { $aData = array( 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('bizPerson'), - 'workgroup_id' => $this->RandomId('bizWorkgroup'), - 'agent_id' => $this->RandomId('bizPerson'), - 'title' => "call$i", + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), + 'title' => 'someevent'.$i, + 'ticket_log' => 'Testing...', ); - $iTicket = $this->CreateObject('bizServiceCall', $aData, $oChange); + $iTicket = $this->CreateObject('UserRequest', $aData, $oChange); // Call/Infra // @@ -357,14 +369,13 @@ class BenchmarkDataCreation for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) { $aData = array( - 'infra_id' => $this->RandomId('bizServer'), - 'call_id' => $iTicket, - 'impact' => 'info on impact '.$iLinked, + 'ci_id' => $this->RandomId('Server'), + 'ticket_id' => $iTicket, ); - $this->CreateObject('lnkInfraCall', $aData, $oChange); + $this->CreateObject('lnkTicketToCI', $aData, $oChange); } } - $this->MakeFeedback($oP, 'bizServiceCall'); + $this->MakeFeedback($oP, 'UserRequest'); // 8 - Documents // @@ -379,13 +390,13 @@ class BenchmarkDataCreation for($i = 0 ; $i < $this->m_aPlanned['Documents'] ; $i++) { $aData = array( - 'org_id' => $iOrg, + //'org_id' => $iOrg, 'name' => "document$i", 'contents' => $oRefDoc, ); - $this->CreateObject('bizDocument', $aData, $oChange); + $this->CreateObject('FileDoc', $aData, $oChange); } - $this->MakeFeedback($oP, 'bizDocument'); + $this->MakeFeedback($oP, 'FileDoc'); } } From fd8184ade7274ecf6d6825d96d8a25d998d443c9 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 18 Aug 2010 10:36:16 +0000 Subject: [PATCH 595/970] #202 audit samples not working SVN:trunk[678] --- modules/itop-config-mgmt-1.0.0/data.struct.Audit.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/itop-config-mgmt-1.0.0/data.struct.Audit.xml b/modules/itop-config-mgmt-1.0.0/data.struct.Audit.xml index 52157855b6..6fe9f5dd5d 100644 --- a/modules/itop-config-mgmt-1.0.0/data.struct.Audit.xml +++ b/modules/itop-config-mgmt-1.0.0/data.struct.Audit.xml @@ -4,7 +4,7 @@ Devices in production Checking all devices in production -SELECT Device AS d WHERE d.status = 'production' +SELECT InfrastructureCI AS ci WHERE ci.status = 'production' @@ -18,7 +18,7 @@ Devices not attached to a monitoring solution -SELECT Device AS d JOIN lnkSolutionToCI AS l ON l.ci_id = d.id JOIN ApplicationSolution AS s ON l.solution_id = s.id WHERE d.status = 'production' AND s.name LIKE '%Monitoring%' +SELECT InfrastructureCI AS ci JOIN lnkSolutionToCI AS l ON l.ci_id = ci.id JOIN ApplicationSolution AS s ON l.solution_id = s.id WHERE ci.status = 'production' AND s.name LIKE '%Monitoring%' true 1 From f59cdc523b8f632af45cdc7f6bdb62e86987cbff Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 18 Aug 2010 14:09:39 +0000 Subject: [PATCH 596/970] #208 Missing email notification tabs for tickets - now automatically added for any class SVN:trunk[679] --- application/cmdbabstract.class.inc.php | 19 +++++++++++++++++++ dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + 3 files changed, 21 insertions(+) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 73c4c877d3..7c3a7fc21b 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -228,6 +228,25 @@ abstract class cmdbAbstractObject extends CMDBObject } } $oPage->SetCurrentTab(''); + + if (!$bEditMode) + { + // Get the actual class of the current object + // And look for triggers referring to it + // If any trigger has been found then display a tab with notifications + // + $sClass = get_class($this); + $oTriggerSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject AS T WHERE T.target_class = '$sClass'")); + if ($oTriggerSet->Count() > 0) + { + $oPage->SetCurrentTab(Dict::S('UI:NotificationsTab')); + + // Display notifications regarding the object + $iId = $this->GetKey(); + $oNotifSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT EventNotificationEmail AS Ev JOIN TriggerOnObject AS T ON Ev.trigger_id = T.id WHERE T.target_class = '$sClass' AND Ev.object_id = $iId")); + self::DisplaySet($oPage, $oNotifSet); + } + } } function GetBareProperties(WebPage $oPage, $bEditMode = false) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 1d558b4dd4..bcd9281390 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -365,6 +365,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', + 'UI:NotificationsTab' => 'Notifications', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date of the change', 'UI:History:User' => 'User', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index dec3b9e167..68a91b4bfc 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -366,6 +366,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:NoObject_Class_ToDisplay' => 'Aucun objet %1$s à afficher', 'UI:History:LastModified_On_By' => 'Dernière modification par %2$s le %1$s.', 'UI:HistoryTab' => 'Historique', + 'UI:NotificationsTab' => 'Notifications', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date de modification', 'UI:History:User' => 'Utilisateur', From 2387176142958aa9d4960c68de8f6f3c3454a8ac Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 18 Aug 2010 22:21:39 +0000 Subject: [PATCH 597/970] Improved the benchmarking utility SVN:trunk[680] --- setup/benchmark.php | 223 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 173 insertions(+), 50 deletions(-) diff --git a/setup/benchmark.php b/setup/benchmark.php index ad78b27f38..10469c6054 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -33,18 +33,26 @@ require_once('./setuppage.class.inc.php'); class BenchmarkDataCreation { - var $m_aPlanned = array(); + var $m_aRequested; + var $m_aPlanned; var $m_aCreated = array(); var $m_aStatsByClass = array(); public function __construct($iPlannedCIs, $iPlannedContacts, $iPlannedContracts) { - $this->m_aPlanned = array( + $this->m_aRequested = array( 'CIs' => $iPlannedCIs, 'Contacts' => $iPlannedContacts, 'Contracts' => $iPlannedContracts, - 'SubCIs' => 10 * $iPlannedCIs, + ); + + $this->m_aPlanned = array( + 'Network devices' => ceil($iPlannedCIs / 2), + 'Servers' => ceil($iPlannedCIs / 2), + 'Interfaces' => 10 * $iPlannedCIs, + 'Contacts' => $iPlannedContacts, + 'Contracts' => $iPlannedContracts, 'Incidents' => 2 * 12 * $iPlannedCIs, 'ServiceCalls' => 1 * 12 * $iPlannedContacts, 'Changes' => 1 * 12 * $iPlannedCIs, @@ -52,11 +60,16 @@ class BenchmarkDataCreation ); } - public function GetPlan() + public function GetPlans() { return $this->m_aPlanned; } + public function GetRequestInfo() + { + return $this->m_aRequested; + } + protected function CreateObject($sClass, $aData, $oChange) { $mu_t1 = MyHelpers::getmicrotime(); @@ -187,15 +200,60 @@ class BenchmarkDataCreation public function GoVolume(WebPage $oP, $oChange) { - // 1 - Organizations + ///////////////////////// + // + // Organizations // $aData = array( - 'name' => 'benchmark', + 'name' => 'Benchmark', ); $iOrg = $this->CreateObject('Organization', $aData, $oChange); $this->MakeFeedback($oP, 'Organization'); - // 1' - Services + ///////////////////////// + // + // Locations + // + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Rio', + ); + $iLoc = $this->CreateObject('Location', $aData, $oChange); + $this->MakeFeedback($oP, 'Location'); + + ///////////////////////// + // + // Teams + // + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'Fluminense', + 'email' => 'fluminense@nowhere.fr', + ); + $iTeam = $this->CreateObject('Team', $aData, $oChange); + $this->MakeFeedback($oP, 'Team'); + + ///////////////////////// + // + // Persons + // + for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'first_name' => 'Joaõ', + 'name' => 'Ningem #'.$i, + 'email' => 'foo'.$i.'@nowhere.fr', + ); + $this->CreateObject('Person', $aData, $oChange); + } + $this->MakeFeedback($oP, 'Person'); + + ///////////////////////// + // + // Services // $aData = array( 'name' => 'My Service', @@ -203,7 +261,9 @@ class BenchmarkDataCreation $iOrg = $this->CreateObject('Service', $aData, $oChange); $this->MakeFeedback($oP, 'Service'); - // 1'' - Service subcategories + ///////////////////////// + // + // Service subcategories // $aData = array( 'name' => 'My subcategory', @@ -211,54 +271,111 @@ class BenchmarkDataCreation $iOrg = $this->CreateObject('ServiceSubcategory', $aData, $oChange); $this->MakeFeedback($oP, 'ServiceSubcategory'); - // 2 - Locations + ///////////////////////// // - $aData = array( - 'org_id' => $iOrg, - 'name' => 'rio', - ); - $iLoc = $this->CreateObject('Location', $aData, $oChange); - $this->MakeFeedback($oP, 'Location'); - - // 3 - Teams + // Contracts // - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'fluminense', - 'email' => 'fluminense@nowhere.fr', - ); - $iTeam = $this->CreateObject('Team', $aData, $oChange); - $this->MakeFeedback($oP, 'Team'); - - // 4 - Persons - // - for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++) + for($i = 0 ; $i < $this->m_aPlanned['Contracts'] ; $i++) { $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'ningem'.$i, - 'email' => 'foo'.$i.'@nowhere.fr', + 'name' => "Contract #$i", + 'description' => 'Created for benchmarking purposes', + 'org_id' => $this->RandomId('Organization'), + 'provider_id' => $this->RandomId('Organization'), + 'start_date' => '2009-12-25', + 'end_date' => '2019-08-01', + 'support_team_id' => $this->RandomId('Team'), ); - $this->CreateObject('Person', $aData, $oChange); + $iContract = $this->CreateObject('CustomerContract', $aData, $oChange); } - $this->MakeFeedback($oP, 'Person'); + $this->MakeFeedback($oP, 'CustomerContract'); - // 5 - Servers + ///////////////////////// // - for($i = 0 ; $i < $this->m_aPlanned['CIs'] ; $i++) + // Servers + // + for($i = 0 ; $i < $this->m_aPlanned['Servers'] ; $i++) { $aData = array( 'org_id' => $iOrg, 'location_id' => $iLoc, 'name' => 'server'.$i, ); - $this->CreateObject('Server', $aData, $oChange); + $iServer = $this->CreateObject('Server', $aData, $oChange); + + // Contract/Infra + // + $iContractCount = 1; + for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) + { + $aData = array( + 'contract_id' => $this->RandomId('CustomerContract'), + 'ci_id' => $iServer, + ); + $this->CreateObject('lnkContractToCI', $aData, $oChange); + } + + // Interfaces + // + $iInterfaceCount = 5; // See how aPlanned['Interfaces'] is computed + for($iLinked = 0 ; $iLinked < $iInterfaceCount ; $iLinked++) + { + $aData = array( + 'name' => "eth$iLinked", + 'status' => 'implementation', + 'org_id' => $iOrg, + 'device_id' => $iServer, + ); + $this->CreateObject('NetworkInterface', $aData, $oChange); + } } $this->MakeFeedback($oP, 'Server'); - // 6 - Incident Tickets + ///////////////////////// + // + // Network devices + // + for($i = 0 ; $i < $this->m_aPlanned['Network devices'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'server'.$i, + ); + $iNWDevice = $this->CreateObject('NetworkDevice', $aData, $oChange); + + // Contract/Infra + // + $iContractCount = 1; + for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) + { + $aData = array( + 'contract_id' => $this->RandomId('CustomerContract'), + 'ci_id' => $iNWDevice, + ); + $this->CreateObject('lnkContractToCI', $aData, $oChange); + } + + // Interfaces + // + $iInterfaceCount = 5; // See how aPlanned['Interfaces'] is computed + for($iLinked = 0 ; $iLinked < $iInterfaceCount ; $iLinked++) + { + $aData = array( + 'name' => "eth$iLinked", + 'status' => 'implementation', + 'org_id' => $iOrg, + 'device_id' => $iNWDevice, + ); + $this->CreateObject('NetworkInterface', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'NetworkDevice'); + $this->MakeFeedback($oP, 'NetworkInterface'); + + ///////////////////////// + // + // Incident Tickets // for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++) { @@ -269,7 +386,7 @@ class BenchmarkDataCreation 'agent_id' => $this->RandomId('Person'), 'service_id' => $this->RandomId('Service'), 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'someevent'.$i, + 'title' => 'Incident #'.$i, 'ticket_log' => 'Testing...', ); $iTicket = $this->CreateObject('Incident', $aData, $oChange); @@ -301,8 +418,9 @@ class BenchmarkDataCreation } $this->MakeFeedback($oP, 'Incident'); - - // 7 - Change Tickets + ///////////////////////// + // + // Change Tickets // for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++) { @@ -315,7 +433,7 @@ class BenchmarkDataCreation 'supervisor_id' => $this->RandomId('Person'), 'manager_group_id' => $this->RandomId('Team'), 'manager_id' => $this->RandomId('Person'), - 'title' => "change$i", + 'title' => 'change #'.$i, 'description' => "Let's do something there", ); $iTicket = $this->CreateObject('NormalChange', $aData, $oChange); @@ -347,7 +465,9 @@ class BenchmarkDataCreation } $this->MakeFeedback($oP, 'NormalChange'); - // 8 - Service calls + ///////////////////////// + // + // Service calls // for($i = 0 ; $i < $this->m_aPlanned['ServiceCalls'] ; $i++) { @@ -358,7 +478,7 @@ class BenchmarkDataCreation 'agent_id' => $this->RandomId('Person'), 'service_id' => $this->RandomId('Service'), 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'someevent'.$i, + 'title' => 'Call #'.$i, 'ticket_log' => 'Testing...', ); $iTicket = $this->CreateObject('UserRequest', $aData, $oChange); @@ -377,7 +497,9 @@ class BenchmarkDataCreation } $this->MakeFeedback($oP, 'UserRequest'); - // 8 - Documents + ///////////////////////// + // + // Documents // $sMyDoc = ''; for($i = 0 ; $i < 1000 ; $i++) @@ -443,8 +565,8 @@ function DisplayStep2(SetupWebPage $oP, $oDataCreation) $oP->add("

            iTop benchmarking

            \n"); $oP->add("

            Step 2: review planned volumes

            \n"); - $aPlanned = $oDataCreation->GetPlan(); + $aPlanned = $oDataCreation->GetPlans(); $aForm = array(); foreach ($aPlanned as $sKey => $iCount) { @@ -455,11 +577,12 @@ function DisplayStep2(SetupWebPage $oP, $oDataCreation) } $oP->form($aForm); + $aRequested = $oDataCreation->GetRequestInfo(); $oP->add("
            \n"); $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); $oP->add("\n"); $oP->add("
            \n"); } From b555f104df286bac1a8cc3ba5fcfa1545d170a14 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 19 Aug 2010 13:08:42 +0000 Subject: [PATCH 598/970] Improved user rights management: SELECT filtered on objects authorized for the current user (not yet fully implemented) SVN:trunk[681] --- .../userrights/userrightsmatrix.class.inc.php | 6 + .../userrights/userrightsnull.class.inc.php | 6 + .../userrightsprofile.class.inc.php | 157 ++++++- application/user.preferences.class.inc.php | 2 +- core/dbobjectsearch.class.php | 24 + core/expression.class.inc.php | 23 + core/metamodel.class.php | 21 + core/userrights.class.inc.php | 29 ++ core/valuesetdef.class.inc.php | 13 +- pages/usermanagement_profileproj.php | 2 +- pages/usermanagement_userstatus.php | 2 +- setup/benchmark.php | 431 ++++++++++++++++-- 12 files changed, 666 insertions(+), 50 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 2198f7e544..d7e7578d78 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -274,6 +274,12 @@ class UserRightsMatrix extends UserRightsAddOnAPI return $oNullFilter; } + public function GetSelectFilter($oUser, $sClass) + { + $oNullFilter = new DBObjectSearch($sClass); + return $oNullFilter; + } + public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) { if (!array_key_exists($iActionCode, self::$m_aActionCodes)) diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index 4958321493..44fe4e3058 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -53,6 +53,12 @@ class UserRightsNull extends UserRightsAddOnAPI return $oNullFilter; } + public function GetSelectFilter($oUser, $sClass) + { + $oNullFilter = new DBObjectSearch($sClass); + return $oNullFilter; + } + public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) { return UR_ALLOWED_YES; diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 0971c27a69..3941cfb5ff 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -228,7 +228,7 @@ class URP_Dimensions extends UserRightsBaseClass // Evaluate wether it is a constant or not try { - $oObjectSearch = DBObjectSearch::FromOQL($sExpression); + $oObjectSearch = DBObjectSearch::FromOQL_AllData($sExpression); $sTargetClass = $oObjectSearch->GetClass(); } @@ -366,7 +366,7 @@ class URP_ProfileProjection extends UserRightsBaseClass } } - elseif ($sExpr == '') + elseif (($sExpr == '') || ($sExpr == '')) { $aRes = null; } @@ -374,7 +374,7 @@ class URP_ProfileProjection extends UserRightsBaseClass { $sColumn = $this->Get('attribute'); // SELECT... - $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); + $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); $aRes = $oValueSetDef->GetValues(array('user' => $oUser), ''); } else @@ -437,7 +437,7 @@ class URP_ClassProjection extends UserRightsBaseClass } } - elseif ($sExpr == '') + elseif (($sExpr == '') || ($sExpr == '')) { $aRes = null; } @@ -445,13 +445,9 @@ class URP_ClassProjection extends UserRightsBaseClass { $sColumn = $this->Get('attribute'); // SELECT... - $oValueSetDef = new ValueSetObjects($sExpr, $sColumn); + $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); $aRes = $oValueSetDef->GetValues(array('this' => $oObject), ''); } - elseif ($sExpr == '') - { - $aRes = null; - } else { // Constant value(s) @@ -459,6 +455,7 @@ class URP_ClassProjection extends UserRightsBaseClass } return $aRes; } + } @@ -678,28 +675,28 @@ class UserRightsProfile extends UserRightsAddOnAPI { // Could be loaded in a shared memory (?) - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); $this->m_aDimensions = array(); while ($oDimension = $oDimensionSet->Fetch()) { $this->m_aDimensions[$oDimension->GetKey()] = $oDimension; } - $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); + $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ClassProjection")); $this->m_aClassProjs = array(); while ($oClassProj = $oClassProjSet->Fetch()) { $this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; } - $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); $this->m_aProfiles = array(); while ($oProfile = $oProfileSet->Fetch()) { $this->m_aProfiles[$oProfile->GetKey()] = $oProfile; } - $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile")); + $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserProfile")); $this->m_aUserProfiles = array(); $this->m_aAdmins = array(); while ($oUserProfile = $oUserProfileSet->Fetch()) @@ -711,7 +708,7 @@ class UserRightsProfile extends UserRightsAddOnAPI } } - $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); + $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ProfileProjection")); $this->m_aProPros = array(); while ($oProPro = $oProProSet->Fetch()) { @@ -738,6 +735,90 @@ exit; return $oNullFilter; } + public function GetSelectFilter($oUser, $sClass) + { + $aConditions = array(); + foreach ($this->m_aDimensions as $iDimension => $oDimension) + { + $oClassProj = @$this->m_aClassProjs[$sClass][$iDimension]; + if (is_null($oClassProj)) + { + // Authorize any for this dimension, then no additional criteria is required + continue; + } + + // 1 - Get class projection info + // + $oExpression = null; + $sExpr = $oClassProj->Get('value'); + if ($sExpr == '') + { + $sColumn = $oClassProj->Get('attribute'); + if (empty($sColumn)) + { + $oExpression = new FieldExpression('id', $sClass); + } + else + { + $oExpression = new FieldExpression($sColumn, $sClass); + } + } + elseif (($sExpr == '') || ($sExpr == '')) + { + // Authorize any for this dimension, then no additional criteria is required + continue; + } + elseif (strtolower(substr($sExpr, 0, 6)) == 'select') + { + throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr)); + } + else + { + // Constant value(s) + // unsupported + throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr)); +// $aRes = explode(';', trim($sExpr)); + } + + // 2 - Get profile projection info and use it if needed + // + $aProjections = self::GetReadableProjectionsByDim($oUser, $sClass, $oDimension); + if (is_null($aProjections)) + { + // Authorize any for this dimension, then no additional criteria is required + continue; + } + elseif (count($aProjections) == 0) + { + // Authorize none, then exit as quickly as possible + return false; + } + else + { + // Authorize the given set of values + $oListExpr = ListExpression::FromScalars($aProjections); + $oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr); + $aConditions[] = $oCondition; + } + } + + if (count($aConditions) == 0) + { + // allow all + return true; + } + else + { + $oFilter = new DBObjectSearch($sClass); + foreach($aConditions as $oCondition) + { + $oFilter->AddConditionExpression($oCondition); + } + //return true; + return $oFilter; + } + } + // This verb has been made public to allow the development of an accurate feedback for the current configuration public function GetClassActionGrant($iProfile, $sClass, $sAction) { @@ -747,7 +828,7 @@ exit; } // Get the permission for this profile/class/action - $oSearch = DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'"); + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'"); $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile)); if ($oSet->Count() >= 1) { @@ -802,7 +883,7 @@ exit; // update the list of attributes with those allowed for this profile // - $oSearch = DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); if (count($aProfileAttributes) == 0) @@ -928,7 +1009,7 @@ exit; } // Get the permission for this profile/class/stimulus - $oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); if ($oSet->Count() >= 1) { @@ -999,6 +1080,44 @@ exit; } } + // Copied from GetMatchingProfilesByDim() + // adapted to the optimized implementation of GetSelectFilter() + // Note: shares the cache m_aProPros with GetMatchingProfilesByDim() + // Returns null if any object is readable + // an array of allowed projections otherwise (could be an empty array if none is allowed) + protected function GetReadableProjectionsByDim($oUser, $sClass, $oDimension) + { + // + // Given a dimension, lists the values for which the user will be allowed to read the objects + // + $iUser = $oUser->GetKey(); + $iDimension = $oDimension->GetKey(); + + $aRes = array(); + if (array_key_exists($iUser, $this->m_aUserProfiles)) + { + foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) + { + // user projection to be cached on a given page ! + if (!array_key_exists($iDimension, $this->m_aProPros[$iProfile])) + { + // No projection for a given profile: default to 'any' + return null; + } + + $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + if (is_null($aUserProjection)) + { + // No projection for a given profile: default to 'any' + return null; + } + $aRes = array_merge($aRes, $aUserProjection); + } + } + return $aRes; + } + + // Note: shares the cache m_aProPros with GetReadableProjectionsByDim() protected function GetMatchingProfilesByDim($oUser, $oObject, $oDimension) { // @@ -1245,7 +1364,7 @@ class SetupProfiles // Project in every dimension // - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); while ($oDimension = $oDimensionSet->Fetch()) { $iDimension = $oDimension->GetKey(); @@ -1393,7 +1512,7 @@ class SetupProfiles 'Problem' => MetaModel::GetClasses('problemmgmt'), 'Change' => MetaModel::GetClasses('changemgmt'), 'Service' => MetaModel::GetClasses('servicemgmt'), - 'Call' => MetaModel::GetClasses('callmgmt'), + 'Call' => MetaModel::GetClasses('requestmgmt'), 'KnownError' => MetaModel::GetClasses('knownerrormgmt'), ); diff --git a/application/user.preferences.class.inc.php b/application/user.preferences.class.inc.php index cc7de99759..14a7ed1e48 100644 --- a/application/user.preferences.class.inc.php +++ b/application/user.preferences.class.inc.php @@ -143,7 +143,7 @@ class appUserPreferences extends DBObject { $aParams = array ( - "category" => "gui", + "category" => "gui,alwaysreadable", "key_type" => "autoincrement", "name_attcode" => "userid", "state_attcode" => "", diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 9ea7451292..68cc4230bb 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -34,6 +34,10 @@ class DBObjectSearch private $m_aReferencedBy; private $m_aRelatedTo; + // By default, some information may be hidden to the current user + // But it may happen that we need to disable that feature + private $m_bAllowAllData = false; + public function __construct($sClass, $sClassAlias = null) { if (is_null($sClassAlias)) $sClassAlias = $sClass; @@ -51,6 +55,9 @@ class DBObjectSearch $this->m_aRelatedTo = array(); } + public function AllowAllData() {$this->m_bAllowAllData = true;} + public function IsAllDataAllowed() {return $this->m_bAllowAllData;} + public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];} public function GetJoinedClasses() {return $this->m_aClasses;} @@ -681,8 +688,25 @@ class DBObjectSearch } } + // Create a search definition that leads to 0 result, still a valid search object + static public function FromEmptySet($sClass) + { + $oResultFilter = new DBObjectSearch($sClass); + $oResultFilter->m_oSearchCondition = new FalseExpression; + return $oResultFilter; + } + static protected $m_aOQLQueries = array(); + // Do not filter out depending on user rights + // In particular when we are currently in the process of evaluating the user rights... + static public function FromOQL_AllData($sQuery) + { + $oRes = self::FromOQL($sQuery); + $oRes->AllowAllData(); + return $oRes; + } + static public function FromOQL($sQuery) { if (empty($sQuery)) return null; diff --git a/core/expression.class.inc.php b/core/expression.class.inc.php index 2cb0f25132..dc16464f23 100644 --- a/core/expression.class.inc.php +++ b/core/expression.class.inc.php @@ -230,6 +230,19 @@ class TrueExpression extends ScalarExpression } } +class FalseExpression extends ScalarExpression +{ + public function __construct() + { + parent::__construct(0); + } + + public function IsTrue() + { + return false; + } +} + class FieldExpression extends UnaryExpression { protected $m_sParent; @@ -348,6 +361,16 @@ class ListExpression extends Expression $this->m_aExpressions = $aExpressions; } + public static function FromScalars($aScalars) + { + $aExpressions = array(); + foreach($aScalars as $value) + { + $aExpressions[] = new ScalarExpression($value); + } + return new ListExpression($aExpressions); + } + public function IsTrue() { // return true if we are certain that it will be true diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 4bf3e7234c..05089c1fdc 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1509,6 +1509,27 @@ abstract class MetaModel public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) { + // Hide objects that are not visible to the current user + // + if (!$oFilter->IsAllDataAllowed()) + { + $oVisibleObjects = UserRights::GetSelectFilter($oFilter->GetClass()); + if ($oVisibleObjects === false) + { + // Make sure this is a valid search object, saying NO for all + $oVisibleObjects = DBObjectSearch::FromEmptySet($oFilter->GetClass()); + } + if (is_object($oVisibleObjects)) + { + $oFilter->MergeWith($oVisibleObjects); + } + else + { + // should be true at this point, meaning that no additional filtering + // is required + } + } + // Query caching // $bQueryCacheEnabled = true; diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index fcb39e522d..7f9c60cc13 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -57,6 +57,8 @@ abstract class UserRightsAddOnAPI // Cf UserContext... abstract public function GetFilter($sLogin, $sClass); // returns a filter object + // Used to build select queries showing only objects visible for the given user + abstract public function GetSelectFilter($sLogin, $sClass); // returns a filter object abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null); @@ -494,6 +496,9 @@ class UserRights public static function GetFilter($sClass) { + // #@# to cleanup ! + return new DBObjectSearch($sClass); + if (!self::CheckLogin()) return false; if (self::IsAdministrator()) return new DBObjectSearch($sClass); @@ -506,11 +511,35 @@ class UserRights return self::$m_oAddOn->GetFilter(self::$m_oUser->GetKey(), $sClass); } + public static function GetSelectFilter($sClass) + { + // Need to load some records before the login is performed (user preferences) + if (MetaModel::HasCategory($sClass, 'alwaysreadable')) return true; + + // ne marche pas... pourquoi? + //if (!self::CheckLogin()) return false; + + if (self::IsAdministrator()) return true; + + // this module is forbidden for non admins.... BUT I NEED IT HERE TO DETERMINE USER RIGHTS + if (MetaModel::HasCategory($sClass, 'addon/userrights')) return true; + + // the rest is allowed (#@# to be improved) + if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true; + + return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass); + } + public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null) { if (!self::CheckLogin()) return false; if (self::IsAdministrator($oUser)) return true; + + // #@# Temporary????? + // The read access is controlled in MetaModel::MakeSelectQuery() + if ($iActionCode == UR_ACTION_READ) return true; + // this module is forbidden for non admins if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false; diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 44f08c3e76..0b0fade6c7 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -93,19 +93,28 @@ class ValueSetObjects extends ValueSetDefinition protected $m_sFilterExpr; // in OQL protected $m_sValueAttCode; protected $m_aOrderBy; + private $m_bAllowAllData; - public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array()) + public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false) { $this->m_sFilterExpr = $sFilterExp; $this->m_sValueAttCode = $sValueAttCode; $this->m_aOrderBy = $aOrderBy; + $this->m_bAllowAllData = $bAllowAllData; } protected function LoadValues($aArgs) { $this->m_aValues = array(); - $oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr, $aArgs); + if ($this->m_bAllowAllData) + { + $oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr, $aArgs); + } + else + { + $oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr, $aArgs); + } if (!$oFilter) return false; $oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs); diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index ef42dd1cc8..24cda4f709 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -72,7 +72,7 @@ function ComputeProjections($oPage) // Load users, and create a record per couple user/profile // $aDisplayData = array(); - $oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users")); + $oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("User")); while ($oUser = $oUserSet->Fetch()) { $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile WHERE userid = :user->id"), array(), array('user' => $oUser)); diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index 5a175aca39..6ba64e54de 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -305,7 +305,7 @@ else $oPage->p('

            If one profile says YES, then the answer is YES

            '); - $oUser = MetaModel::GetObject('URP_Users', $iUser); + $oUser = MetaModel::GetObject('User', $iUser); $oPage->p('

            Projections for user '.$oUser->GetName().'

            '); ComputeUserProjections($oPage, $oUser); diff --git a/setup/benchmark.php b/setup/benchmark.php index 10469c6054..2fc2e55c50 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -33,13 +33,16 @@ require_once('./setuppage.class.inc.php'); class BenchmarkDataCreation { + var $m_iIfByServer; + var $m_iIfByNWDevice; var $m_aRequested; var $m_aPlanned; - var $m_aCreated = array(); + var $m_aCreatedByClass = array(); + var $m_aCreatedByDesc = array(); var $m_aStatsByClass = array(); - public function __construct($iPlannedCIs, $iPlannedContacts, $iPlannedContracts) + public function __construct($iPlannedCIs = 0, $iPlannedContacts = 0, $iPlannedContracts = 0) { $this->m_aRequested = array( 'CIs' => $iPlannedCIs, @@ -47,10 +50,27 @@ class BenchmarkDataCreation 'Contracts' => $iPlannedContracts, ); + $this->m_iIfByServer = 2; + $this->m_iIfByNWDevice = 10; + + $iServers = ceil($iPlannedCIs * 9 / 10); + $iNWDevices = ceil($iPlannedCIs / 10); + $iBigTicketCIs = ceil($iPlannedCIs / 10); + $iInterfaces = $iServers * $this->m_iIfByServer + $iNWDevices * $this->m_iIfByNWDevice; + $iApplications = $iServers * 10; + $iSolutions = $iApplications; + $iProcesses = $iSolutions * 2; + $this->m_aPlanned = array( - 'Network devices' => ceil($iPlannedCIs / 2), - 'Servers' => ceil($iPlannedCIs / 2), - 'Interfaces' => 10 * $iPlannedCIs, + 'Network devices' => $iNWDevices, + 'Servers' => $iServers, + 'Big ticket: CIs' => $iBigTicketCIs, + 'Interfaces' => $iInterfaces, + 'Application SW' => 2, + 'Applications' => $iApplications, + 'Solutions' => $iSolutions, + 'Processes' => $iProcesses, + 'Contacts' => $iPlannedContacts, 'Contracts' => $iPlannedContracts, 'Incidents' => 2 * 12 * $iPlannedCIs, @@ -70,7 +90,7 @@ class BenchmarkDataCreation return $this->m_aRequested; } - protected function CreateObject($sClass, $aData, $oChange) + protected function CreateObject($sClass, $aData, $oChange, $sClassDesc = '') { $mu_t1 = MyHelpers::getmicrotime(); @@ -82,7 +102,9 @@ class BenchmarkDataCreation $iId = $oMyObject->DBInsertTrackedNoReload($oChange); - $this->m_aCreated[$sClass][] = $iId; + $sClassId = "$sClass ($sClassDesc)"; + $this->m_aCreatedByDesc[$sClassId][] = $iId; + $this->m_aCreatedByClass[$sClass][] = $iId; $mu_t2 = MyHelpers::getmicrotime(); $this->m_aStatsByClass[$sClass][] = $mu_t2 - $mu_t1; @@ -90,9 +112,10 @@ class BenchmarkDataCreation return $iId; } - protected function RandomId($sClass) + protected function RandomId($sClass, $sClassDesc = '') { - return $this->m_aCreated[$sClass][array_rand($this->m_aCreated[$sClass])]; + $sClassId = "$sClass ($sClassDesc)"; + return $this->m_aCreatedByDesc[$sClassId][array_rand($this->m_aCreatedByDesc[$sClassId])]; } static protected function FindId($sClass) @@ -121,7 +144,7 @@ class BenchmarkDataCreation protected function MakeFeedback($oP, $sClass) { - $iSample = reset($this->m_aCreated[$sClass]); + $iSample = reset($this->m_aCreatedByClass[$sClass]); $sSample = "sample"; $iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3); @@ -131,13 +154,156 @@ class BenchmarkDataCreation $oP->add("
              "); $oP->add("
            • "); - $oP->add("$sClass: ".count($this->m_aCreated[$sClass])." - $sSample
              "); + $oP->add("$sClass: ".count($this->m_aStatsByClass[$sClass])." - $sSample
              "); $oP->add("Duration: $fDurationMin => $fDurationMax; Avg:$fDurationAverage; Total: $iDuration"); $oP->add("
            • "); $oP->add("
            "); } - public function GoProjections(WebPage $oP, $oChange) + public function GoProjectionsOrganization(WebPage $oP, $oChange) + { + $aClasses = MetaModel::GetClasses(); + $aActions = array('Read', 'Bulk Read', 'Delete', 'Bulk Delete', 'Modify', 'Bulk Modify'); + $aStdProfiles = array(2, 3, 4, 5, 6, 7, 8, 9); + + //////////////////////////////////////// + // Dimension: Organization + // + $aData = array( + 'name' => 'organization', + 'description' => '', + 'type' => 'Organization', + ); + $iDimLocation = $this->CreateObject('URP_Dimensions', $aData, $oChange); + + //////////////////////////////////////// + // New specific profile, given access to everything + // + $aData = array( + 'name' => 'data guru', + 'description' => 'could do anything, because everything is granted', + ); + $iGuruProfile = $this->CreateObject('URP_Profiles', $aData, $oChange); + foreach($aClasses as $sClass) + { + foreach($aActions as $sAction) + { + $aData = array( + 'profileid' => $iGuruProfile, + 'class' => $sClass, + 'permission' => 'yes', + 'action' => $sAction, + ); + $this->CreateObject('URP_ActionGrant', $aData, $oChange); + } + } + $aData = array( + 'dimensionid' => $iDimLocation, + 'profileid' => $iGuruProfile, + 'value' => '', + 'attribute' => 'org_id', + ); + $this->CreateObject('URP_ProfileProjection', $aData, $oChange); + + // User login with super access rights + // + $aData = array( + 'org_id' => self::FindId('Organization'), + 'location_id' => self::FindId('Location'), + 'first_name' => 'Jesus', + 'name' => 'Deus', + 'email' => '', + ); + $iPerson = $this->CreateObject('Person', $aData, $oChange); + $aData = array( + 'contactid' => $iPerson, + 'login' => 'guru', + 'password' => 'guru', + 'language' => 'EN US', + ); + $iLogin = $this->CreateObject('UserLocal', $aData, $oChange); + + // Assign profiles to the new login + // + $aData = array( + 'userid' => $iLogin, + 'profileid' => $iGuruProfile, + 'reason' => 'he is the one', + ); + $this->CreateObject('URP_UserProfile', $aData, $oChange); + + //////////////////////////////////////// + // User login having all profiles, but seeing only his organization + // + $aData = array( + 'org_id' => self::FindId('Organization'), + 'location_id' => self::FindId('Location'), + 'first_name' => 'Little ze', + 'name' => 'Foo', + 'email' => '', + ); + $iPerson = $this->CreateObject('Person', $aData, $oChange); + $aData = array( + 'contactid' => $iPerson, + 'login' => 'foo', + 'password' => 'foo', + 'language' => 'EN US', + ); + $iLogin = $this->CreateObject('UserLocal', $aData, $oChange); + + // Assign profiles to the new login + // + foreach($aStdProfiles as $iProfileId) + { + $aData = array( + 'userid' => $iLogin, + 'profileid' => $iProfileId, + 'reason' => '', + ); + $this->CreateObject('URP_UserProfile', $aData, $oChange); + } + + // Project classes + // + $aMyClassesToProject = array(); + foreach($aClasses as $sClass) + { + if (MetaModel::IsValidAttCode($sClass, 'org_id')) + { + $aMyClassesToProject[$sClass] = 'org_id'; + } + } + foreach($aMyClassesToProject as $sClass => $sAttCode) + { + $aData = array( + 'dimensionid' => $iDimLocation, + 'class' => $sClass, + 'value' => '', + 'attribute' => $sAttCode, + ); + $this->CreateObject('URP_ClassProjection', $aData, $oChange); + } + + // Project profiles + // + foreach($aStdProfiles as $iProfileId) + { + $aData = array( + 'dimensionid' => $iDimLocation, + 'profileid' => $iProfileId, + 'value' => 'SELECT Person WHERE id = :user->contactid', + 'attribute' => 'org_id', + ); + $this->CreateObject('URP_ProfileProjection', $aData, $oChange); + } + + $oP->p('Created projections (Cf. login "foo", pwd "foo")'); + $oP->p('* foo can do configuration management for a given customer'); + $oP->p('* guru can do everything'); + } + + // For testing purposes -Romain + public function GoProjectionsLocation(WebPage $oP, $oChange) { // User login // @@ -156,7 +322,7 @@ class BenchmarkDataCreation 'profileid' => self::FindIdFromOQL("SELECT URP_Profiles WHERE name LIKE 'Configuration Manager'"), 'reason' => '', ); - $iFoo = $this->CreateObject('URP_UserProfile', $aData, $oChange); + $this->CreateObject('URP_UserProfile', $aData, $oChange); // Dimension // @@ -178,7 +344,7 @@ class BenchmarkDataCreation 'value' => '', 'attribute' => 'location_name', ); - $iFoo = $this->CreateObject('URP_ClassProjection', $aData, $oChange); + $this->CreateObject('URP_ClassProjection', $aData, $oChange); } // Project profiles @@ -192,7 +358,7 @@ class BenchmarkDataCreation 'value' => 'Grenoble', 'attribute' => '', ); - $iFoo = $this->CreateObject('URP_ProfileProjection', $aData, $oChange); + $this->CreateObject('URP_ProfileProjection', $aData, $oChange); } $oP->p('Created projections (Cf. login "foo", pwd "foo")'); @@ -216,7 +382,7 @@ class BenchmarkDataCreation // $aData = array( 'org_id' => $iOrg, - 'name' => 'Rio', + 'name' => 'Rio de Janeiro', ); $iLoc = $this->CreateObject('Location', $aData, $oChange); $this->MakeFeedback($oP, 'Location'); @@ -247,7 +413,15 @@ class BenchmarkDataCreation 'name' => 'Ningem #'.$i, 'email' => 'foo'.$i.'@nowhere.fr', ); - $this->CreateObject('Person', $aData, $oChange); + $iPerson = $this->CreateObject('Person', $aData, $oChange); + + // Contract/Infra + // + $aData = array( + 'contact_id' => $iPerson, + 'team_id' => $this->RandomId('Team'), + ); + $this->CreateObject('lnkTeamToContact', $aData, $oChange); } $this->MakeFeedback($oP, 'Person'); @@ -287,6 +461,19 @@ class BenchmarkDataCreation 'support_team_id' => $this->RandomId('Team'), ); $iContract = $this->CreateObject('CustomerContract', $aData, $oChange); + + // Contract/Contact (10% of contacts) + // + $iContactCount = ceil($this->m_aPlanned['Contracts'] / 10); + for($iLinked = 0 ; $iLinked < $iContactCount ; $iLinked++) + { + $aData = array( + 'contact_id' => $this->RandomId('Person'), + 'contract_id' => $iContract, + 'role' => 'role '.$iLinked, + ); + $this->CreateObject('lnkContractToContact', $aData, $oChange); + } } $this->MakeFeedback($oP, 'CustomerContract'); @@ -300,6 +487,7 @@ class BenchmarkDataCreation 'org_id' => $iOrg, 'location_id' => $iLoc, 'name' => 'server'.$i, + 'status' => 'production', ); $iServer = $this->CreateObject('Server', $aData, $oChange); @@ -317,16 +505,16 @@ class BenchmarkDataCreation // Interfaces // - $iInterfaceCount = 5; // See how aPlanned['Interfaces'] is computed - for($iLinked = 0 ; $iLinked < $iInterfaceCount ; $iLinked++) + for($iLinked = 0 ; $iLinked < $this->m_iIfByServer ; $iLinked++) { $aData = array( 'name' => "eth$iLinked", 'status' => 'implementation', 'org_id' => $iOrg, 'device_id' => $iServer, + 'status' => 'production', ); - $this->CreateObject('NetworkInterface', $aData, $oChange); + $this->CreateObject('NetworkInterface', $aData, $oChange, 'server if'); } } $this->MakeFeedback($oP, 'Server'); @@ -340,7 +528,8 @@ class BenchmarkDataCreation $aData = array( 'org_id' => $iOrg, 'location_id' => $iLoc, - 'name' => 'server'.$i, + 'name' => 'equipment #'.$i, + 'status' => 'production', ); $iNWDevice = $this->CreateObject('NetworkDevice', $aData, $oChange); @@ -358,21 +547,118 @@ class BenchmarkDataCreation // Interfaces // - $iInterfaceCount = 5; // See how aPlanned['Interfaces'] is computed - for($iLinked = 0 ; $iLinked < $iInterfaceCount ; $iLinked++) + for($iLinked = 0 ; $iLinked < $this->m_iIfByNWDevice ; $iLinked++) { $aData = array( 'name' => "eth$iLinked", 'status' => 'implementation', 'org_id' => $iOrg, 'device_id' => $iNWDevice, + 'connected_if' => $this->RandomId('NetworkInterface', 'server if'), + 'status' => 'production', ); - $this->CreateObject('NetworkInterface', $aData, $oChange); + $this->CreateObject('NetworkInterface', $aData, $oChange, 'equipment if'); } } $this->MakeFeedback($oP, 'NetworkDevice'); $this->MakeFeedback($oP, 'NetworkInterface'); + ///////////////////////// + // + // Application Software + // + for($i = 0 ; $i < $this->m_aPlanned['Application SW'] ; $i++) + { + $aData = array( + 'name' => 'Software #'.$i, + ); + $iNWDevice = $this->CreateObject('Application', $aData, $oChange); + } + $this->MakeFeedback($oP, 'Application'); + + ///////////////////////// + // + // Applications + // + for($i = 0 ; $i < $this->m_aPlanned['Applications'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'device_id' => $this->RandomId('Server'), + 'software_id' => $this->RandomId('Application'), + 'name' => 'Application #'.$i, + 'status' => 'production', + ); + $iNWDevice = $this->CreateObject('ApplicationInstance', $aData, $oChange); + + // Contract/Infra + // + $iContractCount = 1; + for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) + { + $aData = array( + 'contract_id' => $this->RandomId('CustomerContract'), + 'ci_id' => $iNWDevice, + ); + $this->CreateObject('lnkContractToCI', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'ApplicationInstance'); + + ///////////////////////// + // + // Application Solution + // + for($i = 0 ; $i < $this->m_aPlanned['Solutions'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Solution #'.$i, + 'status' => 'production', + ); + $iNWDevice = $this->CreateObject('ApplicationSolution', $aData, $oChange); + + // Contract/Infra + // + $iContractCount = 1; + for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) + { + $aData = array( + 'contract_id' => $this->RandomId('CustomerContract'), + 'ci_id' => $iNWDevice, + ); + $this->CreateObject('lnkContractToCI', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'ApplicationSolution'); + + ///////////////////////// + // + // Business Process + // + for($i = 0 ; $i < $this->m_aPlanned['Processes'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Process #'.$i, + 'status' => 'production', + ); + $iNWDevice = $this->CreateObject('BusinessProcess', $aData, $oChange); + + // Contract/Infra + // + $iContractCount = 1; + for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) + { + $aData = array( + 'contract_id' => $this->RandomId('CustomerContract'), + 'ci_id' => $iNWDevice, + ); + $this->CreateObject('lnkContractToCI', $aData, $oChange); + } + } + $this->MakeFeedback($oP, 'BusinessProcess'); + ///////////////////////// // // Incident Tickets @@ -418,6 +704,47 @@ class BenchmarkDataCreation } $this->MakeFeedback($oP, 'Incident'); + ///////////////////////// + // + // Big Ticket + // + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), + 'title' => 'Big ticket', + 'ticket_log' => 'Testing...', + ); + $iTicket = $this->CreateObject('Incident', $aData, $oChange); + + // Incident/Infra + // + $iInfraCount = $this->m_aPlanned['Big ticket: CIs']; + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'ci_id' => $this->RandomId('Server'), + 'ticket_id' => $iTicket, + ); + $this->CreateObject('lnkTicketToCI', $aData, $oChange); + } + + // Incident/Contact + // + $iInfraCount = rand(0, 6) ; + for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) + { + $aData = array( + 'contact_id' => $this->RandomId('Person'), + 'ticket_id' => $iTicket, + 'role' => 'role '.$iLinked, + ); + $this->CreateObject('lnkTicketToContact', $aData, $oChange); + } + ///////////////////////// // // Change Tickets @@ -529,6 +856,17 @@ function DisplayStep1(SetupWebPage $oP) { $sNextOperation = 'step2'; $oP->add("

            iTop benchmarking

            \n"); + + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); + + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); + $oP->add("

            Please specify the requested volumes

            \n"); $oP->add("
            \n"); $oP->add("
            Data load configuration\n"); @@ -600,10 +938,39 @@ function DisplayStep3(SetupWebPage $oP, $oDataCreation) $oMyChange->Set("userinfo", "Administrator"); $iChangeId = $oMyChange->DBInsertNoReload(); - $oDataCreation->GoProjections($oP, $oMyChange); $oDataCreation->GoVolume($oP, $oMyChange); } +/** + * Do create a profile management context + */ +function CreateProfilesOrganization(SetupWebPage $oP, $oDataCreation) +{ +// $sNextOperation = 'step3'; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Administrator"); + $iChangeId = $oMyChange->DBInsertNoReload(); + + $oDataCreation->GoProjectionsOrganization($oP, $oMyChange); +} + +/** + * Do create the data set... could take some time to execute + */ +function CreateProfilesLocation(SetupWebPage $oP, $oDataCreation) +{ +// $sNextOperation = 'step3'; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Administrator"); + $iChangeId = $oMyChange->DBInsertNoReload(); + + $oDataCreation->GoProjectionsLocation($oP, $oMyChange); +} + /** * Main program */ @@ -620,7 +987,19 @@ try case 'step1': DisplayStep1($oP); break; - + + case 'createprofiles_organization': + $oP->no_cache(); + $oDataCreation = new BenchmarkDataCreation(); + CreateProfilesOrganization($oP, $oDataCreation); + break; + + case 'createprofiles_location': + $oP->no_cache(); + $oDataCreation = new BenchmarkDataCreation(); + CreateProfilesLocation($oP, $oDataCreation); + break; + case 'step2': $oP->no_cache(); $iPlannedCIs = Utils::ReadParam('plannedcis'); From a33dfa9f10bedef6709319e931c29f69720b4acd Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 19 Aug 2010 14:04:22 +0000 Subject: [PATCH 599/970] updated benchmark procedure SVN:trunk[682] --- setup/benchmark.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup/benchmark.php b/setup/benchmark.php index 2fc2e55c50..902a450722 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -31,6 +31,8 @@ require_once('../application/loginwebpage.class.inc.php'); require_once('../application/utils.inc.php'); require_once('./setuppage.class.inc.php'); +ini_set('memory_limit', '2048M'); + class BenchmarkDataCreation { var $m_iIfByServer; @@ -57,9 +59,9 @@ class BenchmarkDataCreation $iNWDevices = ceil($iPlannedCIs / 10); $iBigTicketCIs = ceil($iPlannedCIs / 10); $iInterfaces = $iServers * $this->m_iIfByServer + $iNWDevices * $this->m_iIfByNWDevice; - $iApplications = $iServers * 10; - $iSolutions = $iApplications; - $iProcesses = $iSolutions * 2; + $iApplications = $iServers * 5; + $iSolutions = ceil($iApplications / 2); + $iProcesses = ceil($iSolutions / 2); $this->m_aPlanned = array( 'Network devices' => $iNWDevices, From 7aacef8d7d2547d66155eb95b464ff18650b1d76 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 23 Aug 2010 12:27:21 +0000 Subject: [PATCH 600/970] - Get rid of the class UserContext, since now everything is implemented directly in MetaModel - Make the Accordion menu collapsible SVN:trunk[683] --- .../userrights/userrightsmatrix.class.inc.php | 6 - .../userrights/userrightsnull.class.inc.php | 6 - .../userrightsprofile.class.inc.php | 6 - application/application.inc.php | 1 - application/displayblock.class.inc.php | 3 +- application/itopwebpage.class.inc.php | 7 +- application/menunode.class.inc.php | 1 + application/ui.linkswidget.class.inc.php | 24 ++-- application/uilinkswizard.class.inc.php | 22 +-- application/usercontext.class.inc.php | 129 ------------------ core/userrights.class.inc.php | 19 --- pages/UI.php | 28 ++-- pages/UniversalSearch.php | 1 - pages/ajax.csvimport.php | 2 - pages/ajax.render.php | 28 ++-- pages/audit.php | 9 +- pages/csvimport.php | 24 ++-- pages/logoff.php | 1 - pages/navigator.php | 1 - pages/run_query.php | 1 - pages/schema.php | 1 - pages/testlist.inc.php | 2 - pages/usermanagement_classproj.php | 1 - pages/usermanagement_profileproj.php | 1 - pages/usermanagement_userstatus.php | 1 - pages/xml.navigator.php | 4 +- webservices/export.php | 1 - webservices/import.php | 1 - 28 files changed, 65 insertions(+), 266 deletions(-) delete mode 100644 application/usercontext.class.inc.php diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index d7e7578d78..9919204d12 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -268,12 +268,6 @@ class UserRightsMatrix extends UserRightsAddOnAPI return true; } - public function GetFilter($sUserName, $sClass) - { - $oNullFilter = new DBObjectSearch($sClass); - return $oNullFilter; - } - public function GetSelectFilter($oUser, $sClass) { $oNullFilter = new DBObjectSearch($sClass); diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index 44fe4e3058..ad1b807b83 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -47,12 +47,6 @@ class UserRightsNull extends UserRightsAddOnAPI return true; } - public function GetFilter($sUserName, $sClass) - { - $oNullFilter = new DBObjectSearch($sClass); - return $oNullFilter; - } - public function GetSelectFilter($oUser, $sClass) { $oNullFilter = new DBObjectSearch($sClass); diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 3941cfb5ff..ad0895fd71 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -729,12 +729,6 @@ exit; return true; } - public function GetFilter($sUserName, $sClass) - { - $oNullFilter = new DBObjectSearch($sClass); - return $oNullFilter; - } - public function GetSelectFilter($oUser, $sClass) { $aConditions = array(); diff --git a/application/application.inc.php b/application/application.inc.php index 962b88e710..6d63f8941f 100644 --- a/application/application.inc.php +++ b/application/application.inc.php @@ -24,7 +24,6 @@ */ require_once('../application/applicationcontext.class.inc.php'); -require_once('../application/usercontext.class.inc.php'); require_once('../application/cmdbabstract.class.inc.php'); require_once('../application/displayblock.class.inc.php'); require_once('../application/audit.category.class.inc.php'); diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 46c1d590d8..aa68ed1a91 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -743,10 +743,9 @@ class HistoryBlock extends DisplayBlock if (is_object($oLatestChangeOp)) { - $oContext = new UserContext(); // There is one change in the list... only when the object has been created ! $sDate = $oLatestChangeOp->GetAsHTML('date'); - $oChange = $oContext->GetObject('CMDBChange', $oLatestChangeOp->Get('change')); + $oChange = MetaModel::GetObject('CMDBChange', $oLatestChangeOp->Get('change')); $sUserInfo = $oChange->GetAsHTML('userinfo'); $sHtml .= $oPage->GetStartCollapsibleSection(Dict::Format('UI:History:LastModified_On_By', $sDate, $sUserInfo)); $sHtml .= $this->GetHistoryTable($oPage, $oSet); diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 7df18238d1..9b276edf5b 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -24,7 +24,6 @@ */ require_once("../application/nicewebpage.class.inc.php"); -require_once("../application/usercontext.class.inc.php"); require_once("../application/applicationcontext.class.inc.php"); require_once("../application/user.preferences.class.inc.php"); /** @@ -113,7 +112,7 @@ class iTopWebPage extends NiceWebPage } // Accordion Menu - $("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false, icons: false }); + $("#accordion").accordion({ header: "h3", navigation: true, autoHeight: false, collapsible: false, icons: false }); // collapsible will be enabled once the item will be selected }); //add new widget called TruncatedList to properly display truncated lists when they are sorted $.tablesorter.addWidget({ @@ -293,10 +292,9 @@ EOF { // List of visible Organizations $iCount = 0; - $oContext = new UserContext(); if (MetaModel::IsValidClass('Organization')) { - $oSearchFilter = $oContext->NewFilter('Organization'); + $oSearchFilter = new DBObjectSearch('Organization'); $oSet = new CMDBObjectSet($oSearchFilter); $iCount = $oSet->Count(); } @@ -346,7 +344,6 @@ EOF public function DisplayMenu() { - $oContext = new UserContext(); // Display the menu $oAppContext = new ApplicationContext(); $iActiveNodeId = ApplicationMenu::GetActiveNodeId(); diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index ab3b9e1f61..e511e85a57 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -110,6 +110,7 @@ class ApplicationMenu if ($bActive) { $oPage->add_ready_script("$('#accordion').accordion('activate', $iAccordion);"); + $oPage->add_ready_script("$('#accordion').accordion('option', {collapsible: true});"); // Make it auto-collapsible once it has been opened properly } } $oPage->AddToMenu('
            '); diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 9843a247c2..30cdecc5ea 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -212,12 +212,11 @@ class UILinksWidget $sHtmlValue .= "
            m_sAttCode}{$this->m_sNameSuffix}\">\n"; $oValue->Rewind(); $aForm = array(); - $oContext = new UserContext(); while($oCurrentLink = $oValue->Fetch()) { $aRow = array(); $key = $oCurrentLink->GetKey(); - $oLinkedObj = $oContext->GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote)); + $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote)); $aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs); } @@ -238,14 +237,13 @@ EOF /** * This static function is called by the Ajax Page when there is a need to fill an autocomplete combo * @param $oPage WebPage The ajax page used for the output (sent back to the browser) - * @param $oContext UserContext The context of the user (for limiting the search) * @param $sClass string The name of the class of the current object being edited * @param $sAttCode string The name of the attribute being edited * @param $sName string The partial name that was typed by the user * @param $iMaxCount integer The maximum number of items to return * @return void */ - static public function Autocomplete(WebPage $oPage, UserContext $oContext, $sClass, $sAttCode, $sName, $iMaxCount) + static public function Autocomplete(WebPage $oPage, $sClass, $sAttCode, $sName, $iMaxCount) { // #@# todo - add context information, otherwise any value will be authorized for external keys $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array() /* $aArgs */, $sName); @@ -265,7 +263,7 @@ EOF $oAttDef = $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $sLinkedClass = $oAttDef->GetLinkedClass(); $sSearchClass = self::GetTargetClass($sClass, $sAttCode); - $oFilter = $oContext->NewFilter($sSearchClass); + $oFilter = new DBObjectSearch($sSearchClass); $sSearchAttCode = MetaModel::GetNameAttributeCode($sSearchClass); $oFilter->AddCondition($sSearchAttCode, $sName, 'Begins with'); $oSet = new CMDBObjectSet($oFilter, array($sSearchAttCode => true)); @@ -346,9 +344,8 @@ EOF { $sHtml = "
            m_sAttCode}{$this->m_sNameSuffix}\">"; $sHtml .= "
            \n"; - $oContext = new UserContext(); $iWidgetIndex = self::$iWidgetIndex; - $oFilter = $oContext->NewFilter($this->m_sRemoteClass); + $oFilter = new DBObjectSearch($this->m_sRemoteClass); $oSet = new CMDBObjectSet($oFilter); $oBlock = new DisplayBlock($oFilter, 'search', false); $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true)); @@ -370,21 +367,20 @@ EOF /** * Search for objects to be linked to the current object (i.e "remote" objects) * @param WebPage $oP The page used for the output (usually an AjaxWebPage) - * @param UserContext $oContext User context to limit the search... * @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass * @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search */ - public function SearchObjectsToAdd(WebPage $oP, UserContext $oContext, $sRemoteClass = '', $aAlreadyLinkedIds = array()) + public function SearchObjectsToAdd(WebPage $oP, $sRemoteClass = '', $aAlreadyLinkedIds = array()) { if ($sRemoteClass != '') { // assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass)); - $oFilter = $oContext->NewFilter($sRemoteClass); + $oFilter = new DBObjectSearch($sRemoteClass); } else { // No remote class specified use the one defined in the linkedset - $oFilter = $oContext->NewFilter($this->m_sRemoteClass); + $oFilter = new DBObjectSearch($this->m_sRemoteClass); } if (count($aAlreadyLinkedIds) > 0) { @@ -407,7 +403,7 @@ EOF if (count($aLinkIds) >0) { // Search for the links to find to which "remote" object they are linked - $oLinkFilter = $oContext->NewFilter($this->m_sLinkedClass); + $oLinkFilter = new DBObjectSearch($this->m_sLinkedClass); $oLinkFilter->AddCondition('id', $aLinkIds, 'IN'); $oLinkSet = new CMDBObjectSet($oLinkFilter); while($oLink = $oLinkSet->Fetch()) @@ -422,12 +418,12 @@ EOF $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results } - public function DoAddObjects(WebPage $oP, UserContext $oContext, $aLinkedObjectIds = array()) + public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array()) { $aTable = array(); foreach($aLinkedObjectIds as $iObjectId) { - $oLinkedObj = $oContext->GetObject($this->m_sRemoteClass, $iObjectId); + $oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId); if (is_object($oLinkedObj)) { $aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index efd9558cef..6eac530202 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -79,11 +79,11 @@ class UILinksWizard } } - public function Display(WebPage $oP, UserContext $oContext, $aExtraParams = array()) + public function Display(WebPage $oP, $aExtraParams = array()) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); $sTargetClass = $oAttDef->GetTargetClass(); - $oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); + $oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId); $oP->set_title("iTop - ".MetaModel::GetName($this->m_sLinkedClass)." objects linked with ".MetaModel::GetName(get_class($oTargetObj)).": ".$oTargetObj->GetName()); $oP->add("
            \n"); @@ -259,7 +259,7 @@ EOF ); $oP->Add("\n"); $oP->add_ready_script("InitForm();"); - $oFilter = $oContext->NewFilter($this->m_sClass); + $oFilter = new DBObjectSearch($this->m_sClass); $oFilter->AddCondition($this->m_sLinkageAttr, $this->m_iObjectId, '='); $oSet = new DBObjectSet($oFilter); $aForm = array(); @@ -267,7 +267,7 @@ EOF { $aRow = array(); $key = $oCurrentLink->GetKey(); - $oLinkedObj = $oContext->GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode)); + $oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $oCurrentLink->Get($this->m_sLinkingAttCode)); $aForm[$key] = $this->GetFormRow($oP, $oLinkedObj, $oCurrentLink); } @@ -363,17 +363,17 @@ EOF $oP->add("\n"); } - public function DisplayAddForm(WebPage $oP, UserContext $oContext) + public function DisplayAddForm(WebPage $oP) { $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); $sTargetClass = $oAttDef->GetTargetClass(); - $oTargetObj = $oContext->GetObject($sTargetClass, $this->m_iObjectId); + $oTargetObj = MetaModel::GetObject($sTargetClass, $this->m_iObjectId); $oP->add("
            \n"); //$oP->add("
            \n"); //$oP->add("

            ".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class_Instance', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName(get_class($oTargetObj)), "".$oTargetObj->GetHyperlink()."")."

            \n"); //$oP->add("
            \n"); - $oFilter = $oContext->NewFilter($this->m_sLinkedClass); + $oFilter = new DBObjectSearch($this->m_sLinkedClass); $oSet = new CMDBObjectSet($oFilter); $oBlock = new DisplayBlock($oFilter, 'search', false); $oBlock->Display($oP, 'SearchFormToAdd', array('open' => true)); @@ -388,17 +388,17 @@ EOF $oP->add_ready_script("$('div#SearchFormToAdd form').bind('submit.uilinksWizard', SubmitHook);"); } - public function SearchObjectsToAdd(WebPage $oP, UserContext $oContext) + public function SearchObjectsToAdd(WebPage $oP) { //$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); - $oFilter = $oContext->NewFilter($this->m_sLinkedClass); + $oFilter = new DBObjectSearch($this->m_sLinkedClass); $oSet = new CMDBObjectSet($oFilter); $oBlock = new DisplayBlock($oFilter, 'list', false); $oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results } - public function DoAddObjects(WebPage $oP, UserContext $oContext, $aLinkedObjectIds = array()) + public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array()) { //$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sLinkageAttr); //$sTargetClass = $oAttDef->GetTargetClass(); @@ -406,7 +406,7 @@ EOF $aTable = array(); foreach($aLinkedObjectIds as $iObjectId) { - $oLinkedObj = $oContext->GetObject($this->m_sLinkedClass, $iObjectId); + $oLinkedObj = MetaModel::GetObject($this->m_sLinkedClass, $iObjectId); if (is_object($oLinkedObj)) { $aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids diff --git a/application/usercontext.class.inc.php b/application/usercontext.class.inc.php deleted file mode 100644 index e38195ff1d..0000000000 --- a/application/usercontext.class.inc.php +++ /dev/null @@ -1,129 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -require_once('../core/cmdbobject.class.inc.php'); -/** - * Helper class to capture a user's restrictions (access rights, profiles...) as a set of limiting conditions - * - * **** NOW OBSOLETE *** SHOULD BE REPLACED EVERYWHERE BY UserRights ***** - * - * - * - * - * Usage: - * 1) Build the user's context (from her rights, a lookup in the database, a cookie, whatever) - * $oContext = new UserContext(); - * $oContext->AddCondition('SomeClass', 'someFilter', 'SomeValue', '='); - * ... - * - * 2) Use the restrictions contained in the context when retrieving objects either when: - * getting directly an instance of an object - * $oObj = $oContext->GetObject('myClass', 'someKey'); // Instead of $oObj = MetaModel::GetObject('Klass', 'someKey'); - * or when building a new search filter - * $oFilter = $oContext->NewFilter('myClass'); // Instead of $oFilter = new CMDBSearchFilter('Klass'); - */ -class UserContext -{ - /** - * Hash array to store the restricting conditions by myClass - */ - protected $m_aConditions; - - /** - * Constructor - */ - public function __construct() - { - $this->m_aConditions = array(); - } - /** - * Create a new search filter for the given class of objects that already contains the context's restrictions - */ - public function NewFilter($sClass) - { - return UserRights::GetFilter($sClass); - /* - $oFilter = new CMDBSearchFilter($sClass); - foreach($this->m_aConditions as $sConditionClass => $aConditionList) - { - // Add to the filter all the conditions of the parent classes of this class - if ($this->IsSubclass($sConditionClass,$sClass)) - { - foreach($aConditionList as $sFilterCode => $aCondition) - { - $oFilter->AddCondition($sFilterCode, $aCondition['value'], $aCondition['operator']); - } - } - } - return $oFilter; - */ - } - /** - * Retrieve an instance of an object (if allowed by the context) - */ - public function GetObject($sClass, $sKey) - { - $oObject = null; - $oFilter = $this->NewFilter($sClass); - $oFilter->AddCondition('id', $sKey, '='); - $oSet = new CMDBObjectSet($oFilter); - if ($oSet->Count() > 0) - { - $oObject = $oSet->Fetch(); - } - return $oObject; - } - - /** - * Add a restriction to the context for a given class of objects (and all its persistent subclasses) - */ - public function AddCondition($sClass, $sFilterCode, $value, $sOperator) - { - if(!isset($this->m_aConditions[$sClass])) - { - $this->m_aConditions[$sClass] = array(); - } - $this->m_aConditions[$sClass][$sFilterCode] = array('value'=>$value, 'operator'=>$sOperator); - } - - /** - * Check if a given class is a subclass of (or same as) another one - */ - protected function IsSubclass($sParentClass, $sSubclass) - { - $bResult = false; - if ($sParentClass == $sSubclass) - { - $bResult = true; - } - else - { - $aParentList = MetaModel::EnumParentClasses($sSubclass); - $bResult = in_array($sParentClass, $aParentList); - } - return $bResult; - } -} -?> diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 7f9c60cc13..a4341a10fc 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -55,8 +55,6 @@ abstract class UserRightsAddOnAPI abstract public function Init(); // loads data (possible optimizations) - // Cf UserContext... - abstract public function GetFilter($sLogin, $sClass); // returns a filter object // Used to build select queries showing only objects visible for the given user abstract public function GetSelectFilter($sLogin, $sClass); // returns a filter object @@ -494,23 +492,6 @@ class UserRights return true; } - public static function GetFilter($sClass) - { - // #@# to cleanup ! - return new DBObjectSearch($sClass); - - if (!self::CheckLogin()) return false; - if (self::IsAdministrator()) return new DBObjectSearch($sClass); - - // this module is forbidden for non admins - if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false; - - // the rest is allowed (#@# to be improved) - if (!MetaModel::HasCategory($sClass, 'bizmodel')) return new DBObjectSearch($sClass); - - return self::$m_oAddOn->GetFilter(self::$m_oUser->GetKey(), $sClass); - } - public static function GetSelectFilter($sClass) { // Need to load some records before the login is performed (user preferences) diff --git a/pages/UI.php b/pages/UI.php index 456ad506a1..42f2cb01de 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -445,7 +445,6 @@ try require_once('../application/wizardhelper.class.inc.php'); require_once('../application/startup.inc.php'); - $oContext = new UserContext(); $oAppContext = new ApplicationContext(); $currentOrganization = utils::ReadParam('org_id', ''); $operation = utils::ReadParam('operation', ''); @@ -469,7 +468,7 @@ try { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); if ($oObj != null) { $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetName(), $sClassLabel)); @@ -540,7 +539,7 @@ try throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $oFilter = $oContext->NewFilter($sClass); + $oFilter = new DBObjectSearch($sClass); $oSet = new DBObjectSet($oFilter); if ($bSearchForm) { @@ -762,7 +761,6 @@ try $oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/jquery.blockUI.js"); - $oContext = new UserContext(); $aArgs = array_merge($oAppContext->GetAsHash(), utils::ReadParam('default', array())); // If the specified class has subclasses, ask the user an instance of which class to create @@ -860,7 +858,7 @@ try { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); if (!utils::IsTransactionValid($sTransactionId)) { $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); @@ -950,7 +948,7 @@ try } foreach($aSelectObject as $iId) { - $aObjects[] = $oContext->GetObject($sClass, $iId); + $aObjects[] = MetaModel::GetObject($sClass, $iId); } if (MetaModel::IsReadOnlyClass($sClass) || !UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) { @@ -965,7 +963,7 @@ try $sClass = utils::ReadParam('class', ''); $sClassLabel = MetaModel::GetName($sClass); $id = utils::ReadParam('id', ''); - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); if (MetaModel::IsReadOnlyClass($sClass) || !UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj))) { @@ -985,7 +983,7 @@ try } else { - $oObj = $oContext->GetObject($sClass, $iCloneId); + $oObj = MetaModel::GetObject($sClass, $iCloneId); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); if (UserRights::IsImpersonated()) @@ -1091,7 +1089,7 @@ try { throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); } - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); if ($oObj != null) { $aTransitions = $oObj->EnumTransitions(); @@ -1183,7 +1181,7 @@ EOF { throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); } - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); if ($oObj != null) { $aTransitions = $oObj->EnumTransitions(); @@ -1258,7 +1256,7 @@ EOF } require_once('../application/uilinkswizard.class.inc.php'); $oWizard = new UILinksWizard($sClass, $sLinkAttr, $id, $sTargetClass); - $oWizard->Display($oP, $oContext, array('StartWithAdd' => $bAddObjects)); + $oWizard->Display($oP, array('StartWithAdd' => $bAddObjects)); break; case 'do_modify_links': @@ -1291,7 +1289,7 @@ EOF { if ($iLinkId > 0) // Negative IDs are objects that were not even created { - $oLink = $oContext->GetObject($sClass, $iLinkId); + $oLink = MetaModel::GetObject($sClass, $iLinkId); $oLink->DBDeleteTracked($oMyChange); } } @@ -1314,7 +1312,7 @@ EOF if ($iLinkId > 0) { // This is an existing link to be modified - $oLink = $oContext->GetObject($sClass, $iLinkId); + $oLink = MetaModel::GetObject($sClass, $iLinkId); // Update all the attributes of the link foreach($aEditableFields as $sAttCode) @@ -1352,9 +1350,9 @@ EOF // Display again the details of the linked object $oAttDef = MetaModel::GetAttributeDef($sClass, $sLinkageAtt); $sTargetClass = $oAttDef->GetTargetClass(); - $oObj = $oContext->GetObject($sTargetClass, $iObjectId); + $oObj = MetaModel::GetObject($sTargetClass, $iObjectId); - $oSearch = $oContext->NewFilter(get_class($oObj)); + $oSearch = new DBObjectSearch(get_class($oObj)); $oBlock = new DisplayBlock($oSearch, 'search', false); $oBlock->Display($oP, 0); $oObj->DisplayDetails($oP); diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index ac0a08275b..f4ecf5f1d2 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -32,7 +32,6 @@ require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index 42c280c140..58594d6b86 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -173,8 +173,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed - -$oContext = new UserContext(); $sOperation = utils::ReadParam('operation', ''); switch($sOperation) diff --git a/pages/ajax.render.php b/pages/ajax.render.php index f5bee6eddc..cbd1796daf 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -38,7 +38,6 @@ LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oPage = new ajax_page(""); $oPage->no_cache(); -$oContext = new UserContext(); $operation = utils::ReadParam('operation', ''); $sFilter = stripslashes(utils::ReadParam('filter', '')); $sEncoding = utils::ReadParam('encoding', 'serialize'); @@ -54,7 +53,7 @@ switch($operation) $sLinkageAttr = utils::ReadParam('linkageAttr', '', 'get'); $iObjectId = utils::ReadParam('objectId', '', 'get'); $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); - $oLinksWizard->DisplayAddForm($oPage, $oContext); + $oLinksWizard->DisplayAddForm($oPage); break; // ui.linkswidget @@ -65,7 +64,7 @@ switch($operation) $sSuffix = utils::ReadParam('sSuffix', ''); $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix); - $oWidget->SearchObjectsToAdd($oPage, $oContext, $sRemoteClass, $aAlreadyLinked); + $oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked); break; // ui.linkswidget @@ -75,7 +74,7 @@ switch($operation) $sSuffix = utils::ReadParam('sSuffix', ''); $aLinkedObjectIds = utils::ReadParam('selectObject', array(), 'get'); $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix); - $oWidget->DoAddObjects($oPage, $oContext, $aLinkedObjectIds); + $oWidget->DoAddObjects($oPage, $aLinkedObjectIds); break; case 'wizard_helper_preview': @@ -145,7 +144,7 @@ switch($operation) case 'details': $key = utils::ReadParam('id', 0); - $oFilter = $oContext->NewFilter($sClass); + $oFilter = new DBObjectSearch($sClass); $oFilter->AddCondition('id', $key, '='); $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); $oDisplayBlock->RenderContent($oPage); @@ -153,7 +152,7 @@ switch($operation) case 'preview': $key = utils::ReadParam('id', 0); - $oFilter = $oContext->NewFilter($sClass); + $oFilter = new DBObjectSearch($sClass); $oFilter->AddCondition('id', $key, '='); $oDisplayBlock = new DisplayBlock($oFilter, 'preview', false); $oDisplayBlock->RenderContent($oPage); @@ -205,7 +204,7 @@ switch($operation) case 'modal_details': $key = utils::ReadParam('id', 0); - $oFilter = $oContext->NewFilter($sClass); + $oFilter = new DBObjectSearch($sClass); $oFilter->AddCondition('id', $key, '='); $oPage->Add("

            Object Details

            \n"); $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); @@ -220,7 +219,7 @@ switch($operation) $sOrg = utils::ReadParam('org_id', ''); $sName = utils::ReadParam('q', ''); $iMaxCount = utils::ReadParam('max', 30); - UILinksWidget::Autocomplete($oPage, $oContext, $sClass, $sAttCode, $sName, $iMaxCount); + UILinksWidget::Autocomplete($oPage, $sClass, $sAttCode, $sName, $iMaxCount); */ break; @@ -268,7 +267,7 @@ switch($operation) $sName = utils::ReadParam('q', ''); $iMaxCount = utils::ReadParam('max', 30); $iCount = 0; - $oFilter = $oContext->NewFilter($sClass); + $oFilter = new DBObjectSearch($sClass); $oFilter->AddCondition($sAttCode, $sName, 'Begins with'); //$oFilter->AddCondition('org_id', $sOrg, '='); $oSet = new CMDBObjectSet($oFilter, array($sAttCode => true)); @@ -300,7 +299,7 @@ switch($operation) $sField = utils::ReadParam('field', ''); if (!empty($sClass) && !empty($id) && !empty($sField)) { - DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'inline'); + DownloadDocument($oPage, $sClass, $id, $sField, 'inline'); } break; @@ -309,7 +308,7 @@ switch($operation) $sField = utils::ReadParam('field', ''); if (!empty($sClass) && !empty($id) && !empty($sField)) { - DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'attachement'); + DownloadDocument($oPage, $sClass, $id, $sField, 'attachement'); } break; @@ -317,7 +316,7 @@ switch($operation) $sClass = utils::ReadParam('className', ''); $sRootClass = utils::ReadParam('baseClass', ''); $currentId = utils::ReadParam('currentId', ''); - $oFilter = $oContext->NewFilter($sClass); + $oFilter = new DBObjectSearch($sClass); $oSet = new CMDBObjectSet($oFilter); $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass)); $oPage->add($sHtml); @@ -343,18 +342,17 @@ $oPage->output(); * Downloads a document to the browser, either as 'inline' or 'attachment' * * @param WebPage $oPage The web page for the output - * @param UserContext $oContext The current User/security context to retreive the objects * @param string $sClass Class name of the object * @param mixed $id Identifier of the object * @param string $sAttCode Name of the attribute containing the document to download * @param string $sContentDisposition Either 'inline' or 'attachment' * @return none */ -function DownloadDocument(WebPage $oPage, UserContext $oContext, $sClass, $id, $sAttCode, $sContentDisposition = 'attachement') +function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachement') { try { - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); if (is_object($oObj)) { $oDocument = $oObj->Get($sAttCode); diff --git a/pages/audit.php b/pages/audit.php index d3c282fee3..2ba598f924 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -38,9 +38,7 @@ $oP = new iTopWebPage(Dict::S('UI:Audit:Title'), $currentOrganization); function GetRuleResultSet($iRuleId, $oDefinitionFilter) { - $oContext = new UserContext(); - - $oRule = $oContext->GetObject('AuditRule', $iRuleId); + $oRule = MetaModel::GetObject('AuditRule', $iRuleId); $sOql = $oRule->Get('query'); $oRuleFilter = DBObjectSearch::FromOQL($sOql); if ($oRule->Get('valid_flag') == 'false') @@ -87,8 +85,7 @@ switch($operation) $iCategory = utils::ReadParam('category', ''); $iRuleIndex = utils::ReadParam('rule', 0); - $oContext = new UserContext(); - $oAuditCategory = $oContext->GetObject('AuditCategory', $iCategory); + $oAuditCategory = MetaModel::GetObject('AuditCategory', $iCategory); $oDefinitionFilter = DBObjectSearch::FromOQL($oAuditCategory->Get('definition_set')); if (!empty($currentOrganization)) { @@ -96,7 +93,7 @@ switch($operation) } $oDefinitionSet = new CMDBObjectSet($oDefinitionFilter); $oErrorObjectSet = GetRuleResultSet($iRuleIndex, $oDefinitionFilter); - $oAuditRule = $oContext->GetObject('AuditRule', $iRuleIndex); + $oAuditRule = MetaModel::GetObject('AuditRule', $iRuleIndex); $oP->add(''); $oP->p('[Back to audit results]'); $sBlockId = 'audit_errors'; diff --git a/pages/csvimport.php b/pages/csvimport.php index accac719af..881dcaede5 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -33,7 +33,6 @@ require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $currentOrganization = utils::ReadParam('org_id', 1); $iStep = utils::ReadParam('step', 1); @@ -208,11 +207,10 @@ function GuessParameters($sCSVData) /** * Process the CSV data, for real or as a simulation * @param WebPage $oPage The page used to display the wizard - * @param UserContext $oContext The current user context * @param bool $bSimulate Whether or not to simulate the data load * @return array The CSV lines in error that were rejected from the load (with the header line - if any) or null */ -function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true) +function ProcessCSVData(WebPage $oPage, $bSimulate = true) { $aResult = array(); $sCSVData = utils::ReadParam('csvdata', ''); @@ -362,7 +360,7 @@ function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true case 'RowStatus_NoChange': $iUnchanged++; $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = $oContext->GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); $sUrl = $oObj->GetHyperlink(); $sStatus = ''; $sCSSRowClass = 'row_unchanged'; @@ -371,7 +369,7 @@ function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true case 'RowStatus_Modify': $iModified++; $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = $oContext->GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); $sUrl = $oObj->GetHyperlink(); $sStatus = ''; $sCSSRowClass = 'row_modified'; @@ -389,7 +387,7 @@ function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true else { $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = $oContext->GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); $sUrl = $oObj->GetHyperlink(); $sMessage = 'Object created'; } @@ -541,13 +539,12 @@ EOF /** * Perform the actual load of the CSV data and display the results * @param WebPage $oPage The web page to display the wizard - * @param UserContext $oContext Current user's context * @return void */ -function LoadData(WebPage $oPage, UserContext $oContext) +function LoadData(WebPage $oPage) { $oPage->add('

            '.Dict::S('UI:Title:CSVImportStep5').'

            '); - $aResult = ProcessCSVData($oPage, $oContext, false /* simulate = false */); + $aResult = ProcessCSVData($oPage, false /* simulate = false */); if (is_array($aResult)) { $oPage->StartCollapsibleSection(Dict::S('UI:CSVImport:LinesNotImported'), false); @@ -562,13 +559,12 @@ function LoadData(WebPage $oPage, UserContext $oContext) /** * Simulate the load of the CSV data and display the results * @param WebPage $oPage The web page to display the wizard - * @param UserContext $oContext Current user's context * @return void */ -function Preview(WebPage $oPage, UserContext $oContext) +function Preview(WebPage $oPage) { $oPage->add('

            '.Dict::S('UI:Title:CSVImportStep4').'

            '); - ProcessCSVData($oPage, $oContext, true /* simulate */); + ProcessCSVData($oPage, true /* simulate */); } /** @@ -1049,11 +1045,11 @@ EOF switch($iStep) { case 5: - LoadData($oPage, $oContext); + LoadData($oPage); break; case 4: - Preview($oPage, $oContext); + Preview($oPage); break; case 3: diff --git a/pages/logoff.php b/pages/logoff.php index b77b36b746..5f8c2ccd7b 100644 --- a/pages/logoff.php +++ b/pages/logoff.php @@ -19,7 +19,6 @@ require_once('../application/itopwebpage.class.inc.php'); require_once('../application/wizardhelper.class.inc.php'); require_once('../application/startup.inc.php'); -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $currentOrganization = utils::ReadParam('org_id', ''); $operation = utils::ReadParam('operation', ''); diff --git a/pages/navigator.php b/pages/navigator.php index ec912999fb..0d543a9db7 100755 --- a/pages/navigator.php +++ b/pages/navigator.php @@ -26,7 +26,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed $sOperation = utils::ReadParam('operation', 'menu'); -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); diff --git a/pages/run_query.php b/pages/run_query.php index fd7c5cf87a..d1ef26029d 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -91,7 +91,6 @@ function ShowExamples($oP, $sExpression) } $sOperation = utils::ReadParam('operation', 'menu'); -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); diff --git a/pages/schema.php b/pages/schema.php index 5ff26f348b..1914bd31b9 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -491,7 +491,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index ad2043fba8..5d53f74aa9 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -356,8 +356,6 @@ class TestUserRightsMatrixItop extends TestUserRights echo "

            User: ".UserRights::GetUser()."

            \n"; echo "

            On behalf of...".UserRights::GetRealUser()."

            \n"; - UserRights::GetFilter('bizOrganization'); // returns a filter object - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); echo "

            IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

            \n"; echo "

            IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

            \n"; diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php index 22db2525a5..379b8d7a09 100644 --- a/pages/usermanagement_classproj.php +++ b/pages/usermanagement_classproj.php @@ -107,7 +107,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php index 24cda4f709..2b018b8cb0 100644 --- a/pages/usermanagement_profileproj.php +++ b/pages/usermanagement_profileproj.php @@ -118,7 +118,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php index 6ba64e54de..9c5f6ff3dc 100644 --- a/pages/usermanagement_userstatus.php +++ b/pages/usermanagement_userstatus.php @@ -270,7 +270,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed // Display the menu on the left -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', 1); diff --git a/pages/xml.navigator.php b/pages/xml.navigator.php index 66188dc9c8..04c398a727 100755 --- a/pages/xml.navigator.php +++ b/pages/xml.navigator.php @@ -60,7 +60,6 @@ function AddNodeDetails(&$oNode, $oObj) */ function GetRelatedObjects(DBObject $oObj, $sRelationName, &$oLinks, &$oXmlDoc, &$oXmlNode) { - $oContext = new UserContext(); $aResults = array(); $oObj->GetRelatedObjects($sRelationName, 1 /* iMaxDepth */, $aResults); static $iDepth = 0; @@ -115,7 +114,6 @@ LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oPage = new ajax_page(""); $oPage->no_cache(); -$oContext = new UserContext(); $sClass = utils::ReadParam('class', 'Contact'); $id = utils::ReadParam('id', 1); $sRelation = utils::ReadParam('relation', 'impacts'); @@ -129,7 +127,7 @@ if (!in_array($sRelation, $aValidRelations)) if ($id != 0) { - $oObj = $oContext->GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); // Build the root XML part $oXmlDoc = new DOMDocument( '1.0', 'UTF-8' ); $oXmlRoot = $oXmlDoc->CreateElement('root'); diff --git a/webservices/export.php b/webservices/export.php index 94aa77ef33..8469754d93 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -34,7 +34,6 @@ require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed $sOperation = utils::ReadParam('operation', 'menu'); -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); $currentOrganization = utils::ReadParam('org_id', ''); diff --git a/webservices/import.php b/webservices/import.php index b78d2a92c4..a151a424e7 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -53,7 +53,6 @@ class WebServiceException extends Exception LoginWebPage::DoLogin(); // Check user rights and prompt if needed -$oContext = new UserContext(); $oAppContext = new ApplicationContext(); //$iActiveNodeId = utils::ReadParam('menu', -1); //$currentOrganization = utils::ReadParam('org_id', ''); From a7799e524afa42b8a1e328cec5921a32f1be6327 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 23 Aug 2010 12:54:27 +0000 Subject: [PATCH 601/970] - Objects filtering (read) is now performed by the core directly, no need to check further which objects in a set are read-able. Only those that can be read are returned. SVN:trunk[684] --- application/cmdbabstract.class.inc.php | 6 +++--- application/displayblock.class.inc.php | 25 ++++++++----------------- pages/UI.php | 8 +++----- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 7c3a7fc21b..a0cbf37d56 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -529,7 +529,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aAuthorizedClasses = array(); foreach($aClasses as $sAlias => $sClassName) { - if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) && + if ( (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS)) && ( (count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases))) ) { $aAuthorizedClasses[$sAlias] = $sClassName; @@ -629,7 +629,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aAuthorizedClasses = array(); foreach($aClasses as $sAlias => $sClassName) { - if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS)) { $aAuthorizedClasses[$sAlias] = $sClassName; } @@ -687,7 +687,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aAuthorizedClasses = array(); foreach($aClasses as $sAlias => $sClassName) { - if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES) + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS)) { $aAuthorizedClasses[$sAlias] = $sClassName; } diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index aa68ed1a91..0d35c4c2fe 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -436,7 +436,7 @@ class DisplayBlock // Check the classes that can be read (i.e authorized) by this user... foreach($aClasses as $sAlias => $sClassName) { - if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $this->m_oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS)) { $aAuthorizedClasses[$sAlias] = $sClassName; } @@ -536,32 +536,23 @@ class DisplayBlock break; case 'details': - if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + while($oObj = $this->m_oSet->Fetch()) { - while($oObj = $this->m_oSet->Fetch()) - { - $sHtml .= $oObj->GetDetails($oPage); // Still used ??? - } + $sHtml .= $oObj->GetDetails($oPage); // Still used ??? } break; case 'bare_details': - if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) + while($oObj = $this->m_oSet->Fetch()) { - while($oObj = $this->m_oSet->Fetch()) - { - $sHtml .= $oObj->GetBareProperties($oPage); - } + $sHtml .= $oObj->GetBareProperties($oPage); } break; case 'csv': - if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) - { - $sHtml .= "\n"; - } + $sHtml .= "\n"; break; case 'modify': diff --git a/pages/UI.php b/pages/UI.php index 42f2cb01de..9e23ce387c 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -676,8 +676,7 @@ try } $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); - $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); - if( ($oObj != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) + if( ($oObj != null) && $bIsModifiedAllowed ) { $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->add("
            \n"); @@ -707,14 +706,13 @@ try $oSearch = new DBObjectSearch($sClass); $oSearch->AddCondition('id', $id, '='); $oSet = new CMDBObjectSet($oSearch); - if ($oSet->Count() > 0) + if ($oSet->Count() > 0) // Set is empty if not allowed to read this object { $oObjToClone = $oSet->Fetch(); } $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); - $bIsReadAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_YES); - if( ($oObjToClone != null) && ($bIsModifiedAllowed) && ($bIsReadAllowed)) + if( ($oObjToClone != null) && ($bIsModifiedAllowed)) { $oP->add_linked_script("../js/json.js"); $oP->add_linked_script("../js/forms-json-utils.js"); From 80439759b75ea94cda7568f99ad339a383b11392 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 23 Aug 2010 13:02:09 +0000 Subject: [PATCH 602/970] Finalized module userrightsprofile SVN:trunk[685] --- .../userrightsprofile.class.inc.php | 19 ++++++++++++++----- core/valuesetdef.class.inc.php | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index ad0895fd71..a0961b5941 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -350,8 +350,16 @@ class URP_ProfileProjection extends UserRightsBaseClass MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'profileid')); // Criteria of the advanced search form } + protected $m_aUserProjections; // cache + public function ProjectUser(User $oUser) { + if (is_array($this->m_aUserProjections)) + { + // Hit! + return $this->m_aUserProjections; + } + $sExpr = $this->Get('value'); if ($sExpr == '') { @@ -371,7 +379,7 @@ class URP_ProfileProjection extends UserRightsBaseClass $aRes = null; } elseif (strtolower(substr($sExpr, 0, 6)) == 'select') - { + { $sColumn = $this->Get('attribute'); // SELECT... $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); @@ -382,6 +390,7 @@ class URP_ProfileProjection extends UserRightsBaseClass // Constant value(s) $aRes = explode(';', trim($sExpr)); } + $this->m_aUserProjections = $aRes; return $aRes; } } @@ -1093,7 +1102,7 @@ exit; foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) { // user projection to be cached on a given page ! - if (!array_key_exists($iDimension, $this->m_aProPros[$iProfile])) + if (!isset($this->m_aProPros[$iProfile][$iDimension])) { // No projection for a given profile: default to 'any' return null; @@ -1105,7 +1114,7 @@ exit; // No projection for a given profile: default to 'any' return null; } - $aRes = array_merge($aRes, $aUserProjection); + $aRes = array_unique(array_merge($aRes, $aUserProjection)); } } return $aRes; @@ -1122,7 +1131,7 @@ exit; $iPKey = $oObject->GetKey(); $iDimension = $oDimension->GetKey(); - if (array_key_exists($iDimension, $this->m_aClassProjs[$sClass])) + if (isset($this->m_aClassProjs[$sClass][$iDimension])) { $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); } @@ -1144,7 +1153,7 @@ exit; else { // user projection to be cached on a given page ! - if (array_key_exists($iDimension, $this->m_aProPros[$iProfile])) + if (isset($this->m_aProPros[$iProfile][$iDimension])) { $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); } diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 0b0fade6c7..0797d185eb 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -126,7 +126,7 @@ class ValueSetObjects extends ValueSetDefinition } else { - $this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($this->m_sValueAttCode); + $this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode); } } return true; From 95b27bfcf4174dfcb7b66b1df54af29d6513a2ef Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 23 Aug 2010 13:03:18 +0000 Subject: [PATCH 603/970] Reviewed benchmark tool (split creation of structure / CIs / Tickets) SVN:trunk[686] --- core/cmdbobject.class.inc.php | 2 +- core/dbobject.class.php | 2 +- setup/benchmark.php | 1105 ++++++++++++++------------------- 3 files changed, 474 insertions(+), 635 deletions(-) diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index c99c260f8d..3a8de347c1 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -338,7 +338,7 @@ abstract class CMDBObject extends DBObject $aChanges = $this->ListChanges(); if (count($aChanges) == 0) { - throw new CoreWarning("Attempting to update an unchanged object"); + //throw new CoreWarning("Attempting to update an unchanged object"); return; } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 9f3a80b200..33438af1a3 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -948,7 +948,7 @@ abstract class DBObject $aChanges = $this->ListChanges(); if (count($aChanges) == 0) { - throw new CoreWarning("Attempting to update an unchanged object"); + //throw new CoreWarning("Attempting to update an unchanged object"); return; } $bHasANewExternalKeyValue = false; diff --git a/setup/benchmark.php b/setup/benchmark.php index 902a450722..de8eaa5568 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -31,7 +31,7 @@ require_once('../application/loginwebpage.class.inc.php'); require_once('../application/utils.inc.php'); require_once('./setuppage.class.inc.php'); -ini_set('memory_limit', '2048M'); +//ini_set('memory_limit', '2048M'); class BenchmarkDataCreation { @@ -44,12 +44,32 @@ class BenchmarkDataCreation var $m_aStatsByClass = array(); - public function __construct($iPlannedCIs = 0, $iPlannedContacts = 0, $iPlannedContracts = 0) + var $m_oChange; + public function __construct() + { + $this->m_oChange = MetaModel::NewObject("CMDBChange"); + $this->m_oChange->Set("date", time()); + $this->m_oChange->Set("userinfo", "Benchmark setup"); + $iChangeId = $this->m_oChange->DBInsertNoReload(); + } + + public function PlanStructure($iPlannedContacts, $iPlannedContracts) { $this->m_aRequested = array( - 'CIs' => $iPlannedCIs, + 'plannedcontacts' => $iPlannedContacts, + 'plannedcontracts' => $iPlannedContracts, + ); + $this->m_aPlanned = array( 'Contacts' => $iPlannedContacts, 'Contracts' => $iPlannedContracts, + 'Documents' => $iPlannedContracts * 2, + ); + } + + public function PlanCis($iPlannedCIs) + { + $this->m_aRequested = array( + 'plannedcis' => $iPlannedCIs, ); $this->m_iIfByServer = 2; @@ -57,7 +77,6 @@ class BenchmarkDataCreation $iServers = ceil($iPlannedCIs * 9 / 10); $iNWDevices = ceil($iPlannedCIs / 10); - $iBigTicketCIs = ceil($iPlannedCIs / 10); $iInterfaces = $iServers * $this->m_iIfByServer + $iNWDevices * $this->m_iIfByNWDevice; $iApplications = $iServers * 5; $iSolutions = ceil($iApplications / 2); @@ -66,33 +85,57 @@ class BenchmarkDataCreation $this->m_aPlanned = array( 'Network devices' => $iNWDevices, 'Servers' => $iServers, - 'Big ticket: CIs' => $iBigTicketCIs, 'Interfaces' => $iInterfaces, 'Application SW' => 2, 'Applications' => $iApplications, 'Solutions' => $iSolutions, 'Processes' => $iProcesses, - - 'Contacts' => $iPlannedContacts, - 'Contracts' => $iPlannedContracts, - 'Incidents' => 2 * 12 * $iPlannedCIs, - 'ServiceCalls' => 1 * 12 * $iPlannedContacts, - 'Changes' => 1 * 12 * $iPlannedCIs, - 'Documents' => 12 * $iPlannedContracts + $iPlannedCIs, ); } - public function GetPlans() + public function PlanTickets($iPlannedTickets, $iBigTicketCis) { - return $this->m_aPlanned; + $this->m_aRequested = array( + 'plannedtickets' => $iPlannedTickets, + 'plannedbigticketcis' => $iBigTicketCis, + ); + + $this->m_aPlanned = array( + 'Incidents' => ceil($iPlannedTickets / 2), + 'Changes' => ceil($iPlannedTickets / 2), + 'Big ticket: CIs' => $iBigTicketCis, + ); } - public function GetRequestInfo() + public function ShowPlans($oP) { - return $this->m_aRequested; + $oP->add("

            Planned creations

            \n"); + $aPlanned = $this->m_aPlanned; + $aForm = array(); + foreach ($aPlanned as $sKey => $iCount) + { + $aForm[] = array( + 'label' => $sKey, + 'input' => $iCount, + ); + } + $oP->form($aForm); + } + + public function ShowForm($oP, $sNextOperation) + { + $aRequested = $this->m_aRequested; + $oP->add("\n"); + $oP->add("\n"); + foreach($this->m_aRequested as $sName => $sValue) + { + $oP->add("\n"); + } + $oP->add("\n"); + $oP->add("\n"); } - protected function CreateObject($sClass, $aData, $oChange, $sClassDesc = '') + protected function CreateObject($sClass, $aData, $sClassDesc = '') { $mu_t1 = MyHelpers::getmicrotime(); @@ -102,7 +145,7 @@ class BenchmarkDataCreation $oMyObject->Set($sProp, $value); } - $iId = $oMyObject->DBInsertTrackedNoReload($oChange); + $iId = $oMyObject->DBInsertTrackedNoReload($this->m_oChange); $sClassId = "$sClass ($sClassDesc)"; $this->m_aCreatedByDesc[$sClassId][] = $iId; @@ -114,10 +157,33 @@ class BenchmarkDataCreation return $iId; } + static $m_aClassIdCache = array(); + protected function GetClassIds($sClass) + { + if (!isset(self::$m_aClassIdCache[$sClass])) + { + // Load the cache now + self::$m_aClassIdCache[$sClass] = array(); + + $oSet = new DBObjectSet(new DBObjectSearch($sClass)); + while($oObj = $oSet->Fetch()) + { + self::$m_aClassIdCache[$sClass][] = $oObj->GetKey(); + } + } + return self::$m_aClassIdCache[$sClass]; + } + protected function RandomId($sClass, $sClassDesc = '') { $sClassId = "$sClass ($sClassDesc)"; - return $this->m_aCreatedByDesc[$sClassId][array_rand($this->m_aCreatedByDesc[$sClassId])]; + if (isset($this->m_aCreatedByDesc[$sClassId])) + { + return $this->m_aCreatedByDesc[$sClassId][array_rand($this->m_aCreatedByDesc[$sClassId])]; + } + + $aIds = self::GetClassIds($sClass); + return $aIds[array_rand($aIds)]; } static protected function FindId($sClass) @@ -144,25 +210,46 @@ class BenchmarkDataCreation return $oObj->GetKey(); } - protected function MakeFeedback($oP, $sClass) + protected function my_array_rand($aData, $iCount) { - $iSample = reset($this->m_aCreatedByClass[$sClass]); - $sSample = "sample"; - - $iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3); - $fDurationMin = number_format(min($this->m_aStatsByClass[$sClass]), 3); - $fDurationMax = number_format(max($this->m_aStatsByClass[$sClass]), 3); - $fDurationAverage = number_format(array_sum($this->m_aStatsByClass[$sClass]) / count($this->m_aStatsByClass[$sClass]), 3); - - $oP->add("
              "); - $oP->add("
            • "); - $oP->add("$sClass: ".count($this->m_aStatsByClass[$sClass])." - $sSample
              "); - $oP->add("Duration: $fDurationMin => $fDurationMax; Avg:$fDurationAverage; Total: $iDuration"); - $oP->add("
            • "); - $oP->add("
            "); + if ($iCount == 0) + { + return array(); + } + elseif ($iCount == 1) + { + $aSample = array(array_rand($aData)); + } + elseif ($iCount <= count($aData)) + { + $aSample = array_rand($aData, $iCount); + } + else + { + $aSample = array_merge(array_rand($aData, count($aData)), self::my_array_rand($aData, $iCount - count($aData))); + } + return $aSample; } - public function GoProjectionsOrganization(WebPage $oP, $oChange) + protected function CreateLinks($iFrom, $iCount, $sLinkClass, $sAttCodeFrom, $sAttCodeTo) + { + $oAttTo = MetaModel::GetAttributeDef($sLinkClass, $sAttCodeTo); + $sToClass = $oAttTo->GetTargetClass(); + + $aTargets = self::GetClassIds($sToClass); + $aSample = self::my_array_rand($aTargets, $iCount); + + for($iLinked = 0 ; $iLinked < $iCount ; $iLinked++) + { + $aData = array( + $sAttCodeFrom => $iFrom, + $sAttCodeTo => $aSample[$iLinked], + ); + $this->CreateObject($sLinkClass, $aData); + } + } + + public function CreateStructure($oP) { $aClasses = MetaModel::GetClasses(); $aActions = array('Read', 'Bulk Read', 'Delete', 'Bulk Delete', 'Modify', 'Bulk Modify'); @@ -171,21 +258,16 @@ class BenchmarkDataCreation //////////////////////////////////////// // Dimension: Organization // - $aData = array( - 'name' => 'organization', - 'description' => '', - 'type' => 'Organization', - ); - $iDimLocation = $this->CreateObject('URP_Dimensions', $aData, $oChange); + $iDimension = self::FindId('URP_Dimensions'); //////////////////////////////////////// - // New specific profile, given access to everything + // New specific profile, giving access to everything // $aData = array( - 'name' => 'data guru', - 'description' => 'could do anything, because everything is granted', + 'name' => 'Data guru', + 'description' => 'Could do anything, because everything is granted', ); - $iGuruProfile = $this->CreateObject('URP_Profiles', $aData, $oChange); + $iGuruProfile = $this->CreateObject('URP_Profiles', $aData); foreach($aClasses as $sClass) { foreach($aActions as $sAction) @@ -196,16 +278,16 @@ class BenchmarkDataCreation 'permission' => 'yes', 'action' => $sAction, ); - $this->CreateObject('URP_ActionGrant', $aData, $oChange); + $this->CreateObject('URP_ActionGrant', $aData); } } $aData = array( - 'dimensionid' => $iDimLocation, + 'dimensionid' => $iDimension, 'profileid' => $iGuruProfile, 'value' => '', - 'attribute' => 'org_id', + 'attribute' => '', ); - $this->CreateObject('URP_ProfileProjection', $aData, $oChange); + $this->CreateObject('URP_ProfileProjection', $aData); // User login with super access rights // @@ -216,26 +298,26 @@ class BenchmarkDataCreation 'name' => 'Deus', 'email' => '', ); - $iPerson = $this->CreateObject('Person', $aData, $oChange); + $iPerson = $this->CreateObject('Person', $aData); $aData = array( 'contactid' => $iPerson, 'login' => 'guru', 'password' => 'guru', 'language' => 'EN US', ); - $iLogin = $this->CreateObject('UserLocal', $aData, $oChange); + $iLogin = $this->CreateObject('UserLocal', $aData); - // Assign profiles to the new login + // Assign the guru profile to the new login // $aData = array( 'userid' => $iLogin, 'profileid' => $iGuruProfile, 'reason' => 'he is the one', ); - $this->CreateObject('URP_UserProfile', $aData, $oChange); + $this->CreateObject('URP_UserProfile', $aData); //////////////////////////////////////// - // User login having all profiles, but seeing only his organization + // User login having all std profiles // $aData = array( 'org_id' => self::FindId('Organization'), @@ -244,14 +326,14 @@ class BenchmarkDataCreation 'name' => 'Foo', 'email' => '', ); - $iPerson = $this->CreateObject('Person', $aData, $oChange); + $iPerson = $this->CreateObject('Person', $aData); $aData = array( 'contactid' => $iPerson, 'login' => 'foo', 'password' => 'foo', 'language' => 'EN US', ); - $iLogin = $this->CreateObject('UserLocal', $aData, $oChange); + $iLogin = $this->CreateObject('UserLocal', $aData); // Assign profiles to the new login // @@ -262,112 +344,9 @@ class BenchmarkDataCreation 'profileid' => $iProfileId, 'reason' => '', ); - $this->CreateObject('URP_UserProfile', $aData, $oChange); + $this->CreateObject('URP_UserProfile', $aData); } - // Project classes - // - $aMyClassesToProject = array(); - foreach($aClasses as $sClass) - { - if (MetaModel::IsValidAttCode($sClass, 'org_id')) - { - $aMyClassesToProject[$sClass] = 'org_id'; - } - } - foreach($aMyClassesToProject as $sClass => $sAttCode) - { - $aData = array( - 'dimensionid' => $iDimLocation, - 'class' => $sClass, - 'value' => '', - 'attribute' => $sAttCode, - ); - $this->CreateObject('URP_ClassProjection', $aData, $oChange); - } - - // Project profiles - // - foreach($aStdProfiles as $iProfileId) - { - $aData = array( - 'dimensionid' => $iDimLocation, - 'profileid' => $iProfileId, - 'value' => 'SELECT Person WHERE id = :user->contactid', - 'attribute' => 'org_id', - ); - $this->CreateObject('URP_ProfileProjection', $aData, $oChange); - } - - $oP->p('Created projections (Cf. login "foo", pwd "foo")'); - $oP->p('* foo can do configuration management for a given customer'); - $oP->p('* guru can do everything'); - } - - // For testing purposes -Romain - public function GoProjectionsLocation(WebPage $oP, $oChange) - { - // User login - // - $aData = array( - 'contactid' => self::FindId('Person'), - 'login' => 'foo', - 'password' => 'foo', - 'language' => 'EN US', - ); - $iLogin = $this->CreateObject('UserLocal', $aData, $oChange); - - // Assign profiles to the new login - // - $aData = array( - 'userid' => $iLogin, - 'profileid' => self::FindIdFromOQL("SELECT URP_Profiles WHERE name LIKE 'Configuration Manager'"), - 'reason' => '', - ); - $this->CreateObject('URP_UserProfile', $aData, $oChange); - - // Dimension - // - $aData = array( - 'name' => 'location', - 'description' => '', - 'type' => 'Location', - ); - $iDimLocation = $this->CreateObject('URP_Dimensions', $aData, $oChange); - - // Project classes - // - $aMyClassesToProject = array('NetworkDevice', 'Server'); - foreach($aMyClassesToProject as $sClass) - { - $aData = array( - 'dimensionid' => $iDimLocation, - 'class' => $sClass, - 'value' => '', - 'attribute' => 'location_name', - ); - $this->CreateObject('URP_ClassProjection', $aData, $oChange); - } - - // Project profiles - // - $aProfilesToProject = array(1, 2, 3, 4, 5, 6, 7, 8, 9); - foreach($aProfilesToProject as $iProfileId) - { - $aData = array( - 'dimensionid' => $iDimLocation, - 'profileid' => $iProfileId, - 'value' => 'Grenoble', - 'attribute' => '', - ); - $this->CreateObject('URP_ProfileProjection', $aData, $oChange); - } - - $oP->p('Created projections (Cf. login "foo", pwd "foo")'); - } - - public function GoVolume(WebPage $oP, $oChange) - { ///////////////////////// // // Organizations @@ -375,8 +354,7 @@ class BenchmarkDataCreation $aData = array( 'name' => 'Benchmark', ); - $iOrg = $this->CreateObject('Organization', $aData, $oChange); - $this->MakeFeedback($oP, 'Organization'); + $iOrg = $this->CreateObject('Organization', $aData); ///////////////////////// // @@ -386,8 +364,7 @@ class BenchmarkDataCreation 'org_id' => $iOrg, 'name' => 'Rio de Janeiro', ); - $iLoc = $this->CreateObject('Location', $aData, $oChange); - $this->MakeFeedback($oP, 'Location'); + $iLoc = $this->CreateObject('Location', $aData); ///////////////////////// // @@ -399,8 +376,7 @@ class BenchmarkDataCreation 'name' => 'Fluminense', 'email' => 'fluminense@nowhere.fr', ); - $iTeam = $this->CreateObject('Team', $aData, $oChange); - $this->MakeFeedback($oP, 'Team'); + $iTeam = $this->CreateObject('Team', $aData); ///////////////////////// // @@ -415,7 +391,7 @@ class BenchmarkDataCreation 'name' => 'Ningem #'.$i, 'email' => 'foo'.$i.'@nowhere.fr', ); - $iPerson = $this->CreateObject('Person', $aData, $oChange); + $iPerson = $this->CreateObject('Person', $aData); // Contract/Infra // @@ -423,19 +399,18 @@ class BenchmarkDataCreation 'contact_id' => $iPerson, 'team_id' => $this->RandomId('Team'), ); - $this->CreateObject('lnkTeamToContact', $aData, $oChange); + $this->CreateObject('lnkTeamToContact', $aData); } - $this->MakeFeedback($oP, 'Person'); ///////////////////////// // // Services // $aData = array( + 'org_id' => $iOrg, 'name' => 'My Service', ); - $iOrg = $this->CreateObject('Service', $aData, $oChange); - $this->MakeFeedback($oP, 'Service'); + $iOrg = $this->CreateObject('Service', $aData); ///////////////////////// // @@ -444,8 +419,7 @@ class BenchmarkDataCreation $aData = array( 'name' => 'My subcategory', ); - $iOrg = $this->CreateObject('ServiceSubcategory', $aData, $oChange); - $this->MakeFeedback($oP, 'ServiceSubcategory'); + $iOrg = $this->CreateObject('ServiceSubcategory', $aData); ///////////////////////// // @@ -462,7 +436,7 @@ class BenchmarkDataCreation 'end_date' => '2019-08-01', 'support_team_id' => $this->RandomId('Team'), ); - $iContract = $this->CreateObject('CustomerContract', $aData, $oChange); + $iContract = $this->CreateObject('CustomerContract', $aData); // Contract/Contact (10% of contacts) // @@ -474,357 +448,9 @@ class BenchmarkDataCreation 'contract_id' => $iContract, 'role' => 'role '.$iLinked, ); - $this->CreateObject('lnkContractToContact', $aData, $oChange); + $this->CreateObject('lnkContractToContact', $aData); } } - $this->MakeFeedback($oP, 'CustomerContract'); - - ///////////////////////// - // - // Servers - // - for($i = 0 ; $i < $this->m_aPlanned['Servers'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'server'.$i, - 'status' => 'production', - ); - $iServer = $this->CreateObject('Server', $aData, $oChange); - - // Contract/Infra - // - $iContractCount = 1; - for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) - { - $aData = array( - 'contract_id' => $this->RandomId('CustomerContract'), - 'ci_id' => $iServer, - ); - $this->CreateObject('lnkContractToCI', $aData, $oChange); - } - - // Interfaces - // - for($iLinked = 0 ; $iLinked < $this->m_iIfByServer ; $iLinked++) - { - $aData = array( - 'name' => "eth$iLinked", - 'status' => 'implementation', - 'org_id' => $iOrg, - 'device_id' => $iServer, - 'status' => 'production', - ); - $this->CreateObject('NetworkInterface', $aData, $oChange, 'server if'); - } - } - $this->MakeFeedback($oP, 'Server'); - - ///////////////////////// - // - // Network devices - // - for($i = 0 ; $i < $this->m_aPlanned['Network devices'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'equipment #'.$i, - 'status' => 'production', - ); - $iNWDevice = $this->CreateObject('NetworkDevice', $aData, $oChange); - - // Contract/Infra - // - $iContractCount = 1; - for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) - { - $aData = array( - 'contract_id' => $this->RandomId('CustomerContract'), - 'ci_id' => $iNWDevice, - ); - $this->CreateObject('lnkContractToCI', $aData, $oChange); - } - - // Interfaces - // - for($iLinked = 0 ; $iLinked < $this->m_iIfByNWDevice ; $iLinked++) - { - $aData = array( - 'name' => "eth$iLinked", - 'status' => 'implementation', - 'org_id' => $iOrg, - 'device_id' => $iNWDevice, - 'connected_if' => $this->RandomId('NetworkInterface', 'server if'), - 'status' => 'production', - ); - $this->CreateObject('NetworkInterface', $aData, $oChange, 'equipment if'); - } - } - $this->MakeFeedback($oP, 'NetworkDevice'); - $this->MakeFeedback($oP, 'NetworkInterface'); - - ///////////////////////// - // - // Application Software - // - for($i = 0 ; $i < $this->m_aPlanned['Application SW'] ; $i++) - { - $aData = array( - 'name' => 'Software #'.$i, - ); - $iNWDevice = $this->CreateObject('Application', $aData, $oChange); - } - $this->MakeFeedback($oP, 'Application'); - - ///////////////////////// - // - // Applications - // - for($i = 0 ; $i < $this->m_aPlanned['Applications'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'device_id' => $this->RandomId('Server'), - 'software_id' => $this->RandomId('Application'), - 'name' => 'Application #'.$i, - 'status' => 'production', - ); - $iNWDevice = $this->CreateObject('ApplicationInstance', $aData, $oChange); - - // Contract/Infra - // - $iContractCount = 1; - for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) - { - $aData = array( - 'contract_id' => $this->RandomId('CustomerContract'), - 'ci_id' => $iNWDevice, - ); - $this->CreateObject('lnkContractToCI', $aData, $oChange); - } - } - $this->MakeFeedback($oP, 'ApplicationInstance'); - - ///////////////////////// - // - // Application Solution - // - for($i = 0 ; $i < $this->m_aPlanned['Solutions'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'name' => 'Solution #'.$i, - 'status' => 'production', - ); - $iNWDevice = $this->CreateObject('ApplicationSolution', $aData, $oChange); - - // Contract/Infra - // - $iContractCount = 1; - for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) - { - $aData = array( - 'contract_id' => $this->RandomId('CustomerContract'), - 'ci_id' => $iNWDevice, - ); - $this->CreateObject('lnkContractToCI', $aData, $oChange); - } - } - $this->MakeFeedback($oP, 'ApplicationSolution'); - - ///////////////////////// - // - // Business Process - // - for($i = 0 ; $i < $this->m_aPlanned['Processes'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'name' => 'Process #'.$i, - 'status' => 'production', - ); - $iNWDevice = $this->CreateObject('BusinessProcess', $aData, $oChange); - - // Contract/Infra - // - $iContractCount = 1; - for($iLinked = 0 ; $iLinked < $iContractCount ; $iLinked++) - { - $aData = array( - 'contract_id' => $this->RandomId('CustomerContract'), - 'ci_id' => $iNWDevice, - ); - $this->CreateObject('lnkContractToCI', $aData, $oChange); - } - } - $this->MakeFeedback($oP, 'BusinessProcess'); - - ///////////////////////// - // - // Incident Tickets - // - for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'service_id' => $this->RandomId('Service'), - 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'Incident #'.$i, - 'ticket_log' => 'Testing...', - ); - $iTicket = $this->CreateObject('Incident', $aData, $oChange); - - // Incident/Infra - // - $iInfraCount = rand(0, 6); - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'ci_id' => $this->RandomId('Server'), - 'ticket_id' => $iTicket, - ); - $this->CreateObject('lnkTicketToCI', $aData, $oChange); - } - - // Incident/Contact - // - $iInfraCount = rand(0, 6); - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'contact_id' => $this->RandomId('Person'), - 'ticket_id' => $iTicket, - 'role' => 'role '.$iLinked, - ); - $this->CreateObject('lnkTicketToContact', $aData, $oChange); - } - } - $this->MakeFeedback($oP, 'Incident'); - - ///////////////////////// - // - // Big Ticket - // - $aData = array( - 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'service_id' => $this->RandomId('Service'), - 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'Big ticket', - 'ticket_log' => 'Testing...', - ); - $iTicket = $this->CreateObject('Incident', $aData, $oChange); - - // Incident/Infra - // - $iInfraCount = $this->m_aPlanned['Big ticket: CIs']; - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'ci_id' => $this->RandomId('Server'), - 'ticket_id' => $iTicket, - ); - $this->CreateObject('lnkTicketToCI', $aData, $oChange); - } - - // Incident/Contact - // - $iInfraCount = rand(0, 6) ; - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'contact_id' => $this->RandomId('Person'), - 'ticket_id' => $iTicket, - 'role' => 'role '.$iLinked, - ); - $this->CreateObject('lnkTicketToContact', $aData, $oChange); - } - - ///////////////////////// - // - // Change Tickets - // - for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'requestor_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'supervisor_group_id' => $this->RandomId('Team'), - 'supervisor_id' => $this->RandomId('Person'), - 'manager_group_id' => $this->RandomId('Team'), - 'manager_id' => $this->RandomId('Person'), - 'title' => 'change #'.$i, - 'description' => "Let's do something there", - ); - $iTicket = $this->CreateObject('NormalChange', $aData, $oChange); - - // Change/Infra - // - $iInfraCount = rand(0, 6); - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'ci_id' => $this->RandomId('Server'), - 'ticket_id' => $iTicket, - ); - $this->CreateObject('lnkTicketToCI', $aData, $oChange); - } - - // Change/Contact - // - $iInfraCount = rand(0, 6); - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'contact_id' => $this->RandomId('Person'), - 'ticket_id' => $iTicket, - 'role' => 'role '.$iLinked, - ); - $this->CreateObject('lnkTicketToContact', $aData, $oChange); - } - } - $this->MakeFeedback($oP, 'NormalChange'); - - ///////////////////////// - // - // Service calls - // - for($i = 0 ; $i < $this->m_aPlanned['ServiceCalls'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'service_id' => $this->RandomId('Service'), - 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'Call #'.$i, - 'ticket_log' => 'Testing...', - ); - $iTicket = $this->CreateObject('UserRequest', $aData, $oChange); - - // Call/Infra - // - $iInfraCount = rand(0, 6); - for($iLinked = 0 ; $iLinked < $iInfraCount ; $iLinked++) - { - $aData = array( - 'ci_id' => $this->RandomId('Server'), - 'ticket_id' => $iTicket, - ); - $this->CreateObject('lnkTicketToCI', $aData, $oChange); - } - } - $this->MakeFeedback($oP, 'UserRequest'); ///////////////////////// // @@ -845,9 +471,250 @@ class BenchmarkDataCreation 'name' => "document$i", 'contents' => $oRefDoc, ); - $this->CreateObject('FileDoc', $aData, $oChange); + $this->CreateObject('FileDoc', $aData); + } + } + + public function CreateCis($oP) + { + $iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'"); + $iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg"); + + ///////////////////////// + // + // Servers + // + for($i = 0 ; $i < $this->m_aPlanned['Servers'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'server'.$i, + 'status' => 'production', + ); + $iServer = $this->CreateObject('Server', $aData); + + // Contract/Infra + $this->CreateLinks($iServer, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + + // Interfaces + for($iLinked = 0 ; $iLinked < $this->m_iIfByServer ; $iLinked++) + { + $aData = array( + 'name' => "eth$iLinked", + 'status' => 'implementation', + 'org_id' => $iOrg, + 'device_id' => $iServer, + 'status' => 'production', + ); + $this->CreateObject('NetworkInterface', $aData, 'server if'); + } + } + + ///////////////////////// + // + // Network devices + // + for($i = 0 ; $i < $this->m_aPlanned['Network devices'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'equipment #'.$i, + 'status' => 'production', + ); + $iNWDevice = $this->CreateObject('NetworkDevice', $aData); + + // Contract/Infra + $this->CreateLinks($iNWDevice, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + + // Interfaces + // + for($iLinked = 0 ; $iLinked < $this->m_iIfByNWDevice ; $iLinked++) + { + $aData = array( + 'name' => "eth$iLinked", + 'status' => 'implementation', + 'org_id' => $iOrg, + 'device_id' => $iNWDevice, + 'connected_if' => $this->RandomId('NetworkInterface', 'server if'), + 'status' => 'production', + ); + $this->CreateObject('NetworkInterface', $aData, 'equipment if'); + } + } + + ///////////////////////// + // + // Application Software + // + for($i = 0 ; $i < $this->m_aPlanned['Application SW'] ; $i++) + { + $aData = array( + 'name' => 'Software #'.$i, + ); + $iNWDevice = $this->CreateObject('Application', $aData); + } + + ///////////////////////// + // + // Applications + // + for($i = 0 ; $i < $this->m_aPlanned['Applications'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'device_id' => $this->RandomId('Server'), + 'software_id' => $this->RandomId('Application'), + 'name' => 'Application #'.$i, + 'status' => 'production', + ); + $iAppInstance = $this->CreateObject('ApplicationInstance', $aData); + + // Contract/Infra + $this->CreateLinks($iAppInstance, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + } + + ///////////////////////// + // + // Application Solution + // + for($i = 0 ; $i < $this->m_aPlanned['Solutions'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Solution #'.$i, + 'status' => 'production', + ); + $iAppSolution = $this->CreateObject('ApplicationSolution', $aData); + + // Contract/Infra + $this->CreateLinks($iAppSolution, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + } + + ///////////////////////// + // + // Business Process + // + for($i = 0 ; $i < $this->m_aPlanned['Processes'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Process #'.$i, + 'status' => 'production', + ); + $iProcess = $this->CreateObject('BusinessProcess', $aData); + + // Contract/Infra + $this->CreateLinks($iProcess, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + } + } + + public function CreateTickets($oP) + { + $iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'"); + $iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg"); + + ///////////////////////// + // + // Incident Tickets + // + for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), + 'title' => 'Incident #'.$i, + 'ticket_log' => 'Testing...', + ); + $iTicket = $this->CreateObject('Incident', $aData); + + // Incident/Infra + $iInfraCount = rand(1, 6); + $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); + + // Incident/Infra + $iContactCount = rand(1, 6); + $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); + } + + ///////////////////////// + // + // Big Ticket + // + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), + 'title' => 'Big ticket', + 'ticket_log' => 'Testing...', + ); + $iTicket = $this->CreateObject('Incident', $aData); + + // Incident/Infra + $iInfraCount = $this->m_aPlanned['Big ticket: CIs']; + $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); + + // Incident/Infra + $iContactCount = rand(1, 6); + $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); + + ///////////////////////// + // + // Change Tickets + // + for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'requestor_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'supervisor_group_id' => $this->RandomId('Team'), + 'supervisor_id' => $this->RandomId('Person'), + 'manager_group_id' => $this->RandomId('Team'), + 'manager_id' => $this->RandomId('Person'), + 'title' => 'change #'.$i, + 'description' => "Let's do something there", + ); + $iTicket = $this->CreateObject('NormalChange', $aData); + + // Incident/Infra + $iInfraCount = rand(1, 6); + $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); + + // Incident/Infra + $iContactCount = rand(1, 6); + $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); + } + } + + public function MakeFeedback($oP) + { + foreach($this->m_aCreatedByClass as $sClass => $aClassIds) + { + $iSample = reset($aClassIds); + $sSample = "sample"; + + $iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3); + $fDurationMin = number_format(min($this->m_aStatsByClass[$sClass]), 3); + $fDurationMax = number_format(max($this->m_aStatsByClass[$sClass]), 3); + $fDurationAverage = number_format(array_sum($this->m_aStatsByClass[$sClass]) / count($this->m_aStatsByClass[$sClass]), 3); + + $oP->add("
              "); + $oP->add("
            • "); + $oP->add("$sClass: ".count($this->m_aStatsByClass[$sClass])." - $sSample
              "); + $oP->add("Duration: $fDurationMin => $fDurationMax; Avg:$fDurationAverage; Total: $iDuration"); + $oP->add("
            • "); + $oP->add("
            "); } - $this->MakeFeedback($oP, 'FileDoc'); } } @@ -859,25 +726,9 @@ function DisplayStep1(SetupWebPage $oP) $sNextOperation = 'step2'; $oP->add("

            iTop benchmarking

            \n"); - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
            \n"); - - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
            \n"); - - $oP->add("

            Please specify the requested volumes

            \n"); $oP->add("
            \n"); $oP->add("
            Data load configuration\n"); $aForm = array(); - $aForm[] = array( - 'label' => "Main CIs:", - 'input' => "", - 'help' => ' exclude interfaces, subnets or any other type of secondary CI', - ); $aForm[] = array( 'label' => "Contacts:", 'input' => "", @@ -890,89 +741,45 @@ function DisplayStep1(SetupWebPage $oP) ); $oP->form($aForm); $oP->add("
            \n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("\n"); $oP->add("
            \n"); -} - -/** - * Inform the user how many items will be created - */ -function DisplayStep2(SetupWebPage $oP, $oDataCreation) -{ - $sNextOperation = 'step3'; - $oP->add("

            iTop benchmarking

            \n"); - $oP->add("

            Step 2: review planned volumes

            \n"); - - - $aPlanned = $oDataCreation->GetPlans(); + $oP->add("
            \n"); + $oP->add("
            Data load configuration\n"); $aForm = array(); - foreach ($aPlanned as $sKey => $iCount) - { - $aForm[] = array( - 'label' => $sKey, - 'input' => $iCount, - ); - } + $aForm[] = array( + 'label' => "Main CIs:", + 'input' => "", + 'help' => ' exclude interfaces, subnets or any other type of secondary CI', + ); $oP->form($aForm); + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); - $aRequested = $oDataCreation->GetRequestInfo(); - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            Data load configuration\n"); + $aForm = array(); + $aForm[] = array( + 'label' => "Tickets:", + 'input' => "", + 'help' => ' 50% incidents, 50% changes', + ); + $aForm[] = array( + 'label' => "CIs for the big ticket:", + 'input' => "", + 'help' => 'Number of CI for the single big ticket', + ); + $oP->form($aForm); + $oP->add("
            \n"); + $oP->add("\n"); $oP->add("\n"); $oP->add("
            \n"); } -/** - * Do create the data set... could take some time to execute - */ -function DisplayStep3(SetupWebPage $oP, $oDataCreation) -{ -// $sNextOperation = 'step3'; - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Administrator"); - $iChangeId = $oMyChange->DBInsertNoReload(); - - $oDataCreation->GoVolume($oP, $oMyChange); -} - -/** - * Do create a profile management context - */ -function CreateProfilesOrganization(SetupWebPage $oP, $oDataCreation) -{ -// $sNextOperation = 'step3'; - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Administrator"); - $iChangeId = $oMyChange->DBInsertNoReload(); - - $oDataCreation->GoProjectionsOrganization($oP, $oMyChange); -} - -/** - * Do create the data set... could take some time to execute - */ -function CreateProfilesLocation(SetupWebPage $oP, $oDataCreation) -{ -// $sNextOperation = 'step3'; - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Administrator"); - $iChangeId = $oMyChange->DBInsertNoReload(); - - $oDataCreation->GoProjectionsLocation($oP, $oMyChange); -} - /** * Main program */ @@ -990,36 +797,68 @@ try DisplayStep1($oP); break; - case 'createprofiles_organization': + case 'create_structure': $oP->no_cache(); - $oDataCreation = new BenchmarkDataCreation(); - CreateProfilesOrganization($oP, $oDataCreation); - break; - - case 'createprofiles_location': - $oP->no_cache(); - $oDataCreation = new BenchmarkDataCreation(); - CreateProfilesLocation($oP, $oDataCreation); - break; - - case 'step2': - $oP->no_cache(); - $iPlannedCIs = Utils::ReadParam('plannedcis'); $iPlannedContacts = Utils::ReadParam('plannedcontacts'); $iPlannedContracts = Utils::ReadParam('plannedcontracts'); - $oDataCreation = new BenchmarkDataCreation($iPlannedCIs, $iPlannedContacts, $iPlannedContracts); - DisplayStep2($oP, $oDataCreation); + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts); + $oDataCreation->ShowPlans($oP); + $oDataCreation->ShowForm($oP, 'create_structure_go'); break; - case 'step3': + case 'create_structure_go': $oP->no_cache(); - $iPlannedCIs = Utils::ReadParam('plannedcis'); $iPlannedContacts = Utils::ReadParam('plannedcontacts'); $iPlannedContracts = Utils::ReadParam('plannedcontracts'); - $oDataCreation = new BenchmarkDataCreation($iPlannedCIs, $iPlannedContacts, $iPlannedContracts); - DisplayStep3($oP, $oDataCreation); + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts); + $oDataCreation->CreateStructure($oP); + $oDataCreation->MakeFeedback($oP); + break; + + case 'create_cis': + $oP->no_cache(); + $iPlannedCIs = Utils::ReadParam('plannedcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanCis($iPlannedCIs); + $oDataCreation->ShowPlans($oP); + $oDataCreation->ShowForm($oP, 'create_cis_go'); + break; + + case 'create_cis_go': + $oP->no_cache(); + $iPlannedCIs = Utils::ReadParam('plannedcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanCis($iPlannedCIs); + $oDataCreation->CreateCis($oP); + $oDataCreation->MakeFeedback($oP); + break; + + case 'create_tickets': + $oP->no_cache(); + $iPlannedTickets = Utils::ReadParam('plannedtickets'); + $iBigTicketCis = Utils::ReadParam('plannedbigticketcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis); + $oDataCreation->ShowPlans($oP); + $oDataCreation->ShowForm($oP, 'create_tickets_go'); + break; + + case 'create_tickets_go': + $oP->no_cache(); + $iPlannedTickets = Utils::ReadParam('plannedtickets'); + $iBigTicketCis = Utils::ReadParam('plannedbigticketcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis); + $oDataCreation->CreateTickets($oP); + $oDataCreation->MakeFeedback($oP); break; default: From 646128e3dde5a3482d9d6c0951c8c69e14c6b83a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 23 Aug 2010 13:17:42 +0000 Subject: [PATCH 604/970] Removed unused user management tools SVN:trunk[687] --- pages/usermanagement_classproj.php | 121 ---------- pages/usermanagement_profileproj.php | 131 ----------- pages/usermanagement_userstatus.php | 326 --------------------------- 3 files changed, 578 deletions(-) delete mode 100644 pages/usermanagement_classproj.php delete mode 100644 pages/usermanagement_profileproj.php delete mode 100644 pages/usermanagement_userstatus.php diff --git a/pages/usermanagement_classproj.php b/pages/usermanagement_classproj.php deleted file mode 100644 index 379b8d7a09..0000000000 --- a/pages/usermanagement_classproj.php +++ /dev/null @@ -1,121 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -require_once('../application/application.inc.php'); -require_once('../application/itopwebpage.class.inc.php'); - -require_once('../application/startup.inc.php'); - - -function ComputeProjections($oPage, $sScope) -{ - // Load the classes for a further usage - // - $aClasses = MetaModel::GetClasses(); - - // Load the dimensions for a further usage - // - $aDimensions = array(); - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $aDimensions[$oDimension->GetKey()] = $oDimension; - } - - // Load the class projections for a further usage - // - $aClassProj = array(); - $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); - while ($oClassProj = $oClassProjSet->Fetch()) - { - $aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; - } - - // Setup display structure - // - $aDisplayConfig = array(); - $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); - $aDisplayConfig['object'] = array('label' => Dict::S('UI:UserManagement:ProjectedObject'), 'description' => Dict::S('UI:UserManagement:ProjectedObject+')); - foreach ($aDimensions as $iDimension => $oDimension) - { - $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); - } - - // Load objects - // - $aDisplayData = array(); - $oObjectSet = new DBObjectSet(DBObjectSearch::FromOQL($sScope)); - $sClass = $oObjectSet->GetClass(); - while ($oObject = $oObjectSet->Fetch()) - { - $aObjectProj = array(); - $oObjectProj['class'] = $sClass; - $oObjectProj['object'] = $oObject->GetName(); - foreach ($aDimensions as $iDimension => $oDimension) - { - // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension], $sClass); - - $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); - if (is_null($aValues)) - { - $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); - } - else - { - $sValues = implode(', ', $aValues); - } - $oObjectProj['dim'.$oDimension->GetKey()] = $sValues; - } - - $aDisplayData[] = $oObjectProj; - } - - $oPage->table($aDisplayConfig, $aDisplayData); - -//$oPage->SetCurrentTab('Attributes'); -//$oPage->p("[All classes]"); -//$oPage->add("
          \n"); - -} - - -require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - -// Display the menu on the left -$oAppContext = new ApplicationContext(); -$iActiveNodeId = utils::ReadParam('menu', -1); -$currentOrganization = utils::ReadParam('org_id', 1); -$sScope = utils::ReadParam('scope', 'SELECT bizDevice'); - -$oPage = new iTopWebPage(Dict::S('UI:PageTitle:ClassProjections'), $currentOrganization); -$oPage->no_cache(); - -ComputeProjections($oPage, $sScope); -$oPage->output(); - -?> diff --git a/pages/usermanagement_profileproj.php b/pages/usermanagement_profileproj.php deleted file mode 100644 index 2b018b8cb0..0000000000 --- a/pages/usermanagement_profileproj.php +++ /dev/null @@ -1,131 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -require_once('../application/application.inc.php'); -require_once('../application/itopwebpage.class.inc.php'); - -require_once('../application/startup.inc.php'); - - -function ComputeProjections($oPage) -{ - // Load the profiles for a further usage - // - $aProfiles = array(); - $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); - while ($oProfile = $oProfileSet->Fetch()) - { - $aProfiles[$oProfile->GetKey()] = $oProfile; - } - - // Load the dimensions for a further usage - // - $aDimensions = array(); - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $aDimensions[$oDimension->GetKey()] = $oDimension; - } - - // Load the profile projections for a further usage - // - $aProPro = array(); - $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); - while ($oProPro = $oProProSet->Fetch()) - { - $aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; - } - - // Setup display structure - // - $aDisplayConfig = array(); - $aDisplayConfig['user'] = array('label' => Dict::S('UI:UserManagement:User'), 'description' => Dict::S('UI:UserManagement:User+')); - $aDisplayConfig['profile'] = array('label' => Dict::S('UI:UserManagement:Profile'), 'description' => Dict::S('UI:UserManagement:Profile+')); - foreach ($aDimensions as $iDimension => $oDimension) - { - $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); - } - - // Load users, and create a record per couple user/profile - // - $aDisplayData = array(); - $oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("User")); - while ($oUser = $oUserSet->Fetch()) - { - $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile WHERE userid = :user->id"), array(), array('user' => $oUser)); - while ($oUserProfile = $oUserProfileSet->Fetch()) - { - $iProfile = $oUserProfile->Get('profileid'); - $oProfile = $aProfiles[$iProfile]; - - $aUserProfileProj = array(); - $aUserProfileProj['user'] = $oUser->GetName(); - $aUserProfileProj['profile'] = $oProfile->GetName(); - foreach ($aDimensions as $iDimension => $oDimension) - { - // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension], get_class($oUser)); - - $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); - if (is_null($aValues)) - { - $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); - } - else - { - $sValues = implode(', ', $aValues); - } - $aUserProfileProj['dim'.$oDimension->GetKey()] = $sValues; - } - - $aDisplayData[] = $aUserProfileProj; - } - } - - $oPage->table($aDisplayConfig, $aDisplayData); - -//$oPage->SetCurrentTab('Attributes'); -//$oPage->p("[All classes]"); -//$oPage->add("
        \n"); - -} - - -require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - -// Display the menu on the left -$oAppContext = new ApplicationContext(); -$iActiveNodeId = utils::ReadParam('menu', -1); -$currentOrganization = utils::ReadParam('org_id', 1); - -$oPage = new iTopWebPage(Dict::S('UI:PageTitle:ProfileProjections'), $currentOrganization); -$oPage->no_cache(); - -ComputeProjections($oPage); -$oPage->output(); - -?> diff --git a/pages/usermanagement_userstatus.php b/pages/usermanagement_userstatus.php deleted file mode 100644 index 9c5f6ff3dc..0000000000 --- a/pages/usermanagement_userstatus.php +++ /dev/null @@ -1,326 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -require_once('../application/application.inc.php'); -require_once('../application/itopwebpage.class.inc.php'); - -require_once('../application/startup.inc.php'); - - -function ComputeObjectProjections($oPage, $oObject) -{ - // Load the classes for a further usage - // - $aClasses = MetaModel::GetClasses(); - - // Load the dimensions for a further usage - // - $aDimensions = array(); - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $aDimensions[$oDimension->GetKey()] = $oDimension; - } - - // Load the class projections for a further usage - // - $aClassProj = array(); - $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection")); - while ($oClassProj = $oClassProjSet->Fetch()) - { - $aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; - } - - // Setup display structure - // - $aDisplayConfig = array(); - foreach ($aDimensions as $iDimension => $oDimension) - { - $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); - } - - // Load objects - // - $aDisplayData = array(); - $sClass = get_class($oObject); - $aObjectProj = array(); - foreach ($aDimensions as $iDimension => $oDimension) - { - // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension], $sClass); - - $aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); - if (is_null($aValues)) - { - $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); - } - else - { - $sValues = implode(', ', $aValues); - } - $oObjectProj['dim'.$oDimension->GetKey()] = $sValues; - } - - $aDisplayData[] = $oObjectProj; - - $oPage->table($aDisplayConfig, $aDisplayData); -} - - -function ComputeUserProjections($oPage, $oUser) -{ - // Load the profiles for a further usage - // - $aProfiles = array(); - $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles")); - while ($oProfile = $oProfileSet->Fetch()) - { - $aProfiles[$oProfile->GetKey()] = $oProfile; - } - - // Load the dimensions for a further usage - // - $aDimensions = array(); - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $aDimensions[$oDimension->GetKey()] = $oDimension; - } - - // Load the profile projections for a further usage - // - $aProPro = array(); - $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection")); - while ($oProPro = $oProProSet->Fetch()) - { - $aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; - } - - // Setup display structure - // - $aDisplayConfig = array(); - $aDisplayConfig['profile'] = array('label' => Dict::S('UI:UserManagement:Profile'), 'description' => Dict::S('UI:UserManagement:Profile+')); - foreach ($aDimensions as $iDimension => $oDimension) - { - $aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description')); - } - - // Create a record per profile - // - $aDisplayData = array(); - $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile WHERE userid = :user->id"), array(), array('user' => $oUser)); - while ($oUserProfile = $oUserProfileSet->Fetch()) - { - $iProfile = $oUserProfile->Get('profileid'); - $oProfile = $aProfiles[$iProfile]; - - $aUserProfileProj = array(); - $aUserProfileProj['profile'] = $oProfile->GetName(); - foreach ($aDimensions as $iDimension => $oDimension) - { - // #@# to be moved, may be time consuming - $oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension], get_class($oUser)); - - $aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser); - if (is_null($aValues)) - { - $sValues = htmlentities(Dict::S('UI:UserManagement:AnyObject')); - } - else - { - $sValues = implode(', ', $aValues); - } - $aUserProfileProj['dim'.$oDimension->GetKey()] = $sValues; - } - - $aDisplayData[] = $aUserProfileProj; - } - - $oPage->table($aDisplayConfig, $aDisplayData); -} - - -function ComputeUserRights($oPage, $oUser, $oObject) -{ - // Set the stage - // - $iUser = $oUser->GetKey(); - $sClass = get_class($oObject); - $iPKey = $oObject->GetKey(); - $oInstances = DBObjectSet::FromArray($sClass, array($oObject)); - $aPermissions = array( - UR_ALLOWED_NO => 'UR_ALLOWED_NO', - UR_ALLOWED_YES => 'UR_ALLOWED_YES', - UR_ALLOWED_DEPENDS => 'UR_ALLOWED_DEPENDS', - ); - $aActions = array( - UR_ACTION_READ => Dict::S('UI:UserManagement:Action:Read'), - UR_ACTION_MODIFY => Dict::S('UI:UserManagement:Action:Modify'), - UR_ACTION_DELETE => Dict::S('UI:UserManagement:Action:Delete'), - UR_ACTION_BULK_READ => Dict::S('UI:UserManagement:Action:BulkRead'), - UR_ACTION_BULK_MODIFY => Dict::S('UI:UserManagement:Action:BulkModify'), - UR_ACTION_BULK_DELETE => Dict::S('UI:UserManagement:Action:BulkDelete'), - ); - $aAttributeActions = array( - UR_ACTION_READ => Dict::S('UI:UserManagement:Action:Read'), - UR_ACTION_MODIFY => Dict::S('UI:UserManagement:Action:Modify'), - UR_ACTION_BULK_READ => Dict::S('UI:UserManagement:Action:BulkRead'), - UR_ACTION_BULK_MODIFY => Dict::S('UI:UserManagement:Action:BulkModify'), - ); - - // Determine allowed actions for the object - // - $aDisplayData = array(); - foreach($aActions as $iActionCode => $sActionDesc) - { - $iPermission = UserRights::IsActionAllowed($sClass, $iActionCode, $oInstances, $iUser); - $aDisplayData[] = array( - 'action' => $sActionDesc, - 'permission' => $aPermissions[$iPermission], - ); - } - $aDisplayConfig = array(); - $aDisplayConfig['action'] = array('label' => Dict::S('UI:UserManagement:Action'), 'description' => Dict::S('UI:UserManagement:Action+')); - $aDisplayConfig['permission'] = array('label' => Dict::S('UI:UserManagement:Permission'), 'description' => Dict::S('UI:UserManagement:Permission+')); - $oPage->p('

        '.Dict::S('UI:UserManagement:Actions').'

        '); - $oPage->table($aDisplayConfig, $aDisplayData); - - - // Determine allowed actions for the object - // - $aDisplayData = array(); - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - if (!$oAttDef->IsDirectField()) continue; - - foreach($aAttributeActions as $iActionCode => $sActionDesc) - { - $iPermission = UserRights::IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, $oInstances, $iUser); - $aDisplayData[] = array( - 'attribute' => $sAttCode, - 'action' => $sActionDesc, - 'permission' => $aPermissions[$iPermission], - ); - } - } - $oPage->p('

        '.Dict::S('UI:UserManagement:Attributes').'

        '); - if (count($aDisplayData) > 0) - { - $aDisplayConfig = array(); - $aDisplayConfig['attribute'] = array('label' => 'Attribute', 'description' => ''); - $aDisplayConfig['action'] = array('label' => 'Action', 'description' => ''); - $aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => ''); - $oPage->table($aDisplayConfig, $aDisplayData); - } - else - { - $oPage->p('none'); - } - - // Determine allowed stimuli - // - $aDisplayData = array(); - foreach(MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) - { - $iPermission = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oInstances, $iUser); - $aDisplayData[] = array( - 'stimulus' => $sStimulusCode, - 'permission' => $aPermissions[$iPermission], - ); - } - $oPage->p('

        Stimuli

        '); - if (count($aDisplayData) > 0) - { - $aDisplayConfig = array(); - $aDisplayConfig['stimulus'] = array('label' => 'Stimulus', 'description' => ''); - $aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => ''); - $oPage->table($aDisplayConfig, $aDisplayData); - } - else - { - $oPage->p('none'); - } -} - - -require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - -// Display the menu on the left -$oAppContext = new ApplicationContext(); -$iActiveNodeId = utils::ReadParam('menu', -1); -$currentOrganization = utils::ReadParam('org_id', 1); -$iUser = utils::ReadParam('user_id', -1); -$sObjectClass = utils::ReadParam('object_class', ''); -$iObjectId = utils::ReadParam('object_id', 0); - -$oPage = new iTopWebPage("iTop user management - user status", $currentOrganization); -$oPage->no_cache(); - - -if ($iUser == -1) -{ - $oPage->p('Missing parameter "user_id" - current user is '.UserRights::GetUserId()); -} -else -{ - $oPage->p('

        How are the user rights computed?

        '); - - $oPage->p('

        1st, find the profiles that apply

        '); - $oPage->p('

        Project the current object in every existing dimension

        '); - $oPage->p('

        Project the observed profile in every existing dimension (might depend on the user)

        '); - $oPage->p('

        If an overlap is found in any dimension, then the profile applies

        '); - - $oPage->p('

        2nd, interpret the profiles

        '); - $oPage->p('

        Note: granting rights for specific attributes is not fully implemented. It is still not taking into account the inheritance of rights AND the UI will not take that information into account!

        '); - $oPage->p('

        Actions: looks into URP_ActionGrant for a permission (yes or no) and goes up into the class hierarchy until an answer is found, defaults to no

        '); - $oPage->p('

        Stimuli: looks into URP_StimulusGrant for a permission (yes or no), defaults to no

        '); - - - $oPage->p('

        3rd, keep the most permissive one

        '); - $oPage->p('

        If one profile says YES, then the answer is YES

        '); - - - $oUser = MetaModel::GetObject('User', $iUser); - - $oPage->p('

        Projections for user '.$oUser->GetName().'

        '); - ComputeUserProjections($oPage, $oUser); - - if (strlen($sObjectClass) != 0) - { - $oObject = MetaModel::GetObject($sObjectClass, $iObjectId); - - $oPage->p('

        Projections for object '.$oObject->GetName().'

        '); - ComputeObjectProjections($oPage, $oObject); - - $oPage->p('

        Resulting rights

        '); - ComputeUserRights($oPage, $oUser, $oObject); - } -} - -$oPage->output(); - -?> From b4b4d7980e5f9102c76d4ea058c716ec63df9ad1 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 23 Aug 2010 13:21:33 +0000 Subject: [PATCH 605/970] SVN:trunk[688] --- .../userrightsprojection.class.inc.php | 1594 +++++++++++++++++ 1 file changed, 1594 insertions(+) create mode 100644 addons/userrights/userrightsprojection.class.inc.php diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php new file mode 100644 index 0000000000..a0961b5941 --- /dev/null +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -0,0 +1,1594 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +define('ADMIN_PROFILE_ID', 1); + +class UserRightsBaseClass extends cmdbAbstractObject +{ + // Whenever something changes, reload the privileges + + public function DBInsertTracked(CMDBChange $oChange) + { + parent::DBInsertTracked($oChange); + UserRights::FlushPrivileges(); + } + + public function DBUpdateTracked(CMDBChange $oChange) + { + parent::DBUpdateTracked($oChange); + UserRights::FlushPrivileges(); + } + + public function DBDeleteTracked(CMDBChange $oChange) + { + parent::DBDeleteTracked($oChange); + UserRights::FlushPrivileges(); + } +} + + + + +class URP_Profiles extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_profiles", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("user_list", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"profileid", "ext_key_to_remote"=>"userid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + function GetGrantAsHtml($oUserRights, $sClass, $sAction) + { + $oGrant = $oUserRights->GetClassActionGrant($this->GetKey(), $sClass, $sAction); + if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) + { + return ''.Dict::S('UI:UserManagement:ActionAllowed:Yes').''; + } + else + { + return ''.Dict::S('UI:UserManagement:ActionAllowed:No').''; + } + } + + function DoShowGrantSumary($oPage) + { + if ($this->GetName() == "Administrator") + { + // Looks dirty, but ok that's THE ONE + $oPage->p(Dict::S('UI:UserManagement:AdminProfile+')); + return; + } + + // Note: for sure, we assume that the instance is derived from UserRightsProfile + $oUserRights = UserRights::GetModuleInstance(); + + $aDisplayData = array(); + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + // Skip non instantiable classes + if (MetaModel::IsAbstract($sClass)) continue; + + $aStimuli = array(); + foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); + if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) + { + $aStimuli[] = ''.htmlentities($oStimulus->GetLabel()).''; + } + } + $sStimuli = implode(', ', $aStimuli); + + $aDisplayData[] = array( + 'class' => MetaModel::GetName($sClass), + 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'), + 'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Read'), + 'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Modify'), + 'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Modify'), + 'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Delete'), + 'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Delete'), + 'stimuli' => $sStimuli, + ); + } + + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); + $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); + $aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+')); + $aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+')); + $aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+')); + $aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+')); + $aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+')); + $aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+')); + $oPage->table($aDisplayConfig, $aDisplayData); + } + + function DisplayBareRelations(WebPage $oPage, $bEditMode = false) + { + parent::DisplayBareRelations($oPage); + if (!$bEditMode) + { + $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); + $this->DoShowGrantSumary($oPage); + } + } +} + + +class URP_Dimensions extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_dimensions", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeClass("type", array("class_category"=>"bizmodel", "more_values"=>"String,Integer", "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description', 'type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + public function CheckProjectionSpec($oProjectionSpec, $sProjectedClass) + { + $sExpression = $oProjectionSpec->Get('value'); + $sAttribute = $oProjectionSpec->Get('attribute'); + + // Shortcut: "any value" or "no value" means no projection + if (empty($sExpression)) return; + if ($sExpression == '') return; + + // 1st - compute the data type for the dimension + // + $sType = $this->Get('type'); + if (MetaModel::IsValidClass($sType)) + { + $sExpectedType = $sType; + } + else + { + $sExpectedType = '_scalar_'; + } + + // 2nd - compute the data type for the projection + // + $sTargetClass = ''; + if (($sExpression == '') || ($sExpression == '')) + { + $sTargetClass = $sProjectedClass; + } + elseif ($sExpression == '') + { + $sTargetClass = ''; + } + else + { + // Evaluate wether it is a constant or not + try + { + $oObjectSearch = DBObjectSearch::FromOQL_AllData($sExpression); + + $sTargetClass = $oObjectSearch->GetClass(); + } + catch (OqlException $e) + { + } + } + + if (empty($sTargetClass)) + { + $sFoundType = '_void_'; + } + else + { + if (empty($sAttribute)) + { + $sFoundType = $sTargetClass; + } + else + { + if (!MetaModel::IsValidAttCode($sTargetClass, $sAttribute)) + { + throw new CoreException('Unkown attribute code in projection specification', array('found' => $sAttribute, 'expecting' => MetaModel::GetAttributesList($sTargetClass), 'class' => $sTargetClass, 'projection' => $oProjectionSpec)); + } + $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttribute); + if ($oAttDef->IsExternalKey()) + { + $sFoundType = $oAttDef->GetTargetClass(); + } + else + { + $sFoundType = '_scalar_'; + } + } + } + + // Compare the dimension type and projection type + if (($sFoundType != '_void_') && ($sFoundType != $sExpectedType)) + { + throw new CoreException('Wrong type in projection specification', array('found' => $sFoundType, 'expecting' => $sExpectedType, 'expression' => $sExpression, 'attribute' => $sAttribute, 'projection' => $oProjectionSpec)); + } + } +} + + +class URP_UserProfile extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "userid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_userprofile", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('profileid', 'reason')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form + } + + public function GetName() + { + return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); + } +} + + +class URP_ProfileProjection extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "profileid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_profileprojection", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attribute", array("allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('dimensionid', 'profileid', 'value', 'attribute')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('profileid', 'value', 'attribute')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('dimensionid', 'profileid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'profileid')); // Criteria of the advanced search form + } + + protected $m_aUserProjections; // cache + + public function ProjectUser(User $oUser) + { + if (is_array($this->m_aUserProjections)) + { + // Hit! + return $this->m_aUserProjections; + } + + $sExpr = $this->Get('value'); + if ($sExpr == '') + { + $sColumn = $this->Get('attribute'); + if (empty($sColumn)) + { + $aRes = array($oUser->GetKey()); + } + else + { + $aRes = array($oUser->Get($sColumn)); + } + + } + elseif (($sExpr == '') || ($sExpr == '')) + { + $aRes = null; + } + elseif (strtolower(substr($sExpr, 0, 6)) == 'select') + { + $sColumn = $this->Get('attribute'); + // SELECT... + $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); + $aRes = $oValueSetDef->GetValues(array('user' => $oUser), ''); + } + else + { + // Constant value(s) + $aRes = explode(';', trim($sExpr)); + } + $this->m_aUserProjections = $aRes; + return $aRes; + } +} + + +class URP_ClassProjection extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "dimensionid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_classprojection", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attribute", array("allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('dimensionid', 'class', 'value', 'attribute')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('class', 'value', 'attribute')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('dimensionid', 'class')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'class')); // Criteria of the advanced search form + } + + public function ProjectObject($oObject) + { + $sExpr = $this->Get('value'); + if ($sExpr == '') + { + $sColumn = $this->Get('attribute'); + if (empty($sColumn)) + { + $aRes = array($oObject->GetKey()); + } + else + { + $aRes = array($oObject->Get($sColumn)); + } + + } + elseif (($sExpr == '') || ($sExpr == '')) + { + $aRes = null; + } + elseif (strtolower(substr($sExpr, 0, 6)) == 'select') + { + $sColumn = $this->Get('attribute'); + // SELECT... + $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); + $aRes = $oValueSetDef->GetValues(array('this' => $oObject), ''); + } + else + { + // Constant value(s) + $aRes = explode(';', trim($sExpr)); + } + return $aRes; + } + +} + + +class URP_ActionGrant extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "profileid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_actions", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'action')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('class', 'permission', 'action')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the advanced search form + } +} + + +class URP_StimulusGrant extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "profileid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_stimulus", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'stimulus')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('class', 'permission', 'stimulus')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the advanced search form + } +} + + +class URP_AttributeGrant extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "actiongrantid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_attributes", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('actiongrantid', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('attcode')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('actiongrantid', 'attcode')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('actiongrantid', 'attcode')); // Criteria of the advanced search form + } +} + + + + +class UserRightsProfile extends UserRightsAddOnAPI +{ + static public $m_aActionCodes = array( + UR_ACTION_READ => 'read', + UR_ACTION_MODIFY => 'modify', + UR_ACTION_DELETE => 'delete', + UR_ACTION_BULK_READ => 'bulk read', + UR_ACTION_BULK_MODIFY => 'bulk modify', + UR_ACTION_BULK_DELETE => 'bulk delete', + ); + + // Installation: create the very first user + public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') + { + // Create a change to record the history of the User object + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + + $oOrg = new Organization(); + $oOrg->Set('name', 'My Company/Department'); + $oOrg->Set('code', 'SOMECODE'); +// $oOrg->Set('status', 'implementation'); + //$oOrg->Set('parent_id', xxx); + $iOrgId = $oOrg->DBInsertTrackedNoReload($oChange); + + // Location : optional + //$oLocation = new bizLocation(); + //$oLocation->Set('name', 'MyOffice'); + //$oLocation->Set('status', 'implementation'); + //$oLocation->Set('org_id', $iOrgId); + //$oLocation->Set('severity', 'high'); + //$oLocation->Set('address', 'my building in my city'); + //$oLocation->Set('country', 'my country'); + //$oLocation->Set('parent_location_id', xxx); + //$iLocationId = $oLocation->DBInsertNoReload(); + + $oContact = new Person(); + $oContact->Set('name', 'My last name'); + //$oContact->Set('first_name', 'My first name'); + //$oContact->Set('status', 'available'); + $oContact->Set('org_id', $iOrgId); + $oContact->Set('email', 'my.email@foo.org'); + //$oContact->Set('phone', ''); + //$oContact->Set('location_id', $iLocationId); + //$oContact->Set('employee_number', ''); + $iContactId = $oContact->DBInsertTrackedNoReload($oChange); + + $oUser = new UserLocal(); + $oUser->Set('login', $sAdminUser); + $oUser->Set('password', $sAdminPwd); + $oUser->Set('contactid', $iContactId); + $oUser->Set('language', $sLanguage); // Language was chosen during the installation + $iUserId = $oUser->DBInsertTrackedNoReload($oChange); + + // Add this user to the very specific 'admin' profile + $oUserProfile = new URP_UserProfile(); + $oUserProfile->Set('userid', $iUserId); + $oUserProfile->Set('profileid', ADMIN_PROFILE_ID); + $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); + $oUserProfile->DBInsertTrackedNoReload($oChange); + return true; + } + + public function IsAdministrator($oUser) + { + if (in_array($oUser->GetKey(), $this->m_aAdmins)) + { + return true; + } + else + { + return false; + } + } + + public function Setup() + { + SetupProfiles::ComputeITILProfiles(); + //SetupProfiles::ComputeBasicProfiles(); + + SetupProfiles::DoCreateDimensions(); + SetupProfiles::DoCreateProfiles(); + return true; + } + + public function Init() + { + MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData')); + } + + protected $m_aDimensions = array(); // id -> object + protected $m_aClassProj = array(); // class,dimensionid -> object + protected $m_aProfiles = array(); // id -> object + protected $m_aUserProfiles = array(); // userid,profileid -> object + protected $m_aProPro = array(); // profileid,dimensionid -> object + + protected $m_aAdmins = array(); // id of users being linked to the well-known admin profile + + protected $m_aClassActionGrants = array(); // profile, class, action -> permission + protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission + protected $m_aObjectActionGrants = array(); // userid, class, id, action -> permission, list of attributes + + public function CacheData() + { + // Could be loaded in a shared memory (?) + + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); + $this->m_aDimensions = array(); + while ($oDimension = $oDimensionSet->Fetch()) + { + $this->m_aDimensions[$oDimension->GetKey()] = $oDimension; + } + + $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ClassProjection")); + $this->m_aClassProjs = array(); + while ($oClassProj = $oClassProjSet->Fetch()) + { + $this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; + } + + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); + $this->m_aProfiles = array(); + while ($oProfile = $oProfileSet->Fetch()) + { + $this->m_aProfiles[$oProfile->GetKey()] = $oProfile; + } + + $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserProfile")); + $this->m_aUserProfiles = array(); + $this->m_aAdmins = array(); + while ($oUserProfile = $oUserProfileSet->Fetch()) + { + $this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; + if ($oUserProfile->Get('profileid') == ADMIN_PROFILE_ID) + { + $this->m_aAdmins[] = $oUserProfile->Get('userid'); + } + } + + $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ProfileProjection")); + $this->m_aProPros = array(); + while ($oProPro = $oProProSet->Fetch()) + { + $this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; + } + +/* + echo "
        \n";
        +		print_r($this->m_aDimensions);
        +		print_r($this->m_aClassProjs);
        +		print_r($this->m_aProfiles);
        +		print_r($this->m_aUserProfiles);
        +		print_r($this->m_aProPros);
        +		echo "
        \n"; +exit; +*/ + + return true; + } + + public function GetSelectFilter($oUser, $sClass) + { + $aConditions = array(); + foreach ($this->m_aDimensions as $iDimension => $oDimension) + { + $oClassProj = @$this->m_aClassProjs[$sClass][$iDimension]; + if (is_null($oClassProj)) + { + // Authorize any for this dimension, then no additional criteria is required + continue; + } + + // 1 - Get class projection info + // + $oExpression = null; + $sExpr = $oClassProj->Get('value'); + if ($sExpr == '') + { + $sColumn = $oClassProj->Get('attribute'); + if (empty($sColumn)) + { + $oExpression = new FieldExpression('id', $sClass); + } + else + { + $oExpression = new FieldExpression($sColumn, $sClass); + } + } + elseif (($sExpr == '') || ($sExpr == '')) + { + // Authorize any for this dimension, then no additional criteria is required + continue; + } + elseif (strtolower(substr($sExpr, 0, 6)) == 'select') + { + throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr)); + } + else + { + // Constant value(s) + // unsupported + throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr)); +// $aRes = explode(';', trim($sExpr)); + } + + // 2 - Get profile projection info and use it if needed + // + $aProjections = self::GetReadableProjectionsByDim($oUser, $sClass, $oDimension); + if (is_null($aProjections)) + { + // Authorize any for this dimension, then no additional criteria is required + continue; + } + elseif (count($aProjections) == 0) + { + // Authorize none, then exit as quickly as possible + return false; + } + else + { + // Authorize the given set of values + $oListExpr = ListExpression::FromScalars($aProjections); + $oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr); + $aConditions[] = $oCondition; + } + } + + if (count($aConditions) == 0) + { + // allow all + return true; + } + else + { + $oFilter = new DBObjectSearch($sClass); + foreach($aConditions as $oCondition) + { + $oFilter->AddConditionExpression($oCondition); + } + //return true; + return $oFilter; + } + } + + // This verb has been made public to allow the development of an accurate feedback for the current configuration + public function GetClassActionGrant($iProfile, $sClass, $sAction) + { + if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) + { + return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; + } + + // Get the permission for this profile/class/action + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'"); + $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile)); + if ($oSet->Count() >= 1) + { + $oGrantRecord = $oSet->Fetch(); + } + else + { + $sParentClass = MetaModel::GetParentPersistentClass($sClass); + if (empty($sParentClass)) + { + $oGrantRecord = null; + } + else + { + $oGrantRecord = $this->GetClassActionGrant($iProfile, $sParentClass, $sAction); + } + } + + $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $oGrantRecord; + return $oGrantRecord; + } + + protected function GetObjectActionGrant($oUser, $sClass, $iActionCode, /*DBObject*/ $oObject = null) + { + if(is_null($oObject)) + { + $iObjectRef = -999; + } + else + { + $iObjectRef = $oObject->GetKey(); + } + // load and cache permissions for the current user on the given object + // + $aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode]; + if (is_array($aTest)) return $aTest; + + $sAction = self::$m_aActionCodes[$iActionCode]; + + $iInstancePermission = UR_ALLOWED_NO; + $aAttributes = array(); + foreach($this->GetMatchingProfiles($oUser, $sClass, $oObject) as $iProfile) + { + $oGrantRecord = $this->GetClassActionGrant($iProfile, $sClass, $sAction); + if (is_null($oGrantRecord)) + { + continue; // loop to the next profile + } + else + { + $iInstancePermission = UR_ALLOWED_YES; + + // update the list of attributes with those allowed for this profile + // + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); + $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); + $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); + if (count($aProfileAttributes) == 0) + { + $aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass)); + $aAttributes = array_merge($aAttributes, $aAllAttributes); + } + else + { + $aAttributes = array_merge($aAttributes, $aProfileAttributes); + } + } + } + + $aRes = array( + 'permission' => $iInstancePermission, + 'attributes' => $aAttributes, + ); + $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode] = $aRes; + return $aRes; + } + + public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) + { + if (is_null($oInstanceSet)) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode); + return $aObjectPermissions['permission']; + } + + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); + + $iInstancePermission = $aObjectPermissions['permission']; + if (isset($iGlobalPermission)) + { + if ($iInstancePermission != $iGlobalPermission) + { + $iGlobalPermission = UR_ALLOWED_DEPENDS; + break; + } + } + else + { + $iGlobalPermission = $iInstancePermission; + } + } + $oInstanceSet->Rewind(); + + if (isset($iGlobalPermission)) + { + return $iGlobalPermission; + } + else + { + return UR_ALLOWED_NO; + } + } + + public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) + { + if (is_null($oInstanceSet)) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode); + $aAttributes = $aObjectPermissions['attributes']; + if (in_array($sAttCode, $aAttributes)) + { + return $aObjectPermissions['permission']; + } + else + { + return UR_ALLOWED_NO; + } + } + + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) + { + $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); + + $aAttributes = $aObjectPermissions['attributes']; + if (in_array($sAttCode, $aAttributes)) + { + $iInstancePermission = $aObjectPermissions['permission']; + } + else + { + $iInstancePermission = UR_ALLOWED_NO; + } + + if (isset($iGlobalPermission)) + { + if ($iInstancePermission != $iGlobalPermission) + { + $iGlobalPermission = UR_ALLOWED_DEPENDS; + } + } + else + { + $iGlobalPermission = $iInstancePermission; + } + } + $oInstanceSet->Rewind(); + + if (isset($iGlobalPermission)) + { + return $iGlobalPermission; + } + else + { + return UR_ALLOWED_NO; + } + } + + // This verb has been made public to allow the development of an accurate feedback for the current configuration + public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode) + { + if (isset($this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode])) + { + return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]; + } + + // Get the permission for this profile/class/stimulus + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); + $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); + if ($oSet->Count() >= 1) + { + $oGrantRecord = $oSet->Fetch(); + } + else + { + $oGrantRecord = null; + } + + $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode] = $oGrantRecord; + return $oGrantRecord; + } + + public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null) + { + // Note: this code is VERY close to the code of IsActionAllowed() + + if (is_null($oInstanceSet)) + { + $iInstancePermission = UR_ALLOWED_NO; + foreach($this->GetMatchingProfiles($oUser, $sClass) as $iProfile) + { + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (!is_null($oGrantRecord)) + { + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; + } + } + return $iInstancePermission; + } + + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) + { + $iInstancePermission = UR_ALLOWED_NO; + foreach($this->GetMatchingProfiles($oUser, $sClass, $oObject) as $iProfile) + { + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (!is_null($oGrantRecord)) + { + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; + } + } + if (isset($iGlobalPermission)) + { + if ($iInstancePermission != $iGlobalPermission) + { + $iGlobalPermission = UR_ALLOWED_DEPENDS; + } + } + else + { + $iGlobalPermission = $iInstancePermission; + } + } + $oInstanceSet->Rewind(); + + if (isset($iGlobalPermission)) + { + return $iGlobalPermission; + } + else + { + return UR_ALLOWED_NO; + } + } + + // Copied from GetMatchingProfilesByDim() + // adapted to the optimized implementation of GetSelectFilter() + // Note: shares the cache m_aProPros with GetMatchingProfilesByDim() + // Returns null if any object is readable + // an array of allowed projections otherwise (could be an empty array if none is allowed) + protected function GetReadableProjectionsByDim($oUser, $sClass, $oDimension) + { + // + // Given a dimension, lists the values for which the user will be allowed to read the objects + // + $iUser = $oUser->GetKey(); + $iDimension = $oDimension->GetKey(); + + $aRes = array(); + if (array_key_exists($iUser, $this->m_aUserProfiles)) + { + foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) + { + // user projection to be cached on a given page ! + if (!isset($this->m_aProPros[$iProfile][$iDimension])) + { + // No projection for a given profile: default to 'any' + return null; + } + + $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + if (is_null($aUserProjection)) + { + // No projection for a given profile: default to 'any' + return null; + } + $aRes = array_unique(array_merge($aRes, $aUserProjection)); + } + } + return $aRes; + } + + // Note: shares the cache m_aProPros with GetReadableProjectionsByDim() + protected function GetMatchingProfilesByDim($oUser, $oObject, $oDimension) + { + // + // List profiles for which the user projection overlaps the object projection in the given dimension + // + $iUser = $oUser->GetKey(); + $sClass = get_class($oObject); + $iPKey = $oObject->GetKey(); + $iDimension = $oDimension->GetKey(); + + if (isset($this->m_aClassProjs[$sClass][$iDimension])) + { + $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); + } + else + { + // No projection for a given class: default to 'any' + $aObjectProjection = null; + } + + $aRes = array(); + if (array_key_exists($iUser, $this->m_aUserProfiles)) + { + foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) + { + if (is_null($aObjectProjection)) + { + $aRes[] = $iProfile; + } + else + { + // user projection to be cached on a given page ! + if (isset($this->m_aProPros[$iProfile][$iDimension])) + { + $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); + } + else + { + // No projection for a given profile: default to 'any' + $aUserProjection = null; + } + + if (is_null($aUserProjection)) + { + $aRes[] = $iProfile; + } + else + { + $aMatchingValues = array_intersect($aObjectProjection, $aUserProjection); + if (count($aMatchingValues) > 0) + { + $aRes[] = $iProfile; + } + } + } + } + } + return $aRes; + } + + protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object + + protected function GetMatchingProfiles($oUser, $sClass, /*DBObject*/ $oObject = null) + { + $iUser = $oUser->GetKey(); + + if(is_null($oObject)) + { + $iObjectRef = -999; + } + else + { + $iObjectRef = $oObject->GetKey(); + } + + // + // List profiles for which the user projection overlaps the object projection in each and every dimension + // Caches the result + // + $aTest = @$this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef]; + if (is_array($aTest)) + { + return $aTest; + } + + if (is_null($oObject)) + { + if (array_key_exists($iUser, $this->m_aUserProfiles)) + { + $aRes = array_keys($this->m_aUserProfiles[$iUser]); + } + else + { + // no profile has been defined for this user + $aRes = array(); + } + } + else + { + $aProfileRes = array(); + foreach ($this->m_aDimensions as $iDimension => $oDimension) + { + foreach ($this->GetMatchingProfilesByDim($oUser, $oObject, $oDimension) as $iProfile) + { + @$aProfileRes[$iProfile] += 1; + } + } + + $aRes = array(); + $iDimCount = count($this->m_aDimensions); + foreach ($aProfileRes as $iProfile => $iMatches) + { + if ($iMatches == $iDimCount) + { + $aRes[] = $iProfile; + } + } + } + + // store into the cache + $this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef] = $aRes; + return $aRes; + } + + public function FlushPrivileges() + { + $this->CacheData(); + } +} + +// +// Create simple profiles into our user management model: +// - administrator +// - readers +// - contributors +// +class SetupProfiles +{ + protected static $m_aDimensions = array( + 'organization' => array( + 'description' => '', + 'type' => 'Organization', + ), + ); + + protected static $m_aActions = array( + UR_ACTION_READ => 'Read', + UR_ACTION_MODIFY => 'Modify', + UR_ACTION_DELETE => 'Delete', + UR_ACTION_BULK_READ => 'Bulk Read', + UR_ACTION_BULK_MODIFY => 'Bulk Modify', + UR_ACTION_BULK_DELETE => 'Bulk Delete', + ); + + // Note: It is possible to specify the same class in several modules + // + protected static $m_aModules = array(); + protected static $m_aProfiles = array(); + + protected static function DoCreateClassProjection($iDimension, $sClass) + { + $oNewObj = MetaModel::NewObject("URP_ClassProjection"); + $oNewObj->Set('dimensionid', $iDimension); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('attribute', ''); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateDimension($sName, $aDimensionData) + { + $oNewObj = MetaModel::NewObject("URP_Dimensions"); + $oNewObj->Set('name', $sName); + $oNewObj->Set('description', $aDimensionData['description']); + $oNewObj->Set('type', $aDimensionData['type']); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + + protected static function DoCreateProfileProjection($iProfile, $iDimension) + { + $oNewObj = MetaModel::NewObject("URP_ProfileProjection"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('dimensionid', $iDimension); + $oNewObj->Set('value', ''); + $oNewObj->Set('attribute', ''); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + + protected static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) + { + $oNewObj = MetaModel::NewObject("URP_ActionGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('action', self::$m_aActions[$iAction]); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) + { + $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', 'yes'); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('stimulus', $sStimulusCode); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateAdminProfile() + { + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', 'Administrator'); + $oNewObj->Set('description', 'Has the rights on everything (bypassing any control)'); + $iNewId = $oNewObj->DBInsertNoReload(); + if ($iNewId != ADMIN_PROFILE_ID) + { + throw new CoreException('Admin profile could not be created with its standard id', array('requested'=>ADMIN_PROFILE_ID, 'obtained'=>$iNewId)); + } + } + + protected static function DoCreateOneProfile($sName, $aProfileData) + { + $sDescription = $aProfileData['description']; + if (strlen(trim($aProfileData['write_modules'])) == 0) + { + $aWriteModules = array(); + } + else + { + $aWriteModules = explode(',', trim($aProfileData['write_modules'])); + } + $aStimuli = $aProfileData['stimuli']; + + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', $sName); + $oNewObj->Set('description', $sDescription); + $iProfile = $oNewObj->DBInsertNoReload(); + + // Project in every dimension + // + $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); + while ($oDimension = $oDimensionSet->Fetch()) + { + $iDimension = $oDimension->GetKey(); + self::DoCreateProfileProjection($iProfile, $iDimension); + } + + // Grant read rights for everything + // + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + // Skip non instantiable classes + if (MetaModel::IsAbstract($sClass)) continue; + + self::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass); + } + + // Grant write for given modules + // Start by compiling the information, because some modules may overlap + $aWriteableClasses = array(); + foreach ($aWriteModules as $sModule) + { + //$oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); + foreach (self::$m_aModules[$sModule] as $sClass) + { + $aWriteableClasses[$sClass] = true; + } + } + foreach ($aWriteableClasses as $sClass => $foo) + { + // Skip non instantiable classes + if (MetaModel::IsAbstract($sClass)) continue; + + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } + self::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass); + // By default, do not allow bulk deletion operations for standard users + // self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_DELETE, $sClass); + } + + // Grant stimuli for given classes + foreach ($aStimuli as $sClass => $sAllowedStimuli) + { + if (!MetaModel::IsValidClass($sClass)) + { + // Could be a class defined in a module that wasn't installed + continue; + //throw new CoreException("Invalid class name '$sClass'"); + } + + if ($sAllowedStimuli == 'any') + { + $aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass)); + } + elseif ($sAllowedStimuli == 'none') + { + $aAllowedStimuli = array(); + } + else + { + $aAllowedStimuli = explode(',', $sAllowedStimuli); + } + foreach ($aAllowedStimuli as $sStimulusCode) + { + self::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass); + } + } + } + + public static function DoCreateDimensions() + { + $aClass = MetaModel::GetClasses(); + foreach(self::$m_aDimensions as $sName => $aDimensionData) + { + $iDimension = self::DoCreateDimension($sName, $aDimensionData); + + foreach($aClass as $sClass) + { + // Skip non instantiable classes + if (MetaModel::IsAbstract($sClass)) continue; + + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } + self::DoCreateClassProjection($iDimension, $sClass); + } + } + } + + public static function DoCreateProfiles() + { + self::DoCreateAdminProfile(); + + foreach(self::$m_aProfiles as $sName => $aProfileData) + { + self::DoCreateOneProfile($sName, $aProfileData); + } + } + + public static function ComputeBasicProfiles() + { + // In this profiling scheme, one single module represents all the classes + // + self::$m_aModules = array( + 'UserData' => MetaModel::GetClasses('bizmodel'), + ); + + self::$m_aProfiles = array( + 'Reader' => array( + 'description' => 'Person having a ready-only access to the data', + 'write_modules' => '', + 'stimuli' => array( + ), + ), + 'Writer' => array( + 'description' => 'Contributor to the contents (read + write access)', + 'write_modules' => 'UserData', + 'stimuli' => array( + // any class => 'any' + ), + ), + ); + } + + public static function ComputeITILProfiles() + { + // In this profiling scheme, modules are based on ITIL recommendations + // + self::$m_aModules = array( + /* + 'WriteModule' => array( + 'someclass', + 'anotherclass', + ), + */ + 'General' => MetaModel::GetClasses('structure'), + 'Documentation' => MetaModel::GetClasses('documentation'), + 'Configuration' => MetaModel::GetClasses('configmgmt'), + 'Incident' => MetaModel::GetClasses('incidentmgmt'), + 'Problem' => MetaModel::GetClasses('problemmgmt'), + 'Change' => MetaModel::GetClasses('changemgmt'), + 'Service' => MetaModel::GetClasses('servicemgmt'), + 'Call' => MetaModel::GetClasses('requestmgmt'), + 'KnownError' => MetaModel::GetClasses('knownerrormgmt'), + ); + + self::$m_aProfiles = array( + 'Configuration Manager' => array( + 'description' => 'Person in charge of the documentation of the managed CIs', + 'write_modules' => 'General,Documentation,Configuration', + 'stimuli' => array( + //'bizServer' => 'none', + //'bizContract' => 'none', + //'bizIncidentTicket' => 'none', + //'bizChangeTicket' => 'any', + ), + ), + 'Service Desk Agent' => array( + 'description' => 'Person in charge of creating incident reports', + 'write_modules' => 'Incident,Call', + 'stimuli' => array( + 'Incident' => 'ev_assign', + 'UserRequest' => 'ev_assign', + ), + ), + 'Support Agent' => array( + 'description' => 'Person analyzing and solving the current incidents or problems', + 'write_modules' => 'Incident,Problem,KnownError', + 'stimuli' => array( + 'Incident' => 'ev_assign,ev_reassign,ev_resolve,ev_close', + 'UserRequest' => 'ev_assign,ev_reassign,ev_resolve,ev_close,ev_freeze', + ), + ), + 'Change Implementor' => array( + 'description' => 'Person executing the changes', + 'write_modules' => 'Change', + 'stimuli' => array( + 'NormalChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + 'EmergencyChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + 'RoutineChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + ), + ), + 'Change Supervisor' => array( + 'description' => 'Person responsible for the overall change execution', + 'write_modules' => 'Change', + 'stimuli' => array( + 'NormalChange' => 'ev_validate,ev_reject,ev_assign,ev_reopen,ev_finish', + 'EmergencyChange' => 'ev_assign,ev_reopen,ev_finish', + 'RoutineChange' => 'ev_assign,ev_reopen,ev_finish', + ), + ), + 'Change Approver' => array( + 'description' => 'Person who could be impacted by some changes', + 'write_modules' => 'Change', + 'stimuli' => array( + 'NormalChange' => 'ev_approve,ev_notapprove', + 'EmergencyChange' => 'ev_approve,ev_notapprove', + 'RoutineChange' => 'none', + ), + ), + 'Service Manager' => array( + 'description' => 'Person responsible for the service delivered to the [internal] customer', + 'write_modules' => 'Service', + 'stimuli' => array( + ), + ), + 'Document author' => array( + 'description' => 'Any person who could contribute to documentation', + 'write_modules' => 'Documentation', + 'stimuli' => array( + ), + ), + ); + } +} + +UserRights::SelectModule('UserRightsProfile'); + +?> From 1b9174ef17b2a45604f032e682887f7592ceb7ef Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 23 Aug 2010 13:22:43 +0000 Subject: [PATCH 606/970] - Fixed Trac #218: direct URL to an object was too long. Now use the shorter version (?operation=details&class=xxx&id=yyy&menu=zzz) SVN:trunk[689] --- application/displayblock.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 0d35c4c2fe..ca4fcc3aa3 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -850,8 +850,8 @@ class MenuBlock extends DisplayBlock } else { - $sUrl = utils::GetAbsoluteUrl(); - $aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("$sUrl?operation=search&filter=$sFilter&$sContext")); + $sUrl = utils::GetAbsoluteUrl(false); + $aActions[] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".$oObj->GetName()."&body=".urlencode("$sUrl?operation=details&class=$sClass&id=$id&$sContext")); $aActions[] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext"); //$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger'); if ($bIsModifyAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext{$sDefault}"); } From fa72a6f6729b05fd296a2af620cde3e0aa6c9b58 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 23 Aug 2010 16:18:36 +0000 Subject: [PATCH 607/970] Bug fix: n-n links with no attributes on the link were not managed properly. SVN:trunk[690] --- application/ui.linkswidget.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 30cdecc5ea..f0a453511b 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -121,7 +121,7 @@ class UILinksWidget $sNameSuffix = "]"; // To make a tabular form $aArgs['prefix'] = $sPrefix; $aRow['form::checkbox'] = ""; - $aRow['form::checkbox'] .= ""; + $aRow['form::checkbox'] .= ""; foreach($this->m_aEditableFields as $sFieldCode) { $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); From 3c8f4cca209bb737c0468d99d4b8cd9c86fad2eb Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 23 Aug 2010 16:19:19 +0000 Subject: [PATCH 608/970] Put back exception handler ! SVN:trunk[691] --- pages/UI.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 9e23ce387c..aa6e7b7396 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1385,7 +1385,7 @@ EOF ////MetaModel::ShowQueryTrace(); $oP->output(); } -catch(ZZCoreException $e) +catch(CoreException $e) { require_once('../setup/setuppage.class.inc.php'); $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); @@ -1414,7 +1414,7 @@ catch(ZZCoreException $e) // For debugging only //throw $e; } -catch(ZZException $e) +catch(Exception $e) { require_once('../setup/setuppage.class.inc.php'); $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); From b3777568e6f6c130b9b0446773059b4c2705c8bd Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 23 Aug 2010 16:24:20 +0000 Subject: [PATCH 609/970] Draft of the addon for user management by profile (simplified) SVN:trunk[692] --- .../userrightsprofile.class.inc.php | 729 +++--------------- .../userrightsprojection.class.inc.php | 10 +- core/userrights.class.inc.php | 5 +- modules/authent-local/model.authent-local.php | 2 +- 4 files changed, 110 insertions(+), 636 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index a0961b5941..9039cea2c9 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -85,7 +85,7 @@ class URP_Profiles extends UserRightsBaseClass function GetGrantAsHtml($oUserRights, $sClass, $sAction) { - $oGrant = $oUserRights->GetClassActionGrant($this->GetKey(), $sClass, $sAction); + $oGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) { return ''.Dict::S('UI:UserManagement:ActionAllowed:Yes').''; @@ -161,118 +161,6 @@ class URP_Profiles extends UserRightsBaseClass } -class URP_Dimensions extends UserRightsBaseClass -{ - public static function Init() - { - $aParams = array - ( - "category" => "addon/userrights", - "key_type" => "autoincrement", - "name_attcode" => "name", - "state_attcode" => "", - "reconc_keys" => array(), - "db_table" => "priv_urp_dimensions", - "db_key_field" => "id", - "db_finalclass_field" => "", - "display_template" => "", - ); - MetaModel::Init_Params($aParams); - //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeClass("type", array("class_category"=>"bizmodel", "more_values"=>"String,Integer", "sql"=>"type", "default_value"=>'String', "is_null_allowed"=>false, "depends_on"=>array()))); - - // Display lists - MetaModel::Init_SetZListItems('details', array('name', 'description', 'type')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list - // Search criteria - MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form - MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form - } - - public function CheckProjectionSpec($oProjectionSpec, $sProjectedClass) - { - $sExpression = $oProjectionSpec->Get('value'); - $sAttribute = $oProjectionSpec->Get('attribute'); - - // Shortcut: "any value" or "no value" means no projection - if (empty($sExpression)) return; - if ($sExpression == '') return; - - // 1st - compute the data type for the dimension - // - $sType = $this->Get('type'); - if (MetaModel::IsValidClass($sType)) - { - $sExpectedType = $sType; - } - else - { - $sExpectedType = '_scalar_'; - } - - // 2nd - compute the data type for the projection - // - $sTargetClass = ''; - if (($sExpression == '') || ($sExpression == '')) - { - $sTargetClass = $sProjectedClass; - } - elseif ($sExpression == '') - { - $sTargetClass = ''; - } - else - { - // Evaluate wether it is a constant or not - try - { - $oObjectSearch = DBObjectSearch::FromOQL_AllData($sExpression); - - $sTargetClass = $oObjectSearch->GetClass(); - } - catch (OqlException $e) - { - } - } - - if (empty($sTargetClass)) - { - $sFoundType = '_void_'; - } - else - { - if (empty($sAttribute)) - { - $sFoundType = $sTargetClass; - } - else - { - if (!MetaModel::IsValidAttCode($sTargetClass, $sAttribute)) - { - throw new CoreException('Unkown attribute code in projection specification', array('found' => $sAttribute, 'expecting' => MetaModel::GetAttributesList($sTargetClass), 'class' => $sTargetClass, 'projection' => $oProjectionSpec)); - } - $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttribute); - if ($oAttDef->IsExternalKey()) - { - $sFoundType = $oAttDef->GetTargetClass(); - } - else - { - $sFoundType = '_scalar_'; - } - } - } - - // Compare the dimension type and projection type - if (($sFoundType != '_void_') && ($sFoundType != $sExpectedType)) - { - throw new CoreException('Wrong type in projection specification', array('found' => $sFoundType, 'expecting' => $sExpectedType, 'expression' => $sExpression, 'attribute' => $sAttribute, 'projection' => $oProjectionSpec)); - } - } -} - class URP_UserProfile extends UserRightsBaseClass { @@ -314,8 +202,7 @@ class URP_UserProfile extends UserRightsBaseClass } } - -class URP_ProfileProjection extends UserRightsBaseClass +class URP_UserOrg extends UserRightsBaseClass { public static function Init() { @@ -323,151 +210,39 @@ class URP_ProfileProjection extends UserRightsBaseClass ( "category" => "addon/userrights", "key_type" => "autoincrement", - "name_attcode" => "profileid", + "name_attcode" => "userid", "state_attcode" => "", "reconc_keys" => array(), - "db_table" => "priv_urp_profileprojection", + "db_table" => "priv_urp_userorg", "db_key_field" => "id", "db_finalclass_field" => "", "display_template" => "", ); MetaModel::Init_Params($aParams); //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("allowed_org_id", array("targetclass"=>"Organization", "jointype"=> "", "allowed_values"=>null, "sql"=>"allowed_org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("allowed_org_name", array("allowed_values"=>null, "extkey_attcode"=> 'allowed_org_id', "target_attcode"=>"name"))); - MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attribute", array("allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); // Display lists - MetaModel::Init_SetZListItems('details', array('dimensionid', 'profileid', 'value', 'attribute')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('profileid', 'value', 'attribute')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('details', array('userid', 'allowed_org_id', 'reason')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('allowed_org_id', 'reason')); // Attributes to be displayed for a list // Search criteria - MetaModel::Init_SetZListItems('standard_search', array('dimensionid', 'profileid')); // Criteria of the std search form - MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'profileid')); // Criteria of the advanced search form + MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form } - protected $m_aUserProjections; // cache - - public function ProjectUser(User $oUser) + public function GetName() { - if (is_array($this->m_aUserProjections)) - { - // Hit! - return $this->m_aUserProjections; - } - - $sExpr = $this->Get('value'); - if ($sExpr == '') - { - $sColumn = $this->Get('attribute'); - if (empty($sColumn)) - { - $aRes = array($oUser->GetKey()); - } - else - { - $aRes = array($oUser->Get($sColumn)); - } - - } - elseif (($sExpr == '') || ($sExpr == '')) - { - $aRes = null; - } - elseif (strtolower(substr($sExpr, 0, 6)) == 'select') - { - $sColumn = $this->Get('attribute'); - // SELECT... - $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); - $aRes = $oValueSetDef->GetValues(array('user' => $oUser), ''); - } - else - { - // Constant value(s) - $aRes = explode(';', trim($sExpr)); - } - $this->m_aUserProjections = $aRes; - return $aRes; + return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name')); } } -class URP_ClassProjection extends UserRightsBaseClass -{ - public static function Init() - { - $aParams = array - ( - "category" => "addon/userrights", - "key_type" => "autoincrement", - "name_attcode" => "dimensionid", - "state_attcode" => "", - "reconc_keys" => array(), - "db_table" => "priv_urp_classprojection", - "db_key_field" => "id", - "db_finalclass_field" => "", - "display_template" => "", - ); - MetaModel::Init_Params($aParams); - //MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeExternalKey("dimensionid", array("targetclass"=>"URP_Dimensions", "jointype"=> "", "allowed_values"=>null, "sql"=>"dimensionid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("dimension", array("allowed_values"=>null, "extkey_attcode"=> 'dimensionid', "target_attcode"=>"name"))); - - MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - - MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("attribute", array("allowed_values"=>null, "sql"=>"attribute", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - - // Display lists - MetaModel::Init_SetZListItems('details', array('dimensionid', 'class', 'value', 'attribute')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('class', 'value', 'attribute')); // Attributes to be displayed for a list - // Search criteria - MetaModel::Init_SetZListItems('standard_search', array('dimensionid', 'class')); // Criteria of the std search form - MetaModel::Init_SetZListItems('advanced_search', array('dimensionid', 'class')); // Criteria of the advanced search form - } - - public function ProjectObject($oObject) - { - $sExpr = $this->Get('value'); - if ($sExpr == '') - { - $sColumn = $this->Get('attribute'); - if (empty($sColumn)) - { - $aRes = array($oObject->GetKey()); - } - else - { - $aRes = array($oObject->Get($sColumn)); - } - - } - elseif (($sExpr == '') || ($sExpr == '')) - { - $aRes = null; - } - elseif (strtolower(substr($sExpr, 0, 6)) == 'select') - { - $sColumn = $this->Get('attribute'); - // SELECT... - $oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/); - $aRes = $oValueSetDef->GetValues(array('this' => $oObject), ''); - } - else - { - // Constant value(s) - $aRes = explode(';', trim($sExpr)); - } - return $aRes; - } - -} - - class URP_ActionGrant extends UserRightsBaseClass { public static function Init() @@ -603,17 +378,6 @@ class UserRightsProfile extends UserRightsAddOnAPI //$oOrg->Set('parent_id', xxx); $iOrgId = $oOrg->DBInsertTrackedNoReload($oChange); - // Location : optional - //$oLocation = new bizLocation(); - //$oLocation->Set('name', 'MyOffice'); - //$oLocation->Set('status', 'implementation'); - //$oLocation->Set('org_id', $iOrgId); - //$oLocation->Set('severity', 'high'); - //$oLocation->Set('address', 'my building in my city'); - //$oLocation->Set('country', 'my country'); - //$oLocation->Set('parent_location_id', xxx); - //$iLocationId = $oLocation->DBInsertNoReload(); - $oContact = new Person(); $oContact->Set('name', 'My last name'); //$oContact->Set('first_name', 'My first name'); @@ -658,7 +422,6 @@ class UserRightsProfile extends UserRightsAddOnAPI SetupProfiles::ComputeITILProfiles(); //SetupProfiles::ComputeBasicProfiles(); - SetupProfiles::DoCreateDimensions(); SetupProfiles::DoCreateProfiles(); return true; } @@ -668,36 +431,19 @@ class UserRightsProfile extends UserRightsAddOnAPI MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData')); } - protected $m_aDimensions = array(); // id -> object - protected $m_aClassProj = array(); // class,dimensionid -> object protected $m_aProfiles = array(); // id -> object protected $m_aUserProfiles = array(); // userid,profileid -> object - protected $m_aProPro = array(); // profileid,dimensionid -> object + protected $m_aUserOrgs = array(); // userid,orgid -> object protected $m_aAdmins = array(); // id of users being linked to the well-known admin profile protected $m_aClassActionGrants = array(); // profile, class, action -> permission protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission - protected $m_aObjectActionGrants = array(); // userid, class, id, action -> permission, list of attributes public function CacheData() { // Could be loaded in a shared memory (?) - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); - $this->m_aDimensions = array(); - while ($oDimension = $oDimensionSet->Fetch()) - { - $this->m_aDimensions[$oDimension->GetKey()] = $oDimension; - } - - $oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ClassProjection")); - $this->m_aClassProjs = array(); - while ($oClassProj = $oClassProjSet->Fetch()) - { - $this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; - } - $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); $this->m_aProfiles = array(); while ($oProfile = $oProfileSet->Fetch()) @@ -717,20 +463,17 @@ class UserRightsProfile extends UserRightsAddOnAPI } } - $oProProSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ProfileProjection")); - $this->m_aProPros = array(); - while ($oProPro = $oProProSet->Fetch()) + $oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserOrg")); + $this->m_aUserOrgs = array(); + while ($oUserOrg = $oUserOrgSet->Fetch()) { - $this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; + $this->m_aUserOrgs[$oUserOrg->Get('userid')][$oUserOrg->Get('allowed_org_id')] = $oUserOrg; } - /* echo "
        \n";
        -		print_r($this->m_aDimensions);
        -		print_r($this->m_aClassProjs);
         		print_r($this->m_aProfiles);
         		print_r($this->m_aUserProfiles);
        -		print_r($this->m_aProPros);
        +		print_r($this->m_aUserOrgs);
         		echo "
        \n"; exit; */ @@ -740,90 +483,50 @@ exit; public function GetSelectFilter($oUser, $sClass) { - $aConditions = array(); - foreach ($this->m_aDimensions as $iDimension => $oDimension) + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); + if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) { - $oClassProj = @$this->m_aClassProjs[$sClass][$iDimension]; - if (is_null($oClassProj)) - { - // Authorize any for this dimension, then no additional criteria is required - continue; - } - - // 1 - Get class projection info - // - $oExpression = null; - $sExpr = $oClassProj->Get('value'); - if ($sExpr == '') - { - $sColumn = $oClassProj->Get('attribute'); - if (empty($sColumn)) - { - $oExpression = new FieldExpression('id', $sClass); - } - else - { - $oExpression = new FieldExpression($sColumn, $sClass); - } - } - elseif (($sExpr == '') || ($sExpr == '')) - { - // Authorize any for this dimension, then no additional criteria is required - continue; - } - elseif (strtolower(substr($sExpr, 0, 6)) == 'select') - { - throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr)); - } - else - { - // Constant value(s) - // unsupported - throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr)); -// $aRes = explode(';', trim($sExpr)); - } - - // 2 - Get profile projection info and use it if needed - // - $aProjections = self::GetReadableProjectionsByDim($oUser, $sClass, $oDimension); - if (is_null($aProjections)) - { - // Authorize any for this dimension, then no additional criteria is required - continue; - } - elseif (count($aProjections) == 0) - { - // Authorize none, then exit as quickly as possible - return false; - } - else - { - // Authorize the given set of values - $oListExpr = ListExpression::FromScalars($aProjections); - $oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr); - $aConditions[] = $oCondition; - } + return false; } - if (count($aConditions) == 0) + // Determine how to position the objects of this class + // + if ($sClass == 'Organization') { - // allow all - return true; + $sAttCode = 'id'; + } + elseif(MetaModel::IsValidAttCode($sClass, 'org_id')) + { + $sAttCode = 'org_id'; } else { - $oFilter = new DBObjectSearch($sClass); - foreach($aConditions as $oCondition) - { - $oFilter->AddConditionExpression($oCondition); - } - //return true; - return $oFilter; + // The objects of this class are not positioned in this dimension + // All of them are visible + return true; } + $oExpression = new FieldExpression($sAttCode, $sClass); + + // Position the user + // + @$aUserOrgs = $this->m_aUserOrgs[$oUser->GetKey()]; + if (!isset($aUserOrgs) || count($aUserOrgs) == 0) + { + // No position means 'Everywhere' + return true; + } + + $aIds = array_keys($aUserOrgs); + $oListExpr = ListExpression::FromScalars($aIds); + $oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr); + + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddConditionExpression($oCondition); + return $oFilter; } // This verb has been made public to allow the development of an accurate feedback for the current configuration - public function GetClassActionGrant($iProfile, $sClass, $sAction) + public function GetProfileActionGrant($iProfile, $sClass, $sAction) { if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) { @@ -846,7 +549,8 @@ exit; } else { - $oGrantRecord = $this->GetClassActionGrant($iProfile, $sParentClass, $sAction); + // Recursively look for the grant record in the class hierarchy + $oGrantRecord = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction); } } @@ -854,49 +558,44 @@ exit; return $oGrantRecord; } - protected function GetObjectActionGrant($oUser, $sClass, $iActionCode, /*DBObject*/ $oObject = null) + protected function GetUserActionGrant($oUser, $sClass, $iActionCode) { - if(is_null($oObject)) - { - $iObjectRef = -999; - } - else - { - $iObjectRef = $oObject->GetKey(); - } - // load and cache permissions for the current user on the given object + // load and cache permissions for the current user on the given class // - $aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode]; + $aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode]; if (is_array($aTest)) return $aTest; $sAction = self::$m_aActionCodes[$iActionCode]; $iInstancePermission = UR_ALLOWED_NO; $aAttributes = array(); - foreach($this->GetMatchingProfiles($oUser, $sClass, $oObject) as $iProfile) + if (isset($this->m_aUserProfiles[$oUser->GetKey()])) { - $oGrantRecord = $this->GetClassActionGrant($iProfile, $sClass, $sAction); - if (is_null($oGrantRecord)) + foreach($this->m_aUserProfiles[$oUser->GetKey()] as $iProfile => $oProfile) { - continue; // loop to the next profile - } - else - { - $iInstancePermission = UR_ALLOWED_YES; - - // update the list of attributes with those allowed for this profile - // - $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); - $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); - $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); - if (count($aProfileAttributes) == 0) + $oGrantRecord = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); + if (is_null($oGrantRecord)) { - $aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass)); - $aAttributes = array_merge($aAttributes, $aAllAttributes); + continue; // loop to the next profile } else { - $aAttributes = array_merge($aAttributes, $aProfileAttributes); + $iInstancePermission = UR_ALLOWED_YES; + + // update the list of attributes with those allowed for this profile + // + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); + $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); + $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); + if (count($aProfileAttributes) == 0) + { + $aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass)); + $aAttributes = array_merge($aAttributes, $aAllAttributes); + } + else + { + $aAttributes = array_merge($aAttributes, $aProfileAttributes); + } } } } @@ -905,7 +604,7 @@ exit; 'permission' => $iInstancePermission, 'attributes' => $aAttributes, ); - $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode] = $aRes; + $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode] = $aRes; return $aRes; } @@ -913,14 +612,14 @@ exit; { if (is_null($oInstanceSet)) { - $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode); + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); return $aObjectPermissions['permission']; } $oInstanceSet->Rewind(); while($oObject = $oInstanceSet->Fetch()) { - $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); + $aObjectPermissions = $this->GetUserActionGrant($oUser, get_class($oObject), $iActionCode); $iInstancePermission = $aObjectPermissions['permission']; if (isset($iGlobalPermission)) @@ -952,7 +651,7 @@ exit; { if (is_null($oInstanceSet)) { - $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode); + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); $aAttributes = $aObjectPermissions['attributes']; if (in_array($sAttCode, $aAttributes)) { @@ -967,8 +666,7 @@ exit; $oInstanceSet->Rewind(); while($oObject = $oInstanceSet->Fetch()) { - $aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject); - + $aObjectPermissions = $this->GetUserActionGrant($oUser, get_class($oObject), $iActionCode); $aAttributes = $aObjectPermissions['attributes']; if (in_array($sAttCode, $aAttributes)) { @@ -1034,13 +732,16 @@ exit; if (is_null($oInstanceSet)) { $iInstancePermission = UR_ALLOWED_NO; - foreach($this->GetMatchingProfiles($oUser, $sClass) as $iProfile) + if (isset($this->m_aUserProfiles[$oUser->GetKey()])) { - $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); - if (!is_null($oGrantRecord)) + foreach($this->m_aUserProfiles[$oUser->GetKey()] as $iProfile => $oProfile) { - // no need to fetch the record, we've requested the records having permission = 'yes' - $iInstancePermission = UR_ALLOWED_YES; + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (!is_null($oGrantRecord)) + { + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; + } } } return $iInstancePermission; @@ -1050,13 +751,16 @@ exit; while($oObject = $oInstanceSet->Fetch()) { $iInstancePermission = UR_ALLOWED_NO; - foreach($this->GetMatchingProfiles($oUser, $sClass, $oObject) as $iProfile) + if (isset($this->m_aUserProfiles[$oUser->GetKey()])) { - $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); - if (!is_null($oGrantRecord)) + foreach($this->m_aUserProfiles[$oUser->GetKey()] as $iProfile => $oProfile) { - // no need to fetch the record, we've requested the records having permission = 'yes' - $iInstancePermission = UR_ALLOWED_YES; + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, get_class($oObject), $sStimulusCode); + if (!is_null($oGrantRecord)) + { + // no need to fetch the record, we've requested the records having permission = 'yes' + $iInstancePermission = UR_ALLOWED_YES; + } } } if (isset($iGlobalPermission)) @@ -1083,168 +787,6 @@ exit; } } - // Copied from GetMatchingProfilesByDim() - // adapted to the optimized implementation of GetSelectFilter() - // Note: shares the cache m_aProPros with GetMatchingProfilesByDim() - // Returns null if any object is readable - // an array of allowed projections otherwise (could be an empty array if none is allowed) - protected function GetReadableProjectionsByDim($oUser, $sClass, $oDimension) - { - // - // Given a dimension, lists the values for which the user will be allowed to read the objects - // - $iUser = $oUser->GetKey(); - $iDimension = $oDimension->GetKey(); - - $aRes = array(); - if (array_key_exists($iUser, $this->m_aUserProfiles)) - { - foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) - { - // user projection to be cached on a given page ! - if (!isset($this->m_aProPros[$iProfile][$iDimension])) - { - // No projection for a given profile: default to 'any' - return null; - } - - $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); - if (is_null($aUserProjection)) - { - // No projection for a given profile: default to 'any' - return null; - } - $aRes = array_unique(array_merge($aRes, $aUserProjection)); - } - } - return $aRes; - } - - // Note: shares the cache m_aProPros with GetReadableProjectionsByDim() - protected function GetMatchingProfilesByDim($oUser, $oObject, $oDimension) - { - // - // List profiles for which the user projection overlaps the object projection in the given dimension - // - $iUser = $oUser->GetKey(); - $sClass = get_class($oObject); - $iPKey = $oObject->GetKey(); - $iDimension = $oDimension->GetKey(); - - if (isset($this->m_aClassProjs[$sClass][$iDimension])) - { - $aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject); - } - else - { - // No projection for a given class: default to 'any' - $aObjectProjection = null; - } - - $aRes = array(); - if (array_key_exists($iUser, $this->m_aUserProfiles)) - { - foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) - { - if (is_null($aObjectProjection)) - { - $aRes[] = $iProfile; - } - else - { - // user projection to be cached on a given page ! - if (isset($this->m_aProPros[$iProfile][$iDimension])) - { - $aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser); - } - else - { - // No projection for a given profile: default to 'any' - $aUserProjection = null; - } - - if (is_null($aUserProjection)) - { - $aRes[] = $iProfile; - } - else - { - $aMatchingValues = array_intersect($aObjectProjection, $aUserProjection); - if (count($aMatchingValues) > 0) - { - $aRes[] = $iProfile; - } - } - } - } - } - return $aRes; - } - - protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object - - protected function GetMatchingProfiles($oUser, $sClass, /*DBObject*/ $oObject = null) - { - $iUser = $oUser->GetKey(); - - if(is_null($oObject)) - { - $iObjectRef = -999; - } - else - { - $iObjectRef = $oObject->GetKey(); - } - - // - // List profiles for which the user projection overlaps the object projection in each and every dimension - // Caches the result - // - $aTest = @$this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef]; - if (is_array($aTest)) - { - return $aTest; - } - - if (is_null($oObject)) - { - if (array_key_exists($iUser, $this->m_aUserProfiles)) - { - $aRes = array_keys($this->m_aUserProfiles[$iUser]); - } - else - { - // no profile has been defined for this user - $aRes = array(); - } - } - else - { - $aProfileRes = array(); - foreach ($this->m_aDimensions as $iDimension => $oDimension) - { - foreach ($this->GetMatchingProfilesByDim($oUser, $oObject, $oDimension) as $iProfile) - { - @$aProfileRes[$iProfile] += 1; - } - } - - $aRes = array(); - $iDimCount = count($this->m_aDimensions); - foreach ($aProfileRes as $iProfile => $iMatches) - { - if ($iMatches == $iDimCount) - { - $aRes[] = $iProfile; - } - } - } - - // store into the cache - $this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef] = $aRes; - return $aRes; - } - public function FlushPrivileges() { $this->CacheData(); @@ -1259,13 +801,6 @@ exit; // class SetupProfiles { - protected static $m_aDimensions = array( - 'organization' => array( - 'description' => '', - 'type' => 'Organization', - ), - ); - protected static $m_aActions = array( UR_ACTION_READ => 'Read', UR_ACTION_MODIFY => 'Modify', @@ -1280,38 +815,6 @@ class SetupProfiles protected static $m_aModules = array(); protected static $m_aProfiles = array(); - protected static function DoCreateClassProjection($iDimension, $sClass) - { - $oNewObj = MetaModel::NewObject("URP_ClassProjection"); - $oNewObj->Set('dimensionid', $iDimension); - $oNewObj->Set('class', $sClass); - $oNewObj->Set('attribute', ''); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - protected static function DoCreateDimension($sName, $aDimensionData) - { - $oNewObj = MetaModel::NewObject("URP_Dimensions"); - $oNewObj->Set('name', $sName); - $oNewObj->Set('description', $aDimensionData['description']); - $oNewObj->Set('type', $aDimensionData['type']); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - - protected static function DoCreateProfileProjection($iProfile, $iDimension) - { - $oNewObj = MetaModel::NewObject("URP_ProfileProjection"); - $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('dimensionid', $iDimension); - $oNewObj->Set('value', ''); - $oNewObj->Set('attribute', ''); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - protected static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) { @@ -1365,15 +868,6 @@ class SetupProfiles $oNewObj->Set('description', $sDescription); $iProfile = $oNewObj->DBInsertNoReload(); - // Project in every dimension - // - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $iDimension = $oDimension->GetKey(); - self::DoCreateProfileProjection($iProfile, $iDimension); - } - // Grant read rights for everything // foreach (MetaModel::GetClasses('bizmodel') as $sClass) @@ -1441,27 +935,6 @@ class SetupProfiles } } - public static function DoCreateDimensions() - { - $aClass = MetaModel::GetClasses(); - foreach(self::$m_aDimensions as $sName => $aDimensionData) - { - $iDimension = self::DoCreateDimension($sName, $aDimensionData); - - foreach($aClass as $sClass) - { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - - if (!MetaModel::IsValidClass($sClass)) - { - throw new CoreException("Invalid class name '$sClass'"); - } - self::DoCreateClassProjection($iDimension, $sClass); - } - } - } - public static function DoCreateProfiles() { self::DoCreateAdminProfile(); diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php index a0961b5941..077d451afc 100644 --- a/addons/userrights/userrightsprojection.class.inc.php +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -15,8 +15,8 @@ // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA /** - * UserRightsProfile - * User management Module, basing the right on profiles and a matrix (similar to UserRightsMatrix, but profiles and other decorations have been added) + * UserRightsProjection + * User management Module, basing the right on profiles and a matrix (similar to UserRightsProfile, but enhanced with dimensions and projection of classes and profile over the dimensions) * * @author Erwan Taloc * @author Romain Quetiez @@ -105,7 +105,7 @@ class URP_Profiles extends UserRightsBaseClass return; } - // Note: for sure, we assume that the instance is derived from UserRightsProfile + // Note: for sure, we assume that the instance is derived from UserRightsProjection $oUserRights = UserRights::GetModuleInstance(); $aDisplayData = array(); @@ -576,7 +576,7 @@ class URP_AttributeGrant extends UserRightsBaseClass -class UserRightsProfile extends UserRightsAddOnAPI +class UserRightsProjection extends UserRightsAddOnAPI { static public $m_aActionCodes = array( UR_ACTION_READ => 'read', @@ -1589,6 +1589,6 @@ class SetupProfiles } } -UserRights::SelectModule('UserRightsProfile'); +UserRights::SelectModule('UserRightsProjection'); ?> diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index a4341a10fc..bd4e91e72a 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -95,9 +95,10 @@ abstract class User extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>"EN US", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profile_list", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("allowed_org_list", array("linked_class"=>"URP_UserOrg", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"allowed_org_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); // Display lists - MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form @@ -228,7 +229,7 @@ abstract class UserInternal extends User MetaModel::Init_InheritAttributes(); // Display lists - MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index 8602a66024..c2b11d927e 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -47,7 +47,7 @@ class UserLocal extends UserInternal MetaModel::Init_AddAttribute(new AttributePassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); // Display lists - MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'password', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'password', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form From d0cfd699a4901fafae1088f73afd461916db4d41 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 23 Aug 2010 17:01:40 +0000 Subject: [PATCH 610/970] Fixed for Trac #223: when checking the value of an attribute also use the pattern matching defined at the atribute level itself to ensure consistency. SVN:trunk[693] --- core/dbobject.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 33438af1a3..751ec39878 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -648,7 +648,7 @@ abstract class DBObject } } } - return true; + return $oAtt->CheckValue($toCheck); // Check the format } // check attributes together From 9748f8a6b3a3e7e681a8c5138e781ee30ad4a853 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 24 Aug 2010 09:40:42 +0000 Subject: [PATCH 611/970] Allow the ADministrator to load more objects via CSV load (Users, Audit rules) SVN:trunk[694] --- pages/csvimport.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 881dcaede5..14bc4a0101 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -52,7 +52,14 @@ function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null $sHtml = "\n"); AddParamsToForm($oP, $aParamValues, array('sample_data')); - if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) && UserRights::Setup()) + if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd, $sLanguage)) { $oP->add("

        Loading of sample data

        \n"); $oP->p("
        Do you want to load sample data into the database ? \n"); diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 035c38a1a6..8e416a8f91 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -202,10 +202,12 @@ class XMLDataLoader else { // tested by Romain, little impact on perf (not significant on the intial setup) - if (!$oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode)) + $res = $oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode); + if ($res !== true) { - SetupWebPage::log_error("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."'"); - throw(new Exception("Wrong value for attribute $sAttCode: '".$oXmlObj->$sAttCode."'")); + // $res contains the error description + SetupWebPage::log_error("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."' ; $res"); + throw(new Exception("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."' ; $res")); } $oTargetObj->Set($sAttCode, (string)$oXmlObj->$sAttCode); } diff --git a/webservices/webservices.class.inc.php b/webservices/webservices.class.inc.php index 8af6eca102..8989e51b50 100644 --- a/webservices/webservices.class.inc.php +++ b/webservices/webservices.class.inc.php @@ -280,15 +280,15 @@ class WebServices */ protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes) { - if ($oTargetObj->CheckValue($sAttCode, $value)) + $res = $oTargetObj->CheckValue($sAttCode, $value); + if ($res === true) { $oTargetObj->Set($sAttCode, $value); } else { - $aAllowedValues = MetaModel::GetAllowedValues_att(get_class($oTargetObj), $sAttCode); - $sValues = implode(', ', $aAllowedValues); - $oRes->LogError("Parameter $sParamName: found '$value' while expecting a value in {".$sValues."}"); + // $res contains the error description + $oRes->LogError("Unexpected value for parameter $sParamName: $res"); } } @@ -483,7 +483,7 @@ class WebServices { if ($oRes->IsOk()) { - list($bRes, $aIssues) = $oTargetObj->CheckToInsert(); + list($bRes, $aIssues) = $oTargetObj->CheckToWrite(); if ($bRes) { $iId = $oTargetObj->DBInsertTrackedNoReload($oChange); @@ -493,6 +493,10 @@ class WebServices else { $oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)"); + foreach($aIssues as $iIssue => $sIssue) + { + $oRes->LogError("Issue #$iIssue: $sIssue"); + } } } } From 7c91d266ea2447134a2a2073aaac5f63c9016925 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 25 Aug 2010 15:18:03 +0000 Subject: [PATCH 615/970] Typo in dictionary SVN:trunk[698] --- dictionaries/fr.dictionary.itop.ui.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index c102072e82..7693f56b5a 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -163,7 +163,7 @@ Dict::Add('FR FR', 'French', 'Français', array( // Class: URP_UserOrg // -Dict::Add('EN US', 'English', 'English', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:URP_UserOrg' => 'Utilisateur/Organisation', 'Class:URP_UserOrg+' => 'Organizations permises pour l\'utilisateur', 'Class:URP_UserProfile/Attribute:userid' => 'Utilisateur', From 34cd4a01dab71622970beb501675db660ba6a9a5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 25 Aug 2010 15:27:44 +0000 Subject: [PATCH 616/970] Allowed were missing on LDAP/External user logins SVN:trunk[699] --- modules/authent-external/model.authent-external.php | 2 +- modules/authent-ldap/model.authent-ldap.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/authent-external/model.authent-external.php b/modules/authent-external/model.authent-external.php index f42b2e65f9..18d440cacd 100644 --- a/modules/authent-external/model.authent-external.php +++ b/modules/authent-external/model.authent-external.php @@ -50,7 +50,7 @@ class UserExternal extends User MetaModel::Init_InheritAttributes(); // Display lists - MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form diff --git a/modules/authent-ldap/model.authent-ldap.php b/modules/authent-ldap/model.authent-ldap.php index 0f255a40f9..6509363441 100644 --- a/modules/authent-ldap/model.authent-ldap.php +++ b/modules/authent-ldap/model.authent-ldap.php @@ -45,7 +45,7 @@ class UserLDAP extends UserInternal MetaModel::Init_InheritAttributes(); // Display lists - MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form From b6b8c432a39a19616e6a5282841c74366cf97db8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 25 Aug 2010 15:53:59 +0000 Subject: [PATCH 617/970] - Check for some optional extensions (mcrypt, ldap,...) during the setup at tell the user what are the consequences. SVN:trunk[700] --- setup/index.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/setup/index.php b/setup/index.php index e1dd3122c2..7eb710b534 100644 --- a/setup/index.php +++ b/setup/index.php @@ -166,10 +166,14 @@ function CheckPHPVersion(SetupWebPage $oP) return false; } $aMandatoryExtensions = array('mysql', 'iconv', 'simplexml', 'soap'); + $aOptionalExtensions = array('mcrypt' => 'Strong encryption will not be used.', + 'ldap' => 'LDAP authentication will be disabled.'); asort($aMandatoryExtensions); // Sort the list to look clean ! + ksort($aOptionalExtensions); // Sort the list to look clean ! $aExtensionsOk = array(); $aMissingExtensions = array(); $aMissingExtensionsLinks = array(); + // First check the mandatory extensions foreach($aMandatoryExtensions as $sExtension) { if (extension_loaded($sExtension)) @@ -191,6 +195,31 @@ function CheckPHPVersion(SetupWebPage $oP) $oP->error("Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks)."."); $bResult = false; } + // Next check the optional extensions + $aExtensionsOk = array(); + $aMissingExtensions = array(); + foreach($aOptionalExtensions as $sExtension => $sMessage) + { + if (extension_loaded($sExtension)) + { + $aExtensionsOk[] = $sExtension; + } + else + { + $aMissingExtensions[$sExtension] = $sMessage; + } + } + if (count($aExtensionsOk) > 0) + { + $oP->ok("Optional PHP extension(s): ".implode(', ', $aExtensionsOk)."."); + } + if (count($aMissingExtensions) > 0) + { + foreach($aMissingExtensions as $sExtension => $sMessage) + { + $oP->warning("Missing optional PHP extension: $sExtension. ".$sMessage); + } + } // Check some ini settings here if (function_exists('php_ini_loaded_file')) // PHP >= 5.2.4 { From ecd017a03068e6c85282a95b2899ba8273e90351 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 25 Aug 2010 16:21:06 +0000 Subject: [PATCH 618/970] #200 ToOQL() was not working anytime an argument was set onto the Filter object SVN:trunk[701] --- core/dbobjectsearch.class.php | 11 ++++------- core/metamodel.class.php | 7 +++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 68cc4230bb..47253927b5 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -560,14 +560,12 @@ class DBObjectSearch public function ToOQL(&$aParams = null) { - $bRetrofitParams = (!is_null($aParams)); + // Currently unused, but could be useful later + $bRetrofitParams = false; + if (is_null($aParams)) { - if (count($this->m_aParams) > 0) - { - $aParams = $this->m_aParams; - } - $bRetrofitParams = false; + // Leave it as is, the rendering will be made with parameters in clear } else { @@ -575,7 +573,6 @@ class DBObjectSearch { $aParams = array_merge($aParams, $this->m_aParams); } - $bRetrofitParams = true; } $sSelectedClasses = implode(', ', array_keys($this->m_aSelectedClasses)); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index b18a23dbae..a4217a3ddd 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1533,8 +1533,7 @@ abstract class MetaModel // Query caching // $bQueryCacheEnabled = true; - $aParams = array(); - $sOqlQuery = $oFilter->ToOql($aParams); // Render with arguments in clear + $sOqlQuery = $oFilter->ToOql(); if ($bQueryCacheEnabled) { // Warning: using directly the query string as the key to the hash array can FAIL if the string @@ -1553,6 +1552,10 @@ abstract class MetaModel $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; } } + else + { + $sOqlQuery = "dummy"; + } if (!isset($oSelect)) { From 11be873fe4dbc70beffd889126e52df4549a408b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 25 Aug 2010 16:35:35 +0000 Subject: [PATCH 619/970] - Fixed bug #222: properly setup dependent fields in the form when creating a new object. SVN:trunk[702] --- application/cmdbabstract.class.inc.php | 56 ++++++++++++++++++-------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index a0cbf37d56..37a6908da7 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -475,6 +475,22 @@ abstract class cmdbAbstractObject extends CMDBObject } $sHtml .= ''; $sColspan = ''; + if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) + { + // list truncated + $divId = $aExtraParams['block_id']; + $sFilter = $oSet->GetFilter()->serialize(); + $aExtraParams['display_limit'] = false; // To expand the full list + $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them + $sHtml .= ''; } @@ -1218,6 +1218,30 @@ EOF { $oObj = clone $oObjectToClone; } + // Pre-fill the object with default values, when there is only on possible choice + // AND the field is mandatory (otherwise there is always the possiblity to let it empty) + $aArgs['this'] = $oObj; + $aDetailsList = self::FLattenZList(MetaModel::GetZListItems($sClass, 'details')); + // TO DO: the list should be ordered based on the dependencies between fields + // i.e. if some fields depend on another field, the latter should be computed first... + // Right now we depend on the display order... anyhow if the display order does not + // reflect the dependency order, the UI will not be so intuitive to use... beware + foreach($aDetailsList as $sAttCode) + { + $bMandatory = false; + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); + if (count($aAllowedValues) == 1) + { + // If the field is mandatory, set it to the only possible value + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $iFlags = $oObj->GetAttributeFlags($sAttCode); + if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) + { + $aValues = array_keys($aAllowedValues); + $oObj->Set($sAttCode, $aValues[0]); + } + } + } return $oObj->DisplayModifyForm( $oPage, $aExtraParams = array()); } From ea33128df896057b53d20021508c7d725025ceda Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 26 Aug 2010 09:51:30 +0000 Subject: [PATCH 620/970] #227 Could not search on Server/asset_ref SVN:trunk[703] --- core/metamodel.class.php | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a4217a3ddd..6b9ab63e61 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1002,18 +1002,19 @@ abstract class MetaModel foreach ($oAttDef->GetFilterDefinitions() as $sFilterCode => $oFilterDef) { self::$m_aFilterDefs[$sClass][$sFilterCode] = $oFilterDef; + + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $oKeyDef = self::GetAttributeDef($sClass, $sKeyAttCode); + self::$m_aFilterOrigins[$sClass][$sFilterCode] = $oKeyDef->GetTargetClass(); + } + else + { + self::$m_aFilterOrigins[$sClass][$sFilterCode] = self::$m_aAttribOrigins[$sClass][$sAttCode]; + } } - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $oKeyDef = self::GetAttributeDef($sClass, $sKeyAttCode); - self::$m_aFilterOrigins[$sClass][$sFilterCode] = $oKeyDef->GetTargetClass(); - } - else - { - self::$m_aFilterOrigins[$sClass][$sFilterCode] = self::$m_aAttribOrigins[$sClass][$sAttCode]; - } // Compute the fields that will be used to display a pointer to another object // if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) @@ -3138,6 +3139,12 @@ abstract class MetaModel // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) self::InitPlugins(); + + if (false) + { + echo "Debug
        \n"; + self::static_var_dump(); + } } public static function LoadConfig($sConfigFile) From 38fec49fe557a4dff20d5b0bcf5a34f450c5d40a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 26 Aug 2010 15:46:23 +0000 Subject: [PATCH 621/970] - First spanish localization (Many thanks to Randall Badilla Castro from Costa Rica) SVN:trunk[704] --- core/config.class.inc.php | 3 + dictionaries/es_cr.dictionary.itop.core.php | 377 +++++++ dictionaries/es_cr.dictionary.itop.ui.php | 834 +++++++++++++++ modules/authent-local/model.authent-local.php | 1 + .../disabled.module.itop-basic.php | 1 + .../es_cr.dict.itop-basic.php | 50 + .../es_cr.dict.itop-change-mgmt.php | 340 +++++++ .../module.itop-change-mgmt.php | 1 + .../es_cr.dict.itop-config-mgmt.php | 959 ++++++++++++++++++ .../module.itop-config-mgmt.php | 1 + .../es_cr.dict.itop-incident-mgmt.php | 68 ++ .../module.itop-incident-mgmt.php | 1 + .../es_cr.dict.itop-knownerror-mgmt.php | 50 + .../module.itop-knownerror-mgmt.php | 1 + .../es_cr.dict.itop-problem-mgmt.php | 50 + .../module.itop-problem-mgmt.php | 1 + .../es_cr.dict.itop-request-mgmt.php | 71 ++ .../es_cr.dict.itop-service-mgmt.php | 430 ++++++++ .../module.itop-service-mgmt.php | 1 + .../module.itop-tickets.php | 1 + 20 files changed, 3241 insertions(+) create mode 100644 dictionaries/es_cr.dictionary.itop.core.php create mode 100644 dictionaries/es_cr.dictionary.itop.ui.php create mode 100644 modules/itop-basic-1.0.0/es_cr.dict.itop-basic.php create mode 100644 modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php create mode 100644 modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php create mode 100644 modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php create mode 100644 modules/itop-knownerror-mgmt-1.0.0/es_cr.dict.itop-knownerror-mgmt.php create mode 100644 modules/itop-problem-mgmt-1.0.0/es_cr.dict.itop-problem-mgmt.php create mode 100644 modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php create mode 100644 modules/itop-service-mgmt-1.0.0/es_cr.dict.itop-service-mgmt.php diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 07bfcc7a7b..8b83ed22b5 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -154,6 +154,9 @@ class Config '../dictionaries/dictionary.itop.core.php', '../dictionaries/dictionary.itop.ui.php', // Support for English '../dictionaries/fr.dictionary.itop.ui.php', // Support for French + '../dictionaries/fr.dictionary.itop.core.php', // Support for French + '../dictionaries/es_cr.dictionary.itop.ui.php', // Support for Spanish (from Costa Rica) + '../dictionaries/es_cr.dictionary.itop.core.php', // Support for Spanish (from Costa Rica) ); $this->m_sDBHost = ''; diff --git a/dictionaries/es_cr.dictionary.itop.core.php b/dictionaries/es_cr.dictionary.itop.core.php new file mode 100644 index 0000000000..e56d1c89aa --- /dev/null +++ b/dictionaries/es_cr.dictionary.itop.core.php @@ -0,0 +1,377 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +////////////////////////////////////////////////////////////////////// +// Classes in 'core/cmdb' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: CMDBChange +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChange' => 'Modificación', + 'Class:CMDBChange+' => 'Seguimiento a Modificaciones', + 'Class:CMDBChange/Attribute:date' => 'fecha', + 'Class:CMDBChange/Attribute:date+' => 'fecha y hora en que los cambios fueron registrados', + 'Class:CMDBChange/Attribute:userinfo' => 'misc. info', + 'Class:CMDBChange/Attribute:userinfo+' => 'información definida por el solicitante', +)); + +// +// Class: CMDBChangeOp +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOp' => 'Operacion de Modificación', + 'Class:CMDBChangeOp+' => 'Seguimiento Operaciones de Modificación', + 'Class:CMDBChangeOp/Attribute:change' => 'modificación', + 'Class:CMDBChangeOp/Attribute:change+' => 'modificación', + 'Class:CMDBChangeOp/Attribute:date' => 'fecha', + 'Class:CMDBChangeOp/Attribute:date+' => 'fecha y hora de la modificacón', + 'Class:CMDBChangeOp/Attribute:userinfo' => 'usuario', + 'Class:CMDBChangeOp/Attribute:userinfo+' => 'quien hizo este cambio', + 'Class:CMDBChangeOp/Attribute:objclass' => 'clase de objeto', + 'Class:CMDBChangeOp/Attribute:objclass+' => 'clase de objeto', + 'Class:CMDBChangeOp/Attribute:objkey' => 'id de objeto', + 'Class:CMDBChangeOp/Attribute:objkey+' => 'id de objeto', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'tipo', + 'Class:CMDBChangeOp/Attribute:finalclass+' => '', +)); + +// +// Class: CMDBChangeOpCreate +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOpCreate' => 'creación de objeto', + 'Class:CMDBChangeOpCreate+' => 'Seguimiento de creación de objeto', +)); + +// +// Class: CMDBChangeOpDelete +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOpDelete' => 'borrado de objeto', + 'Class:CMDBChangeOpDelete+' => 'Seguimiento de borrado de objeto', +)); + +// +// Class: CMDBChangeOpSetAttribute +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOpSetAttribute' => 'modificación de objeto', + 'Class:CMDBChangeOpSetAttribute+' => 'Seguimiento de modificacion de propiedades de objeto', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Atributo', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => 'código de la propiedad modificada', +)); + +// +// Class: CMDBChangeOpSetAttributeScalar +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOpSetAttributeScalar' => 'modificación de propiedad', + 'Class:CMDBChangeOpSetAttributeScalar+' => 'Seguimiento de modificación de propiedades escalares del objeto', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Valor anterior', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => 'valor anterior del atributo', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Nuevo valor', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'nuevo valor del atributo', +)); + +// +// Class: CMDBChangeOpSetAttributeBlob +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOpSetAttributeBlob' => 'modificación de datos', + 'Class:CMDBChangeOpSetAttributeBlob+' => 'seguimiento de modificación de datos', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Dato anterior', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'contenido anterior del atributo', +)); + +// +// Class: CMDBChangeOpSetAttributeText +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CMDBChangeOpSetAttributeText' => 'modificación de texto', + 'Class:CMDBChangeOpSetAttributeText+' => 'seguimiento de modificación de texto', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Dato anterior', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'contenido anterior del atributo', +)); + +// +// Class: Event +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Event' => 'Bitacora de Evento', + 'Class:Event+' => 'evento interno de aplicación', + 'Class:Event/Attribute:message' => 'mensaje', + 'Class:Event/Attribute:message+' => 'corta descripción del evento', + 'Class:Event/Attribute:date' => 'fecha', + 'Class:Event/Attribute:date+' => 'fecha y hora en que las modificaciones fueron registradas', + 'Class:Event/Attribute:userinfo' => 'información de usuario', + 'Class:Event/Attribute:userinfo+' => 'indentificación de la actividad que realizaba el usuario durante la cual se disparó este evento', + 'Class:Event/Attribute:finalclass' => 'tipo', + 'Class:Event/Attribute:finalclass+' => '', +)); + +// +// Class: EventNotification +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:EventNotification' => 'Notificación de evento', + 'Class:EventNotification+' => 'Seguimiento de notificación enviada', + 'Class:EventNotification/Attribute:trigger_id' => 'Disparador', + 'Class:EventNotification/Attribute:trigger_id+' => 'cuenta de usuario', + 'Class:EventNotification/Attribute:action_id' => 'usuario', + 'Class:EventNotification/Attribute:action_id+' => 'cuenta de usuario', + 'Class:EventNotification/Attribute:object_id' => 'Id de Objeto', + 'Class:EventNotification/Attribute:object_id+' => 'id de objeto (¿clase definida por el disparador?)', +)); + +// +// Class: EventNotificationEmail +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:EventNotificationEmail' => 'Emision de correo electrónico de evento', + 'Class:EventNotificationEmail+' => 'Evidencia de correo electrónico enviado', + 'Class:EventNotificationEmail/Attribute:to' => 'Destinatario', + 'Class:EventNotificationEmail/Attribute:to+' => 'Destinatario', + 'Class:EventNotificationEmail/Attribute:cc' => 'C.C', + 'Class:EventNotificationEmail/Attribute:cc+' => 'C.C', + 'Class:EventNotificationEmail/Attribute:bcc' => 'C.C.O', + 'Class:EventNotificationEmail/Attribute:bcc+' => 'C.C.O', + 'Class:EventNotificationEmail/Attribute:from' => 'Remitente', + 'Class:EventNotificationEmail/Attribute:from+' => 'Remitente del mensaje', + 'Class:EventNotificationEmail/Attribute:subject' => 'Asunto', + 'Class:EventNotificationEmail/Attribute:subject+' => 'Asunto', + 'Class:EventNotificationEmail/Attribute:body' => 'Cuerpo del mensaje', + 'Class:EventNotificationEmail/Attribute:body+' => 'Cuerpo del mensaje', +)); + +// +// Class: EventIssue +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:EventIssue' => 'Registro de Evento', + 'Class:EventIssue+' => 'Evidencia de un evento (warning, error, etc.)', + 'Class:EventIssue/Attribute:issue' => 'Evento', + 'Class:EventIssue/Attribute:issue+' => 'Qué pasó', + 'Class:EventIssue/Attribute:impact' => 'Impacto', + 'Class:EventIssue/Attribute:impact+' => 'Cuales son las consecuencias', + 'Class:EventIssue/Attribute:page' => 'Página', + 'Class:EventIssue/Attribute:page+' => 'Punto de entrada HTTP', + 'Class:EventIssue/Attribute:arguments_post' => 'Argumentos usados', + 'Class:EventIssue/Attribute:arguments_post+' => 'Argumentos HTTP POST', + 'Class:EventIssue/Attribute:arguments_get' => 'Argumentos URL', + 'Class:EventIssue/Attribute:arguments_get+' => 'Argumentos HTTP GET', + 'Class:EventIssue/Attribute:callstack' => 'Secuencia de llamadas', + 'Class:EventIssue/Attribute:callstack+' => 'Pila de llamadas', + 'Class:EventIssue/Attribute:data' => 'Datos', + 'Class:EventIssue/Attribute:data+' => 'Más información', +)); + +// +// Class: EventWebService +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:EventWebService' => 'Evento de servicio Web', + 'Class:EventWebService+' => 'Evidencia de una llamada de servicio Web', + 'Class:EventWebService/Attribute:verb' => 'Verbo', + 'Class:EventWebService/Attribute:verb+' => 'Nombre de la operación', + 'Class:EventWebService/Attribute:result' => 'Resultado', + 'Class:EventWebService/Attribute:result+' => 'Exito/Falla Total', + 'Class:EventWebService/Attribute:log_info' => 'Bitácora de Información', + 'Class:EventWebService/Attribute:log_info+' => 'Bitácora de Resultado', + 'Class:EventWebService/Attribute:log_warning' => 'Bitácora de Advertencia', + 'Class:EventWebService/Attribute:log_warning+' => 'Bitácora de Resultado de Advertencia', + 'Class:EventWebService/Attribute:log_error' => 'Bitácora de Error', + 'Class:EventWebService/Attribute:log_error+' => 'Bitácora de Error de Resultado', + 'Class:EventWebService/Attribute:data' => 'Datos', + 'Class:EventWebService/Attribute:data+' => 'Datos de Resultado', +)); + +// +// Class: Action +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Action' => 'Custom Action', + 'Class:Action+' => 'Acción definida por el usuario', + 'Class:Action/Attribute:name' => 'Nombre', + 'Class:Action/Attribute:name+' => '', + 'Class:Action/Attribute:description' => 'Descripción', + 'Class:Action/Attribute:description+' => '', + 'Class:Action/Attribute:status' => 'Estado', + 'Class:Action/Attribute:status+' => 'En produccion o ?', +//The following value is linked with 'Class:ActionEmail/Attribute:test_recipient+' => ? + 'Class:Action/Attribute:status/Value:test' => 'En pruebas', + 'Class:Action/Attribute:status/Value:test+' => 'En pruebas', + 'Class:Action/Attribute:status/Value:enabled' => 'En producción', + 'Class:Action/Attribute:status/Value:enabled+' => 'En producción', + 'Class:Action/Attribute:status/Value:disabled' => 'Inactivo', + 'Class:Action/Attribute:status/Value:disabled+' => 'Inactivo', + 'Class:Action/Attribute:trigger_list' => 'Disparadores relacionados', + 'Class:Action/Attribute:trigger_list+' => 'Disparadores asociados a esta acción', + 'Class:Action/Attribute:finalclass' => 'Tipo', + 'Class:Action/Attribute:finalclass+' => '', +)); + +// +// Class: ActionNotification +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ActionNotification' => 'Notificación', + 'Class:ActionNotification+' => 'Notificación (resúmen)', +)); + +// +// Class: ActionEmail +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ActionEmail' => 'Notificación por correo electrónico', + 'Class:ActionEmail+' => '', + 'Class:ActionEmail/Attribute:test_recipient' => 'Destinatario de prueba', + 'Class:ActionEmail/Attribute:test_recipient+' => 'Destinatario en caso que el estado sea "En pruebas"', + 'Class:ActionEmail/Attribute:from' => 'Remitente', + 'Class:ActionEmail/Attribute:from+' => 'Será enviando en el encabezado del correo electrónico', + 'Class:ActionEmail/Attribute:reply_to' => 'Responder a', + 'Class:ActionEmail/Attribute:reply_to+' => 'Será enviando en el encabezado del correo electrónico', + 'Class:ActionEmail/Attribute:to' => 'Destinatario', + 'Class:ActionEmail/Attribute:to+' => 'Destinatario del correo electrónico', + 'Class:ActionEmail/Attribute:cc' => 'C.C', + 'Class:ActionEmail/Attribute:cc+' => 'Copia al carbón', + 'Class:ActionEmail/Attribute:bcc' => 'C.C.O', + 'Class:ActionEmail/Attribute:bcc+' => 'Copia al carbón oculta', + 'Class:ActionEmail/Attribute:subject' => 'asunto', + 'Class:ActionEmail/Attribute:subject+' => 'Asunto del correo electrónico', + 'Class:ActionEmail/Attribute:body' => 'cuerpo', + 'Class:ActionEmail/Attribute:body+' => 'Contenido del correo electronico', + 'Class:ActionEmail/Attribute:importance' => 'importancia', + 'Class:ActionEmail/Attribute:importance+' => 'Bandera de importancia', + 'Class:ActionEmail/Attribute:importance/Value:low' => 'baja', + 'Class:ActionEmail/Attribute:importance/Value:low+' => 'baja', + 'Class:ActionEmail/Attribute:importance/Value:normal' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:normal+' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:high' => 'alta', + 'Class:ActionEmail/Attribute:importance/Value:high+' => 'alta', +)); + +// +// Class: Trigger +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Trigger' => 'Disparador', + 'Class:Trigger+' => 'Custom event handler', + 'Class:Trigger/Attribute:description' => 'Descripción', + 'Class:Trigger/Attribute:description+' => 'descripción en una línea', + 'Class:Trigger/Attribute:action_list' => 'Acciones disparadas', + 'Class:Trigger/Attribute:action_list+' => 'Acciones realizadas cuando se activó el disparador', + 'Class:Trigger/Attribute:finalclass' => 'Tipo', + 'Class:Trigger/Attribute:finalclass+' => '', +)); + +// +// Class: TriggerOnObject +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:TriggerOnObject' => 'Disparador (Depende de la clase)', + 'Class:TriggerOnObject+' => 'Disparador en una clase de objeto dada', + 'Class:TriggerOnObject/Attribute:target_class' => 'Clase destino', + 'Class:TriggerOnObject/Attribute:target_class+' => '', +)); + +// +// Class: TriggerOnStateChange +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:TriggerOnStateChange' => 'Disparador (en cambio de estado)', + 'Class:TriggerOnStateChange+' => 'Disparador en cambio de estado de objeto', + 'Class:TriggerOnStateChange/Attribute:state' => 'Estado', + 'Class:TriggerOnStateChange/Attribute:state+' => '', +)); + +// +// Class: TriggerOnStateEnter +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:TriggerOnStateEnter' => 'Disparador (entrando a un estado)', + 'Class:TriggerOnStateEnter+' => 'Disparador en cambio de estado de objeto - entrando', +)); + +// +// Class: TriggerOnStateLeave +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:TriggerOnStateLeave' => 'Disparador (saliendo de un estado)', + 'Class:TriggerOnStateLeave+' => 'Disparador en cambio de estado de objeto - saliendo', +)); + +// +// Class: TriggerOnObjectCreate +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:TriggerOnObjectCreate' => 'Disparador (creación de objeto)', + 'Class:TriggerOnObjectCreate+' => 'Disparador en la creación de objeto (hija de clase) de una clase dada', +)); + +// +// Class: lnkTriggerAction +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkTriggerAction' => 'Acción/Disparador', + 'Class:lnkTriggerAction+' => 'Asociación entre un disparador y una acción', + 'Class:lnkTriggerAction/Attribute:action_id' => 'Acción', + 'Class:lnkTriggerAction/Attribute:action_id+' => 'Acción a ser realizada', + 'Class:lnkTriggerAction/Attribute:action_name' => 'Acción', + 'Class:lnkTriggerAction/Attribute:action_name+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_id' => 'Disparador', + 'Class:lnkTriggerAction/Attribute:trigger_id+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_name' => 'Disparador', + 'Class:lnkTriggerAction/Attribute:trigger_name+' => '', + 'Class:lnkTriggerAction/Attribute:order' => 'Orden', + 'Class:lnkTriggerAction/Attribute:order+' => 'Orden de realización de acciones', +)); +?> diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php new file mode 100644 index 0000000000..433ade8d2d --- /dev/null +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -0,0 +1,834 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +////////////////////////////////////////////////////////////////////// +// Classes in 'gui' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: menuNode +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:menuNode' => 'Nodo de menú', + 'Class:menuNode+' => 'Menú principal de configuración de elementos', + 'Class:menuNode/Attribute:name' => 'Nombre de Menú', + 'Class:menuNode/Attribute:name+' => 'Nombre corto para este menú', + 'Class:menuNode/Attribute:label' => 'Descripción del menú', + 'Class:menuNode/Attribute:label+' => 'Descripción larga para este menú', + 'Class:menuNode/Attribute:hyperlink' => 'Liga', + 'Class:menuNode/Attribute:hyperlink+' => 'Liga (URL) hacia la página', + 'Class:menuNode/Attribute:icon_path' => 'Ícono de menú', + 'Class:menuNode/Attribute:icon_path+' => 'Ruta hacia el ícono del menú', + 'Class:menuNode/Attribute:template' => 'Plantilla', + 'Class:menuNode/Attribute:template+' => 'Plantilla HTML para la vista', + 'Class:menuNode/Attribute:type' => 'Tipo', + 'Class:menuNode/Attribute:type+' => 'Tipo de menú', + 'Class:menuNode/Attribute:type/Value:application' => 'aplicación', + 'Class:menuNode/Attribute:type/Value:application+' => 'aplicación', + 'Class:menuNode/Attribute:type/Value:user' => 'usuario', + 'Class:menuNode/Attribute:type/Value:user+' => 'usuario', + 'Class:menuNode/Attribute:type/Value:administrator' => 'administrator', + 'Class:menuNode/Attribute:type/Value:administrator+' => 'administrator', + 'Class:menuNode/Attribute:rank' => 'Muestra categoría', + 'Class:menuNode/Attribute:rank+' => 'Orden de despliegue del menú', + 'Class:menuNode/Attribute:parent_id' => 'Ítem del Menú Padre', + 'Class:menuNode/Attribute:parent_id+' => 'Ítem del Menú Padre', + 'Class:menuNode/Attribute:parent_name' => 'Ítem del Menú Padre', + 'Class:menuNode/Attribute:parent_name+' => 'Ítem del Menú Padre', + 'Class:menuNode/Attribute:user_id' => 'Dueño del menú', + 'Class:menuNode/Attribute:user_id+' => 'Usuario dueño de este menú (para menúes definidos por el usuario)', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'application' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: AuditCategory +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:AuditCategory' => 'Categoría Auditoría', + 'Class:AuditCategory+' => 'Una sección intrínseca a la auditoría', + 'Class:AuditCategory/Attribute:name' => 'Nombre de Categoría', + 'Class:AuditCategory/Attribute:name+' => 'Nombre corto para esta categoría', + 'Class:AuditCategory/Attribute:description' => 'Descripcción de Categoría de Auditoría', + 'Class:AuditCategory/Attribute:description+' => 'Descripción larga para esta categoría de auditoría', + 'Class:AuditCategory/Attribute:definition_set' => 'Conjunto de definición', + 'Class:AuditCategory/Attribute:definition_set+' => 'Expresión OQL que define el conjunto de objetos a auditar', +)); + +// +// Class: AuditRule +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:AuditRule' => 'Regla de Auditoría', + 'Class:AuditRule+' => 'Regla a revisar para una categoría de auditoría específica', + 'Class:AuditRule/Attribute:name' => 'Nombre de la Regla', + 'Class:AuditRule/Attribute:name+' => 'Nombre corto para esta regla', + 'Class:AuditRule/Attribute:description' => 'Descripción de regla de auditoría', + 'Class:AuditRule/Attribute:description+' => 'Descripcion larga para esta regla de auditoría', + 'Class:AuditRule/Attribute:query' => 'Consulta a Ejecutar', + 'Class:AuditRule/Attribute:query+' => 'Expresión OQL a ejecutar', + 'Class:AuditRule/Attribute:valid_flag' => '¿Objetos Válidos?', + 'Class:AuditRule/Attribute:valid_flag+' => 'Verdadero si la regla retorna los objetos válidos, falso cualquier otra cosa', + 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'verdadero', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => 'verdadero', + 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'falso', + 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'falso', + 'Class:AuditRule/Attribute:category_id' => 'Categoría', + 'Class:AuditRule/Attribute:category_id+' => 'La categoría para esta regla', + 'Class:AuditRule/Attribute:category_name' => 'Categoría', + 'Class:AuditRule/Attribute:category_name+' => 'Nombre de la categoría para esta regla', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'addon/userrights' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: URP_Users +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_Users' => 'Usuario', + 'Class:URP_Users+' => 'Usuarios y credenciales', + 'Class:URP_Users/Attribute:userid' => 'Contacto (persona)', + 'Class:URP_Users/Attribute:userid+' => 'Detalles personales de los datos empresariales', + 'Class:URP_Users/Attribute:last_name' => 'Apellido', + 'Class:URP_Users/Attribute:last_name+' => 'Apellido del contacto', + 'Class:URP_Users/Attribute:first_name' => 'Nombre', + 'Class:URP_Users/Attribute:first_name+' => 'Nombre de pila del contacto', + 'Class:URP_Users/Attribute:email' => 'Correo Electrónico', + 'Class:URP_Users/Attribute:email+' => 'Correo Electrónico del contacto', + 'Class:URP_Users/Attribute:login' => 'Login', + 'Class:URP_Users/Attribute:login+' => 'Nombre de usuario', + 'Class:URP_Users/Attribute:password' => 'Password', + 'Class:URP_Users/Attribute:password+' => 'Palabra clave del usuario', + 'Class:URP_Users/Attribute:language' => 'Lenguaje', + 'Class:URP_Users/Attribute:language+' => 'Lenguaje de la interfase de usuario', + 'Class:URP_Users/Attribute:language/Value:EN US' => 'English', + 'Class:URP_Users/Attribute:language/Value:EN US+' => 'English U.S.', + 'Class:URP_Users/Attribute:language/Value:FR FR' => 'French', + 'Class:URP_Users/Attribute:language/Value:FR FR+' => 'FR FR', + 'Class:URP_Users/Attribute:language/Value:ES CR' => 'Español', + 'Class:URP_Users/Attribute:language/Value:ES CR+' => 'Español Costa Rica', + 'Class:URP_Users/Attribute:profile_list' => 'Perfiles', + 'Class:URP_Users/Attribute:profile_list+' => 'Roles, herencia de derechos para este contacto', +)); + +// +// Class: URP_Profiles +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_Profiles' => 'Perfil', + 'Class:URP_Profiles+' => 'Perfil de usuario', + 'Class:URP_Profiles/Attribute:name' => 'Nombre', + 'Class:URP_Profiles/Attribute:name+' => 'Etiqueta', + 'Class:URP_Profiles/Attribute:description' => 'Descripción', + 'Class:URP_Profiles/Attribute:description+' => 'descripción en una línea', + 'Class:URP_Profiles/Attribute:user_list' => 'Usuarios', + 'Class:URP_Profiles/Attribute:user_list+' => 'Personas que tienen este Rol.', +)); + +// +// Class: URP_Dimensions +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_Dimensions' => 'dimensión', + 'Class:URP_Dimensions+' => 'dimensión de aplicación (definiendo silos)', + 'Class:URP_Dimensions/Attribute:name' => 'Nombre', + 'Class:URP_Dimensions/Attribute:name+' => 'Etiqueta', + 'Class:URP_Dimensions/Attribute:description' => 'Descripción', + 'Class:URP_Dimensions/Attribute:description+' => 'descripción en una línea', + 'Class:URP_Dimensions/Attribute:type' => 'Tipo', + 'Class:URP_Dimensions/Attribute:type+' => 'nombre de clase o tipo de datos (unidad de proyección)', +)); + +// +// Class: URP_UserProfile +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_UserProfile' => 'Asignación de Perfiles', + 'Class:URP_UserProfile+' => 'Perfiles de Usuarios', + 'Class:URP_UserProfile/Attribute:userid' => 'Usuario', + 'Class:URP_UserProfile/Attribute:userid+' => 'Cuenta de usuario', + 'Class:URP_UserProfile/Attribute:userlogin' => 'Login', + 'Class:URP_UserProfile/Attribute:userlogin+' => 'Nomber de usuario', + 'Class:URP_UserProfile/Attribute:profileid' => 'Perfil', + 'Class:URP_UserProfile/Attribute:profileid+' => 'uso de perfil', + 'Class:URP_UserProfile/Attribute:profile' => 'Perfil', + 'Class:URP_UserProfile/Attribute:profile+' => 'Nombre del perfil', + 'Class:URP_UserProfile/Attribute:reason' => 'Razón', + 'Class:URP_UserProfile/Attribute:reason+' => 'Justificación de por qué esta persona tiene este rol', +)); + +// +// Class: URP_ProfileProjection +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_ProfileProjection' => 'Proyecciones_de_Perfil', + 'Class:URP_ProfileProjection+' => 'Proyecciones de perfil', + 'Class:URP_ProfileProjection/Attribute:dimensionid' => 'Dimensión', + 'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'dimensión de aplicación', + 'Class:URP_ProfileProjection/Attribute:dimension' => 'Dimensión', + 'Class:URP_ProfileProjection/Attribute:dimension+' => 'dimensión de aplicación', + 'Class:URP_ProfileProjection/Attribute:profileid' => 'Perfile', + 'Class:URP_ProfileProjection/Attribute:profileid+' => 'uso del perfil', + 'Class:URP_ProfileProjection/Attribute:profile' => 'Perfil', + 'Class:URP_ProfileProjection/Attribute:profile+' => 'Nombre del perfil', + 'Class:URP_ProfileProjection/Attribute:value' => 'Valor de la expresión', + 'Class:URP_ProfileProjection/Attribute:value+' => 'Expresión OQL (usando $user) | constante | | +código de atributo', + 'Class:URP_ProfileProjection/Attribute:attribute' => 'Atributo', + 'Class:URP_ProfileProjection/Attribute:attribute+' => 'Código de atributo destino (opcional)', +)); + +// +// Class: URP_ClassProjection +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_ClassProjection' => 'proyecciones_de_clase', + 'Class:URP_ClassProjection+' => 'proyecciones de clase', + 'Class:URP_ClassProjection/Attribute:dimensionid' => 'Dimensión', + 'Class:URP_ClassProjection/Attribute:dimensionid+' => 'dimensión de aplicación', + 'Class:URP_ClassProjection/Attribute:dimension' => 'Dimensión', + 'Class:URP_ClassProjection/Attribute:dimension+' => 'dimensión de aplicación', + 'Class:URP_ClassProjection/Attribute:class' => 'Clase', + 'Class:URP_ClassProjection/Attribute:class+' => 'Clase destino', + 'Class:URP_ClassProjection/Attribute:value' => 'Valor de la expresión', + 'Class:URP_ClassProjection/Attribute:value+' => 'Expresión OQL (usando $this) | constante | | +código de atributo', + 'Class:URP_ClassProjection/Attribute:attribute' => 'Atributo', + 'Class:URP_ClassProjection/Attribute:attribute+' => 'Código de atributo destino (opcional)', +)); + +// +// Class: URP_ActionGrant +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_ActionGrant' => 'permisos_acciones', + 'Class:URP_ActionGrant+' => 'permisos en las clases', + 'Class:URP_ActionGrant/Attribute:profileid' => 'Perfil', + 'Class:URP_ActionGrant/Attribute:profileid+' => 'uso del perfil', + 'Class:URP_ActionGrant/Attribute:profile' => 'Perfil', + 'Class:URP_ActionGrant/Attribute:profile+' => 'usage profile', + 'Class:URP_ActionGrant/Attribute:class' => 'Clase', + 'Class:URP_ActionGrant/Attribute:class+' => 'Clase destino', + 'Class:URP_ActionGrant/Attribute:permission' => 'Permisos', + 'Class:URP_ActionGrant/Attribute:permission+' => 'permitido o no permitido?', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes' => 'si', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => 'si', + 'Class:URP_ActionGrant/Attribute:permission/Value:no' => 'no', + 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => 'no', + 'Class:URP_ActionGrant/Attribute:action' => 'Acción', + 'Class:URP_ActionGrant/Attribute:action+' => 'operaciones a realizar en la case especificada', +)); + +// +// Class: URP_StimulusGrant +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_StimulusGrant' => 'permisos_cambios_de_estado', + 'Class:URP_StimulusGrant+' => 'permisos de cambio de estado en el ciclo de vida del objeto', + 'Class:URP_StimulusGrant/Attribute:profileid' => 'Perfil', + 'Class:URP_StimulusGrant/Attribute:profileid+' => 'uso del perfil', + 'Class:URP_StimulusGrant/Attribute:profile' => 'Perfil', + 'Class:URP_StimulusGrant/Attribute:profile+' => 'uso del perfil', + 'Class:URP_StimulusGrant/Attribute:class' => 'Clase', + 'Class:URP_StimulusGrant/Attribute:class+' => 'Clase destino', + 'Class:URP_StimulusGrant/Attribute:permission' => 'Permiso', + 'Class:URP_StimulusGrant/Attribute:permission+' => '¿permitido o no permitido?', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'si', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => 'si', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'no', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => 'no', + 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Cambio de estado', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'código de cambio de estado', +)); + +// +// Class: URP_AttributeGrant +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:URP_AttributeGrant' => 'permisos_de_atributo', + 'Class:URP_AttributeGrant+' => 'permisos a nivel de atributos', + 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'Concesión de acción', + 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => 'concesión de acción', + 'Class:URP_AttributeGrant/Attribute:attcode' => 'Atributo', + 'Class:URP_AttributeGrant/Attribute:attcode+' => 'código de atributo', +)); + +// +// String from the User Interface: menu, messages, buttons, etc... +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Menu:WelcomeMenu' => 'Bievenido', + 'Menu:WelcomeMenu+' => 'Bievenido a iTop', + 'Menu:WelcomeMenuPage' => 'Bievenido', + 'Menu:WelcomeMenuPage+' => 'Bievenido a iTop', + 'UI:WelcomeMenu:Title' => 'Bievenido a iTop', + + 'UI:WelcomeMenu:LeftBlock' => '

        iTop es un completo; portal IT funcioanl basado en código abierto (OpenSource).

        +
          Incluye: +
        • Una CMDB competa (Configuration management database) para documentar y manejar el inverntario de TI..
        • +
        • Un módul de gestión de incidentes, para llevar la trazabilidad y comunicar los eventos que estan afectando IT.
        • +
        • Un módulo de gestion de cambio para planear y llevar la trazabilidad hechos al ambiente de TI.
        • +
        • Una base de conocimiento para acelerar la correción de incidentes.
        • +
        • Un moódulo de Cortes/Caídas para documentar todas las caídas planeadas o no y notificar a los contactods del caso.
        • +
        • Tableros de controles para rapidamente tener visión general del ambiente TI..
        • +
        +

        All the modules can be setup, step by step, indepently of each other.

        ', + + 'UI:WelcomeMenu:RightBlock' => '

        iTop is service provider oriented, it allows IT engineers to manage easily multiple customers or organizations. +

          iTop, delivers a feature-rich set of business processes that: +
        • Enhances IT management effectiveness
        • +
        • Drives IT operations performance
        • +
        • Improves customer satisfaction and provides executives with insights into business performance.
        • +
        +

        +

        iTop is completely opened to be integrated within your current IT Management infrastructure.

        +

        +

          Adopting this new generation of IT Operational portal will help you to: +
        • Better manage a more and more complex IT environment.
        • +
        • Implement ITIL processes at your own pace.
        • +
        • Manage the most important asset of your IT: Documentation.
        • +
        +

        ', + + 'UI:WelcomeMenu:MyCalls' => 'User Requests assigned to me', + 'UI:WelcomeMenu:MyIncidents' => 'Incidents assigned to me', + 'UI:AllOrganizations' => ' All Organizations ', + 'UI:YourSearch' => 'Your Search', + 'UI:LoggedAsMessage' => 'Logged in as %1$s', + 'UI:LoggedAsMessage+Admin' => 'Logged in as %1$s (Administrator)', + 'UI:Button:Logoff' => 'Log off', + 'UI:Button:GlobalSearch' => 'Search', + 'UI:Button:Search' => ' Search ', + 'UI:Button:Query' => ' Query ', + 'UI:Button:Ok' => 'Ok', + 'UI:Button:Cancel' => 'Cancel', + 'UI:Button:Apply' => 'Apply', + 'UI:Button:Back' => ' << Back ', + 'UI:Button:Next' => ' Next >> ', + 'UI:Button:Finish' => ' Finish ', + 'UI:Button:DoImport' => ' Run the Import ! ', + 'UI:Button:Done' => ' Done ', + 'UI:Button:SimulateImport' => ' Simulate the Import ', + 'UI:Button:Test' => 'Test!', + 'UI:Button:Evaluate' => ' Evaluate ', + 'UI:Button:AddObject' => ' Add... ', + 'UI:Button:BrowseObjects' => ' Browse... ', + 'UI:Button:Add' => ' Add ', + 'UI:Button:AddToList' => ' << Add ', + 'UI:Button:RemoveFromList' => ' Remove >> ', + 'UI:Button:FilterList' => ' Filter... ', + 'UI:Button:Create' => ' Create ', + 'UI:Button:Delete' => ' Delete ! ', + 'UI:Button:ChangePassword' => ' Change Password ', + + 'UI:SearchToggle' => 'Search', + 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', + 'UI:NoObjectToDisplay' => 'No object to display.', + 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template.', + 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template.', + 'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by is mandatory. Check the definition of the display template.', + 'UI:Error:InvalidGroupByFields' => 'Invalid list of fields to group by: "%1$s".', + 'UI:Error:UnsupportedStyleOfBlock' => 'Error: unsupported style of block: "%1$s".', + 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Incorrect link definition: the class of objects to manage: %1$s was not found as an external key in the class %2$s', + 'UI:Error:Object_Class_Id_NotFound' => 'Object: %1$s:%2$d not found.', + 'UI:Error:WizardCircularReferenceInDependencies' => 'Error: Circular reference in the dependencies between the fields, check the data model.', + 'UI:Error:UploadedFileTooBig' => 'Uploaded file is too big. (Max allowed size is %1$s. Check you PHP configuration for upload_max_filesize.', + 'UI:Error:UploadedFileTruncated.' => 'Uploaded file has been truncated !', + 'UI:Error:NoTmpDir' => 'The temporary directory is not defined.', + 'UI:Error:CannotWriteToTmp_Dir' => 'Unable to write the temporary file to the disk. upload_tmp_dir = "%1$s".', + 'UI:Error:UploadStoppedByExtension_FileName' => 'Upload stopped by extension. (Original file name = "%1$s").', + 'UI:Error:UploadFailedUnknownCause_Code' => 'File upload failed, unknown cause. (Error code = "%1$s").', + + 'UI:Error:1ParametersMissing' => 'Error: the following parameter must be specified for this operation: %1$s.', + 'UI:Error:2ParametersMissing' => 'Error: the following parameters must be specified for this operation: %1$s and %2$s.', + 'UI:Error:3ParametersMissing' => 'Error: the following parameters must be specified for this operation: %1$s, %2$s and %3$s.', + 'UI:Error:4ParametersMissing' => 'Error: the following parameters must be specified for this operation: %1$s, %2$s, %3$s and %4$s.', + 'UI:Error:IncorrectOQLQuery_Message' => 'Error: incorrect OQL query: %1$s', + 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'An error occured while running the query: %1$s', + 'UI:Error:ObjectAlreadyUpdated' => 'Error: the object has already been updated.', + 'UI:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated.', + 'UI:Error:ObjectsAlreadyDeleted' => 'Error: objects have already been deleted!', + 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'You are not allowed to perform a bulk delete of objects of class %1$s', + 'UI:Error:DeleteNotAllowedOn_Class' => 'You are not allowed to delete objects of class %1$s', + 'UI:Error:ObjectAlreadyCloned' => 'Error: the object has already been cloned!', + 'UI:Error:ObjectAlreadyCreated' => 'Error: the object has already been created!', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Error: invalid stimulus "%1$s" on object %2$s in state "%3$s".', + + + 'UI:GroupBy:Count' => 'Count', + 'UI:GroupBy:Count+' => 'Number of elements', + 'UI:CountOfObjects' => '%1$d objects matching the criteria.', + 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', + 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', + 'UI:HistoryTab' => 'History', + 'UI:History:Date' => 'Date', + 'UI:History:Date+' => 'Date of the change', + 'UI:History:User' => 'User', + 'UI:History:User+' => 'User who made the change', + 'UI:History:Changes' => 'Changes', + 'UI:History:Changes+' => 'Changes made to the object', + 'UI:Loading' => 'Loading...', + 'UI:Menu:Actions' => 'Actions', + 'UI:Menu:New' => 'New...', + 'UI:Menu:Add' => 'Add...', + 'UI:Menu:Manage' => 'Manage...', + 'UI:Menu:EMail' => 'eMail', + 'UI:Menu:CSVExport' => 'CSV Export', + 'UI:Menu:Modify' => 'Modify...', + 'UI:Menu:Delete' => 'Delete...', + 'UI:Menu:Manage' => 'Manage...', + 'UI:Menu:BulkDelete' => 'Delete...', + 'UI:UndefinedObject' => 'undefined', + 'UI:Document:OpenInNewWindow:Download' => 'Open in new window: %1$s, Download: %2$s', + 'UI:SelectAllToggle+' => 'Select / Deselect All', + 'UI:TruncatedResults' => '%1$d objects displayed out of %2$d', + 'UI:DisplayAll' => 'Display All', + 'UI:CountOfResults' => '%1$d object(s)', + 'UI:ChangesLogTitle' => 'Changes log (%1$d):', + 'UI:EmptyChangesLogTitle' => 'Changes log is empty', + 'UI:SearchFor_Class_Objects' => 'Search for %1$s Objects', + 'UI:OQLQueryBuilderTitle' => 'OQL Query Builder', + 'UI:OQLQueryTab' => 'OQL Query', + 'UI:SimpleSearchTab' => 'Simple Search', + 'UI:Details+' => 'Details', + 'UI:SearchValue:Any' => '* Any *', + 'UI:SearchValue:Mixed' => '* mixed *', + 'UI:SelectOne' => '-- select one --', + 'UI:Login:Welcome' => 'Welcome to iTop!', + 'UI:Login:IncorrectLoginPassword' => 'Incorrect login/password, please try again.', + 'UI:Login:IdentifyYourself' => 'Identify yourself before continuing', + 'UI:Login:UserNamePrompt' => 'User Name', + 'UI:Login:PasswordPrompt' => 'Password', + 'UI:Login:ChangeYourPassword' => 'Change Your Password', + 'UI:Login:OldPasswordPrompt' => 'Old password', + 'UI:Login:NewPasswordPrompt' => 'New password', + 'UI:Login:RetypeNewPasswordPrompt' => 'Retype new password', + 'UI:Login:IncorrectOldPassword' => 'Error: the old password is incorrect', + 'UI:LogOffMenu' => 'Log off', + 'UI:ChangePwdMenu' => 'Change Password...', + 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', + 'UI:Button:Login' => 'Enter iTop', + 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', + 'UI:CSVImport:MappingSelectOne' => '-- select one --', + 'UI:CSVImport:MappingNotApplicable' => '-- ignore this field --', + 'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!', + 'UI:Title:DataPreview' => 'Data Preview', + 'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: The data contains only one column. Did you select the appropriate separator character?', + 'UI:CSVImport:FieldName' => 'Field %1$d', + 'UI:CSVImport:DataLine1' => 'Data Line 1', + 'UI:CSVImport:DataLine2' => 'Data Line 2', + 'UI:CSVImport:idField' => 'id (Primary Key)', + 'UI:Title:BulkImport' => 'iTop - Bulk import', + 'UI:Title:BulkImport+' => 'CSV Import Wizard', + 'UI:CSVImport:ClassesSelectOne' => '-- select one --', + 'UI:CSVImport:ErrorExtendedAttCode' => 'Internal error: "%1$s" is an incorrect code because "%2$s" is NOT an external key of the class "%3$s"', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objects(s) will stay unchanged.', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objects(s) will be modified.', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objects(s) will be added.', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objects(s) will have errors.', + 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d objects(s) remained unchanged.', + 'UI:CSVImport:ObjectsWereModified' => '%1$d objects(s) were modified.', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d objects(s) were added.', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d objects(s) had errors.', + 'UI:Title:CSVImportStep2' => 'Step 2 of 5: CSV data options', + 'UI:Title:CSVImportStep3' => 'Step 3 of 5: Data mapping', + 'UI:Title:CSVImportStep4' => 'Step 4 of 5: Import simulation', + 'UI:Title:CSVImportStep5' => 'Step 5 of 5: Import completed', + 'UI:CSVImport:LinesNotImported' => 'Lines that could not be loaded:', + 'UI:CSVImport:LinesNotImported+' => 'The following lines have not been imported because they contain errors', + 'UI:CSVImport:SeparatorComma+' => ', (comma)', + 'UI:CSVImport:SeparatorSemicolon+' => '; (semicolon)', + 'UI:CSVImport:SeparatorTab+' => 'tab', + 'UI:CSVImport:SeparatorOther' => 'other:', + 'UI:CSVImport:QualifierDoubleQuote+' => '" (double quote)', + 'UI:CSVImport:QualifierSimpleQuote+' => '\' (simple quote)', + 'UI:CSVImport:QualifierOther' => 'other:', + 'UI:CSVImport:TreatFirstLineAsHeader' => 'Treat the first line as a header (column names)', + 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'Skip %1$s line(s) at the beginning of the file', + 'UI:CSVImport:CSVDataPreview' => 'CSV Data Preview', + 'UI:CSVImport:SelectFile' => 'Select the file to import:', + 'UI:CSVImport:Tab:LoadFromFile' => 'Load from a file', + 'UI:CSVImport:Tab:CopyPaste' => 'Copy and paste data', + 'UI:CSVImport:Tab:Templates' => 'Templates', + 'UI:CSVImport:PasteData' => 'Paste the data to import:', + 'UI:CSVImport:PickClassForTemplate' => 'Pick the template to download: ', + 'UI:CSVImport:SeparatorCharacter' => 'Separator character:', + 'UI:CSVImport:TextQualifierCharacter' => 'Text qualifier character', + 'UI:CSVImport:CommentsAndHeader' => 'Comments and header', + 'UI:CSVImport:SelectClass' => 'Select the class to import:', + 'UI:CSVImport:AdvancedMode' => 'Advanced mode', + 'UI:CSVImport:AdvancedMode+' => 'In advanced mode the "id" (primary key) of the objects can be used to update and rename objects.' . + 'However the column "id" (if present) can only be used as a search criteria and can not be combined with any other search criteria.', + 'UI:CSVImport:SelectAClassFirst' => 'To configure the mapping, select a class first.', + 'UI:CSVImport:HeaderFields' => 'Fields', + 'UI:CSVImport:HeaderMappings' => 'Mappings', + 'UI:CSVImport:HeaderSearch' => 'Search?', + 'UI:CSVImport:AlertIncompleteMapping' => 'Please select a mapping for every field.', + 'UI:CSVImport:AlertNoSearchCriteria' => 'Please select at least one search criteria', + + 'UI:UniversalSearchTitle' => 'iTop - Universal Search', + 'UI:UniversalSearch:Error' => 'Error: %1$s', + 'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ', + + 'UI:Audit:Title' => 'iTop - CMDB Audit', + 'UI:Audit:InteractiveAudit' => 'Interactive Audit', + 'UI:Audit:HeaderAuditRule' => 'Audit Rule', + 'UI:Audit:HeaderNbObjects' => '# Objects', + 'UI:Audit:HeaderNbErrors' => '# Errors', + 'UI:Audit:PercentageOk' => '% Ok', + + 'UI:RunQuery:Title' => 'iTop - OQL Query Evaluation', + 'UI:RunQuery:QueryExamples' => 'Query Examples', + 'UI:RunQuery:HeaderPurpose' => 'Purpose', + 'UI:RunQuery:HeaderPurpose+' => 'Explanation about the query', + 'UI:RunQuery:HeaderOQLExpression' => 'OQL Expression', + 'UI:RunQuery:HeaderOQLExpression+' => 'The query in OQL syntax', + 'UI:RunQuery:ExpressionToEvaluate' => 'Expression to evaluate: ', + 'UI:RunQuery:MoreInfo' => 'More information about the query: ', + 'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ', + 'UI:RunQuery:SerializedFilter' => 'Serialized filter: ', + 'UI:RunQuery:Error' => 'An error occured while running the query: %1$s', + + 'UI:Schema:Title' => 'iTop objects schema', + 'UI:Schema:CategoryMenuItem' => 'Category %1$s', + 'UI:Schema:Relationships' => 'Relationships', + 'UI:Schema:AbstractClass' => 'Abstract class: no object from this class can be instantiated.', + 'UI:Schema:NonAbstractClass' => 'Non abstract class: objects from this class can be instantiated.', + 'UI:Schema:ClassHierarchyTitle' => 'Class hierarchy', + 'UI:Schema:AllClasses' => 'All classes', + 'UI:Schema:ExternalKey_To' => 'External key to %1$s', + 'UI:Schema:Columns_Description' => 'Columns: %1$s', + 'UI:Schema:Default_Description' => 'Default: "%1$s"', + 'UI:Schema:NullAllowed' => 'Null Allowed', + 'UI:Schema:NullNotAllowed' => 'Null NOT Allowed', + 'UI:Schema:Attributes' => 'Attributes', + 'UI:Schema:AttributeCode' => 'Attribute Code', + 'UI:Schema:AttributeCode+' => 'Internal code of the attribute', + 'UI:Schema:Label' => 'Label', + 'UI:Schema:Label+' => 'Label of the attribute', + 'UI:Schema:Type' => 'Type', + + 'UI:Schema:Type+' => 'Data type of the attribute', + 'UI:Schema:Origin' => 'Origin', + 'UI:Schema:Origin+' => 'The base class in which this attribute is defined', + 'UI:Schema:Description' => 'Description', + 'UI:Schema:Description+' => 'Description of the attribute', + 'UI:Schema:AllowedValues' => 'Allowed values', + 'UI:Schema:AllowedValues+' => 'Restrictions on the possible values for this attribute', + 'UI:Schema:MoreInfo' => 'More info', + 'UI:Schema:MoreInfo+' => 'More information about the field defined in the database', + 'UI:Schema:SearchCriteria' => 'Search criteria', + 'UI:Schema:FilterCode' => 'Filter code', + 'UI:Schema:FilterCode+' => 'Code of this search criteria', + 'UI:Schema:FilterDescription' => 'Description', + 'UI:Schema:FilterDescription+' => 'Description of this search criteria', + 'UI:Schema:AvailOperators' => 'Available operators', + 'UI:Schema:AvailOperators+' => 'Possible operators for this search criteria', + 'UI:Schema:ChildClasses' => 'Child classes', + 'UI:Schema:ReferencingClasses' => 'Referencing classes', + 'UI:Schema:RelatedClasses' => 'Related classes', + 'UI:Schema:LifeCycle' => 'Life cycle', + 'UI:Schema:Triggers' => 'Triggers', + 'UI:Schema:Relation_Code_Description' => 'Relation %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Down: %1$s', + 'UI:Schema:RelationUp_Description' => 'Up: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: propagate to %2$d levels, query: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: does not propagates (%2$d levels), query: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s is referenced by the class %2$s via the field %3$s', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s is linked to %2$s via %3$s::%4$s', + 'UI:Schema:Links:1-n' => 'Classes pointing to %1$s (1:n links):', + 'UI:Schema:Links:n-n' => 'Classes linked to %1$s (n:n links):', + 'UI:Schema:Links:All' => 'Graph of all related classes', + 'UI:Schema:NoLifeCyle' => 'There is no life cycle defined for this class.', + 'UI:Schema:LifeCycleTransitions' => 'Transitions', + 'UI:Schema:LifeCyleAttributeOptions' => 'Attribute options', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Hidden', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Read-only', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Mandatory', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Must change', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'User will be prompted to change the value', + 'UI:Schema:LifeCycleEmptyList' => 'empty list', + + 'UI:LinksWidget:Autocomplete+' => 'Type the first 3 characters...', + 'UI:Combo:SelectValue' => '--- select a value ---', + 'UI:Label:SelectedObjects' => 'Selected objects: ', + 'UI:Label:AvailableObjects' => 'Available objects: ', + 'UI:Link_Class_Attributes' => '%1$s attributes', + 'UI:SelectAllToggle+' => 'Select All / Deselect All', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Add %1$s objects linked with %2$s: %3$s', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Manage %1$s objects linked with %2$s: %3$s', + 'UI:AddLinkedObjectsOf_Class' => 'Add %1$ss...', + 'UI:RemoveLinkedObjectsOf_Class' => 'Remove selected objects', + 'UI:Message:EmptyList:UseAdd' => 'The list is empty, use the "Add..." button to add elements.', + 'UI:Message:EmptyList:UseSearchForm' => 'Use the search form above to search for objects to be added.', + + 'UI:Wizard:FinalStepTitle' => 'Final step: confirmation', + 'UI:Title:DeletionOf_Object' => 'Deletion of %1$s', + 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Bulk deletion of %1$d objects of class %2$s', + 'UI:Delete:NotAllowedToDelete' => 'You are not allowed to delete this object', + 'UI:Delete:NotAllowedToUpdate_Fields' => 'You are not allowed to update the following field(s): %1$s', + 'UI:Error:NotEnoughRightsToDelete' => 'This object could not be deleted because the current user do not have sufficient rights', + 'UI:Error:CannotDeleteBecauseOfDepencies' => 'This object could not be deleted because some manual operations must be performed prior to that', + 'UI:Archive_User_OnBehalfOf_User' => '%1$s on behalf of %2$s', + 'UI:Delete:AutomaticallyDeleted' => 'automatically deleted', + 'UI:Delete:AutomaticResetOf_Fields' => 'automatic reset of field(s): %1$s', + 'UI:Delete:CleaningUpRefencesTo_Object' => 'Cleaning up all references to %1$s...', + 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Cleaning up all references to %1$d objects of class %2$s...', + 'UI:Delete:Done+' => 'What was done...', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s deleted.', + 'UI:Delete:ConfirmDeletionOf_Name' => 'Deletion of %1$s', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Deletion of %1$d objects of class %2$s', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => 'Should be automaticaly deleted, but you are not allowed to do so', + 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => 'Must be deleted manually - but you are not allowed to delete this object, please contact your application admin', + 'UI:Delete:WillBeDeletedAutomatically' => 'Will be automaticaly deleted', + 'UI:Delete:MustBeDeletedManually' => 'Must be deleted manually', + 'UI:Delete:CannotUpdateBecause_Issue' => 'Should be automatically updated, but: %1$s', + 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'will be automaticaly updated (reset: %1$s)', + 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objects/links are referencing %2$s', + 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objects/links are referencing some of the objects to be deleted', + 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'To ensure Database integrity, any reference should be further eliminated', + 'UI:Delete:Consequence+' => 'What will be done', + 'UI:Delete:SorryDeletionNotAllowed' => 'Sorry, you are not allowed to delete this object, see the detailed explanations above', + 'UI:Delete:PleaseDoTheManualOperations' => 'Please perform the manual operations listed above prior to requesting the deletion of this object', + 'UI:Delect:Confirm_Object' => 'Please confirm that you want to delete %1$s.', + 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Please confirm that you want to delete the following %1$d objects of class %2$s.', + 'UI:WelcomeToITop' => 'Welcome to iTop', + 'UI:DetailsPageTitle' => 'iTop - %1$s - %2$s details', + 'UI:ErrorPageTitle' => 'iTop - Error', + 'UI:ObjectDoesNotExist' => 'Sorry, this object does not exist (or you are not allowed to view it).', + 'UI:SearchResultsPageTitle' => 'iTop - Search Results', + 'UI:Search:NoSearch' => 'Nothing to search for', + 'UI:FullTextSearchTitle_Text' => 'Results for "%1$s":', + 'UI:Search:Count_ObjectsOf_Class_Found' => '%1$d object(s) of class %2$s found.', + 'UI:Search:NoObjectFound' => 'No object found.', + 'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s modification', + 'UI:ModificationTitle_Class_Object' => 'Modification of %1$s: %2$s', + 'UI:ClonePageTitle_Object_Class' => 'iTop - Clone %1$s - %2$s modification', + 'UI:CloneTitle_Class_Object' => 'Clone of %1$s: %2$s', + 'UI:CreationPageTitle_Class' => 'iTop - Creation of a new %1$s ', + 'UI:CreationTitle_Class' => 'Creation of a new %1$s', + 'UI:SelectTheTypeOf_Class_ToCreate' => 'Select the type of %1$s to create:', + 'UI:Class_Object_NotUpdated' => 'No change detected, %1$s (%2$s) has not been modified.', + 'UI:Class_Object_Updated' => '%1$s (%2$s) updated.', + 'UI:BulkDeletePageTitle' => 'iTop - Bulk Delete', + 'UI:BulkDeleteTitle' => 'Select the objects you want to delete:', + 'UI:PageTitle:ObjectCreated' => 'iTop Object Created.', + 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s created.', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Applying %1$s on object: %2$s in state %3$s to target state: %4$s.', + 'UI:PageTitle:FatalError' => 'iTop - Fatal Error', + 'UI:FatalErrorMessage' => 'Fatal error, iTop cannot continue.', + 'UI:Error_Details' => 'Error: %1$s.', + + 'UI:PageTitle:ClassProjections' => 'iTop user management - class projections', + 'UI:PageTitle:ProfileProjections' => 'iTop user management - profile projections', + 'UI:UserManagement:Class' => 'Class', + 'UI:UserManagement:Class+' => 'Class of objects', + 'UI:UserManagement:ProjectedObject' => 'Object', + 'UI:UserManagement:ProjectedObject+' => 'Projected object', + 'UI:UserManagement:AnyObject' => '* any *', + 'UI:UserManagement:User' => 'User', + 'UI:UserManagement:User+' => 'User involved in the projection', + 'UI:UserManagement:Profile' => 'Profile', + 'UI:UserManagement:Profile+' => 'Profile in which the projection is specified', + 'UI:UserManagement:Action:Read' => 'Read', + 'UI:UserManagement:Action:Read+' => 'Read/display objects', + 'UI:UserManagement:Action:Modify' => 'Modify', + 'UI:UserManagement:Action:Modify+' => 'Create and edit (modify) objects', + 'UI:UserManagement:Action:Delete' => 'Delete', + 'UI:UserManagement:Action:Delete+' => 'Delete objects', + 'UI:UserManagement:Action:BulkRead' => 'Bulk Read (Export)', + 'UI:UserManagement:Action:BulkRead+' => 'List objects or export massively', + 'UI:UserManagement:Action:BulkModify' => 'Bulk Modify', + 'UI:UserManagement:Action:BulkModify+' => 'Massively create/edit (CSV import)', + 'UI:UserManagement:Action:BulkDelete' => 'Bulk Delete', + 'UI:UserManagement:Action:BulkDelete+' => 'Massively delete objects', + 'UI:UserManagement:Action:Stimuli' => 'Stimuli', + 'UI:UserManagement:Action:Stimuli+' => 'Allowed (compound) actions', + 'UI:UserManagement:Action' => 'Action', + 'UI:UserManagement:Action+' => 'Action performed by the user', + 'UI:UserManagement:TitleActions' => 'Actions', + 'UI:UserManagement:Permission' => 'Permission', + 'UI:UserManagement:Permission+' => 'User\'s permissions', + 'UI:UserManagement:Attributes' => 'Attributes', + 'UI:UserManagement:ActionAllowed:Yes' => 'Yes', + 'UI:UserManagement:ActionAllowed:No' => 'No', + 'UI:UserManagement:AdminProfile+' => 'Administrators have full read/write access to all objects in the database.', + 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', + 'UI:UserManagement:NoLifeCycleApplicable+' => 'No lifecycle has been defined for this class', + 'UI:UserManagement:GrantMatrix' => 'Grant Matrix', + 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Link between %1$s and %2$s', + + 'Menu:AdminTools' => 'Admin tools', + 'Menu:AdminTools+' => 'Administration tools', + 'Menu:AdminTools?' => 'Tools accessible only to users having the administrator profile', + + 'UI:ChangeManagementMenu' => 'Change Management', + 'UI:ChangeManagementMenu+' => 'Change Management', + 'UI:ChangeManagementMenu:Title' => 'Changes Overview', + 'UI-ChangeManagementMenu-ChangesByType' => 'Changes by type', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Changes by status', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Changes by workgroup', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Changes not yet assigned', + + 'UI:ConfigurationItemsMenu'=> 'Configuration Items', + 'UI:ConfigurationItemsMenu+'=> 'All Devices', + 'UI:ConfigurationItemsMenu:Title' => 'Configuration Items Overview', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Servers by criticity', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PCs by criticity', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Network devices by criticity', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Applications by criticity', + + 'UI:ConfigurationManagementMenu' => 'Configuration Management', + 'UI:ConfigurationManagementMenu+' => 'Configuration Management', + 'UI:ConfigurationManagementMenu:Title' => 'Infrastructure Overview', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Infrastructure objects by type', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Infrastructure objects by status', + +'UI:ConfigMgmtMenuOverview:Title' => 'Dashboard for Configuration Management', +'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => 'Configuration Items by status', +'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => 'Configuration Items by type', + +'UI:RequestMgmtMenuOverview:Title' => 'Dashboard for Request Management', +'UI-RequestManagementOverview-RequestByService' => 'User Requests by service', +'UI-RequestManagementOverview-RequestByPriority' => 'User Requests by priority', +'UI-RequestManagementOverview-RequestUnassigned' => 'User Requests not yet assigned to an agent', + +'UI:IncidentMgmtMenuOverview:Title' => 'Dashboard for Incident Management', +'UI-IncidentManagementOverview-IncidentByService' => 'Incidents by service', +'UI-IncidentManagementOverview-IncidentByPriority' => 'Incident by priority', +'UI-IncidentManagementOverview-IncidentUnassigned' => 'Incidents not yet assigned to an agent', + +'UI:ChangeMgmtMenuOverview:Title' => 'Dashboard for Change Management', +'UI-ChangeManagementOverview-ChangeByType' => 'Changes by type', +'UI-ChangeManagementOverview-ChangeUnassigned' => 'Changes not yet assigned to an agent', +'UI-ChangeManagementOverview-ChangeWithOutage' => 'Outages due to changes', + +'UI:ServiceMgmtMenuOverview:Title' => 'Dashboard for Service Management', +'UI-ServiceManagementOverview-CustomerContractToRenew' => 'Customer contracts to be renewed in 30 days', +'UI-ServiceManagementOverview-ProviderContractToRenew' => 'Provider contracts to be renewed in 30 days', + + 'UI:ContactsMenu' => 'Contacts', + 'UI:ContactsMenu+' => 'Contacts', + 'UI:ContactsMenu:Title' => 'Contacts Overview', + 'UI-ContactsMenu-ContactsByLocation' => 'Contacts by location', + 'UI-ContactsMenu-ContactsByType' => 'Contacts by type', + 'UI-ContactsMenu-ContactsByStatus' => 'Contacts by status', + + 'Menu:CSVImportMenu' => 'CSV import', + 'Menu:CSVImportMenu+' => 'Bulk creation or update', + + 'Menu:DataModelMenu' => 'Data Model', + 'Menu:DataModelMenu+' => 'Overview of the Data Model', + + 'Menu:ExportMenu' => 'Export', + 'Menu:ExportMenu+' => 'Export the results of any query in HTML, CSV or XML', + + 'Menu:NotificationsMenu' => 'Notifications', + 'Menu:NotificationsMenu+' => 'Configuration of the Notifications', + 'UI:NotificationsMenu:Title' => 'Configuration of the Notifications', + 'UI:NotificationsMenu:Help' => 'Help', + 'UI:NotificationsMenu:HelpContent' => '

        In iTop the notifications are fully customizable. They are based on two sets of objects: triggers and actions.

        +

        Triggers define when a notification will be executed. There are 3 types of triggers for covering 3 differents phases of an object life cycle: +

          +
        1. the "OnCreate" triggers get executed when an object of the specified class is created
        2. +
        3. the "OnStateEnter" triggers get executed before an object of the given class enters a specified state (coming from another state)
        4. +
        5. the "OnStateLeave" triggers get executed when an object of the given class is leaving a specified state
        6. +
        +

        +

        +Actions define the actions to be performed when the triggers execute. For now there is only one kind of action consisting in sending an email message. +Such actions also define the template to be used for sending the email as well as the other parameters of the message like the recipients, importance, etc. +

        +

        A special page: email.test.php is available for testing and troubleshooting your PHP mail configuration.

        +

        To be executed, actions must be associated to triggers. +When associated with a trigger, each action is given an "order" number, specifying in which order the actions are to be executed.

        ', + 'UI:NotificationsMenu:Triggers' => 'Disparadores', + 'UI:NotificationsMenu:AvailableTriggers' => 'Disparadores disponiblesAvailable triggers', + 'UI:NotificationsMenu:OnCreate' => 'cuando un objeto es creado', + 'UI:NotificationsMenu:OnStateEnter' => 'Cuando un objeto entra a un estado específico', + 'UI:NotificationsMenu:OnStateLeave' => 'Cuando un objeto sale de un estado específico', + 'UI:NotificationsMenu:Actions' => 'Acciones', + 'UI:NotificationsMenu:AvailableActions' => 'Acciones disponibles', + + 'Menu:RunQueriesMenu' => 'Ejecutar Consultas', + 'Menu:RunQueriesMenu+' => 'Ejecute cualquier consulta', + + 'Menu:DataAdministration' => 'Administración de datos', + 'Menu:DataAdministration+' => 'Administración de datos', + + 'Menu:UniversalSearchMenu' => 'Búsqueda universal', + 'Menu:UniversalSearchMenu+' => 'Buscar cualquier cosa...', + + 'Menu:ApplicationLogMenu' => 'Bitácoras de la aplicación', + 'Menu:ApplicationLogMenu+' => 'Bitácoras de la aplicación', + 'Menu:ApplicationLogMenu:Title' => 'Bitácoras de la aplicación', + + 'Menu:UserManagementMenu' => 'Gestión de usuarios', + 'Menu:UserManagementMenu+' => 'Gestión de usuarios', + + 'Menu:ProfilesMenu' => 'Perfiles', + 'Menu:ProfilesMenu+' => 'Perfiles', + 'Menu:ProfilesMenu:Title' => 'Perfiles', + + 'Menu:UserAccountsMenu' => 'Cuentas de usuario', + 'Menu:UserAccountsMenu+' => 'Cuentas de usuario', + 'Menu:UserAccountsMenu:Title' => 'Cuentas de usuario', + + 'UI:iTopVersion:Short' => 'iTop versión %1$s', + 'UI:iTopVersion:Long' => 'iTop versión %1$s-%2$s built on %3$s', + 'UI:PropertiesTab' => 'Propiedades', + + 'UI:OpenDocumentInNewWindow_' => 'Abra este documento en una ventana nueva: %1$s', + 'UI:DownloadDocument_' => 'Descargue este documento: %1$s', + 'UI:Document:NoPreview' => 'No hay prevista disponible para este tipo de archivo', + + 'UI:DeadlineMissedBy_duration' => 'No se cumplió por %1$s', + 'UI:Deadline_LessThan1Min' => '< 1 min', + 'UI:Deadline_Minutes' => '%1$d min', + 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', + 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', + 'UI:Help' => 'Ayuda', +)); + +?> diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index c2b11d927e..1ac7f63719 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -45,6 +45,7 @@ class UserLocal extends UserInternal MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributePassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEncryptedString("encrypted_password", array("allowed_values"=>null, "sql"=>"encrypted_pwd", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); // Display lists MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'password', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details diff --git a/modules/itop-basic-1.0.0/disabled.module.itop-basic.php b/modules/itop-basic-1.0.0/disabled.module.itop-basic.php index 3aef0bb7e8..724c9cfc6a 100644 --- a/modules/itop-basic-1.0.0/disabled.module.itop-basic.php +++ b/modules/itop-basic-1.0.0/disabled.module.itop-basic.php @@ -24,6 +24,7 @@ SetupWebPage::AddModule( ), 'dictionary' => array( 'en.dict.itop-basic.php', + 'es_cr.dict.itop-basic.php', ), 'data.struct' => array( //'data.struct.itop-basic.xml', diff --git a/modules/itop-basic-1.0.0/es_cr.dict.itop-basic.php b/modules/itop-basic-1.0.0/es_cr.dict.itop-basic.php new file mode 100644 index 0000000000..c15cdc6454 --- /dev/null +++ b/modules/itop-basic-1.0.0/es_cr.dict.itop-basic.php @@ -0,0 +1,50 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ +?> diff --git a/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php new file mode 100644 index 0000000000..68a72e3e3f --- /dev/null +++ b/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php @@ -0,0 +1,340 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Menu:ChangeManagement' => 'Gestión del cambio', + 'Menu:Change:Overview' => 'Visión General', + 'Menu:Change:Overview+' => '', + 'Menu:WaitingAcceptance' => 'Cambios esperando ser aceptados', + 'Menu:WaitingAcceptance+' => '', + 'Menu:WaitingApproval' => 'Cambios esperando ser aprovados', + 'Menu:WaitingApproval+' => '', + 'Menu:Changes' => 'Cambios abiertos', + 'Menu:Changes+' => '', + 'Menu:MyChanges' => 'Cambios asignados a mí', + 'Menu:MyChanges+' => 'Cambios asignados a mí (como Agente)', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Change +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Change' => 'Cambio', + 'Class:Change+' => '', + 'Class:Change/Attribute:start_date' => 'Inicio planificado', + 'Class:Change/Attribute:start_date+' => '', + 'Class:Change/Attribute:status' => 'Estado', + 'Class:Change/Attribute:status+' => '', + 'Class:Change/Attribute:status/Value:new' => 'Nuevo', + 'Class:Change/Attribute:status/Value:new+' => '', + 'Class:Change/Attribute:status/Value:validated' => 'Validado', + 'Class:Change/Attribute:status/Value:validated+' => '', + 'Class:Change/Attribute:status/Value:rejected' => 'Rechazado', + 'Class:Change/Attribute:status/Value:rejected+' => '', + 'Class:Change/Attribute:status/Value:assigned' => 'Asignado', + 'Class:Change/Attribute:status/Value:assigned+' => '', + 'Class:Change/Attribute:status/Value:plannedscheduled' => 'Planificado y calendarizado', + 'Class:Change/Attribute:status/Value:plannedscheduled+' => '', + 'Class:Change/Attribute:status/Value:approved' => 'Aprobado', + 'Class:Change/Attribute:status/Value:approved+' => '', + 'Class:Change/Attribute:status/Value:notapproved' => 'No aprobado', + 'Class:Change/Attribute:status/Value:notapproved+' => '', + 'Class:Change/Attribute:status/Value:implemented' => 'Implementado', + 'Class:Change/Attribute:status/Value:implemented+' => '', + 'Class:Change/Attribute:status/Value:monitored' => 'Monitoreado', + 'Class:Change/Attribute:status/Value:monitored+' => '', + 'Class:Change/Attribute:status/Value:closed' => 'Cerrado', + 'Class:Change/Attribute:status/Value:closed+' => '', + 'Class:Change/Attribute:reason' => 'Razón', + 'Class:Change/Attribute:reason+' => '', + 'Class:Change/Attribute:requestor_id' => 'Solicitante', + 'Class:Change/Attribute:requestor_id+' => '', + 'Class:Change/Attribute:requestor_email' => 'Solicitante', + 'Class:Change/Attribute:requestor_email+' => '', + 'Class:Change/Attribute:org_id' => 'Cliente', + 'Class:Change/Attribute:org_id+' => '', + 'Class:Change/Attribute:org_name' => 'Cliente', + 'Class:Change/Attribute:org_name+' => '', + 'Class:Change/Attribute:workgroup_id' => 'Grupo de trabajo', + 'Class:Change/Attribute:workgroup_id+' => '', + 'Class:Change/Attribute:workgroup_name' => 'Grupo de trabajo', + 'Class:Change/Attribute:workgroup_name+' => '', + 'Class:Change/Attribute:creation_date' => 'Creado', + 'Class:Change/Attribute:creation_date+' => '', + 'Class:Change/Attribute:last_update' => 'Última actualización', + 'Class:Change/Attribute:last_update+' => '', + 'Class:Change/Attribute:end_date' => 'Fecha de finalización', + 'Class:Change/Attribute:end_date+' => '', + 'Class:Change/Attribute:close_date' => 'Cerrado', + 'Class:Change/Attribute:close_date+' => '', + 'Class:Change/Attribute:impact' => 'Impacto', + 'Class:Change/Attribute:impact+' => '', + 'Class:Change/Attribute:agent_id' => 'Agente', + 'Class:Change/Attribute:agent_id+' => '', + 'Class:Change/Attribute:agent_name' => 'Agent', + 'Class:Change/Attribute:agent_name+' => '', + 'Class:Change/Attribute:agent_email' => 'Agente', + 'Class:Change/Attribute:agent_email+' => '', + 'Class:Change/Attribute:supervisor_group_id' => 'Equipo supervisor', + 'Class:Change/Attribute:supervisor_group_id+' => '', + 'Class:Change/Attribute:supervisor_group_name' => 'Equipo supervisor', + 'Class:Change/Attribute:supervisor_group_name+' => '', + 'Class:Change/Attribute:supervisor_id' => 'Supervisor', + 'Class:Change/Attribute:supervisor_id+' => '', + 'Class:Change/Attribute:supervisor_email' => 'Supervisor', + 'Class:Change/Attribute:supervisor_email+' => '', + 'Class:Change/Attribute:manager_group_id' => 'Director de equipo', + 'Class:Change/Attribute:manager_group_id+' => '', + 'Class:Change/Attribute:manager_group_name' => 'Director de equipo', + 'Class:Change/Attribute:manager_group_name+' => '', + 'Class:Change/Attribute:manager_id' => 'Director', + 'Class:Change/Attribute:manager_id+' => '', + 'Class:Change/Attribute:manager_email' => 'Director', + 'Class:Change/Attribute:manager_email+' => '', + 'Class:Change/Attribute:outage' => 'Falla', + 'Class:Change/Attribute:outage+' => '', + 'Class:Change/Attribute:outage/Value:yes' => 'Si', + 'Class:Change/Attribute:outage/Value:yes+' => '', + 'Class:Change/Attribute:outage/Value:no' => 'No', + 'Class:Change/Attribute:outage/Value:no+' => '', + 'Class:Change/Attribute:change_request' => 'Solicitud', + 'Class:Change/Attribute:change_request+' => '', + 'Class:Change/Attribute:fallback' => 'Plan de emergencia', + 'Class:Change/Attribute:fallback+' => '', + 'Class:Change/Stimulus:ev_validate' => 'Validado', + 'Class:Change/Stimulus:ev_validate+' => '', + 'Class:Change/Stimulus:ev_reject' => 'Rechazado', + 'Class:Change/Stimulus:ev_reject+' => '', + 'Class:Change/Stimulus:ev_assign' => 'Asignar', + 'Class:Change/Stimulus:ev_assign+' => '', + 'Class:Change/Stimulus:ev_reopen' => 'Re-abrir', + 'Class:Change/Stimulus:ev_reopen+' => '', + 'Class:Change/Stimulus:ev_plan' => 'Planificar', + 'Class:Change/Stimulus:ev_plan+' => '', + 'Class:Change/Stimulus:ev_approve' => 'Aprobar', + 'Class:Change/Stimulus:ev_approve+' => '', + 'Class:Change/Stimulus:ev_replan' => 'Replanificar', + 'Class:Change/Stimulus:ev_replan+' => '', + 'Class:Change/Stimulus:ev_notapprove' => 'Rechazar', + 'Class:Change/Stimulus:ev_notapprove+' => '', + 'Class:Change/Stimulus:ev_implement' => 'Implementar', + 'Class:Change/Stimulus:ev_implement+' => '', + 'Class:Change/Stimulus:ev_monitor' => 'Monitor', + 'Class:Change/Stimulus:ev_monitor+' => '', + 'Class:Change/Stimulus:ev_finish' => 'Finalizar', + 'Class:Change/Stimulus:ev_finish+' => '', +)); + +// +// Class: RoutineChange +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:RoutineChange' => 'Routine Change', + 'Class:RoutineChange+' => '', + 'Class:RoutineChange/Attribute:status/Value:new' => 'Nuevo', + 'Class:RoutineChange/Attribute:status/Value:new+' => '', + 'Class:RoutineChange/Attribute:status/Value:assigned' => 'Asignado', + 'Class:RoutineChange/Attribute:status/Value:assigned+' => '', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled' => 'Planificado y calendarizado', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:RoutineChange/Attribute:status/Value:approved' => 'Aprobado', + 'Class:RoutineChange/Attribute:status/Value:approved+' => '', + 'Class:RoutineChange/Attribute:status/Value:implemented' => 'Implementado', + 'Class:RoutineChange/Attribute:status/Value:implemented+' => '', + 'Class:RoutineChange/Attribute:status/Value:monitored' => 'Monitoreado', + 'Class:RoutineChange/Attribute:status/Value:monitored+' => '', + 'Class:RoutineChange/Attribute:status/Value:closed' => 'Cerrado', + 'Class:RoutineChange/Attribute:status/Value:closed+' => '', + 'Class:RoutineChange/Stimulus:ev_validate' => 'Validado', + 'Class:RoutineChange/Stimulus:ev_validate+' => '', + 'Class:RoutineChange/Stimulus:ev_assign' => 'Asignado', + 'Class:RoutineChange/Stimulus:ev_assign+' => '', + 'Class:RoutineChange/Stimulus:ev_reopen' => 'Re-abrir', + 'Class:RoutineChange/Stimulus:ev_reopen+' => '', + 'Class:RoutineChange/Stimulus:ev_plan' => 'Planificar', + 'Class:RoutineChange/Stimulus:ev_plan+' => '', + 'Class:RoutineChange/Stimulus:ev_replan' => 'Replanificar', + 'Class:RoutineChange/Stimulus:ev_replan+' => '', + 'Class:RoutineChange/Stimulus:ev_implement' => 'Implementar', + 'Class:RoutineChange/Stimulus:ev_implement+' => '', + 'Class:RoutineChange/Stimulus:ev_monitor' => 'Monitor', + 'Class:RoutineChange/Stimulus:ev_monitor+' => '', + 'Class:RoutineChange/Stimulus:ev_finish' => 'Finalizar', + 'Class:RoutineChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: ApprovedChange +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ApprovedChange' => 'Modificaciones aprobadas', + 'Class:ApprovedChange+' => '', + 'Class:ApprovedChange/Attribute:approval_date' => 'Fecha de aprobación', + 'Class:ApprovedChange/Attribute:approval_date+' => '', + 'Class:ApprovedChange/Attribute:approval_comment' => 'Comentario de aprobación', + 'Class:ApprovedChange/Attribute:approval_comment+' => '', + 'Class:ApprovedChange/Stimulus:ev_validate' => 'Validado', + 'Class:ApprovedChange/Stimulus:ev_validate+' => '', + 'Class:ApprovedChange/Stimulus:ev_reject' => 'Rechazado', + 'Class:ApprovedChange/Stimulus:ev_reject+' => '', + 'Class:ApprovedChange/Stimulus:ev_assign' => 'Asignado', + 'Class:ApprovedChange/Stimulus:ev_assign+' => '', + 'Class:ApprovedChange/Stimulus:ev_reopen' => 'Re-abrir', + 'Class:ApprovedChange/Stimulus:ev_reopen+' => '', + 'Class:ApprovedChange/Stimulus:ev_plan' => 'Planificar', + 'Class:ApprovedChange/Stimulus:ev_plan+' => '', + 'Class:ApprovedChange/Stimulus:ev_approve' => 'Aprobar', + 'Class:ApprovedChange/Stimulus:ev_approve+' => '', + 'Class:ApprovedChange/Stimulus:ev_replan' => 'Replanificar', + 'Class:ApprovedChange/Stimulus:ev_replan+' => '', + 'Class:ApprovedChange/Stimulus:ev_notapprove' => 'Aprobación rechazada', + 'Class:ApprovedChange/Stimulus:ev_notapprove+' => '', + 'Class:ApprovedChange/Stimulus:ev_implement' => 'Implementar', + 'Class:ApprovedChange/Stimulus:ev_implement+' => '', + 'Class:ApprovedChange/Stimulus:ev_monitor' => 'Monitor', + 'Class:ApprovedChange/Stimulus:ev_monitor+' => '', + 'Class:ApprovedChange/Stimulus:ev_finish' => 'Finalizar', + 'Class:ApprovedChange/Stimulus:ev_finish+' => '', +)); +// +// Class: NormalChange +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:NormalChange' => 'Modificación Normal', + 'Class:NormalChange+' => '', + 'Class:NormalChange/Attribute:status/Value:new' => 'Nuevo', + 'Class:NormalChange/Attribute:status/Value:new+' => '', + 'Class:NormalChange/Attribute:status/Value:validated' => 'Validado', + 'Class:NormalChange/Attribute:status/Value:validated+' => '', + 'Class:NormalChange/Attribute:status/Value:rejected' => 'Rejected', + 'Class:NormalChange/Attribute:status/Value:rejected+' => '', + 'Class:NormalChange/Attribute:status/Value:assigned' => 'Asignado', + 'Class:NormalChange/Attribute:status/Value:assigned+' => '', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled' => 'Planificado y calendarizado', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:NormalChange/Attribute:status/Value:approved' => 'Aprobado', + 'Class:NormalChange/Attribute:status/Value:approved+' => '', + 'Class:NormalChange/Attribute:status/Value:notapproved' => 'No aprobado', + 'Class:NormalChange/Attribute:status/Value:notapproved+' => '', + 'Class:NormalChange/Attribute:status/Value:implemented' => 'Implementado', + 'Class:NormalChange/Attribute:status/Value:implemented+' => '', + 'Class:NormalChange/Attribute:status/Value:monitored' => 'Monitoreado', + 'Class:NormalChange/Attribute:status/Value:monitored+' => '', + 'Class:NormalChange/Attribute:status/Value:closed' => 'Cerrado', + 'Class:NormalChange/Attribute:status/Value:closed+' => '', + 'Class:NormalChange/Attribute:acceptance_date' => 'Fecha de aceptación', + 'Class:NormalChange/Attribute:acceptance_date+' => '', + 'Class:NormalChange/Attribute:acceptance_comment' => 'Comentario de aceptación', + 'Class:NormalChange/Attribute:acceptance_comment+' => '', + 'Class:NormalChange/Stimulus:ev_validate' => 'Validado', + 'Class:NormalChange/Stimulus:ev_validate+' => '', + 'Class:NormalChange/Stimulus:ev_reject' => 'Rechazado', + 'Class:NormalChange/Stimulus:ev_reject+' => '', + 'Class:NormalChange/Stimulus:ev_assign' => 'Asignado', + 'Class:NormalChange/Stimulus:ev_assign+' => '', + 'Class:NormalChange/Stimulus:ev_reopen' => 'Re-abrir', + 'Class:NormalChange/Stimulus:ev_reopen+' => '', + 'Class:NormalChange/Stimulus:ev_plan' => 'Planificar', + 'Class:NormalChange/Stimulus:ev_plan+' => '', + 'Class:NormalChange/Stimulus:ev_approve' => 'Aprobar', + 'Class:NormalChange/Stimulus:ev_approve+' => '', + 'Class:NormalChange/Stimulus:ev_replan' => 'Replanificar', + 'Class:NormalChange/Stimulus:ev_replan+' => '', + 'Class:NormalChange/Stimulus:ev_notapprove' => 'Aprobación rechazada', + 'Class:NormalChange/Stimulus:ev_notapprove+' => '', + 'Class:NormalChange/Stimulus:ev_implement' => 'Implementar', + 'Class:NormalChange/Stimulus:ev_implement+' => '', + 'Class:NormalChange/Stimulus:ev_monitor' => 'Monitor', + 'Class:NormalChange/Stimulus:ev_monitor+' => '', + 'Class:NormalChange/Stimulus:ev_finish' => 'Finalizar', + 'Class:NormalChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: EmergencyChange +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:EmergencyChange' => 'Modificación de Emergencia', + 'Class:EmergencyChange+' => '', + 'Class:EmergencyChange/Attribute:status/Value:new' => 'Nuevo', + 'Class:EmergencyChange/Attribute:status/Value:new+' => '', + 'Class:EmergencyChange/Attribute:status/Value:validated' => 'Validado', + 'Class:EmergencyChange/Attribute:status/Value:validated+' => '', + 'Class:EmergencyChange/Attribute:status/Value:rejected' => 'Rejected', + 'Class:EmergencyChange/Attribute:status/Value:rejected+' => '', + 'Class:EmergencyChange/Attribute:status/Value:assigned' => 'Asignado', + 'Class:EmergencyChange/Attribute:status/Value:assigned+' => '', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled' => 'Planificado y calendarizado', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:EmergencyChange/Attribute:status/Value:approved' => 'Aprobado', + 'Class:EmergencyChange/Attribute:status/Value:approved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:notapproved' => 'No aprobado', + 'Class:EmergencyChange/Attribute:status/Value:notapproved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:implemented' => 'Implementado', + 'Class:EmergencyChange/Attribute:status/Value:implemented+' => '', + 'Class:EmergencyChange/Attribute:status/Value:monitored' => 'Monitoreado', + 'Class:EmergencyChange/Attribute:status/Value:monitored+' => '', + 'Class:EmergencyChange/Attribute:status/Value:closed' => 'Cerrado', + 'Class:EmergencyChange/Attribute:status/Value:closed+' => '', + 'Class:EmergencyChange/Stimulus:ev_validate' => 'Validado', + 'Class:EmergencyChange/Stimulus:ev_validate+' => '', + 'Class:EmergencyChange/Stimulus:ev_reject' => 'Rechazado', + 'Class:EmergencyChange/Stimulus:ev_reject+' => '', + 'Class:EmergencyChange/Stimulus:ev_assign' => 'Asignado', + 'Class:EmergencyChange/Stimulus:ev_assign+' => '', + 'Class:EmergencyChange/Stimulus:ev_reopen' => 'Re-abrir', + 'Class:EmergencyChange/Stimulus:ev_reopen+' => '', + 'Class:EmergencyChange/Stimulus:ev_plan' => 'Planificar', + 'Class:EmergencyChange/Stimulus:ev_plan+' => '', + 'Class:EmergencyChange/Stimulus:ev_approve' => 'Aprobar', + 'Class:EmergencyChange/Stimulus:ev_approve+' => '', + 'Class:EmergencyChange/Stimulus:ev_replan' => 'Replanificar', + 'Class:EmergencyChange/Stimulus:ev_replan+' => '', + 'Class:EmergencyChange/Stimulus:ev_notapprove' => 'Aprobación rechazada', + 'Class:EmergencyChange/Stimulus:ev_notapprove+' => '', + 'Class:EmergencyChange/Stimulus:ev_implement' => 'Implementar', + 'Class:EmergencyChange/Stimulus:ev_implement+' => '', + 'Class:EmergencyChange/Stimulus:ev_monitor' => 'Monitor', + 'Class:EmergencyChange/Stimulus:ev_monitor+' => '', + 'Class:EmergencyChange/Stimulus:ev_finish' => 'Finalizar', + 'Class:EmergencyChange/Stimulus:ev_finish+' => '', +)); +?> diff --git a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php index f440ecfc2b..6f2aa9e56c 100644 --- a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php @@ -27,6 +27,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-change-mgmt.php', 'fr.dict.itop-change-mgmt.php', + 'es_cr.dict.itop-change-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-change-mgmt.xml', diff --git a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php new file mode 100644 index 0000000000..9e3db24fc1 --- /dev/null +++ b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php @@ -0,0 +1,959 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +////////////////////////////////////////////////////////////////////// +// Relations +////////////////////////////////////////////////////////////////////// +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Relation:impacts/Description' => 'Elementos impactados por', + 'Relation:impacts/VerbUp' => 'Impacto...', + 'Relation:impacts/VerbDown' => 'Elementos impactados por...', + 'Relation:depends on/Description' => 'Elementos de los cuales este elemento depende', + 'Relation:depends on/VerbUp' => 'Depende de...', + 'Relation:depends on/VerbDown' => 'Impacta...', +)); + + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Note: The classes have been grouped by categories: bizmodel +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: Organization +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Organization' => 'Organización', + 'Class:Organization+' => '', + 'Class:Organization/Attribute:name' => 'Nombre', + 'Class:Organization/Attribute:name+' => 'Common name', + 'Class:Organization/Attribute:code' => 'Código', + 'Class:Organization/Attribute:code+' => 'Código de organización (Siret, DUNS,...)', + 'Class:Organization/Attribute:status' => 'Estado', + 'Class:Organization/Attribute:status+' => '', + 'Class:Organization/Attribute:status/Value:active' => 'Activo', + 'Class:Organization/Attribute:status/Value:active+' => 'Activo', + 'Class:Organization/Attribute:status/Value:inactive' => 'Inactivo', + 'Class:Organization/Attribute:status/Value:inactive+' => 'Inactivo', + 'Class:Organization/Attribute:parent_id' => 'Padre', + 'Class:Organization/Attribute:parent_id+' => 'Organización padre', + 'Class:Organization/Attribute:parent_name' => 'Nombre de padre', + 'Class:Organization/Attribute:parent_name+' => 'Nombre de la organización padre', +)); + + +// +// Class: Location +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Location' => 'Ubicación', + 'Class:Location+' => 'Cualquier tipo de ubicación: Región, País, Ciudad, Sitio, Edificio, Piso, Cuarto, Rack,...', + 'Class:Location/Attribute:name' => 'Nombre', + 'Class:Location/Attribute:name+' => '', + 'Class:Location/Attribute:status' => 'Estado', + 'Class:Location/Attribute:status+' => '', + 'Class:Location/Attribute:status/Value:active' => 'Activo', + 'Class:Location/Attribute:status/Value:active+' => 'Activo', + 'Class:Location/Attribute:status/Value:inactive' => 'Inactivo', + 'Class:Location/Attribute:status/Value:inactive+' => 'Inactivo', + 'Class:Location/Attribute:org_id' => 'Organización propietaria', + 'Class:Location/Attribute:org_id+' => '', + 'Class:Location/Attribute:org_name' => 'Nombre de la organización propietaria', + 'Class:Location/Attribute:org_name+' => '', + 'Class:Location/Attribute:address' => 'Dirección', + 'Class:Location/Attribute:address+' => 'Dirección postal', + 'Class:Location/Attribute:postal_code' => 'Código postal', + 'Class:Location/Attribute:postal_code+' => 'ZIP/Código postal', + 'Class:Location/Attribute:city' => 'Ciudad', + 'Class:Location/Attribute:city+' => '', + 'Class:Location/Attribute:country' => 'País', + 'Class:Location/Attribute:country+' => '', + 'Class:Location/Attribute:parent_id' => 'Ubicación Padre', + 'Class:Location/Attribute:parent_id+' => '', + 'Class:Location/Attribute:parent_name' => 'Nombre de padre', + 'Class:Location/Attribute:parent_name+' => '', + 'Class:Location/Attribute:contact_list' => 'Contactos', + 'Class:Location/Attribute:contact_list+' => 'Contactos localizables en ese sitio', + 'Class:Location/Attribute:infra_list' => 'Infraestructura', + 'Class:Location/Attribute:infra_list+' => 'Ítem Configurados (CI) ubicados en este sitio', +)); + +// +// Class: Contact +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Contact' => 'Contacto', + 'Class:Contact+' => '', + 'Class:Contact/Attribute:name' => 'Nombre', + 'Class:Contact/Attribute:name+' => '', + 'Class:Contact/Attribute:status' => 'Estado', + 'Class:Contact/Attribute:status+' => '', + 'Class:Contact/Attribute:status/Value:active' => 'Activo', + 'Class:Contact/Attribute:status/Value:active+' => 'Activo', + 'Class:Contact/Attribute:status/Value:inactive' => 'Inactivo', + 'Class:Contact/Attribute:status/Value:inactive+' => 'Inactivo', + 'Class:Contact/Attribute:org_id' => 'Organización', + 'Class:Contact/Attribute:org_id+' => '', + 'Class:Contact/Attribute:org_name' => 'Organización', + 'Class:Contact/Attribute:org_name+' => '', + 'Class:Contact/Attribute:email' => 'Correo Electrónico', + 'Class:Contact/Attribute:email+' => '', + 'Class:Contact/Attribute:phone' => 'Teléfono', + 'Class:Contact/Attribute:phone+' => '', + 'Class:Contact/Attribute:location_id' => 'Ubicación', + 'Class:Contact/Attribute:location_id+' => '', + 'Class:Contact/Attribute:location_name' => 'Ubicación', + 'Class:Contact/Attribute:location_name+' => '', + 'Class:Contact/Attribute:ci_list' => 'I.C.s', + 'Class:Contact/Attribute:ci_list+' => 'Ítems Configurados relacionados con el contacto', + 'Class:Contact/Attribute:contract_list' => 'Contratos', + 'Class:Contact/Attribute:contract_list+' => 'Contractos relacionados con el contacto', + 'Class:Contact/Attribute:service_list' => 'Servicios', + 'Class:Contact/Attribute:service_list+' => 'Servicios relacionados con el contacto', + 'Class:Contact/Attribute:ticket_list' => 'Tiquetes', + 'Class:Contact/Attribute:ticket_list+' => 'Tiquetes relacionados con el contrato', + 'Class:Contact/Attribute:team_list' => 'Equipos', + 'Class:Contact/Attribute:team_list+' => 'Equipos a los que pertenece este contacto', + 'Class:Contact/Attribute:finalclass' => 'Tipo', + 'Class:Contact/Attribute:finalclass+' => '', +)); + +// +// Class: Person +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Person' => 'Persona', + 'Class:Person+' => '', + 'Class:Person/Attribute:first_name' => 'Nombre', + 'Class:Person/Attribute:first_name+' => '', + 'Class:Person/Attribute:employee_id' => 'Identificación de empleado', + 'Class:Person/Attribute:employee_id+' => '', +)); + +// +// Class: Team +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Team' => 'Equipo', + 'Class:Team+' => '', + 'Class:Team/Attribute:member_list' => 'Miembros', + 'Class:Team/Attribute:member_list+' => 'Contactos que son parte del equipo', +)); + +// +// Class: lnkTeamToContact +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkTeamToContact' => 'Miembros de Equipo', + 'Class:lnkTeamToContact+' => 'Miembros del equipo', + 'Class:lnkTeamToContact/Attribute:team_id' => 'Equipo', + 'Class:lnkTeamToContact/Attribute:team_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_id' => 'Miembro', + 'Class:lnkTeamToContact/Attribute:contact_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_location_id' => 'Ubicación', + 'Class:lnkTeamToContact/Attribute:contact_location_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_email' => 'Correo Electrónico', + 'Class:lnkTeamToContact/Attribute:contact_email+' => '', + 'Class:lnkTeamToContact/Attribute:contact_phone' => 'Teléfono', + 'Class:lnkTeamToContact/Attribute:contact_phone+' => '', + 'Class:lnkTeamToContact/Attribute:role' => 'Rol', + 'Class:lnkTeamToContact/Attribute:role+' => '', +)); + +// +// Class: Document +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Document' => 'Documento', + 'Class:Document+' => '', + 'Class:Document/Attribute:name' => 'Nombre', + 'Class:Document/Attribute:name+' => '', + 'Class:Document/Attribute:description' => 'Descripción', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:type' => 'Tipo', + 'Class:Document/Attribute:type+' => '', + 'Class:Document/Attribute:type/Value:contract' => 'Contrato', + 'Class:Document/Attribute:type/Value:contract+' => '', + 'Class:Document/Attribute:type/Value:networkmap' => 'Mapa de la Red', + 'Class:Document/Attribute:type/Value:networkmap+' => '', + 'Class:Document/Attribute:type/Value:presentation' => 'Presentación', + 'Class:Document/Attribute:type/Value:presentation+' => '', + 'Class:Document/Attribute:type/Value:training' => 'Capacitación', + 'Class:Document/Attribute:type/Value:training+' => '', + 'Class:Document/Attribute:type/Value:whitePaper' => 'Artículo de divulgación', + 'Class:Document/Attribute:type/Value:whitePaper+' => '', + 'Class:Document/Attribute:type/Value:workinginstructions' => 'Instrucciones de trabajo', + 'Class:Document/Attribute:type/Value:workinginstructions+' => '', + 'Class:Document/Attribute:status' => 'Estado', + 'Class:Document/Attribute:status+' => '', + 'Class:Document/Attribute:status/Value:draft' => 'Borrador de documento', + 'Class:Document/Attribute:status/Value:draft+' => '', + 'Class:Document/Attribute:status/Value:obsolete' => 'Obsoleto', + 'Class:Document/Attribute:status/Value:obsolete+' => '', + 'Class:Document/Attribute:status/Value:published' => 'Publicado', + 'Class:Document/Attribute:status/Value:published+' => '', + 'Class:Document/Attribute:ci_list' => 'I.C.s', + 'Class:Document/Attribute:ci_list+' => 'Ítems Configurados referenciados en este documento', + 'Class:Document/Attribute:contract_list' => 'Contratos', + 'Class:Document/Attribute:contract_list+' => 'Contratos referenciados en este documento', + 'Class:Document/Attribute:service_list' => 'Servicios', + 'Class:Document/Attribute:service_list+' => 'Servicios referenciados en este documento', + 'Class:Document/Attribute:ticket_list' => 'Tiquetes', + 'Class:Document/Attribute:ticket_list+' => 'Tiquetes referenciados en este documento', + 'Class:Document:PreviewTab' => 'Preview', +)); + +// +// Class: ExternalDoc +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ExternalDoc' => 'Documento externo', + 'Class:ExternalDoc+' => 'Documento disponible en otro servidor Web', + 'Class:ExternalDoc/Attribute:url' => 'Url', + 'Class:ExternalDoc/Attribute:url+' => '', +)); + +// +// Class: Note +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Note' => 'Nota', + 'Class:Note+' => '', + 'Class:Note/Attribute:note' => 'Texto', + 'Class:Note/Attribute:note+' => '', +)); + +// +// Class: FileDoc +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:FileDoc' => 'Documento (archivo)', + 'Class:FileDoc+' => '', + 'Class:FileDoc/Attribute:contents' => 'Contenido', + 'Class:FileDoc/Attribute:contents+' => '', +)); + +// +// Class: Licence +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Licence' => 'Licencia', + 'Class:Licence+' => '', + 'Class:Licence/Attribute:provider' => 'Proveedor', + 'Class:Licence/Attribute:provider+' => '', + 'Class:Licence/Attribute:product' => 'Producto', + 'Class:Licence/Attribute:product+' => '', + 'Class:Licence/Attribute:name' => 'Nombre', + 'Class:Licence/Attribute:name+' => '', + 'Class:Licence/Attribute:start' => 'Fecha de inicio', + 'Class:Licence/Attribute:start+' => '', + 'Class:Licence/Attribute:end' => 'Fecha de finalización', + 'Class:Licence/Attribute:end+' => '', + 'Class:Licence/Attribute:licence_key' => 'Llave', + 'Class:Licence/Attribute:licence_key+' => 'Llave o cógido (hash) de la licencia', + 'Class:Licence/Attribute:scope' => 'Ámbito', + 'Class:Licence/Attribute:scope+' => '', + 'Class:Licence/Attribute:usage_limit' => 'Límite de uso', + 'Class:Licence/Attribute:usage_limit+' => '', + 'Class:Licence/Attribute:usage_list' => 'Uso', + 'Class:Licence/Attribute:usage_list+' => 'Instancias/Aplicaciones que estan usando esta licencia', +)); + +// +// Class: Subnet +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Subnet' => 'Sub-Red', + 'Class:Subnet+' => '', + //'Class:Subnet/Attribute:name' => 'Nombre', + //'Class:Subnet/Attribute:name+' => '', + 'Class:Subnet/Attribute:org_id' => 'Organización propietaria', + 'Class:Subnet/Attribute:org_id+' => '', + 'Class:Subnet/Attribute:description' => 'Descripción', + 'Class:Subnet/Attribute:description+' => '', + 'Class:Subnet/Attribute:ip' => 'IP', + 'Class:Subnet/Attribute:ip+' => 'Número IP', + 'Class:Subnet/Attribute:ip_mask' => 'Máscara IP', + 'Class:Subnet/Attribute:ip_mask+' => 'Máscara de la red IP', +)); + +// +// Class: Patch +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Patch' => 'Parche', + 'Class:Patch+' => '', + 'Class:Patch/Attribute:name' => 'Nombre', + 'Class:Patch/Attribute:name+' => '', + 'Class:Patch/Attribute:description' => 'Descripción', + 'Class:Patch/Attribute:description+' => '', + 'Class:Patch/Attribute:target_sw' => 'Ámbito de la aplicación', + 'Class:Patch/Attribute:target_sw+' => 'Software destino (S.O. o aplicación)', + 'Class:Patch/Attribute:version' => 'Versión', + 'Class:Patch/Attribute:version+' => '', + 'Class:Patch/Attribute:type' => 'Tipo', + 'Class:Patch/Attribute:type+' => '', + 'Class:Patch/Attribute:type/Value:application' => 'Aplicación', + 'Class:Patch/Attribute:type/Value:application+' => '', + 'Class:Patch/Attribute:type/Value:os' => 'S.O', + 'Class:Patch/Attribute:type/Value:os+' => '', + 'Class:Patch/Attribute:type/Value:security' => 'Seguridad', + 'Class:Patch/Attribute:type/Value:security+' => '', + 'Class:Patch/Attribute:type/Value:servicepack' => 'Paquete de Servicio', + 'Class:Patch/Attribute:type/Value:servicepack+' => '', + 'Class:Patch/Attribute:ci_list' => 'Dispositivos', + 'Class:Patch/Attribute:ci_list+' => 'Dispositivos donde el parche esta instalado', +)); + +// +// Class: Software +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Software' => 'Software', + 'Class:Software+' => '', + 'Class:Software/Attribute:name' => 'Nombre', + 'Class:Software/Attribute:name+' => '', + 'Class:Software/Attribute:description' => 'Descripción', + 'Class:Software/Attribute:description+' => '', + 'Class:Software/Attribute:instance_list' => 'Instalaciones', + 'Class:Software/Attribute:instance_list+' => 'Instancias de este software', + 'Class:Software/Attribute:finalclass' => 'Tipo', + 'Class:Software/Attribute:finalclass+' => '', +)); + +// +// Class: Application +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Application' => 'Aplicación', + 'Class:Application+' => 'Aplicación/Programa', + 'Class:Application/Attribute:name' => 'Nombre', + 'Class:Application/Attribute:name+' => '', + 'Class:Application/Attribute:description' => 'Descripción', + 'Class:Application/Attribute:description+' => '', + 'Class:Application/Attribute:instance_list' => 'Instalaciones', + 'Class:Application/Attribute:instance_list+' => 'Instancias de esta aplicación', +)); + +// +// Class: DBServer +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:DBServer' => 'Base de datos', + 'Class:DBServer+' => 'Software de Base de Datos', + 'Class:DBServer/Attribute:instance_list' => 'Instalaciones', + 'Class:DBServer/Attribute:instance_list+' => 'Instancia de este servidor de Base de Datos', +)); + +// +// Class: lnkPatchToCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkPatchToCI' => 'Uso del parche', + 'Class:lnkPatchToCI+' => '', + 'Class:lnkPatchToCI/Attribute:patch_id' => 'Parche', + 'Class:lnkPatchToCI/Attribute:patch_id+' => '', + 'Class:lnkPatchToCI/Attribute:patch_name' => 'Parche', + 'Class:lnkPatchToCI/Attribute:patch_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_id' => 'I.C.s', + 'Class:lnkPatchToCI/Attribute:ci_id+' => 'ID de los Ítems Configurados', + 'Class:lnkPatchToCI/Attribute:ci_name' => 'I.C.s', + 'Class:lnkPatchToCI/Attribute:ci_name+' => 'Nombre de los I.C.s', + 'Class:lnkPatchToCI/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkPatchToCI/Attribute:ci_status+' => '', +)); + +// +// Class: FunctionalCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:FunctionalCI' => 'Ítem Configurado Funcional', + 'Class:FunctionalCI+' => '', + 'Class:FunctionalCI/Attribute:name' => 'Nombre', + 'Class:FunctionalCI/Attribute:name+' => '', + 'Class:FunctionalCI/Attribute:status' => 'Estado', + 'Class:FunctionalCI/Attribute:status+' => '', + 'Class:FunctionalCI/Attribute:status/Value:implementation' => 'Implementación', + 'Class:FunctionalCI/Attribute:status/Value:implementation+' => '', + 'Class:FunctionalCI/Attribute:status/Value:obsolete' => 'Obsoleto', + 'Class:FunctionalCI/Attribute:status/Value:obsolete+' => '', + 'Class:FunctionalCI/Attribute:status/Value:production' => 'Producción', + 'Class:FunctionalCI/Attribute:status/Value:production+' => '', + 'Class:FunctionalCI/Attribute:org_id' => 'Organización propietaria', + 'Class:FunctionalCI/Attribute:org_id+' => '', + 'Class:FunctionalCI/Attribute:owner_name' => 'Organización propietaria', + 'Class:FunctionalCI/Attribute:owner_name+' => '', + 'Class:FunctionalCI/Attribute:importance' => 'Criticidad para el negocio', + 'Class:FunctionalCI/Attribute:importance+' => 'Qué tan crítco es para el negocio este ítem', + 'Class:FunctionalCI/Attribute:importance/Value:high' => 'Alto', + 'Class:FunctionalCI/Attribute:importance/Value:high+' => 'Alto grado de importancia', + 'Class:FunctionalCI/Attribute:importance/Value:low' => 'Bajo', + 'Class:FunctionalCI/Attribute:importance/Value:low+' => 'Bajo grado de importancia', + 'Class:FunctionalCI/Attribute:importance/Value:medium' => 'Medio', + 'Class:FunctionalCI/Attribute:importance/Value:medium+' => 'Grado medio de importancia', + 'Class:FunctionalCI/Attribute:contact_list' => 'Contactos', + 'Class:FunctionalCI/Attribute:contact_list+' => 'Contactos para este I.C.s', + 'Class:FunctionalCI/Attribute:document_list' => 'Documentos', + 'Class:FunctionalCI/Attribute:document_list+' => 'Documentación para este I.C.s', + 'Class:FunctionalCI/Attribute:solution_list' => 'Soluciones', + 'Class:FunctionalCI/Attribute:solution_list+' => 'Soluciones que estan usando este I.C.s', + 'Class:FunctionalCI/Attribute:contract_list' => 'Contratos', + 'Class:FunctionalCI/Attribute:contract_list+' => 'Contratos soportando este I.C.s', + 'Class:FunctionalCI/Attribute:ticket_list' => 'Tiquetes', + 'Class:FunctionalCI/Attribute:ticket_list+' => 'Tiquetes relacionados con este I.C.s', + 'Class:FunctionalCI/Attribute:finalclass' => 'Tipo', + 'Class:FunctionalCI/Attribute:finalclass+' => '', +)); + +// +// Class: SoftwareInstance +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:SoftwareInstance' => 'Instancia de Software', + 'Class:SoftwareInstance+' => '', + 'Class:SoftwareInstance/Attribute:device_id' => 'Dispositivo', + 'Class:SoftwareInstance/Attribute:device_id+' => '', + 'Class:SoftwareInstance/Attribute:device_name' => 'Dispositivo', + 'Class:SoftwareInstance/Attribute:device_name+' => '', + 'Class:SoftwareInstance/Attribute:licence_id' => 'Licencia', + 'Class:SoftwareInstance/Attribute:licence_id+' => '', + 'Class:SoftwareInstance/Attribute:licence_name' => 'Licencia', + 'Class:SoftwareInstance/Attribute:licence_name+' => '', + 'Class:SoftwareInstance/Attribute:software_id' => 'Software', + 'Class:SoftwareInstance/Attribute:software_id+' => '', + 'Class:SoftwareInstance/Attribute:software_name' => 'Software', + 'Class:SoftwareInstance/Attribute:software_name+' => '', + 'Class:SoftwareInstance/Attribute:version' => 'Versión', + 'Class:SoftwareInstance/Attribute:version+' => '', + 'Class:SoftwareInstance/Attribute:description' => 'Descripción', + 'Class:SoftwareInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationInstance +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ApplicationInstance' => 'Instancia de aplicación', + 'Class:ApplicationInstance+' => '', +)); + +// +// Class: DBServerInstance +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:DBServerInstance' => 'Instancia de Servidor de BD', + 'Class:DBServerInstance+' => '', + 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Bases de Datos', + 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Fuentes de Bases de Datos', +)); + +// +// Class: DatabaseInstance +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:DatabaseInstance' => 'Instancia de Base de Datos', + 'Class:DatabaseInstance+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_id' => 'Servidor de Base de Datos', + 'Class:DatabaseInstance/Attribute:db_server_instance_id+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_version' => 'Versión de Base de Datos', + 'Class:DatabaseInstance/Attribute:db_server_instance_version+' => '', + 'Class:DatabaseInstance/Attribute:description' => 'Descripción', + 'Class:DatabaseInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationSolution +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ApplicationSolution' => 'Soluciones', + 'Class:ApplicationSolution+' => '', + 'Class:ApplicationSolution/Attribute:description' => 'Descripción', + 'Class:ApplicationSolution/Attribute:description+' => '', + 'Class:ApplicationSolution/Attribute:ci_list' => 'I.C.s', + 'Class:ApplicationSolution/Attribute:ci_list+' => 'I.C.s que conforman esta solución', + 'Class:ApplicationSolution/Attribute:process_list' => 'Procesos de Negocios', + 'Class:ApplicationSolution/Attribute:process_list+' => 'Procesos de negocios que dependen en la solución', +)); + +// +// Class: BusinessProcess +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:BusinessProcess' => 'Procesos de negocios', + 'Class:BusinessProcess+' => '', + 'Class:BusinessProcess/Attribute:description' => 'Descripción', + 'Class:BusinessProcess/Attribute:description+' => '', + 'Class:BusinessProcess/Attribute:used_solution_list' => 'Soluciones', + 'Class:BusinessProcess/Attribute:used_solution_list+' => 'Soluciones en la que los procesos se apoyan', +)); + +// +// Class: ConnectableCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ConnectableCI' => 'I.C.s conectable', + 'Class:ConnectableCI+' => 'I.C.s físico', + 'Class:ConnectableCI/Attribute:brand' => 'Marca', + 'Class:ConnectableCI/Attribute:brand+' => '', + 'Class:ConnectableCI/Attribute:model' => 'Modelo', + 'Class:ConnectableCI/Attribute:model+' => '', + 'Class:ConnectableCI/Attribute:serial_number' => 'Número de Serie', + 'Class:ConnectableCI/Attribute:serial_number+' => '', + 'Class:ConnectableCI/Attribute:asset_ref' => 'Placa de Referencia', + 'Class:ConnectableCI/Attribute:asset_ref+' => '', +)); + +// +// Class: NetworkInterface +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:NetworkInterface' => 'Interfase de Red', + 'Class:NetworkInterface+' => '', + 'Class:NetworkInterface/Attribute:device_id' => 'Dispositivo', + 'Class:NetworkInterface/Attribute:device_id+' => '', + 'Class:NetworkInterface/Attribute:device_name' => 'Dispositivo', + 'Class:NetworkInterface/Attribute:device_name+' => '', + 'Class:NetworkInterface/Attribute:logical_type' => 'Tipo Lógico', + 'Class:NetworkInterface/Attribute:logical_type+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup' => 'Respaldo', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical' => 'Lógico', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:port' => 'Puerto', + 'Class:NetworkInterface/Attribute:logical_type/Value:port+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary' => 'Primario', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary' => 'Secundario', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary+' => '', + 'Class:NetworkInterface/Attribute:physical_type' => 'Tipo Físico', + 'Class:NetworkInterface/Attribute:physical_type+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm' => 'ATM', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet' => 'Ethernet', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay' => 'Frame Relay', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan' => 'VLAN', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan+' => '', + 'Class:NetworkInterface/Attribute:ip_address' => 'Dirección IP', + 'Class:NetworkInterface/Attribute:ip_address+' => '', + 'Class:NetworkInterface/Attribute:ip_mask' => 'Máscara IP', + 'Class:NetworkInterface/Attribute:ip_mask+' => '', + 'Class:NetworkInterface/Attribute:mac_address' => 'Dirección MAC', + 'Class:NetworkInterface/Attribute:mac_address+' => '', + 'Class:NetworkInterface/Attribute:speed' => 'Velocidad', + 'Class:NetworkInterface/Attribute:speed+' => '', + 'Class:NetworkInterface/Attribute:duplex' => 'Duplex', + 'Class:NetworkInterface/Attribute:duplex+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:full' => 'Full', + 'Class:NetworkInterface/Attribute:duplex/Value:full+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:half' => 'Half', + 'Class:NetworkInterface/Attribute:duplex/Value:half+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown' => 'Desconocido', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown+' => '', + 'Class:NetworkInterface/Attribute:connected_if' => 'Conectado a', + 'Class:NetworkInterface/Attribute:connected_if+' => 'Interfase conectada', + 'Class:NetworkInterface/Attribute:connected_name' => 'Conectado a', + 'Class:NetworkInterface/Attribute:connected_name+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id' => 'Dispositivo Conectado', + 'Class:NetworkInterface/Attribute:connected_if_device_id+' => '', + 'Class:NetworkInterface/Attribute:link_type' => 'Tipo de Enlace', + 'Class:NetworkInterface/Attribute:link_type+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink' => 'Up link', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink+' => 'Enlace de Subida', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink' => 'Down link', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink+' => 'Enlace de Bajada', +)); + +// +// Class: Device +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Device' => 'Dispositivo', + 'Class:Device+' => '', + 'Class:Device/Attribute:nwinterface_list' => 'Interfases de Red', + 'Class:Device/Attribute:nwinterface_list+' => '', +)); + +// +// Class: PC +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:PC' => 'PC', + 'Class:PC+' => '', + 'Class:PC/Attribute:cpu' => 'CPU', + 'Class:PC/Attribute:cpu+' => 'Tipo de CPU', + 'Class:PC/Attribute:ram' => 'RAM', + 'Class:PC/Attribute:ram+' => 'Memoria RAM', + 'Class:PC/Attribute:hdd' => 'Disco Duro', + 'Class:PC/Attribute:hdd+' => '', + 'Class:PC/Attribute:os_family' => 'Familia de S.O', + 'Class:PC/Attribute:os_family+' => '', + 'Class:PC/Attribute:os_version' => 'Versión de S.O', + 'Class:PC/Attribute:os_version+' => '', + 'Class:PC/Attribute:application_list' => 'Aplicaciones', + 'Class:PC/Attribute:application_list+' => 'Aplicaciones/Programas instalados en este PC', + 'Class:PC/Attribute:patch_list' => 'Parches', + 'Class:PC/Attribute:patch_list+' => 'Parches instalados en este PC', +)); + +// +// Class: MobileCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:MobileCI' => 'I.C.s Móvil', + 'Class:MobileCI+' => '', +)); + +// +// Class: MobilePhone +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:MobilePhone' => 'Teléfono Celular', + 'Class:MobilePhone+' => '', + 'Class:MobilePhone/Attribute:number' => 'Número de Teléfono', + 'Class:MobilePhone/Attribute:number+' => '', + 'Class:MobilePhone/Attribute:imei' => 'IMEI', + 'Class:MobilePhone/Attribute:imei+' => '', + 'Class:MobilePhone/Attribute:hw_pin' => 'PIN del Hardware', + 'Class:MobilePhone/Attribute:hw_pin+' => '', +)); + +// +// Class: InfrastructureCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:InfrastructureCI' => 'I.C.s de Infraestructura', + 'Class:InfrastructureCI+' => '', + 'Class:InfrastructureCI/Attribute:description' => 'Descripción', + 'Class:InfrastructureCI/Attribute:description+' => '', + 'Class:InfrastructureCI/Attribute:location_id' => 'Ubicación', + 'Class:InfrastructureCI/Attribute:location_id+' => '', + 'Class:InfrastructureCI/Attribute:location_name' => 'Ubicación', + 'Class:InfrastructureCI/Attribute:location_name+' => '', + 'Class:InfrastructureCI/Attribute:location_details' => 'Detalles de la ubicación', + 'Class:InfrastructureCI/Attribute:location_details+' => '', + 'Class:InfrastructureCI/Attribute:management_ip' => 'IP de Administración', + 'Class:InfrastructureCI/Attribute:management_ip+' => 'Número IP para la Adminstración', + 'Class:InfrastructureCI/Attribute:default_gateway' => 'Pasarela por defecto', + 'Class:InfrastructureCI/Attribute:default_gateway+' => 'Pararela por defecto (Default Gateway)', +)); + +// +// Class: NetworkDevice +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:NetworkDevice' => 'Dispositivo de Red', + 'Class:NetworkDevice+' => '', + 'Class:NetworkDevice/Attribute:type' => 'Tipo', + 'Class:NetworkDevice/Attribute:type+' => '', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator' => 'Acelerador de enlace WAN', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator+' => '', + 'Class:NetworkDevice/Attribute:type/Value:firewall' => 'Corta Fuego', + 'Class:NetworkDevice/Attribute:type/Value:firewall+' => '', + 'Class:NetworkDevice/Attribute:type/Value:hub' => 'Concentrador', + 'Class:NetworkDevice/Attribute:type/Value:hub+' => '', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer' => 'Balanceador de Carga', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer+' => '', + 'Class:NetworkDevice/Attribute:type/Value:router' => 'Enrutador', + 'Class:NetworkDevice/Attribute:type/Value:router+' => '', + 'Class:NetworkDevice/Attribute:type/Value:switch' => 'Switch', + 'Class:NetworkDevice/Attribute:type/Value:switch+' => '', + 'Class:NetworkDevice/Attribute:ios_version' => 'Versión de I.O.S', + 'Class:NetworkDevice/Attribute:ios_version+' => '', + 'Class:NetworkDevice/Attribute:ram' => 'RAM', + 'Class:NetworkDevice/Attribute:ram+' => '', + 'Class:NetworkDevice/Attribute:snmp_read' => 'SNMP de Lectura', + 'Class:NetworkDevice/Attribute:snmp_read+' => 'Comunidad SNMP de lectura', + 'Class:NetworkDevice/Attribute:snmp_write' => 'SNMP de Escritura', + 'Class:NetworkDevice/Attribute:snmp_write+' => 'Comunidad SNMP de escritura', +)); + +// +// Class: Server +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Server' => 'Server', + 'Class:Server+' => '', + 'Class:Server/Attribute:cpu' => 'CPU', + 'Class:Server/Attribute:cpu+' => '', + 'Class:Server/Attribute:ram' => 'RAM', + 'Class:Server/Attribute:ram+' => '', + 'Class:Server/Attribute:hdd' => 'Disco Duro', + 'Class:Server/Attribute:hdd+' => '', + 'Class:Server/Attribute:os_family' => 'Familia de S.O', + 'Class:Server/Attribute:os_family+' => '', + 'Class:Server/Attribute:os_version' => 'Versión de S.O', + 'Class:Server/Attribute:os_version+' => '', + 'Class:Server/Attribute:application_list' => 'Aplicaciones', + 'Class:Server/Attribute:application_list+' => 'Applications installed on this server', + 'Class:Server/Attribute:patch_list' => 'Parches', + 'Class:Server/Attribute:patch_list+' => 'Patches installed on this server', +)); + +// +// Class: Printer +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Printer' => 'Impresora', + 'Class:Printer+' => '', + 'Class:Printer/Attribute:type' => 'Tipo', + 'Class:Printer/Attribute:type+' => '', + 'Class:Printer/Attribute:type/Value:mopier' => 'Mopier', + 'Class:Printer/Attribute:type/Value:mopier+' => '', + 'Class:Printer/Attribute:type/Value:printer' => 'Impresora', + 'Class:Printer/Attribute:type/Value:printer+' => '', + 'Class:Printer/Attribute:technology' => 'Tecnología', + 'Class:Printer/Attribute:technology+' => '', + 'Class:Printer/Attribute:technology/Value:inkjet' => 'Chorro de Tinta', + 'Class:Printer/Attribute:technology/Value:inkjet+' => '', + 'Class:Printer/Attribute:technology/Value:laser' => 'Laser', + 'Class:Printer/Attribute:technology/Value:laser+' => '', + 'Class:Printer/Attribute:technology/Value:tracer' => 'Tracer', + 'Class:Printer/Attribute:technology/Value:tracer+' => '', +)); + +// +// Class: lnkCIToDoc +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkCIToDoc' => 'Doc/CI', + 'Class:lnkCIToDoc+' => '', + 'Class:lnkCIToDoc/Attribute:ci_id' => 'I.C.s', + 'Class:lnkCIToDoc/Attribute:ci_id+' => '', + 'Class:lnkCIToDoc/Attribute:ci_name' => 'I.C.s', + 'Class:lnkCIToDoc/Attribute:ci_name+' => '', + 'Class:lnkCIToDoc/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkCIToDoc/Attribute:ci_status+' => '', + 'Class:lnkCIToDoc/Attribute:document_id' => 'Documento', + 'Class:lnkCIToDoc/Attribute:document_id+' => '', + 'Class:lnkCIToDoc/Attribute:document_name' => 'Nombre del Documento', + 'Class:lnkCIToDoc/Attribute:document_name+' => '', + 'Class:lnkCIToDoc/Attribute:document_type' => 'Tipo de Documento', + 'Class:lnkCIToDoc/Attribute:document_type+' => '', + 'Class:lnkCIToDoc/Attribute:document_status' => 'Estado del Documento', + 'Class:lnkCIToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkCIToContact +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkCIToContact' => 'CI/Contact', + 'Class:lnkCIToContact+' => '', + 'Class:lnkCIToContact/Attribute:ci_id' => 'I.C.s', + 'Class:lnkCIToContact/Attribute:ci_id+' => '', + 'Class:lnkCIToContact/Attribute:ci_name' => 'I.C.s', + 'Class:lnkCIToContact/Attribute:ci_name+' => '', + 'Class:lnkCIToContact/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkCIToContact/Attribute:ci_status+' => '', + 'Class:lnkCIToContact/Attribute:contact_id' => 'Contacto', + 'Class:lnkCIToContact/Attribute:contact_id+' => '', + 'Class:lnkCIToContact/Attribute:contact_name' => 'Contacto', + 'Class:lnkCIToContact/Attribute:contact_name+' => '', + 'Class:lnkCIToContact/Attribute:contact_email' => 'Correo Electrónico del Contacto', + 'Class:lnkCIToContact/Attribute:contact_email+' => '', + 'Class:lnkCIToContact/Attribute:role' => 'Rol', + 'Class:lnkCIToContact/Attribute:role+' => 'Rol del contacto con respecto al I.C.s', +)); + +// +// Class: lnkSolutionToCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkSolutionToCI' => 'I.C.s/Solución', + 'Class:lnkSolutionToCI+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_id' => 'Soluciones', + 'Class:lnkSolutionToCI/Attribute:solution_id+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_name' => 'Soluciones', + 'Class:lnkSolutionToCI/Attribute:solution_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_id' => 'I.C.s', + 'Class:lnkSolutionToCI/Attribute:ci_id+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_name' => 'I.C.s', + 'Class:lnkSolutionToCI/Attribute:ci_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkSolutionToCI/Attribute:ci_status+' => '', + 'Class:lnkSolutionToCI/Attribute:utility' => 'Utilidad', + 'Class:lnkSolutionToCI/Attribute:utility+' => 'Utilidad del I.C.s en la solución', +)); + +// +// Class: lnkProcessToSolution +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkProcessToSolution' => 'Procesos de Negocios/Solución', + 'Class:lnkProcessToSolution+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_id' => 'Soluciones', + 'Class:lnkProcessToSolution/Attribute:solution_id+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_name' => 'Soluciones', + 'Class:lnkProcessToSolution/Attribute:solution_name+' => '', + 'Class:lnkProcessToSolution/Attribute:process_id' => 'Procesos', + 'Class:lnkProcessToSolution/Attribute:process_id+' => '', + 'Class:lnkProcessToSolution/Attribute:process_name' => 'Procesos', + 'Class:lnkProcessToSolution/Attribute:process_name+' => '', + 'Class:lnkProcessToSolution/Attribute:reason' => 'Razón', + 'Class:lnkProcessToSolution/Attribute:reason+' => 'Más inforacióin del vínculo entre el proceso y la solución', +)); + + + +// +// Class extensions +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +'Class:Subnet/Tab:IPUsage' => 'Uso de Números IPs', +'Class:Subnet/Tab:IPUsage-explain' => 'Interfases que tienen IP en el rango: %1$s hasta %2$s', +'Class:Subnet/Tab:FreeIPs' => 'IPs libres', +'Class:Subnet/Tab:FreeIPs-count' => 'IPs Libres/sin asignar: %1$s', +'Class:Subnet/Tab:FreeIPs-explain' => 'Aquí esta un extracto de las 10 direcciones IPs libres', +)); + +// +// Application Menu +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +'Menu:Catalogs' => 'Catálogos', +'Menu:Catalogs+' => 'Tipos de Datos', +'Menu:Audit' => 'Auditoría', +'Menu:Audit+' => 'Auditoría', +'Menu:Organization' => 'Organizaciones', +'Menu:Organization+' => 'Todas las Organizaciones', +'Menu:Application' => 'Aplicaciones', +'Menu:Application+' => 'Todas las Aplicaiones/Pogramas', +'Menu:DBServer' => 'Servidores de Base de Datos', +'Menu:DBServer+' => 'Servidores de Base de Datos', +'Menu:Audit' => 'Auditoría', +'Menu:ConfigManagement' => 'Gestión de Configuración', +'Menu:ConfigManagement+' => 'Gestión de Configuración', +'Menu:ConfigManagementOverview' => 'Visión General', +'Menu:ConfigManagementOverview+' => 'Visión General', +'Menu:Contact' => 'Contactos', +'Menu:Contact+' => 'Contactos', +'Menu:Person' => 'Personas', +'Menu:Person+' => 'Todas las Personas', +'Menu:Team' => 'Equipos', +'Menu:Team+' => 'Todos los Equipos de Trabajo', +'Menu:Document' => 'Documentos', +'Menu:Document+' => 'Todos los Documentos', +'Menu:Location' => 'Ubicaciones', +'Menu:Location+' => 'Todas las Ubicaciones', +'Menu:ConfigManagementCI' => 'I.C.s', +'Menu:ConfigManagementCI+' => 'Todos los I.C.s', +'Menu:BusinessProcess' => 'Procesos de Negocios', +'Menu:BusinessProcess+' => 'Todos los Procesos de Negocios', +'Menu:ApplicationSolution' => 'Soluciones', +'Menu:ApplicationSolution+' => 'Todas las Soluciones', +'Menu:ConfigManagementSoftware' => 'Gestión de Aplicaciones', +'Menu:Licence' => 'Licencias', +'Menu:Licence+' => 'Todas las Licencias', +'Menu:Patch' => 'Parches', +'Menu:Patch+' => 'Todos los parches', +'Menu:ApplicationInstance' => 'Software Instalado', +'Menu:ApplicationInstance+' => 'Aplicaciones y Servidores de Base de Datos', +'Menu:ConfigManagementHardware' => 'Infrastructure Management', +'Menu:Subnet' => 'Sub-Redes', +'Menu:Subnet+' => 'Todas las Sub-Redes', +'Menu:NetworkDevice' => 'Dispositivos de Red', +'Menu:NetworkDevice+' => 'Todos los Dispositivos de Red', +'Menu:Server' => 'Servidores', +'Menu:Server+' => 'Todos los Servidores', +'Menu:Printer' => 'Impresoras', +'Menu:Printer+' => 'Todas las Impresoras', +'Menu:MobilePhone' => 'Teléfonos Celulares', +'Menu:MobilePhone+' => 'Todos los Teléfonos Celulares', +'Menu:PC' => 'PCs (Computadores de Personales', +'Menu:PC+' => 'Todos los PCs (Computadores de Personales', +)); +?> diff --git a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php index 03734b9ec5..803dca800a 100644 --- a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php @@ -26,6 +26,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-config-mgmt.php', 'fr.dict.itop-config-mgmt.php', + 'es_cr.dict.itop-config-mgmt.php', ), 'data.struct' => array( 'data.struct.Audit.xml', diff --git a/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php new file mode 100644 index 0000000000..abe09149fc --- /dev/null +++ b/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php @@ -0,0 +1,68 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Menu:IncidentManagement' => 'Gestión de Incidentes', + 'Menu:IncidentManagement+' => 'Gestión de Incidentes', + 'Menu:Incident:Overview' => 'Visión General', + 'Menu:Incident:Overview+' => 'Visión General', + 'Menu:Incident:MyIncidents' => 'Incidentes asignados a mí', + 'Menu:Incident:MyIncidents+' => 'Incidentes asignados a mí (como Agente)', + 'Menu:Incident:EscalatedIncidents' => 'Incidentes Escalados', + 'Menu:Incident:EscalatedIncidents+' => 'Incidentes Escalados', + 'Menu:Incident:OpenIncidents' => 'Todos los Incidentes Abiertos', + 'Menu:Incident:OpenIncidents+' => 'Todos los Incidentes Abiertos', + +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: Incident +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Incident' => 'Incidente', + 'Class:Incident+' => '', + 'Class:Incident/Stimulus:ev_assign' => 'Asignar', + 'Class:Incident/Stimulus:ev_assign+' => '', + 'Class:Incident/Stimulus:ev_reassign' => 'Re-asignar', + 'Class:Incident/Stimulus:ev_reassign+' => '', + 'Class:Incident/Stimulus:ev_timeout' => 'Tiempo Fuera del incidente', + 'Class:Incident/Stimulus:ev_timeout+' => '', + 'Class:Incident/Stimulus:ev_resolve' => 'Marcar como resuelto', + 'Class:Incident/Stimulus:ev_resolve+' => '', + 'Class:Incident/Stimulus:ev_close' => 'Cerrar', + 'Class:Incident/Stimulus:ev_close+' => '', +)); +?> diff --git a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php index 589e9070f9..37f7ce6623 100644 --- a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php @@ -28,6 +28,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-incident-mgmt.php', 'fr.dict.itop-incident-mgmt.php', + 'es_cr.dict.itop-incident-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-incident-mgmt.xml', diff --git a/modules/itop-knownerror-mgmt-1.0.0/es_cr.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/es_cr.dict.itop-knownerror-mgmt.php new file mode 100644 index 0000000000..c15cdc6454 --- /dev/null +++ b/modules/itop-knownerror-mgmt-1.0.0/es_cr.dict.itop-knownerror-mgmt.php @@ -0,0 +1,50 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ +?> diff --git a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php index 3414805fa4..50c43aeaa4 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php @@ -27,6 +27,7 @@ SetupWebPage::AddModule( ), 'dictionary' => array( 'en.dict.itop-knownerror-mgmt.php', + 'es_cr.dict.itop-knownerror-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-knownerror-mgmt.xml', diff --git a/modules/itop-problem-mgmt-1.0.0/es_cr.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/es_cr.dict.itop-problem-mgmt.php new file mode 100644 index 0000000000..c15cdc6454 --- /dev/null +++ b/modules/itop-problem-mgmt-1.0.0/es_cr.dict.itop-problem-mgmt.php @@ -0,0 +1,50 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ +?> diff --git a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php index 9a4cce9fd1..4ccd59f3ce 100644 --- a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php @@ -27,6 +27,7 @@ SetupWebPage::AddModule( ), 'dictionary' => array( 'en.dict.itop-problem-mgmt.php', + 'es_cr.dict.itop-problem-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-problem-mgmt.xml', diff --git a/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php new file mode 100644 index 0000000000..c55e62d6a0 --- /dev/null +++ b/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php @@ -0,0 +1,71 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Menu:RequestManagement' => 'Servicio de ayuda', + 'Menu:RequestManagement+' => 'Servicio de ayuda', + 'Menu:UserRequest:Overview' => 'Visión General', + 'Menu:UserRequest:Overview+' => 'Visión General', + 'Menu:UserRequest:MyRequests' => 'Solicitudes asignadas a mí', + 'Menu:UserRequest:MyRequests+' => 'Solicitudes asignadas a mí (como Agente)', + 'Menu:UserRequest:EscalatedRequests' => 'Solicitudes Escaladas', + 'Menu:UserRequest:EscalatedRequests+' => 'Solicitudes Escaladas', + 'Menu:UserRequest:OpenRequests' => 'Todas las Solicitudes Abiertas', + 'Menu:UserRequest:OpenRequests+' => 'Todas las Solicitudes Abiertas', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserRequest +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:UserRequest' => 'Solicitud por parte de Usuario', + 'Class:UserRequest+' => '', + 'Class:UserRequest/Attribute:freeze_reason' => 'Razón de Suspensión', + 'Class:UserRequest/Attribute:freeze_reason+' => '', + 'Class:UserRequest/Stimulus:ev_assign' => 'Asignar', + 'Class:UserRequest/Stimulus:ev_assign+' => '', + 'Class:UserRequest/Stimulus:ev_freeze' => 'Marcar como Suspendida', + 'Class:UserRequest/Stimulus:ev_freeze+' => '', + 'Class:UserRequest/Stimulus:ev_reassign' => 'Re-asignar', + 'Class:UserRequest/Stimulus:ev_reassign+' => '', + 'Class:UserRequest/Stimulus:ev_timeout' => 'Tiempo Fuera del incidente', + 'Class:UserRequest/Stimulus:ev_timeout+' => '', + 'Class:UserRequest/Stimulus:ev_resolve' => 'Marcar como resuelto', + 'Class:UserRequest/Stimulus:ev_resolve+' => '', + 'Class:UserRequest/Stimulus:ev_close' => 'Cerrar', + 'Class:UserRequest/Stimulus:ev_close+' => '', +)); +?> diff --git a/modules/itop-service-mgmt-1.0.0/es_cr.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/es_cr.dict.itop-service-mgmt.php new file mode 100644 index 0000000000..8b626811ed --- /dev/null +++ b/modules/itop-service-mgmt-1.0.0/es_cr.dict.itop-service-mgmt.php @@ -0,0 +1,430 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( +'Menu:ServiceManagement' => 'Gestión de Servicios', +'Menu:ServiceManagement+' => 'Visión General de Gestión de Servicios', +'Menu:Service:Overview' => 'Visión General', +'Menu:Service:Overview+' => '', +'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contratos por Nivel de Servicio', +'UI-ServiceManagementMenu-ContractsByStatus' => 'Contratos por Estado', +'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contratos Finalizando en menos de 30 días', + +'Menu:ServiceType' => 'Tipos de Servicios', +'Menu:ServiceType+' => 'Tipos de Servicios', +'Menu:ProviderContract' => 'Contratos del Proveedor', +'Menu:ProviderContract+' => 'Contratos del Proveedor', +'Menu:CustomerContract' => 'Contratos del Cliente', +'Menu:CustomerContract+' => 'Contratos del Cliente', +'Menu:ServiceSubcategory' => 'Subcategorías de Servicio', +'Menu:ServiceSubcategory+' => 'Subcategorías de Servicio', +'Menu:Service' => 'Servicios', +'Menu:Service+' => 'Servicios', +'Menu:SLA' => 'SLAs', +'Menu:SLA+' => 'Acuerdos de Nivel de Servicio', +'Menu:SLT' => 'SLTs', +'Menu:SLT+' => 'Destinatarios de Nivel de Servicio', + +)); + + +/* + 'UI:ServiceManagementMenu' => 'Gestion des Services', + 'UI:ServiceManagementMenu+' => 'Gestion des Services', + 'UI:ServiceManagementMenu:Title' => 'Résumé des services & contrats', + 'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contrats par niveau de service', + 'UI-ServiceManagementMenu-ContractsByStatus' => 'Contrats par état', + 'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contrats se terminant dans moins de 30 jours', +*/ + + +// +// Class: Contract +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Contract' => 'Contrato', + 'Class:Contract+' => '', + 'Class:Contract/Attribute:name' => 'Nombre', + 'Class:Contract/Attribute:name+' => '', + 'Class:Contract/Attribute:description' => 'Descripción', + 'Class:Contract/Attribute:description+' => '', + 'Class:Contract/Attribute:start_date' => 'Fecha de Inicio', + 'Class:Contract/Attribute:start_date+' => '', + 'Class:Contract/Attribute:end_date' => 'Fecha de finalización', + 'Class:Contract/Attribute:end_date+' => '', + 'Class:Contract/Attribute:cost' => 'Costo', + 'Class:Contract/Attribute:cost+' => '', + 'Class:Contract/Attribute:cost_currency' => 'Moneda del Costo', + 'Class:Contract/Attribute:cost_currency+' => '', + 'Class:Contract/Attribute:cost_currency/Value:dollars' => 'Dólares', + 'Class:Contract/Attribute:cost_currency/Value:dollars+' => 'Dólares de E.U.A', + 'Class:Contract/Attribute:cost_currency/Value:euros' => 'Euros', + 'Class:Contract/Attribute:cost_currency/Value:euros+' => '', + 'Class:Contract/Attribute:cost_currency/Value:crcolones' => 'Colones', + 'Class:Contract/Attribute:cost_currency/Value:crcolones+' => 'Colones Costa Rica', + 'Class:Contract/Attribute:cost_unit' => 'Unidad de Costo', + 'Class:Contract/Attribute:cost_unit+' => '', + 'Class:Contract/Attribute:billing_frequency' => 'Frecuencia de Facturación', + 'Class:Contract/Attribute:billing_frequency+' => '', + 'Class:Contract/Attribute:contact_list' => 'Contactos', + 'Class:Contract/Attribute:contact_list+' => 'Contactos relacionados con el contrato', + 'Class:Contract/Attribute:document_list' => 'Documentos', + 'Class:Contract/Attribute:document_list+' => 'Documentos adjuntos al contrato', + 'Class:Contract/Attribute:ci_list' => 'I.C.s', + 'Class:Contract/Attribute:ci_list+' => 'I.C.s soportados por el contrato', + 'Class:Contract/Attribute:finalclass' => 'Tipo', + 'Class:Contract/Attribute:finalclass+' => '', +)); + +// +// Class: ProviderContract +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ProviderContract' => 'Contrato del Proveedor', + 'Class:ProviderContract+' => '', + 'Class:ProviderContract/Attribute:provider_id' => 'Proveedor', + 'Class:ProviderContract/Attribute:provider_id+' => '', + 'Class:ProviderContract/Attribute:provider_name' => 'Nombre del Proveedor', + 'Class:ProviderContract/Attribute:provider_name+' => '', + 'Class:ProviderContract/Attribute:sla' => 'SLA', + 'Class:ProviderContract/Attribute:sla+' => 'Acuerdo de Nivel de Servicio', + 'Class:ProviderContract/Attribute:coverage' => 'Cobertura', + 'Class:ProviderContract/Attribute:coverage+' => '', +)); + +// +// Class: CustomerContract +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:CustomerContract' => 'Contrato del Cliente', + 'Class:CustomerContract+' => '', + 'Class:CustomerContract/Attribute:org_id' => 'Cliente', + 'Class:CustomerContract/Attribute:org_id+' => '', + 'Class:CustomerContract/Attribute:org_name' => 'Nombre del Cliente', + 'Class:CustomerContract/Attribute:org_name+' => '', + 'Class:CustomerContract/Attribute:provider_id' => 'Proveedor', + 'Class:CustomerContract/Attribute:provider_id+' => '', + 'Class:CustomerContract/Attribute:provider_name' => 'Nombre del Proveedor', + 'Class:CustomerContract/Attribute:provider_name+' => '', + 'Class:CustomerContract/Attribute:support_team_id' => 'Equipo de Soporte', + 'Class:CustomerContract/Attribute:support_team_id+' => '', + 'Class:CustomerContract/Attribute:support_team_name' => 'Nombre del Equipo de Trabajo de Soporte', + 'Class:CustomerContract/Attribute:support_team_name+' => '', + 'Class:CustomerContract/Attribute:provider_list' => 'Proveedores', + 'Class:CustomerContract/Attribute:provider_list+' => '', + 'Class:CustomerContract/Attribute:sla_list' => 'SLAs', + 'Class:CustomerContract/Attribute:sla_list+' => 'Lista de SLAs relacionados con el contrato', +)); + +// +// Class: lnkContractToSLA +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkContractToSLA' => 'Contrato/SLA', + 'Class:lnkContractToSLA+' => '', + 'Class:lnkContractToSLA/Attribute:contract_id' => 'Contrato', + 'Class:lnkContractToSLA/Attribute:contract_id+' => '', + 'Class:lnkContractToSLA/Attribute:contract_name' => 'Contrato', + 'Class:lnkContractToSLA/Attribute:contract_name+' => '', + 'Class:lnkContractToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_id+' => '', + 'Class:lnkContractToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_name+' => '', + 'Class:lnkContractToSLA/Attribute:coverage' => 'Cobertura', + 'Class:lnkContractToSLA/Attribute:coverage+' => '', +)); + +// +// Class: lnkContractToDoc +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkContractToDoc' => 'Contrato/Documentación', + 'Class:lnkContractToDoc+' => '', + 'Class:lnkContractToDoc/Attribute:contract_id' => 'Contrato', + 'Class:lnkContractToDoc/Attribute:contract_id+' => '', + 'Class:lnkContractToDoc/Attribute:contract_name' => 'Contrato', + 'Class:lnkContractToDoc/Attribute:contract_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_id' => 'Documento', + 'Class:lnkContractToDoc/Attribute:document_id+' => '', + 'Class:lnkContractToDoc/Attribute:document_name' => 'Documento', + 'Class:lnkContractToDoc/Attribute:document_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_type' => 'Tipo de Documento', + 'Class:lnkContractToDoc/Attribute:document_type+' => '', + 'Class:lnkContractToDoc/Attribute:document_status' => 'Estado del Documento', + 'Class:lnkContractToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkContractToContact +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkContractToContact' => 'Contrato/Contacto', + 'Class:lnkContractToContact+' => '', + 'Class:lnkContractToContact/Attribute:contract_id' => 'Contrato', + 'Class:lnkContractToContact/Attribute:contract_id+' => '', + 'Class:lnkContractToContact/Attribute:contract_name' => 'Contrato', + 'Class:lnkContractToContact/Attribute:contract_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_id' => 'Contacto', + 'Class:lnkContractToContact/Attribute:contact_id+' => '', + 'Class:lnkContractToContact/Attribute:contact_name' => 'Contacto', + 'Class:lnkContractToContact/Attribute:contact_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_email' => 'Correo Electrónico del Contacto', + 'Class:lnkContractToContact/Attribute:contact_email+' => '', + 'Class:lnkContractToContact/Attribute:role' => 'Rol', + 'Class:lnkContractToContact/Attribute:role+' => '', +)); + +// +// Class: lnkContractToCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkContractToCI' => 'Contrato/I.C.s', + 'Class:lnkContractToCI+' => '', + 'Class:lnkContractToCI/Attribute:contract_id' => 'Contrato', + 'Class:lnkContractToCI/Attribute:contract_id+' => '', + 'Class:lnkContractToCI/Attribute:contract_name' => 'Contrato', + 'Class:lnkContractToCI/Attribute:contract_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_id' => 'I.C.s', + 'Class:lnkContractToCI/Attribute:ci_id+' => '', + 'Class:lnkContractToCI/Attribute:ci_name' => 'I.C.s', + 'Class:lnkContractToCI/Attribute:ci_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkContractToCI/Attribute:ci_status+' => '', +)); + +// +// Class: Service +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Service' => 'Servicio', + 'Class:Service+' => '', + 'Class:Service/Attribute:org_id' => 'Proveedor', + 'Class:Service/Attribute:org_id+' => 'Identificación del Proveedor', + 'Class:Service/Attribute:provider_name' => 'Proveedor', + 'Class:Service/Attribute:provider_name+' => 'Nombre del Proveedor', + 'Class:Service/Attribute:name' => 'Nombre', + 'Class:Service/Attribute:name+' => '', + 'Class:Service/Attribute:description' => 'Descripción', + 'Class:Service/Attribute:description+' => '', + 'Class:Service/Attribute:type' => 'Tipo', + 'Class:Service/Attribute:type+' => '', + 'Class:Service/Attribute:type/Value:IncidentManagement' => 'Gestión de Incidentes', + 'Class:Service/Attribute:type/Value:IncidentManagement+' => 'Gestión de Incidentes', + 'Class:Service/Attribute:type/Value:RequestManagement' => 'Gestión de Solicitudes', + 'Class:Service/Attribute:type/Value:RequestManagement+' => 'Gestión de Solicitudes', + 'Class:Service/Attribute:status' => 'Estado', + 'Class:Service/Attribute:status+' => '', + 'Class:Service/Attribute:status/Value:design' => 'Diseño', + 'Class:Service/Attribute:status/Value:design+' => '', + 'Class:Service/Attribute:status/Value:obsolete' => 'Obsoleto', + 'Class:Service/Attribute:status/Value:obsolete+' => '', + 'Class:Service/Attribute:status/Value:production' => 'Producción', + 'Class:Service/Attribute:status/Value:production+' => '', + 'Class:Service/Attribute:subcategory_list' => 'Subcategorías de Servicio', + 'Class:Service/Attribute:subcategory_list+' => '', + 'Class:Service/Attribute:sla_list' => 'SLAs', + 'Class:Service/Attribute:sla_list+' => 'Lista de SLAs', + 'Class:Service/Attribute:document_list' => 'Documentos', + 'Class:Service/Attribute:document_list+' => 'Documentos adjuntos al servicio', + 'Class:Service/Attribute:contact_list' => 'Contactos', + 'Class:Service/Attribute:contact_list+' => 'Contactos que tienen participación en este servicio', +)); + +// +// Class: ServiceSubcategory +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ServiceSubcategory' => 'Subcategoría de Servicio', + 'Class:ServiceSubcategory+' => '', + 'Class:ServiceSubcategory/Attribute:name' => 'Nombre', + 'Class:ServiceSubcategory/Attribute:name+' => '', + 'Class:ServiceSubcategory/Attribute:description' => 'Descripción', + 'Class:ServiceSubcategory/Attribute:description+' => '', + 'Class:ServiceSubcategory/Attribute:service_id' => 'Servicio', + 'Class:ServiceSubcategory/Attribute:service_id+' => 'Identificación del Servicio', + 'Class:ServiceSubcategory/Attribute:service_name' => 'Servicio', + 'Class:ServiceSubcategory/Attribute:service_name+' => 'Nombre del Servicio', +)); + +// +// Class: SLA +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:SLA' => 'SLA', + 'Class:SLA+' => '', + 'Class:SLA/Attribute:name' => 'Nombre', + 'Class:SLA/Attribute:name+' => '', + 'Class:SLA/Attribute:service_id' => 'Servicio', + 'Class:SLA/Attribute:service_id+' => 'Identificación del Servicio', + 'Class:SLA/Attribute:service_name' => 'Servicio', + 'Class:SLA/Attribute:service_name+' => 'Nombre del Servicio', + 'Class:SLA/Attribute:slt_list' => 'SLTs', + 'Class:SLA/Attribute:slt_list+' => 'Lista de Umbrales de Nivel de Servicio (Tiempos de Respuesta)', +)); + +// +// Class: SLT +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:SLT' => 'SLT', + 'Class:SLT+' => '', + 'Class:SLT/Attribute:name' => 'Nombre', + 'Class:SLT/Attribute:name+' => '', + 'Class:SLT/Attribute:metric' => 'Métrica', + 'Class:SLT/Attribute:metric+' => '', + 'Class:SLT/Attribute:metric/Value:TTO' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTO+' => 'Tiempo para Tomar el Control', + 'Class:SLT/Attribute:metric/Value:TTR' => 'TTR', + 'Class:SLT/Attribute:metric/Value:TTR+' => 'Tiempo de Respuesta', + 'Class:SLT/Attribute:ticket_priority' => 'Prioridad del Tiquete', + 'Class:SLT/Attribute:ticket_priority+' => '', + 'Class:SLT/Attribute:ticket_priority/Value:1' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:1+' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:2' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:2+' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:3' => '3', + 'Class:SLT/Attribute:ticket_priority/Value:3+' => '3', + 'Class:SLT/Attribute:value' => 'Valor', + 'Class:SLT/Attribute:value+' => '', + 'Class:SLT/Attribute:value_unit' => 'Unidad', + 'Class:SLT/Attribute:value_unit+' => '', + 'Class:SLT/Attribute:value_unit/Value:days' => 'días', + 'Class:SLT/Attribute:value_unit/Value:days+' => 'días', + 'Class:SLT/Attribute:value_unit/Value:hours' => 'horas', + 'Class:SLT/Attribute:value_unit/Value:hours+' => 'horas', + 'Class:SLT/Attribute:value_unit/Value:minutes' => 'minutos', + 'Class:SLT/Attribute:value_unit/Value:minutes+' => 'minutos', + 'Class:SLT/Attribute:sla_list' => 'SLAs', + 'Class:SLT/Attribute:sla_list+' => 'Acuerdos de Nivel de Servicio (SLAs) usando estos SLTs', +)); + +// +// Class: lnkSLTToSLA +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkSLTToSLA' => 'SLT/SLA', + 'Class:lnkSLTToSLA+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_id+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_id' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_id+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_name' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_metric' => 'Métrica', + 'Class:lnkSLTToSLA/Attribute:slt_metric+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority' => 'Prioridad del Tiquete', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value' => 'Valor', + 'Class:lnkSLTToSLA/Attribute:slt_value+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit' => 'Unidad', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit+' => '', +)); + +// +// Class: lnkServiceToDoc +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkServiceToDoc' => 'Servicio/Documentación', + 'Class:lnkServiceToDoc+' => '', + 'Class:lnkServiceToDoc/Attribute:service_id' => 'Servicio', + 'Class:lnkServiceToDoc/Attribute:service_id+' => 'Identificación del Servicio', + 'Class:lnkServiceToDoc/Attribute:service_name' => 'Servicio', + 'Class:lnkServiceToDoc/Attribute:service_name+' => 'Nombre del Servicio', + 'Class:lnkServiceToDoc/Attribute:document_id' => 'Documento', + 'Class:lnkServiceToDoc/Attribute:document_id+' => '', + 'Class:lnkServiceToDoc/Attribute:document_name' => 'Documento', + 'Class:lnkServiceToDoc/Attribute:document_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_type' => 'Tipo de Documento', + 'Class:lnkServiceToDoc/Attribute:document_type+' => '', + 'Class:lnkServiceToDoc/Attribute:document_status' => 'Estado del Documento', + 'Class:lnkServiceToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkServiceToContact +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkServiceToContact' => 'Service/Contact', + 'Class:lnkServiceToContact+' => '', + 'Class:lnkServiceToContact/Attribute:service_id' => 'Servicio', + 'Class:lnkServiceToContact/Attribute:service_id+' => 'Identificación del Servicio', + 'Class:lnkServiceToContact/Attribute:service_name' => 'Servicio', + 'Class:lnkServiceToContact/Attribute:service_name+' => 'Nombre del Servicio', + 'Class:lnkServiceToContact/Attribute:contact_id' => 'Contacto', + 'Class:lnkServiceToContact/Attribute:contact_id+' => 'Identificación del Contacto', + 'Class:lnkServiceToContact/Attribute:contact_name' => 'Contacto', + 'Class:lnkServiceToContact/Attribute:contact_name+' => 'Nombre del Contacto', + 'Class:lnkServiceToContact/Attribute:contact_email' => 'Correo Electrónico del Contacto', + 'Class:lnkServiceToContact/Attribute:contact_email+' => '', + 'Class:lnkServiceToContact/Attribute:role' => 'Rol', + 'Class:lnkServiceToContact/Attribute:role+' => '', +)); + +// +// Class: lnkServiceToCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkServiceToCI' => 'Servicio/I.C.s', + 'Class:lnkServiceToCI+' => '', + 'Class:lnkServiceToCI/Attribute:service_id' => 'Servicio', + 'Class:lnkServiceToCI/Attribute:service_id+' => 'Identificador del Servicio', + 'Class:lnkServiceToCI/Attribute:service_name' => 'Servicio', + 'Class:lnkServiceToCI/Attribute:service_name+' => 'Nombre del Servicio', + 'Class:lnkServiceToCI/Attribute:ci_id' => 'I.C.s', + 'Class:lnkServiceToCI/Attribute:ci_id+' => 'Identificación de los I.C.s', + 'Class:lnkServiceToCI/Attribute:ci_name' => 'I.C.s', + 'Class:lnkServiceToCI/Attribute:ci_name+' => 'Nombre de los I.C.s', + 'Class:lnkServiceToCI/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkServiceToCI/Attribute:ci_status+' => '', +)); +?> diff --git a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php index 42d17fd646..05f77c247b 100644 --- a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php @@ -26,6 +26,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-service-mgmt.php', 'fr.dict.itop-service-mgmt.php', + 'es_cr.dict.itop-service-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-service-mgmt.xml', diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index fe51a920f0..310cb5adcf 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -26,6 +26,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-tickets.php', 'fr.dict.itop-tickets.php', + 'es_cr.dict.itop-tickets.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', From 8e018e4d546a7b10e8051a81d9a73352be859385 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 26 Aug 2010 15:50:20 +0000 Subject: [PATCH 622/970] - Added special thanks section SVN:trunk[705] --- readme.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/readme.txt b/readme.txt index a21a43e812..1d2298b57f 100644 --- a/readme.txt +++ b/readme.txt @@ -26,6 +26,12 @@ on our web site http://www.combodo.com, under the "Support" topic. iTop is released under the GPL (v3) license. (Check license.txt in this directory). The source code of iTop can be found on SourceForge: http://itop.sourceforge.net +1.1 Special Thanks To: + ----------------- +Bruno Bonfils for his guidance about LDAP and authentication. +Randall Badilla Castro for the Spanish translation. + + 2. INSTALLATION ============ From 283cbeddc09ce3ed17339689aa93a8bc5b48f024 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 26 Aug 2010 17:23:31 +0000 Subject: [PATCH 623/970] Fixed bug: grant matrix apparently not updated (but it was) when modifying/creating a user account SVN:trunk[706] --- .../userrightsprofile.class.inc.php | 82 +++++++++++++------ core/dbobject.class.php | 49 ++++++++--- core/userrights.class.inc.php | 24 ++++-- 3 files changed, 112 insertions(+), 43 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 0e38717d54..2b41ece367 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -30,21 +30,18 @@ class UserRightsBaseClass extends cmdbAbstractObject { // Whenever something changes, reload the privileges - public function DBInsertTracked(CMDBChange $oChange) + protected function AfterInsert() { - parent::DBInsertTracked($oChange); UserRights::FlushPrivileges(); } - public function DBUpdateTracked(CMDBChange $oChange) + protected function AfterUpdate() { - parent::DBUpdateTracked($oChange); UserRights::FlushPrivileges(); } - public function DBDeleteTracked(CMDBChange $oChange) + protected function AfterDelete() { - parent::DBDeleteTracked($oChange); UserRights::FlushPrivileges(); } } @@ -405,18 +402,6 @@ class UserRightsProfile extends UserRightsAddOnAPI return true; } - public function IsAdministrator($oUser) - { - if (in_array($oUser->GetKey(), $this->m_aAdmins)) - { - return true; - } - else - { - return false; - } - } - public function Setup() { SetupProfiles::ComputeITILProfiles(); @@ -427,20 +412,36 @@ class UserRightsProfile extends UserRightsAddOnAPI public function Init() { - MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData')); + MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'LoadCache')); } - protected $m_aProfiles = array(); // id -> object - protected $m_aUserProfiles = array(); // userid,profileid -> object - protected $m_aUserOrgs = array(); // userid,orgid -> object - protected $m_aAdmins = array(); // id of users being linked to the well-known admin profile + protected $m_aAdmins; // id of users being linked to the well-known admin profile - protected $m_aClassActionGrants = array(); // profile, class, action -> permission - protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission + protected $m_aProfiles; // id -> object + protected $m_aUserProfiles; // userid,profileid -> object + protected $m_aUserOrgs; // userid,orgid -> object - public function CacheData() + protected $m_aClassActionGrants; // profile, class, action -> permission + protected $m_aClassStimulusGrants; // profile, class, stimulus -> permission + + public function ResetCache() { + // Loaded by Load cache + $this->m_aProfiles = null; + $this->m_aUserProfiles = null; + $this->m_aUserOrgs = null; + + $this->m_aAdmins = null; + + // Loaded on demand + $this->m_aClassActionGrants = array(); + $this->m_aClassStimulusGrants = array(); + } + + public function LoadCache() + { + if (!is_null($this->m_aProfiles)) return; // Could be loaded in a shared memory (?) $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); @@ -480,8 +481,24 @@ exit; return true; } + public function IsAdministrator($oUser) + { + $this->LoadCache(); + + if (in_array($oUser->GetKey(), $this->m_aAdmins)) + { + return true; + } + else + { + return false; + } + } + public function GetSelectFilter($oUser, $sClass) { + $this->LoadCache(); + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) { @@ -527,6 +544,8 @@ exit; // This verb has been made public to allow the development of an accurate feedback for the current configuration public function GetProfileActionGrant($iProfile, $sClass, $sAction) { + $this->LoadCache(); + if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) { return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; @@ -559,6 +578,8 @@ exit; protected function GetUserActionGrant($oUser, $sClass, $iActionCode) { + $this->LoadCache(); + // load and cache permissions for the current user on the given class // $iUser = $oUser->GetKey(); @@ -610,6 +631,8 @@ exit; public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) { + $this->LoadCache(); + if (is_null($oInstanceSet)) { $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); @@ -649,6 +672,8 @@ exit; public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) { + $this->LoadCache(); + if (is_null($oInstanceSet)) { $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); @@ -704,6 +729,8 @@ exit; // This verb has been made public to allow the development of an accurate feedback for the current configuration public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode) { + $this->LoadCache(); + if (isset($this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode])) { return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]; @@ -727,6 +754,7 @@ exit; public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null) { + $this->LoadCache(); // Note: this code is VERY close to the code of IsActionAllowed() $iUser = $oUser->GetKey(); @@ -790,7 +818,7 @@ exit; public function FlushPrivileges() { - $this->CacheData(); + $this->ResetCache(); } } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index add8bed3a3..9e587542ee 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -836,11 +836,6 @@ abstract class DBObject return $this->m_iKey; } - // To be optionaly overloaded - protected function OnInsert() - { - } - // Insert of record for the new object into the database // Returns the key of the newly created object public function DBInsertNoReload() @@ -899,6 +894,8 @@ abstract class DBObject $this->m_bIsInDB = true; $this->m_bDirty = false; + $this->AfterInsert(); + // Activate any existing trigger $sClass = get_class($this); $oSet = new DBObjectSet(new DBObjectSearch('TriggerOnObjectCreate')); @@ -941,11 +938,6 @@ abstract class DBObject $this->m_iKey = self::GetNextTempId(get_class($this)); } - // To be optionaly overloaded - protected function OnUpdate() - { - } - // Update a record public function DBUpdate() { @@ -992,6 +984,8 @@ abstract class DBObject $this->DBWriteLinks(); $this->m_bDirty = false; + $this->AfterUpdate(); + // Reload to get the external attributes if ($bHasANewExternalKeyValue) { @@ -1013,16 +1007,20 @@ abstract class DBObject return $this->DBInsert(); } } - + // Delete a record public function DBDelete() { $oFilter = new DBObjectSearch(get_class($this)); $oFilter->AddCondition('id', $this->m_iKey, '='); + $this->OnDelete(); + $sSQL = MetaModel::MakeDeleteQuery($oFilter); CMDBSource::Query($sSQL); + $this->AfterDelete(); + $this->m_bIsInDB = false; $this->m_iKey = null; } @@ -1106,6 +1104,35 @@ abstract class DBObject return $aScalarArgs; } + // To be optionaly overloaded + protected function OnInsert() + { + } + + // To be optionaly overloaded + protected function AfterInsert() + { + } + + // To be optionaly overloaded + protected function OnUpdate() + { + } + + // To be optionaly overloaded + protected function AfterUpdate() + { + } + + // To be optionaly overloaded + protected function OnDelete() + { + } + + // To be optionaly overloaded + protected function AfterDelete() + { + } // Return an empty set for the parent of all public static function GetRelationQueries($sRelCode) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index e4efc84670..6502085a16 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -164,7 +164,7 @@ abstract class User extends cmdbAbstractObject 'stimuli' => $sStimuli, ); } - + $aDisplayConfig = array(); $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); @@ -274,14 +274,18 @@ class UserRights // Installation: create the very first user public static function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') { - return self::$m_oAddOn->CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage); + $bRes = self::$m_oAddOn->CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage); + self::FlushPrivileges(true /* reset admin cache */); + return $bRes; } // Installation (e.g: give default values for users) public static function Setup() { // to be discussed... - return self::$m_oAddOn->Setup(); + $bRes = self::$m_oAddOn->Setup(); + self::FlushPrivileges(true /* reset admin cache */); + return $bRes; } protected static function IsLoggedIn() @@ -575,6 +579,7 @@ class UserRights return self::$m_oAddOn->IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet); } + static $m_aAdmins = array(); public static function IsAdministrator($oUser = null) { if (!self::CheckLogin()) return false; @@ -583,11 +588,20 @@ class UserRights { $oUser = self::$m_oUser; } - return self::$m_oAddOn->IsAdministrator($oUser); + $iUser = $oUser->GetKey(); + if (!isset(self::$m_aAdmins[$iUser])) + { + self::$m_aAdmins[$iUser] = self::$m_oAddOn->IsAdministrator($oUser); + } + return self::$m_aAdmins[$iUser]; } - public static function FlushPrivileges() + public static function FlushPrivileges($bResetAdminCache = false) { + if ($bResetAdminCache) + { + self::$m_aAdmins = array(); + } return self::$m_oAddOn->FlushPrivileges(); } From af90749778d3f4a335341de8f9f0d5b4a560b221 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Fri, 27 Aug 2010 13:11:31 +0000 Subject: [PATCH 624/970] Update of itop-config-mgmt model SVN:trunk[707] --- .../model.itop-config-mgmt.php | 125 +++++++++--------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 42b7a82a14..8cf7e05c8f 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -112,7 +112,7 @@ abstract class Contact extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","email"), "db_table" => "contact", "db_key_field" => "id", "db_finalclass_field" => "", @@ -122,7 +122,7 @@ abstract class Contact extends cmdbAbstractObject MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('active,inactive'), "sql"=>"status", "default_value"=>"active", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('active,inactive'), "sql"=>"status", "default_value"=>"active", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEmailAddress("email", array("allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -164,10 +164,10 @@ class Person extends Contact MetaModel::Init_AddAttribute(new AttributeString("first_name", array("allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("employee_id", array("allowed_values"=>null, "sql"=>"employee_id", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('first_name', 'name', 'org_id', 'status', 'location_id', 'email', 'phone', 'employee_id', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'team_list')); + MetaModel::Init_SetZListItems('details', array('name','first_name', 'org_id', 'status', 'location_id', 'email', 'phone', 'employee_id', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'team_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'email', 'phone', 'location_id', 'first_name', 'employee_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'email', 'phone', 'location_id', 'first_name', 'employee_id')); - MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'email', 'phone', 'location_id')); + MetaModel::Init_SetZListItems('list', array('first_name','status', 'org_id', 'email', 'phone', 'location_id')); } public function GetName() @@ -260,9 +260,9 @@ abstract class Document extends cmdbAbstractObject MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('contract,networkmap,presentation,training,whitePaper,workinginstructions'), "sql"=>"type", "default_value"=>"presentation", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('draft,published,obsolete'), "sql"=>"status", "default_value"=>"draft", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('draft,published,obsolete'), "sql"=>"status", "default_value"=>"draft", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("contract_list", array("linked_class"=>"lnkContractToDoc", "ext_key_to_me"=>"document_id", "ext_key_to_remote"=>"contract_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("service_list", array("linked_class"=>"lnkServiceToDoc", "ext_key_to_me"=>"document_id", "ext_key_to_remote"=>"service_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ticket_list", array("linked_class"=>"lnkTicketToDoc", "ext_key_to_me"=>"document_id", "ext_key_to_remote"=>"ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); @@ -294,7 +294,7 @@ class ExternalDoc extends Document MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeURL("url", array("target"=>"_blank", "allowed_values"=>null, "sql"=>"url", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeURL("url", array("target"=>"_blank", "allowed_values"=>null, "sql"=>"url", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_SetZListItems('details', array('name', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'url')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'type', 'status', 'url')); @@ -385,7 +385,7 @@ class Licence extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "licence", "db_key_field" => "id", "db_finalclass_field" => "", @@ -394,9 +394,11 @@ class Licence extends cmdbAbstractObject MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("provider", array("allowed_values"=>null, "sql"=>"provider", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("product", array("allowed_values"=>null, "sql"=>"product", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("provider", array("allowed_values"=>null, "sql"=>"provider", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("product", array("allowed_values"=>null, "sql"=>"product", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("start", array("allowed_values"=>null, "sql"=>"start", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDate("end", array("allowed_values"=>null, "sql"=>"end", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("licence_key", array("allowed_values"=>null, "sql"=>"licence_key", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -404,10 +406,10 @@ class Licence extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeInteger("usage_limit", array("allowed_values"=>null, "sql"=>"usage_limit", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSet("usage_list", array("linked_class"=>"SoftwareInstance", "ext_key_to_me"=>"licence_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('provider', 'product', 'name', 'start', 'end', 'licence_key', 'scope', 'usage_limit', 'usage_list')); + MetaModel::Init_SetZListItems('details', array('name','org_id','provider', 'product', 'start', 'end', 'licence_key', 'scope', 'usage_limit', 'usage_list')); MetaModel::Init_SetZListItems('advanced_search', array('provider', 'product', 'name', 'start', 'end', 'licence_key', 'scope')); - MetaModel::Init_SetZListItems('standard_search', array('provider', 'product', 'name', 'start', 'end', 'licence_key', 'scope')); - MetaModel::Init_SetZListItems('list', array('provider', 'product', 'name', 'start', 'end')); + MetaModel::Init_SetZListItems('standard_search', array('org_id','provider', 'product', 'name', 'start', 'end', 'licence_key', 'scope')); + MetaModel::Init_SetZListItems('list', array('org_id','provider', 'product', 'start', 'end')); } } class Subnet extends cmdbAbstractObject @@ -421,7 +423,7 @@ class Subnet extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "ip", "state_attcode" => "", - "reconc_keys" => array("ip", "ip_mask"), + "reconc_keys" => array("ip", "ip_mask","org_id"), "db_table" => "subnet", "db_key_field" => "id", "db_finalclass_field" => "", @@ -434,8 +436,8 @@ class Subnet extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeWikiText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeIPAddress("ip", array("allowed_values"=>null, "sql"=>"ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeIPAddress("ip_mask", array("allowed_values"=>null, "sql"=>"ip_mask", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeIPAddress("ip", array("allowed_values"=>null, "sql"=>"ip", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeIPAddress("ip_mask", array("allowed_values"=>null, "sql"=>"ip_mask", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_SetZListItems('details', array('ip', 'ip_mask', 'org_id', 'description')); MetaModel::Init_SetZListItems('advanced_search', array('ip', 'ip_mask', 'org_id', 'description')); @@ -521,12 +523,12 @@ class Patch extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeWikiText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("target_sw", array("allowed_values"=>null, "sql"=>"target_sw", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('application,os,security,servicepack'), "sql"=>"type", "default_value"=>"security", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('application,os,security,servicepack'), "sql"=>"type", "default_value"=>"security", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkPatchToCI", "ext_key_to_me"=>"patch_id", "ext_key_to_remote"=>"ci_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); MetaModel::Init_SetZListItems('details', array('name', 'description', 'target_sw', 'version', 'type', 'ci_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'target_sw', 'version', 'type')); - MetaModel::Init_SetZListItems('standard_search', array('name', 'target_sw', 'version', 'type')); + MetaModel::Init_SetZListItems('standard_search', array('name', 'target_sw', 'version', 'type','description')); MetaModel::Init_SetZListItems('list', array('target_sw', 'version', 'type')); } } @@ -622,7 +624,7 @@ class lnkPatchToCI extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "patch_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("patch_id","ci_id"), "db_table" => "lnkpatchtoci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -654,7 +656,7 @@ abstract class FunctionalCI extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "functionalci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -664,7 +666,7 @@ abstract class FunctionalCI extends cmdbAbstractObject MetaModel::Init_InheritAttributes(); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('implementation,production,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('implementation,production,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("owner_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,medium,high'), "sql"=>"importance", "default_value"=>"medium", "is_null_allowed"=>false, "depends_on"=>array()))); @@ -706,9 +708,9 @@ abstract class SoftwareInstance extends FunctionalCI ( "category" => "bizmodel,searchable,configmgmt", "key_type" => "autoincrement", - "name_attcode" => "software_name", + "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("software_id", "device_id"), + "reconc_keys" => array("name", "device_id","org_id"), "db_table" => "softwareinstance", "db_key_field" => "id", "db_finalclass_field" => "", @@ -719,17 +721,15 @@ abstract class SoftwareInstance extends FunctionalCI MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"Device", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Device WHERE org_id = :this->org_id'), "sql"=>"device_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("allowed_values"=>null, "extkey_attcode"=>"device_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("licence_id", array("targetclass"=>"Licence", "jointype"=>null, "allowed_values"=>null, "sql"=>"licence_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("licence_id", array("targetclass"=>"Licence", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Licence WHERE org_id = :this->org_id'), "sql"=>"licence_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("licence_name", array("allowed_values"=>null, "extkey_attcode"=>"licence_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("software_id", array("targetclass"=>"Software", "jointype"=>null, "allowed_values"=>null, "sql"=>"software_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("software_name", array("allowed_values"=>null, "extkey_attcode"=>"software_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeWikiText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'device_id', 'licence_id', 'software_id', 'version', 'description', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list')); - MetaModel::Init_SetZListItems('advanced_search', array('status', 'org_id', 'importance', 'device_id', 'licence_id', 'software_id', 'version')); - MetaModel::Init_SetZListItems('standard_search', array('status', 'org_id', 'importance', 'device_id', 'licence_id', 'software_id', 'version')); - MetaModel::Init_SetZListItems('list', array('finalclass', 'status', 'org_id', 'importance', 'device_id', 'software_id', 'version')); + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'device_id', 'licence_id', 'version', 'description', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list')); + MetaModel::Init_SetZListItems('advanced_search', array('status', 'org_id', 'importance', 'device_id', 'licence_id', 'version')); + MetaModel::Init_SetZListItems('standard_search', array('status', 'org_id', 'importance', 'device_id', 'licence_id', 'version')); + MetaModel::Init_SetZListItems('list', array('finalclass', 'status', 'org_id', 'importance', 'device_id', 'version')); } public function GetName() @@ -755,7 +755,7 @@ abstract class SoftwareInstance extends FunctionalCI case 'depends on': $aRels = array( - "applications" => array("sQuery"=>"SELECT Device JOIN ApplicationInstance AS app ON app.device_id = Device.id WHERE app.id = :this->id", "bPropagate"=>true, "iDistance"=>5), + "applications" => array("sQuery"=>"SELECT Device JOIN SoftwareInstance AS app ON app.device_id = Device.id WHERE app.id = :this->id", "bPropagate"=>true, "iDistance"=>5), ); return array_merge($aRels, parent::GetRelationQueries($sRelCode)); break; @@ -775,7 +775,7 @@ class DBServerInstance extends SoftwareInstance "key_type" => "autoincrement", "name_attcode" => "software_name", "state_attcode" => "", - "reconc_keys" => array("software_id", "device_id"), + "reconc_keys" => array("name","software_id", "device_id","org_id"), "db_table" => "softwareinstance_dbserver", "db_key_field" => "id", "db_finalclass_field" => "", @@ -784,8 +784,8 @@ class DBServerInstance extends SoftwareInstance MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - //MetaModel::Init_OverloadAttributeParams("software_id", array("targetclass"=>"DBServer")); - //MetaModel::Init_OverloadAttributeParams("software_id", array("allowed_values"=>new ValueSetObjects('SELECT DBServer'))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("software_id", array("targetclass"=>"DBServer", "jointype"=>null, "allowed_values"=>null, "sql"=>"software_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("software_name", array("allowed_values"=>null, "extkey_attcode"=>"software_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSet("dbinstance_list", array("linked_class"=>"DatabaseInstance", "ext_key_to_me"=>"db_server_instance_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'device_id', 'licence_id', 'software_id', 'version', 'description', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'dbinstance_list')); @@ -804,7 +804,7 @@ class ApplicationInstance extends SoftwareInstance "key_type" => "autoincrement", "name_attcode" => "software_name", "state_attcode" => "", - "reconc_keys" => array("software_id", "device_id"), + "reconc_keys" => array("name","software_id", "device_id","org_id"), "db_table" => "softwareinstance_application", "db_key_field" => "id", "db_finalclass_field" => "", @@ -813,7 +813,8 @@ class ApplicationInstance extends SoftwareInstance MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - //MetaModel::Init_OverloadAttributeParams("software_id", array("targetclass"=>"Application")); + MetaModel::Init_AddAttribute(new AttributeExternalKey("software_id", array("targetclass"=>"Application", "jointype"=>null, "allowed_values"=>null, "sql"=>"software_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("software_name", array("allowed_values"=>null, "extkey_attcode"=>"software_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'device_id', 'licence_id', 'software_id', 'version', 'description', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list')); MetaModel::Init_SetZListItems('advanced_search', array('status', 'org_id', 'importance', 'device_id', 'licence_id', 'software_id', 'version')); @@ -833,7 +834,7 @@ class DatabaseInstance extends FunctionalCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","db_server_instance_id"), "db_table" => "databaseinstance", "db_key_field" => "id", "db_finalclass_field" => "", @@ -884,7 +885,7 @@ class ApplicationSolution extends FunctionalCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "applicationsolution", "db_key_field" => "id", "db_finalclass_field" => "", @@ -937,7 +938,7 @@ class BusinessProcess extends FunctionalCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "businessprocess", "db_key_field" => "id", "db_finalclass_field" => "", @@ -952,7 +953,7 @@ class BusinessProcess extends FunctionalCI MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'description', 'contact_list', 'document_list', 'contract_list', 'ticket_list', 'used_solution_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'description')); MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'description')); - MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'description')); + MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance')); } public static function GetRelationQueries($sRelCode) @@ -982,7 +983,7 @@ abstract class ConnectableCI extends FunctionalCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "connectableci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1013,7 +1014,7 @@ class NetworkInterface extends ConnectableCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","device_id","org_id"), "db_table" => "networkinterface", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1030,7 +1031,7 @@ class NetworkInterface extends ConnectableCI MetaModel::Init_AddAttribute(new AttributeIPAddress("ip_mask", array("allowed_values"=>null, "sql"=>"ip_mask", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("mac_address", array("allowed_values"=>null, "sql"=>"mac_address", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeInteger("speed", array("allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("allowed_values"=>new ValueSetEnum('full,half,unknown'), "sql"=>"duplex", "default_value"=>"full", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("allowed_values"=>new ValueSetEnum('full,half,auto,unknown'), "sql"=>"duplex", "default_value"=>"full", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("connected_if", array("targetclass"=>"NetworkInterface", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT NetworkInterface WHERE org_id = :this->org_id'), "sql"=>"connected_if", "is_null_allowed"=>true, "on_target_delete"=>DEL_AUTO, "depends_on"=>array("org_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("connected_name", array("allowed_values"=>null, "extkey_attcode"=>"connected_if", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("connected_if_device_id", array("allowed_values"=>null, "extkey_attcode"=>"connected_if", "target_attcode"=>"device_id", "is_null_allowed"=>true, "depends_on"=>array()))); @@ -1059,7 +1060,7 @@ abstract class Device extends ConnectableCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "device", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1111,7 +1112,7 @@ class PC extends Device "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "pc", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1130,7 +1131,7 @@ class PC extends Device MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'cpu', 'ram', 'hdd', 'os_family', 'os_version', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list', 'application_list', 'patch_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'cpu', 'ram', 'hdd', 'os_family', 'os_version')); - MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'cpu', 'ram', 'hdd', 'os_family', 'os_version')); + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'os_family', 'os_version')); MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'os_family')); } } @@ -1145,7 +1146,7 @@ abstract class MobileCI extends Device "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "mobileci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1158,7 +1159,7 @@ abstract class MobileCI extends Device MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref')); MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref')); - MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref')); + MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model')); } } class MobilePhone extends MobileCI @@ -1172,7 +1173,7 @@ class MobilePhone extends MobileCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "mobilephone", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1202,7 +1203,7 @@ abstract class InfrastructureCI extends Device "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","location_id"), "db_table" => "infrastructureci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1220,7 +1221,7 @@ abstract class InfrastructureCI extends Device MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway')); - MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway')); + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref','location_id','management_ip', 'default_gateway')); MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'location_id')); } } @@ -1235,7 +1236,7 @@ class NetworkDevice extends InfrastructureCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","location_id"), "db_table" => "networkdevice", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1252,7 +1253,7 @@ class NetworkDevice extends InfrastructureCI MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'ios_version', 'ram', 'snmp_read', 'snmp_write', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'ios_version', 'ram')); - MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'ios_version', 'ram')); + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'location_id','management_ip', 'default_gateway', 'type', 'ios_version')); MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'location_id', 'type')); } } @@ -1267,7 +1268,7 @@ class Server extends InfrastructureCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","location_id"), "db_table" => "server", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1286,7 +1287,7 @@ class Server extends InfrastructureCI MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'cpu', 'ram', 'hdd', 'os_family', 'os_version', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list', 'application_list', 'patch_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'cpu', 'ram', 'hdd', 'os_family', 'os_version')); - MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'cpu', 'ram', 'hdd', 'os_family', 'os_version')); + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'location_id', 'management_ip', 'default_gateway', 'os_family', 'os_version')); MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'location_id', 'os_family')); } } @@ -1301,7 +1302,7 @@ class Printer extends InfrastructureCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","location_id"), "db_table" => "printer", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1316,7 +1317,7 @@ class Printer extends InfrastructureCI MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'technology', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'technology')); MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'technology')); - MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'type', 'technology')); + MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'location_id', 'management_ip', 'default_gateway', 'type', 'technology')); } } class lnkCIToDoc extends cmdbAbstractObject @@ -1330,7 +1331,7 @@ class lnkCIToDoc extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "ci_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("ci_id","document_id"), "db_table" => "lnkcitodoc", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1364,7 +1365,7 @@ class lnkCIToContact extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "ci_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("ci_id","contact_id"), "db_table" => "lnkcitocontact", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1398,7 +1399,7 @@ class lnkSolutionToCI extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "solution_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("solution_id","ci_id"), "db_table" => "lnksolutiontoci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -1431,7 +1432,7 @@ class lnkProcessToSolution extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "solution_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("solution_id","process_id"), "db_table" => "lnkprocesstosolution", "db_key_field" => "id", "db_finalclass_field" => "", From 88efd3752562d24560e10508a1674ab771155dbc Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 27 Aug 2010 14:18:58 +0000 Subject: [PATCH 625/970] SVN:trunk[708] --- application/cmdbabstract.class.inc.php | 13 +- application/menunode.class.inc.php | 159 ++++++++++-- application/template.class.inc.php | 2 +- application/ui.passwordwidget.class.inc.php | 68 +++++ core/attributedef.class.inc.php | 199 ++++++++++++++- core/cmdbchangeop.class.inc.php | 120 ++++++++- core/cmdbobject.class.inc.php | 42 +++- core/config.class.inc.php | 20 ++ core/metamodel.class.php | 5 + core/ormpassword.class.inc.php | 119 +++++++++ core/simplecrypt.class.inc.php | 235 ++++++++++++++++++ dictionaries/dictionary.itop.core.php | 9 + dictionaries/dictionary.itop.ui.php | 8 +- dictionaries/es_cr.dictionary.itop.core.php | 7 + dictionaries/es_cr.dictionary.itop.ui.php | 5 +- dictionaries/fr.dictionary.itop.core.php | 55 ++-- dictionaries/fr.dictionary.itop.ui.php | 3 + js/forms-json-utils.js | 42 ++++ modules/authent-local/model.authent-local.php | 12 +- .../en.dict.itop-change-mgmt.php | 6 + .../es_cr.dict.itop-change-mgmt.php | 6 + .../fr.dict.itop-change-mgmt.php | 6 + .../model.itop-change-mgmt.php | 11 +- .../fr.dict.itop-config-mgmt.php | 5 +- .../model.itop-config-mgmt.php | 22 +- .../en.dict.itop-incident-mgmt.php | 6 + .../es_cr.dict.itop-incident-mgmt.php | 6 + .../fr.dict.itop-incident-mgmt.php | 6 + .../model.itop-incident-mgmt.php | 9 +- .../en.dict.itop-request-mgmt.php | 6 + .../es_cr.dict.itop-request-mgmt.php | 6 + .../fr.dict.itop-request-mgmt.php | 6 + .../model.itop-request-mgmt.php | 9 +- pages/UI.php | 15 +- 34 files changed, 1161 insertions(+), 87 deletions(-) create mode 100644 application/ui.passwordwidget.class.inc.php create mode 100644 core/ormpassword.class.inc.php create mode 100644 core/simplecrypt.class.inc.php diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 37a6908da7..d539c403d5 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -35,6 +35,7 @@ require_once('../core/cmdbobject.class.inc.php'); require_once('../application/utils.inc.php'); require_once('../application/applicationcontext.class.inc.php'); require_once('../application/ui.linkswidget.class.inc.php'); +require_once('../application/ui.passwordwidget.class.inc.php'); abstract class cmdbAbstractObject extends CMDBObject { @@ -220,6 +221,7 @@ abstract class cmdbAbstractObject extends CMDBObject 'target_attr' => $oAttDef->GetExtKeyToRemote(), 'view_link' => false, 'menu' => false, + 'display_limit' => true, // By default limit the list to speed up the initial load & display ); } $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); @@ -1001,6 +1003,12 @@ abstract class cmdbAbstractObject extends CMDBObject $sHTMLValue = ''; break; + case 'One Way Password': + $oWidget = new UIPasswordWidget($sAttCode, $iId, $sNameSuffix); + $sHTMLValue = $oWidget->Display($oPage, $aArgs); + // Event list & validation is handled directly by the widget + break; + case 'String': default: // #@# todo - add context information (depending on dimensions) @@ -1056,7 +1064,10 @@ abstract class cmdbAbstractObject extends CMDBObject break; } $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; - $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate + if (!empty($aEventlist)) + { + $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate + } $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one if (count($aDependencies) > 0) { diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index e511e85a57..1d66369f46 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -131,27 +131,30 @@ class ApplicationMenu { $index = $aMenu['index']; $oMenu = self::GetMenuNode($index); - $aChildren = self::GetChildren($index); - $sCSSClass = (count($aChildren) > 0) ? ' class="submenu"' : ''; - $sHyperlink = $oMenu->GetHyperlink($aExtraParams); - if ($sHyperlink != '') + if ($oMenu->IsEnabled()) { - $oPage->AddToMenu(''.$oMenu->GetTitle().''); - } - else - { - $oPage->AddToMenu(''.$oMenu->GetTitle().''); - } - $aCurrentMenu = self::$aMenusIndex[$index]; - if ($iActiveMenu == $index) - { - $bActive = true; - } - if (count($aChildren) > 0) - { - $oPage->AddToMenu('
          '); - $bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu); - $oPage->AddToMenu('
        '); + $aChildren = self::GetChildren($index); + $sCSSClass = (count($aChildren) > 0) ? ' class="submenu"' : ''; + $sHyperlink = $oMenu->GetHyperlink($aExtraParams); + if ($sHyperlink != '') + { + $oPage->AddToMenu(''.$oMenu->GetTitle().''); + } + else + { + $oPage->AddToMenu(''.$oMenu->GetTitle().''); + } + $aCurrentMenu = self::$aMenusIndex[$index]; + if ($iActiveMenu == $index) + { + $bActive = true; + } + if (count($aChildren) > 0) + { + $oPage->AddToMenu('
          '); + $bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu); + $oPage->AddToMenu('
        '); + } } } return $bActive; @@ -298,6 +301,15 @@ abstract class MenuNode return $this->AddParams('../pages/UI.php', $aExtraParams); } + /** + * Tells whether the menu is enabled (i.e. displayed) for the current user + * @return bool True if enabled, false otherwise + */ + public function IsEnabled() + { + return true; + } + public abstract function RenderContent(WebPage $oPage, $aExtraParams = array()); protected function AddParams($sHyperlink, $aExtraParams) @@ -394,20 +406,23 @@ class OQLMenuNode extends MenuNode { protected $sPageTitle; protected $sOQL; + protected $bSearch; /** * Create a menu item based on an OQL query and inserts it into the application's main menu * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) - * @param string $sPageTitle Title displayed into the page's content (will be looked-up in the dictionnary for translation) + * @param string $sOQL OQL query defining the set of objects to be displayed * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @param bool $bSearch Whether or not to display a (collapsed) search frame at the top of the page * @return MenuNode */ - public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0) + public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false) { parent::__construct($sMenuId, $iParentIndex, $fRank); $this->sPageTitle = "Menu:$sMenuId+"; $this->sOQL = $sOQL; + $this->bSearch = $bSearch; } public function RenderContent(WebPage $oPage, $aExtraParams = array()) @@ -422,8 +437,16 @@ class OQLMenuNode extends MenuNode $sIcon = ''; } // The standard template used for all such pages: a (closed) search form at the top and a list of results at the bottom - $sTemplate = <<bSearch) + { + $sTemplate .= <<$this->sOQL +EOF; + } + + $sTemplate .= <<$sIcon$this->sPageTitle

        $this->sOQL EOF; @@ -431,6 +454,40 @@ EOF; $oTemplate->Render($oPage, $aExtraParams); } } +/** + * This class defines a menu item that displays a search form for the given class of objects + */ +class SearchMenuNode extends MenuNode +{ + protected $sPageTitle; + protected $sClass; + + /** + * Create a menu item based on an OQL query and inserts it into the application's main menu + * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) + * @param string $sClass The class of objects to search for + * @param string $sPageTitle Title displayed into the page's content (will be looked-up in the dictionnary for translation) + * @param integer $iParentIndex ID of the parent menu + * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @return MenuNode + */ + public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0) + { + parent::__construct($sMenuId, $iParentIndex, $fRank); + $this->sPageTitle = "Menu:$sMenuId+"; + $this->sClass = $sClass; + } + + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + // The standard template used for all such pages: an open search form at the top + $sTemplate = <<SELECT $this->sClass +EOF; + $oTemplate = new DisplayTemplate($sTemplate); + $oTemplate->Render($oPage, $aExtraParams); + } +} /** * This class defines a menu that points to any web page. It takes only two parameters: @@ -468,4 +525,60 @@ class WebPageMenuNode extends MenuNode assert(false); // Shall never be called, the external web page will handle the display by itself } } + +/** + * This class defines a menu that points to the page for creating a new object of the specified class. + * It take only one parameter: the name of the class + * Note: the parameter menu=xxx (where xxx is the id of the menu itself) will be added to the hyperlink + * in order to make it the active one + */ +class NewObjectMenuNode extends MenuNode +{ + protected $sClass; + + /** + * Create a menu item that points to the URL for creating a new object, the menu will be added only if the current user has enough + * rights to create such an object (or an object of a child class) + * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) + * @param string $sClass URL to the page to load. Use relative URL if you want to keep the application portable ! + * @param integer $iParentIndex ID of the parent menu + * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @return MenuNode + */ + public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0) + { + parent::__construct($sMenuId, $iParentIndex, $fRank); + $this->sClass = $sClass; + } + + public function GetHyperlink($aExtraParams) + { + $sHyperlink = '../pages/UI.php?operation=new&class='.$this->sClass; + $aExtraParams['menu'] = $this->GetIndex(); + return $this->AddParams($sHyperlink, $aExtraParams); + } + + public function IsEnabled() + { + // Enable this menu, only if the current user has enough rights to create such an object, or an object of + // any child class + + $aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself + $bActionIsAllowed = false; + + foreach($aSubClasses as $sCandidateClass) + { + if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) + { + $bActionIsAllowed = true; + break; // Enough for now + } + } + return $bActionIsAllowed; + } + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + assert(false); // Shall never be called, the external web page will handle the display by itself + } +} ?> diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 964dc40e36..3fb8ad0946 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -170,7 +170,7 @@ class DisplayTemplate case 'itopcheck': $sClassName = $aAttributes['class']; - if (MetaModel::IsValidClass($sClassName)) + if (MetaModel::IsValidClass($sClassName) && UserRights::IsActionAllowed($sClassName, UR_ACTION_READ)) { $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php new file mode 100644 index 0000000000..43f240ba46 --- /dev/null +++ b/application/ui.passwordwidget.class.inc.php @@ -0,0 +1,68 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once('../application/webpage.class.inc.php'); +require_once('../application/displayblock.class.inc.php'); + +class UIPasswordWidget +{ + protected static $iWidgetIndex = 0; + protected $sAttCode; + protected $sNameSuffix; + protected $iId; + + public function __construct($sAttCode, $iInputId, $sNameSuffix = '') + { + self::$iWidgetIndex++; + $this->sAttCode = $sAttCode; + $this->sNameSuffix = $sNameSuffix; + $this->iId = $iInputId; + } + + /** + * Get the HTML fragment corresponding to the linkset editing widget + * @param WebPage $oP The web page used for all the output + * @param Hash $aArgs Extra context arguments + * @return string The HTML fragment to be inserted into the page + */ + public function Display(WebPage $oPage, $aArgs = array()) + { + $sCode = $this->sAttCode.$this->sNameSuffix; + $iWidgetIndex = self::$iWidgetIndex; + $sHtmlValue = ''; + $sHtmlValue = ' 
        '; + $sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' '; + $sHtmlValue .= ''; + + $oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#{$this->iId}_confirm').bind('keyup change', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate + + return $sHtmlValue; + } +} +?> \ No newline at end of file diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index d541eeef6f..895ffec487 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -26,6 +26,7 @@ require_once('MyHelpers.class.inc.php'); require_once('ormdocument.class.inc.php'); +require_once('ormpassword.class.inc.php'); /** * MissingColumnException - sent if an attribute is being created but the column is missing in the row @@ -377,7 +378,7 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet * @package iTopORM */ class AttributeDBFieldVoid extends AttributeDefinition -{ +{ static protected function ListExpectedParams() { return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql")); @@ -465,7 +466,7 @@ class AttributeDBFieldVoid extends AttributeDefinition * @package iTopORM */ class AttributeDBField extends AttributeDBFieldVoid -{ +{ static protected function ListExpectedParams() { return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); @@ -861,6 +862,66 @@ class AttributePassword extends AttributeString } } +/** + * Map a text column (size < 255) to an attribute that is encrypted in the database + * The encryption is based on a key set per iTop instance. Thus if you export your + * database (in SQL) to someone else without providing the key at the same time + * the encrypted fields will remain encrypted + * + * @package iTopORM + */ +class AttributeEncryptedString extends AttributeString +{ + static $sKey = null; // Encryption key used for all encrypted fields + + public function __construct($sCode, $aParams) + { + parent::__construct($sCode, $aParams); + if (self::$sKey == null) + { + self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); + } + } + + protected function GetSQLCol() {return "TINYBLOB";} + + public function GetFilterDefinitions() + { + // Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists) + // not allowed to search on encrypted fields ! + return array(); + } + + public function MakeRealValue($proposedValue) + { + if (is_null($proposedValue)) return null; + return (string)$proposedValue; + } + + /** + * Decrypt the value when reading from the database + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + $oSimpleCrypt = new SimpleCrypt(); + $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]); + return $sValue; + } + + /** + * Encrypt the value before storing it in the database + */ + public function GetSQLValues($value) + { + $oSimpleCrypt = new SimpleCrypt(); + $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value); + + $aValues = array(); + $aValues[$this->Get("sql")] = $encryptedValue; + return $aValues; + } +} + /** * Map a text column (size > ?) to an attribute * @@ -1880,6 +1941,140 @@ class AttributeBlob extends AttributeDefinition return ''; // Not exportable in XML, or as CDATA + some subtags ?? } } +/** + * One way encrypted (hashed) password + */ +class AttributeOneWayPassword extends AttributeDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("depends_on")); + } + + public function GetType() {return "One Way Password";} + public function GetTypeDesc() {return "One Way Password";} + public function GetEditClass() {return "One Way Password";} + + public function IsDirectField() {return true;} + public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetDefaultValue() {return "";} + public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + + // Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted) + public function MakeRealValue($proposedValue) + { + $oPassword = $proposedValue; + if (!is_object($oPassword)) + { + $oPassword = new ormPassword('', ''); + $oPassword->SetPassword($proposedValue); + } + return $oPassword; + } + + public function GetSQLExpressions() + { + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $this->GetCode().'_hash'; + $aColumns['_salt'] = $this->GetCode().'_salt'; + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + if (!isset($aCols[$sPrefix])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $hashed = $aCols[$sPrefix]; + + if (!isset($aCols[$sPrefix.'_salt'])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}"); + } + $sSalt = $aCols[$sPrefix.'_salt']; + + $value = new ormPassword($hashed, $sSalt); + return $value; + } + + public function GetSQLValues($value) + { + // #@# Optimization: do not load blobs anytime + // As per mySQL doc, selecting blob columns will prevent mySQL from + // using memory in case a temporary table has to be created + // (temporary tables created on disk) + // We will have to remove the blobs from the list of attributes when doing the select + // then the use of Get() should finalize the load + if ($value instanceOf ormPassword) + { + $aValues = array(); + $aValues[$this->GetCode().'_hash'] = $value->GetHash(); + $aValues[$this->GetCode().'_salt'] = $value->GetSalt(); + } + else + { + $aValues = array(); + $aValues[$this->GetCode().'_hash'] = ''; + $aValues[$this->GetCode().'_salt'] = ''; + echo "Writing an empty password !!!"; + echo "
        \n";
        +			print_r($value);
        +			echo "
        \n"; + } + return $aValues; + } + + public function GetSQLColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode().'_hash'] = 'TINYBLOB'; + $aColumns[$this->GetCode().'_salt'] = 'TINYBLOB'; + return $aColumns; + } + + public function GetFilterDefinitions() + { + return array(); + // still not working... see later... + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + public function GetAsHTML($value) + { + if (is_object($value)) + { + return $value->GetAsHTML(); + } + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') + { + return ''; // Not exportable in CSV + } + + public function GetAsXML($value) + { + return ''; // Not exportable in XML + } +} // Indexed array having two dimensions class AttributeTable extends AttributeText diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index ab520fe92b..f32dc2bf32 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -102,7 +102,7 @@ class CMDBChangeOpCreate extends CMDBChangeOp */ public function GetDescription() { - return 'Object created'; + return Dict::S('Change:ObjectCreated'); } } @@ -135,7 +135,7 @@ class CMDBChangeOpDelete extends CMDBChangeOp */ public function GetDescription() { - return 'Object deleted'; + return Dict::S('Change:ObjectDeleted'); } } @@ -228,16 +228,16 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end { $sDelta = substr($sNewValue, strlen($sOldValue)); - $sResult = "$sDelta appended to $sAttName"; + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sAttName); } else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning { $sDelta = substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue)); - $sResult = "$sDelta appended to $sAttName"; + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sAttName); } else { - $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue); } } elseif($bIsHtml && $oAttDef->IsExternalKey()) @@ -253,7 +253,7 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute } else { - $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + $sResult = Dict::Format('Change:Att_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue); } } return $sResult; @@ -313,7 +313,111 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute $sDocView .= "
        ".Dict::Format('UI:OpenDocumentInNewWindow_',$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n"; $sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n"; //$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata'); - $sResult = "$sAttName changed, previous value: $sDocView"; + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView); + } + return $sResult; + } +} +/** + * Safely record the modification of one way encrypted password + */ +class CMDBChangeOpSetAttributeOneWayPassword extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_pwd", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeOneWayPassword("prev_pwd", array("sql" => 'data', "default_value" => '', "is_null_allowed"=> true, "allowed_values" => null, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + + $sResult = ''; + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sResult = Dict::Format('Change:AttName_Changed', $sAttName); + } + return $sResult; + } +} + +/** + * Safely record the modification of an encrypted field + */ +class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_encrypted", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEncryptedString("prevstring", array("sql" => 'data', "default_value" => '', "is_null_allowed"=> true, "allowed_values" => null, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + + $sResult = ''; + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sPrevString = $this->Get('prevstring'); + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevString); } return $sResult; } @@ -370,7 +474,7 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute $sTextView = '
        '.$this->GetAsHtml('prevdata').'
        '; //$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata'); - $sResult = "$sAttName changed, previous value: $sTextView"; + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView); } return $sResult; } diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 3a8de347c1..50ba69a6b5 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -187,7 +187,47 @@ abstract class CMDBObject extends DBObject $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); if ($oAttDef->IsLinkSet()) continue; // #@# temporary - if ($oAttDef instanceOf AttributeBlob) + if ($oAttDef instanceOf AttributeOneWayPassword) + { + // One Way encrypted passwords' history is stored -one way- encrypted + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $original = $aOrigValues[$sAttCode]; + } + else + { + $original = ''; + } + $oMyChangeOp->Set("prev_pwd", $original); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + elseif ($oAttDef instanceOf AttributeEncryptedString) + { + // Encrypted string history is stored encrypted + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $original = $aOrigValues[$sAttCode]; + } + else + { + $original = ''; + } + $oMyChangeOp->Set("prevdata", $original); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + elseif ($oAttDef instanceOf AttributeBlob) { // Data blobs $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob"); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 8b83ed22b5..1afedc700a 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -46,6 +46,7 @@ define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); define ('DEFAULT_HTTPS_HYPERLINKS', false); define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external'); define ('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']'); +define ('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random value, later... /** * Config @@ -126,6 +127,13 @@ class Config */ protected $m_sExtAuthVariable; + /** + * @var string Encryption key used for all attributes of type "encrypted string". Can be set to a random value + * unless you want to import a database from another iTop instance, in which case you must use + * the same encryption key in order to properly decode the encrypted fields + */ + protected $m_sEncryptionKey; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; @@ -177,6 +185,7 @@ class Config $this->m_sDefaultLanguage = 'EN US'; $this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE; + $this->m_sEncryptionKey = DEFAULT_ENCRYPTION_KEY; $this->m_aModuleSettings = array(); @@ -278,6 +287,7 @@ class Config $this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US'; $this->m_sAllowedLoginTypes = isset($MySettings['allowed_login_types']) ? trim($MySettings['allowed_login_types']) : DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = isset($MySettings['ext_auth_variable']) ? trim($MySettings['ext_auth_variable']) : DEFAULT_EXT_AUTH_VARIABLE; + $this->m_sEncryptionKey = isset($MySettings['encryption_key']) ? trim($MySettings['encryption_key']) : DEFAULT_ENCRYPTION_KEY; } protected function Verify() @@ -430,6 +440,10 @@ class Config return $this->m_sDefaultLanguage; } + public function GetEncryptionKey() + { + return $this->m_sEncryptionKey; + } public function GetAllowedLoginTypes() { @@ -531,6 +545,11 @@ class Config $this->m_sExtAuthVariable = $sExtAuthVariable; } + public function SetEncryptionKey($sKey) + { + $this->m_sEncryptionKey = $sKey; + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -580,6 +599,7 @@ class Config fwrite($hFile, "\t'https_hyperlinks' => ".($this->m_bHttpsHyperlinks ? 'true' : 'false').",\n"); fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n"); fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n"); + fwrite($hFile, "\t'encryption_key' => '{$this->m_sEncryptionKey}',\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n"); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6b9ab63e61..4c242009d0 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3216,6 +3216,11 @@ abstract class MetaModel return self::$m_oConfig->GetModuleSetting($sModule, $sProperty, $defaultvalue); } + public static function GetConfig() + { + return self::$m_oConfig; + } + protected static $m_aPlugins = array(); public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) { diff --git a/core/ormpassword.class.inc.php b/core/ormpassword.class.inc.php new file mode 100644 index 0000000000..2170764807 --- /dev/null +++ b/core/ormpassword.class.inc.php @@ -0,0 +1,119 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + * @package itopORM + */ + +class ormPassword +{ + protected $m_sHashed; + protected $m_sSalt; + + /** + * Constructor, initializes the password from the encrypted values + */ + public function __construct($sHash = '', $sSalt = '') + { + $this->m_sHashed = $sHash; + $this->m_sSalt = $sSalt; + } + + /** + * Encrypts the clear text password, with a unique salt + */ + public function SetPassword($sClearTextPassword) + { + $this->m_sSalt = SimpleCrypt::GetNewSalt(); + $this->m_sHashed = $this->ComputeHash($sClearTextPassword); + } + + /** + * Print the password: displays some stars + * @return string + */ + public function __toString() + { + return '*****'; // Password can not be read + } + + public function IsEmpty() + { + return ($this->m_hashed == null); + } + + public function GetHash() + { + return $this->m_sHashed; + } + + public function GetSalt() + { + return $this->m_sSalt; + } + + /** + * Displays the password: displays some stars + * @return string + */ + public function GetAsHTML() + { + return '*****'; // Password can not be read + } + + /** + * Check if the supplied clear text password matches the encrypted one + * @param string $sClearTextPassword + * @return boolean True if it matches, false otherwise + */ + public function CheckPassword($sClearTextPassword) + { + $bResult = false; + $sHashedPwd = $this->ComputeHash($sClearTextPassword); + if ($this->m_sHashed == $sHashedPwd) + { + $bResult = true; + } + return $bResult; + } + + /** + * Computes the hashed version of a password using a unique salt + * for this password. A unique salt is generated if needed + * @return string + */ + protected function ComputeHash($sClearTextPwd) + { + if ($this->m_sSalt == null) + { + $this->m_sSalt = SimpleCrypt::GetNewSalt(); + } + return hash('sha256', $this->m_sSalt.$sClearTextPwd); + } +} +?> \ No newline at end of file diff --git a/core/simplecrypt.class.inc.php b/core/simplecrypt.class.inc.php new file mode 100644 index 0000000000..f2bf00a8d6 --- /dev/null +++ b/core/simplecrypt.class.inc.php @@ -0,0 +1,235 @@ +encrypt('a_key','the_text'); + * $sClearText = $oSimpleCrypt->decrypt('a_key',$encrypted); + * + * The result is $plain equals to 'the_text' + * + * You can use a different engine if you don't have Mcrypt: + * $oSimpleCrypt = new SimpleCrypt('Simple'); + * + * A string encrypted with one engine can't be decrypted with + * a different one even if the key is the same. + * + * @author Miguel Ros + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @version 0.3 + * @license GPL + */ + +class SimpleCrypt +{ + /** + * Constructor + * @param string $sEngineName Engine for encryption. Values: Simple, Mcrypt + */ + function __construct($sEngineName = 'Mcrypt') + { + if (($sEngineName == 'Mcrypt') && (!function_exists('mcrypt_module_open'))) + { + // Defaults to Simple encryption if the mcrypt module is not present + $sEngineName = 'Simple'; + } + $sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine'; + $this->oEngine = new $sEngineName; + } + + /** + * Encrypts the string with the given key + * @param string $key + * @param string $sString Plaintext string + * @return string Ciphered string + */ + function Encrypt($key, $sString) + { + return $this->oEngine->Encrypt($key,$sString); + } + + + /** + * Decrypts the string by the given key + * @param string $key + * @param string $string Ciphered string + * @return string Plaintext string + */ + function Decrypt($key, $string) + { + return $this->oEngine->Decrypt($key,$string); + } + + /** + * Returns a random "salt" value, to be used when "hashing" a password + * using a one-way encryption algorithm, to prevent an attack using a "rainbow table" + * Tryes to use the best available random number generator + * @return string The generated random "salt" + */ + static function GetNewSalt() + { + // Copied from http://www.php.net/manual/en/function.mt-rand.php#83655 + // get 128 pseudorandom bits in a string of 16 bytes + + $sRandomBits = null; + + // Unix/Linux platform? + $fp = @fopen('/dev/urandom','rb'); + if ($fp !== FALSE) + { + //echo "Random bits pulled from /dev/urandom
        \n"; + $sRandomBits .= @fread($fp,16); + @fclose($fp); + } + else + { + // MS-Windows platform? + if (@class_exists('COM')) + { + // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx + try + { + $CAPI_Util = new COM('CAPICOM.Utilities.1'); + $sBase64RandomBits = ''.$CAPI_Util->GetRandom(16,0); + + // if we ask for binary data PHP munges it, so we + // request base64 return value. We squeeze out the + // redundancy and useless ==CRLF by hashing... + if ($sBase64RandomBits) + { + //echo "Random bits got from CAPICOM.Utilities.1
        \n"; + $sRandomBits = md5($sBase64RandomBits, TRUE); + } + } + catch (Exception $ex) + { + // echo 'Exception: ' . $ex->getMessage(); + } + } + } + if ($sRandomBits == null) + { + // No "strong" random generator available, use PHP's built-in mechanism + //echo "Random bits generated from mt_rand
        \n"; + mt_srand(crc32(microtime())); + $sRandomBits = ''; + for($i = 0; $i < 4; $i++) + { + $sRandomBits .= sprintf('%04x', mt_rand(0, 65535)); + } + + + } + return $sRandomBits; + } +} + +/** + * Interface for encryption engines + */ +interface CryptEngine +{ + function Encrypt($key, $sString); + function Decrypt($key, $encrypted_data); +} + +/** + * Simple Engine doesn't need any PHP extension. + * Every encryption of the same string with the same key + * will return the same encrypted string + */ +class SimpleCryptSimpleEngine implements CryptEngine +{ + public function Encrypt($key, $sString) + { + $result = ''; + for($i=1; $i<=strlen($sString); $i++) + { + $char = substr($sString, $i-1, 1); + $keychar = substr($key, ($i % strlen($key))-1, 1); + $char = chr(ord($char)+ord($keychar)); + $result.=$char; + } + return $result; + } + + public function Decrypt($key, $encrypted_data) + { + $result = ''; + for($i=1; $i<=strlen($encrypted_data); $i++) + { + $char = substr($encrypted_data, $i-1, 1); + $keychar = substr($key, ($i % strlen($key))-1, 1); + $char = chr(ord($char)-ord($keychar)); + $result.=$char; + } + return $result; + } +} + +/** + * McryptEngine requires Mcrypt extension + * Every encryption of the same string with the same key + * will return a different encrypted string. + */ +class SimpleCryptMcryptEngine implements CryptEngine +{ + var $alg = MCRYPT_BLOWFISH; + var $td = null; + + public function __construct() + { + $this->td = mcrypt_module_open($this->alg,'','cbc',''); + } + + public function Encrypt($key, $sString) + { + $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($this->td), MCRYPT_RAND); // MCRYPT_RAND is the only choice on Windows prior to PHP 5.3 + mcrypt_generic_init($this->td, $key, $iv); + if (empty($sString)) + { + $sString = str_repeat("\0", 8); + } + $encrypted_data = mcrypt_generic($this->td, $sString); + mcrypt_generic_deinit($this->td); + return $iv.$encrypted_data; + } + + public function Decrypt($key, $encrypted_data) + { + $iv = substr($encrypted_data, 0, mcrypt_enc_get_iv_size($this->td)); + $string = substr($encrypted_data, mcrypt_enc_get_iv_size($this->td)); + mcrypt_generic_init($this->td, $key, $iv); + $decrypted_data = rtrim(mdecrypt_generic($this->td, $string), "\0"); + mcrypt_generic_deinit($this->td); + return $decrypted_data; + } + + public function __destruct() + { + mcrypt_module_close($this->td); + } +} +?> \ No newline at end of file diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index e726423361..5a7afaa8ac 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -104,6 +104,15 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'New value', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'new value of the attribute', )); +// Used by CMDBChangeOp... & derived classes +Dict::Add('EN US', 'English', 'English', array( + 'Change:ObjectCreated' => 'Object created', + 'Change:ObjectDeleted' => 'Object deleted', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s set to %2$s (previous value: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s appended to %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modified, previous value: %2$s', + 'Change:AttName_Changed' => '%1$s modified', +)); // // Class: CMDBChangeOpSetAttributeBlob diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index eddc4b8d46..ac399abf0a 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -315,7 +315,7 @@ Dict::Add('EN US', 'English', 'English', array(

        ', - 'UI:WelcomeMenu:MyCalls' => 'User Requests assigned to me', + 'UI:WelcomeMenu:MyCalls' => 'My requests', 'UI:WelcomeMenu:MyIncidents' => 'Incidents assigned to me', 'UI:AllOrganizations' => ' All Organizations ', 'UI:YourSearch' => 'Your Search', @@ -345,9 +345,11 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Button:Create' => ' Create ', 'UI:Button:Delete' => ' Delete ! ', 'UI:Button:ChangePassword' => ' Change Password ', - + 'UI:Button:ResetPassword' => ' Reset Password ', + 'UI:SearchToggle' => 'Search', 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', + 'UI:SearchFor_Class' => 'Search for %1$s objects', 'UI:NoObjectToDisplay' => 'No object to display.', 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template.', 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template.', @@ -383,6 +385,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:GroupBy:Count' => 'Count', 'UI:GroupBy:Count+' => 'Number of elements', 'UI:CountOfObjects' => '%1$d objects matching the criteria.', + 'UI_CountOfObjectsShort' => '%1$d objects.', 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', @@ -818,6 +821,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Help', + 'UI:PasswordConfirm' => '(Confirm)', )); diff --git a/dictionaries/es_cr.dictionary.itop.core.php b/dictionaries/es_cr.dictionary.itop.core.php index e56d1c89aa..e6abb9f8ae 100644 --- a/dictionaries/es_cr.dictionary.itop.core.php +++ b/dictionaries/es_cr.dictionary.itop.core.php @@ -104,6 +104,13 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Nuevo valor', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'nuevo valor del atributo', )); +// Used by CMDBChangeOp... & derived classes +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s modificado en %2$s (valor anterior: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s añadido a %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modificado, valor anterior: %2$s', + 'Change:AttName_Changed' => '%1$s modificado', +)); // // Class: CMDBChangeOpSetAttributeBlob diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 433ade8d2d..a6fa853ba4 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -331,7 +331,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(

        ', - 'UI:WelcomeMenu:MyCalls' => 'User Requests assigned to me', + 'UI:WelcomeMenu:MyCalls' => 'My requests', 'UI:WelcomeMenu:MyIncidents' => 'Incidents assigned to me', 'UI:AllOrganizations' => ' All Organizations ', 'UI:YourSearch' => 'Your Search', @@ -361,6 +361,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Button:Create' => ' Create ', 'UI:Button:Delete' => ' Delete ! ', 'UI:Button:ChangePassword' => ' Change Password ', + 'UI:Button:ResetPassword' => ' Reset Password ', 'UI:SearchToggle' => 'Search', 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', @@ -399,6 +400,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:GroupBy:Count' => 'Count', 'UI:GroupBy:Count+' => 'Number of elements', 'UI:CountOfObjects' => '%1$d objects matching the criteria.', + 'UI_CountOfObjectsShort' => '%1$d objects.', 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', @@ -829,6 +831,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Ayuda', + 'UI:PasswordConfirm' => '(Confirm)', )); ?> diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 742e75ec8e..468f307c06 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -33,7 +33,7 @@ // Class: CMDBChange // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChange' => 'change', 'Class:CMDBChange+' => 'Changes tracking', 'Class:CMDBChange/Attribute:date' => 'date', @@ -46,7 +46,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOp // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOp' => 'change operation', 'Class:CMDBChangeOp+' => 'Change operations tracking', 'Class:CMDBChangeOp/Attribute:change' => 'change', @@ -67,7 +67,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpCreate // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpCreate' => 'object creation', 'Class:CMDBChangeOpCreate+' => 'Object creation tracking', )); @@ -76,7 +76,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpDelete // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpDelete' => 'object deletion', 'Class:CMDBChangeOpDelete+' => 'Object deletion tracking', )); @@ -85,7 +85,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpSetAttribute // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttribute' => 'object change', 'Class:CMDBChangeOpSetAttribute+' => 'Object properties change tracking', 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Attribute', @@ -96,7 +96,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpSetAttributeScalar // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeScalar' => 'property change', 'Class:CMDBChangeOpSetAttributeScalar+' => 'Object scalar properties change tracking', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Previous value', @@ -104,12 +104,21 @@ Dict::Add('EN US', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'New value', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'new value of the attribute', )); +// Used by CMDBChangeOp... & derived classes +Dict::Add('FR FR', 'French', 'Français', array( + 'Change:ObjectCreated' => 'Elément créé', + 'Change:ObjectDeleted' => 'Elément effacé', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s modifié en %2$s (ancienne valeur: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s ajouté à %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifié, ancienne valeur: %2$s', + 'Change:AttName_Changed' => '%1$s modifié', +)); // // Class: CMDBChangeOpSetAttributeBlob // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeBlob' => 'data change', 'Class:CMDBChangeOpSetAttributeBlob+' => 'data change tracking', 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Previous data', @@ -120,7 +129,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpSetAttributeText // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeText' => 'text change', 'Class:CMDBChangeOpSetAttributeText+' => 'text change tracking', 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Previous data', @@ -131,7 +140,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: Event // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:Event' => 'Log Event', 'Class:Event+' => 'An application internal event', 'Class:Event/Attribute:message' => 'message', @@ -148,7 +157,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventNotification // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventNotification' => 'Notification event', 'Class:EventNotification+' => 'Trace of a notification that has been sent', 'Class:EventNotification/Attribute:trigger_id' => 'Trigger', @@ -163,7 +172,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventNotificationEmail // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventNotificationEmail' => 'Email emission event', 'Class:EventNotificationEmail+' => 'Trace of an email that has been sent', 'Class:EventNotificationEmail/Attribute:to' => 'TO', @@ -184,7 +193,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventIssue // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventIssue' => 'Issue event', 'Class:EventIssue+' => 'Trace of an issue (warning, error, etc.)', 'Class:EventIssue/Attribute:issue' => 'Issue', @@ -207,7 +216,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventWebService // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventWebService' => 'Web service event', 'Class:EventWebService+' => 'Trace of an web service call', 'Class:EventWebService/Attribute:verb' => 'Verb', @@ -228,7 +237,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: Action // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:Action' => 'action', 'Class:Action+' => 'Custom action', 'Class:Action/Attribute:name' => 'Name', @@ -253,7 +262,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: ActionNotification // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:ActionNotification' => 'notification', 'Class:ActionNotification+' => 'Notification (abstract)', )); @@ -262,7 +271,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: ActionEmail // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:ActionEmail' => 'email notification', 'Class:ActionEmail+' => 'Action: Email notification', 'Class:ActionEmail/Attribute:test_recipient' => 'Test recipient', @@ -295,7 +304,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: Trigger // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:Trigger' => 'trigger', 'Class:Trigger+' => 'Custom event handler', 'Class:Trigger/Attribute:description' => 'Description', @@ -310,7 +319,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnObject // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnObject' => 'Trigger on a class of objects', 'Class:TriggerOnObject+' => 'Trigger on a given class of objects', 'Class:TriggerOnObject/Attribute:target_class' => 'Target class', @@ -321,7 +330,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnStateChange // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateChange' => 'Trigger on object state change', 'Class:TriggerOnStateChange+' => 'Trigger on object state change', 'Class:TriggerOnStateChange/Attribute:state' => 'State', @@ -332,7 +341,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnStateEnter // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateEnter' => 'Trigger on object entering a state', 'Class:TriggerOnStateEnter+' => 'Trigger on object state change - entering', )); @@ -341,7 +350,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnStateLeave // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateLeave' => 'Trigger on object leaving a state', 'Class:TriggerOnStateLeave+' => 'Trigger on object state change - leaving', )); @@ -350,7 +359,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnObjectCreate // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnObjectCreate' => 'Trigger on object creation', 'Class:TriggerOnObjectCreate+' => 'Trigger on object creation of [a child class of] the given class', )); @@ -359,7 +368,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: lnkTriggerAction // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:lnkTriggerAction' => 'Actions-Trigger', 'Class:lnkTriggerAction+' => 'Link between a trigger and an action', 'Class:lnkTriggerAction/Attribute:action_id' => 'Action', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 7693f56b5a..cbf576f001 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -346,6 +346,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Button:Create' => ' Créer ', 'UI:Button:Delete' => ' Supprimer ! ', 'UI:Button:ChangePassword' => ' Changer ! ', + 'UI:Button:ResetPassword' => ' Ràz du mot de passe ', 'UI:SearchToggle' => 'Recherche', @@ -384,6 +385,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:GroupBy:Count' => 'Nombre', 'UI:GroupBy:Count+' => 'Nombre d\'éléments', 'UI:CountOfObjects' => '%1$d objets correspondants aux critères.', + 'UI_CountOfObjectsShort' => '%1$d objets.', 'UI:NoObject_Class_ToDisplay' => 'Aucun objet %1$s à afficher', 'UI:History:LastModified_On_By' => 'Dernière modification par %2$s le %1$s.', 'UI:HistoryTab' => 'Historique', @@ -829,6 +831,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dj %2$dh %3$dmin', 'UI:Help' => 'Aide', + 'UI:PasswordConfirm' => '(Confirmer)', )); ?> diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index 2588c61d65..f0f0597685 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -207,3 +207,45 @@ function UpdateDependentFields(aFieldNames) } oWizardHelper.AjaxQueryServer(); } + +function ResetPwd(id) +{ + // Reset the values of the password fields + $('#'+id).val('*****'); + $('#'+id+'_confirm').val('*****'); + // And reset the flag, to tell it that the password remains unchanged + $('#'+id+'_changed').val(0); + // Visual feedback, None when it's Ok + $('#v_'+id).html(''); +} + +// Called whenever the content of a one way encrypted password changes +function PasswordFieldChanged(id) +{ + // Set the flag, to tell that the password changed + console.log('Password changed'); + $('#'+id+'_changed').val(1); +} + +// Special validation function for one way encrypted password fields +function ValidatePasswordField(id, sFormId) +{ + var bChanged = $('#'+id+'_changed').val(); + if (bChanged) + { + if ($('#'+id).val() != $('#'+id+'_confirm').val()) + { + oFormErrors['err_'+sFormId]++; + if (oFormErrors['input_'+sFormId] == null) + { + // Let's remember the first input with an error, so that we can put back the focus on it later + oFormErrors['input_'+sFormId] = id; + } + // Visual feedback + $('#v_'+id).html(''); + return false; + } + } + $('#v_'+id).html(''); //'); + return true; +} \ No newline at end of file diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index 1ac7f63719..3da9c9ca1c 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -44,8 +44,7 @@ class UserLocal extends UserInternal MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributePassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEncryptedString("encrypted_password", array("allowed_values"=>null, "sql"=>"encrypted_pwd", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOneWayPassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); // Display lists MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'password', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details @@ -57,7 +56,14 @@ class UserLocal extends UserInternal public function CheckCredentials($sPassword) { - if ($this->Get('password') == $sPassword) +// if ($this->Get('password') == $sPassword) +// { +// return true; +// } + $oPassword = $this->Get('password'); // ormPassword object + // Cannot compare directly the values since they are hashed, so + // Let's ask the password to compare the hashed values + if ($oPassword->CheckPassword($sPassword)) { return true; } diff --git a/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php index 6e983436f4..9e79444b0d 100644 --- a/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php @@ -27,6 +27,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:ChangeManagement' => 'Change management', 'Menu:Change:Overview' => 'Overview', 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'New Change', + 'Menu:NewChange+' => 'Create a new Change ticket', + 'Menu:SearchChanges' => 'Search for Changes', + 'Menu:SearchChanges+' => 'Search for Change tickets', + 'Menu:Change:Shortcuts' => 'Shortcuts', + 'Menu:Change:Shortcuts+' => '', 'Menu:WaitingAcceptance' => 'Changes awaiting acceptance', 'Menu:WaitingAcceptance+' => '', 'Menu:WaitingApproval' => 'Changes awaiting approval', diff --git a/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php index 68a72e3e3f..526d302373 100644 --- a/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php @@ -27,6 +27,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:ChangeManagement' => 'Gestión del cambio', 'Menu:Change:Overview' => 'Visión General', 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'New Change', + 'Menu:NewChange+' => 'Create a new Change ticket', + 'Menu:SearchChanges' => 'Search for Changes', + 'Menu:SearchChanges+' => 'Search for Change tickets', + 'Menu:Change:Shortcuts' => 'Shortcuts', + 'Menu:Change:Shortcuts+' => '', 'Menu:WaitingAcceptance' => 'Cambios esperando ser aceptados', 'Menu:WaitingAcceptance+' => '', 'Menu:WaitingApproval' => 'Cambios esperando ser aprovados', diff --git a/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php index 3ead4d25a9..8d5fdb271e 100644 --- a/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php @@ -27,6 +27,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:ChangeManagement' => 'Gestion des changements', 'Menu:Change:Overview' => 'Vue d\'ensemble', 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'Nouveau changement', + 'Menu:NewChange+' => 'Créer un nouveau ticket de changement', + 'Menu:SearchChanges' => 'Rechercher des changements', + 'Menu:SearchChanges+' => 'Rechercher parmi les tickets de changement', + 'Menu:Change:Shortcuts' => 'Raccourcis', + 'Menu:Change:Shortcuts+' => '', 'Menu:WaitingAcceptance' => 'Tickets en attente d\'acceptance', 'Menu:WaitingAcceptance+' => '', 'Menu:WaitingApproval' => 'Tickets en attente d\'approbation', diff --git a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php index c7cb5e766f..3d7c8cda06 100644 --- a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php @@ -483,9 +483,12 @@ class EmergencyChange extends ApprovedChange $oMyMenuGroup = new MenuGroup('ChangeManagement', 50 /* fRank */); new TemplateMenuNode('Change:Overview', '../modules/itop-change-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); -new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oMyMenuGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oMyMenuGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('WaitingApproval', 'SELECT ApprovedChange WHERE status IN ("plannedscheduled")', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -new OQLMenuNode('WaitingAcceptance', 'SELECT NormalChange WHERE status IN ("new")', $oMyMenuGroup->GetIndex(), 4 /* fRank */); +new NewObjectMenuNode('NewChange', 'Change', $oMyMenuGroup->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchChanges', 'Change', $oMyMenuGroup->GetIndex(), 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('Change:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oShortcutNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('WaitingApproval', 'SELECT ApprovedChange WHERE status IN ("plannedscheduled")', $oShortcutNode->GetIndex(), 3 /* fRank */); +new OQLMenuNode('WaitingAcceptance', 'SELECT NormalChange WHERE status IN ("new")', $oShortcutNode->GetIndex(), 4 /* fRank */); ?> diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php index 30ec9f9ed0..bc041280ab 100644 --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php @@ -31,7 +31,10 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Relation:impacts/Description' => 'Eléments impactés par', 'Relation:impacts/VerbUp' => 'Impacte...', - 'Relation:impacts/VerbDown' => 'Eléments impactés par...', + 'Relation:impacts/VerbDown' => 'Dépend de...', + 'Relation:depends on/Description' => 'Eléments dont dépend cet élément', + 'Relation:depends on/VerbUp' => 'Dépend de...', + 'Relation:depends on/VerbDown' => 'Impacte...', )); diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 8cf7e05c8f..dc984320a0 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -1474,7 +1474,7 @@ new WebPageMenuNode('Audit', '../pages/audit.php', $iAdminGroup, 33 /* fRank */) $oTypologyNode = new TemplateMenuNode('Catalogs', '', $iAdminGroup, 50 /* fRank */); $iTopology = $oTypologyNode->GetIndex(); -new OQLMenuNode('Organization', 'SELECT Organization', $iTopology, 10 /* fRank */); +new OQLMenuNode('Organization', 'SELECT Organization', $iTopology, 10 /* fRank */, true /* bSearch */); new OQLMenuNode('Application', 'SELECT Application', $iTopology, 20 /* fRank */); new OQLMenuNode('DBServer', 'SELECT DBServer', $iTopology, 40 /* fRank */); @@ -1486,24 +1486,28 @@ new TemplateMenuNode('ConfigManagementOverview', '../modules/itop-config-mgmt-1. $oContactNode = new TemplateMenuNode('Contact', '../modules/itop-config-mgmt-1.0.0/contacts_menu.html', $oConfigManagementGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Person', 'SELECT Person', $oContactNode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Team', 'SELECT Team', $oContactNode->GetIndex(), 2 /* fRank */); +new NewObjectMenuNode('NewContact', 'Contact', $oContactNode->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchContacts', 'Contact', $oContactNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('Person', 'SELECT Person', $oContactNode->GetIndex(), 3 /* fRank */); +new OQLMenuNode('Team', 'SELECT Team', $oContactNode->GetIndex(), 4 /* fRank */); -new OQLMenuNode('Document', 'SELECT Document', $oConfigManagementGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Location', 'SELECT Location', $oConfigManagementGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('Document', 'SELECT Document', $oConfigManagementGroup->GetIndex(), 2 /* fRank */, true /* bSearch */); +new OQLMenuNode('Location', 'SELECT Location', $oConfigManagementGroup->GetIndex(), 3 /* fRank */, true /* bSearch */); $oCINode = new TemplateMenuNode('ConfigManagementCI', '../modules/itop-config-mgmt-1.0.0/cis_menu.html', $oConfigManagementGroup->GetIndex(), 4 /* fRank */); +new NewObjectMenuNode('NewCI', 'FunctionalCI', $oCINode->GetIndex(), 0 /* fRank */); +new SearchMenuNode('SearchCIs', 'FunctionalCI', $oCINode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('BusinessProcess', 'SELECT BusinessProcess', $oCINode->GetIndex(), 0 /* fRank */); -new OQLMenuNode('ApplicationSolution', 'SELECT ApplicationSolution', $oCINode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('BusinessProcess', 'SELECT BusinessProcess', $oCINode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('ApplicationSolution', 'SELECT ApplicationSolution', $oCINode->GetIndex(), 3 /* fRank */); -$oSWNode = new TemplateMenuNode('ConfigManagementSoftware', '', $oCINode->GetIndex(), 2 /* fRank */); +$oSWNode = new TemplateMenuNode('ConfigManagementSoftware', '', $oCINode->GetIndex(), 4 /* fRank */); new OQLMenuNode('Licence', 'SELECT Licence', $oSWNode->GetIndex(), 0 /* fRank */); new OQLMenuNode('Patch', 'SELECT Patch', $oSWNode->GetIndex(), 1 /* fRank */); new OQLMenuNode('ApplicationInstance', 'SELECT SoftwareInstance', $oSWNode->GetIndex(), 2 /* fRank */); -$oHWNode = new TemplateMenuNode('ConfigManagementHardware', '', $oCINode->GetIndex(), 3 /* fRank */); +$oHWNode = new TemplateMenuNode('ConfigManagementHardware', '', $oCINode->GetIndex(), 5 /* fRank */); new OQLMenuNode('Subnet', 'SELECT Subnet', $oHWNode->GetIndex(), 0 /* fRank */); new OQLMenuNode('NetworkDevice', 'SELECT NetworkDevice', $oHWNode->GetIndex(), 1 /* fRank */); new OQLMenuNode('Server', 'SELECT Server', $oHWNode->GetIndex(), 2 /* fRank */); diff --git a/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php index 7352570aec..837fa72672 100644 --- a/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:IncidentManagement+' => 'Incident Management', 'Menu:Incident:Overview' => 'Overview', 'Menu:Incident:Overview+' => 'Overview', + 'Menu:NewIncident' => 'New Incident', + 'Menu:NewIncident+' => 'Create a new Incident ticket', + 'Menu:SearchIncidents' => 'Search for Incidents', + 'Menu:SearchIncidents+' => 'Search for Incident tickets', + 'Menu:Incident:Shortcuts' => 'Shortcuts', + 'Menu:Incident:Shortcuts+' => '', 'Menu:Incident:MyIncidents' => 'Incidents assigned to me', 'Menu:Incident:MyIncidents+' => 'Incidents assigned to me (as Agent)', 'Menu:Incident:EscalatedIncidents' => 'Escalated Incidents', diff --git a/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php index abe09149fc..d16e982af0 100644 --- a/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:IncidentManagement+' => 'Gestión de Incidentes', 'Menu:Incident:Overview' => 'Visión General', 'Menu:Incident:Overview+' => 'Visión General', + 'Menu:NewIncident' => 'New Incident', + 'Menu:NewIncident+' => 'Create a new Incident ticket', + 'Menu:SearchIncidents' => 'Search for Incidents', + 'Menu:SearchIncidents+' => 'Search for Incident tickets', + 'Menu:Incident:Shortcuts' => 'Shortcuts', + 'Menu:Incident:Shortcuts+' => '', 'Menu:Incident:MyIncidents' => 'Incidentes asignados a mí', 'Menu:Incident:MyIncidents+' => 'Incidentes asignados a mí (como Agente)', 'Menu:Incident:EscalatedIncidents' => 'Incidentes Escalados', diff --git a/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php index 2ea56673ba..831c4efb64 100644 --- a/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:IncidentManagement+' => 'Gestion des incidents', 'Menu:Incident:Overview' => 'Vue d\'ensemble', 'Menu:Incident:Overview+' => 'Vue d\'ensemble', + 'Menu:NewIncident' => 'Nouvel Incident', + 'Menu:NewIncident+' => 'Créer un nouveau ticket d\'incident', + 'Menu:SearchIncidents' => 'Rechercher des incidents', + 'Menu:SearchIncidents+' => 'Rechercher parmi les tickets d\'incidents', + 'Menu:Incident:Shortcuts' => 'Raccourcis', + 'Menu:Incident:Shortcuts+' => '', 'Menu:Incident:MyIncidents' => 'Mes tickets', 'Menu:Incident:MyIncidents+' => 'Tickets d\'incident qui me sont assignés', 'Menu:Incident:EscalatedIncidents' => 'Ticket en cours d\'escalade', diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index d62989f9b6..2db0f4a156 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -96,8 +96,11 @@ class Incident extends ResponseTicket $oMyMenuGroup = new MenuGroup('IncidentManagement', 40 /* fRank */); new TemplateMenuNode('Incident:Overview', '../modules/itop-incident-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); -new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oMyMenuGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oMyMenuGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new NewObjectMenuNode('NewIncident', 'Incident', $oMyMenuGroup->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchIncidents', 'Incident', $oMyMenuGroup->GetIndex(), 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('Incident:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); ?> diff --git a/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php index 72631ce4ef..e62a916050 100644 --- a/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:RequestManagement+' => 'Helpdesk', 'Menu:UserRequest:Overview' => 'Overview', 'Menu:UserRequest:Overview+' => 'Overview', + 'Menu:NewUserRequest' => 'New User Request', + 'Menu:NewUserRequest+' => 'Create a new User Request ticket', + 'Menu:SearchUserRequests' => 'Search for User Requests', + 'Menu:SearchUserRequests+' => 'Search for User Request tickets', + 'Menu:UserRequest:Shortcuts' => 'Shortcuts', + 'Menu:UserRequest:Shortcuts+' => '', 'Menu:UserRequest:MyRequests' => 'Requests assigned to me', 'Menu:UserRequest:MyRequests+' => 'Requests assigned to me (as Agent)', 'Menu:UserRequest:EscalatedRequests' => 'Escalated Requests', diff --git a/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php index c55e62d6a0..b8d5c1a48c 100644 --- a/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:RequestManagement+' => 'Servicio de ayuda', 'Menu:UserRequest:Overview' => 'Visión General', 'Menu:UserRequest:Overview+' => 'Visión General', + 'Menu:NewUserRequest' => 'New User Request', + 'Menu:NewUserRequest+' => 'Create a new User Request ticket', + 'Menu:SearchUserRequests' => 'Search for User Requests', + 'Menu:SearchUserRequests+' => 'Search for User Request tickets', + 'Menu:UserRequest:Shortcuts' => 'Shortcuts', + 'Menu:UserRequest:Shortcuts+' => '', 'Menu:UserRequest:MyRequests' => 'Solicitudes asignadas a mí', 'Menu:UserRequest:MyRequests+' => 'Solicitudes asignadas a mí (como Agente)', 'Menu:UserRequest:EscalatedRequests' => 'Solicitudes Escaladas', diff --git a/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php index de8db84b45..9f63ff4094 100644 --- a/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:RequestManagement+' => 'Gestion des demandes utilisateurs', 'Menu:UserRequest:Overview' => 'Vue d\'ensemble', 'Menu:UserRequest:Overview+' => 'Vue d\'ensemble des demandes utilisateurs', + 'Menu:NewUserRequest' => 'Nouvelle demande utilisateur', + 'Menu:NewUserRequest+' => 'Créer un nouveau ticket de demande utilisateur', + 'Menu:SearchUserRequests' => 'Rechercher des demandes utilisateur', + 'Menu:SearchUserRequests+' => 'Rechercher parmi les demandes utilisateur', + 'Menu:UserRequest:Shortcuts' => 'Raccourcis', + 'Menu:UserRequest:Shortcuts+' => '', 'Menu:UserRequest:MyRequests' => 'Mes demandes', 'Menu:UserRequest:MyRequests+' => 'Demandes utilisateurs qui me sont assignées', 'Menu:UserRequest:EscalatedRequests' => 'Demandes en escalade', diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index 08bec2255f..8f6602881b 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -86,8 +86,11 @@ class UserRequest extends ResponseTicket $oMyMenuGroup = new MenuGroup('RequestManagement', 30 /* fRank */); new TemplateMenuNode('UserRequest:Overview', '../modules/itop-request-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); -new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id', $oMyMenuGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oMyMenuGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new NewObjectMenuNode('NewUserRequest', 'UserRequest', $oMyMenuGroup->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchUserRequests', 'UserRequest', $oMyMenuGroup->GetIndex(), 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('UserRequest:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); ?> diff --git a/pages/UI.php b/pages/UI.php index a246d632e1..1162235946 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -407,7 +407,7 @@ function UpdateObject(&$oObj) { // Non-visible, or read-only attribute, do nothing } - else if ($oAttDef->GetEditClass() == 'Document') + elseif ($oAttDef->GetEditClass() == 'Document') { // There should be an uploaded file with the named attr_ $oDocument = utils::ReadPostedDocument('file_'.$sAttCode); @@ -417,6 +417,17 @@ function UpdateObject(&$oObj) $oObj->Set($sAttCode, $oDocument); } } + elseif ($oAttDef->GetEditClass() == 'One Way Password') + { + // Check if the password was typed/changed + $bChanged = utils::ReadPostedParam("attr_{$sAttCode}_changed", false); + if ($bChanged) + { + // The password has been changed or set + $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); + $oObj->Set($sAttCode, $rawValue); + } + } else { $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); @@ -769,7 +780,7 @@ try { foreach($aSubClasses as $sCandidateClass) { - if (!MetaModel::IsAbstract($sCandidateClass)) + if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) { $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); } From bd001f8072fece762b154c11e75582344457a247 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 27 Aug 2010 15:34:47 +0000 Subject: [PATCH 626/970] - Better management of (truncated) list of linked objects (Trac #219) - New type of display block to display some "actions" (New/Search) for a given class of objects. SVN:trunk[709] --- application/cmdbabstract.class.inc.php | 4 ++++ application/displayblock.class.inc.php | 29 +++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index d539c403d5..511eca0bd7 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -483,6 +483,10 @@ abstract class cmdbAbstractObject extends CMDBObject $divId = $aExtraParams['block_id']; $sFilter = $oSet->GetFilter()->serialize(); $aExtraParams['display_limit'] = false; // To expand the full list + foreach($oSet->GetFilter()->GetInternalParams() as $sName => $sValue) + { + $aExtraParams['query_params'][$sName] = $sValue; + } $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them $sHtml .= '
        '.Dict::Format('UI:TruncatedResults', utils::GetConfig()->GetMinDisplayLimit(), $oSet->Count()).'  '.Dict::S('UI:DisplayAll').''; + $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); + $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); + } + else + { + // Full list + $sHtml .= '
         '.Dict::Format('UI:CountOfResults', $oSet->Count()).''; + } if ($bDisplayMenu) { $oMenuBlock = new MenuBlock($oSet->GetFilter()); @@ -485,22 +501,6 @@ abstract class cmdbAbstractObject extends CMDBObject //$aMenuExtraParams['linkage'] = $sLinkageAttribute; $aMenuExtraParams = $aExtraParams; } - if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) - { - // list truncated - $divId = $aExtraParams['block_id']; - $sFilter = $oSet->GetFilter()->serialize(); - $aExtraParams['display_limit'] = false; // To expand the full list - $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them - $sHtml .= '
        '.Dict::Format('UI:TruncatedResults', utils::GetConfig()->GetMinDisplayLimit(), $oSet->Count()).'  '.Dict::S('UI:DisplayAll').''; - $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); - $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); - } - else - { - // Full list - $sHtml .= '
         '.Dict::Format('UI:CountOfResults', $oSet->Count()).''; - } $sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams); $sHtml .= '
        '.Dict::Format('UI:TruncatedResults', utils::GetConfig()->GetMinDisplayLimit(), $oSet->Count()).'  '.Dict::S('UI:DisplayAll').''; $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index ca4fcc3aa3..ef466f2ff0 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -290,6 +290,11 @@ class DisplayBlock $bDoSearch = utils::ReadParam('dosearch', false); if ($this->m_oSet == null) { + $aQueryParams = array(); + if (isset($aExtraParams['query_params'])) + { + $aQueryParams = $aExtraParams['query_params']; + } if ($this->m_sStyle != 'links') { $aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass())); @@ -306,7 +311,7 @@ class DisplayBlock } } } - $this->m_oSet = new CMDBObjectSet($this->m_oFilter); + $this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams); } switch($this->m_sStyle) { @@ -340,8 +345,13 @@ class DisplayBlock else { // Simply count the number of elements in the set - $iCount = $oSet->Count(); - $sHtml .= $oPage->GetP(Dict::Format('UI:CountOfObjects', $iCount)); + $iCount = $this->m_oSet->Count(); + $sFormat = 'UI:CountOfObjects'; + if (isset($aExtraParams['format'])) + { + $sFormat = $aExtraParams['format']; + } + $sHtml .= $oPage->GetP(Dict::Format($sFormat, $iCount)); } break; @@ -542,6 +552,19 @@ class DisplayBlock } break; + case 'actions': + $sClass = $this->m_oFilter->GetClass(); + $oAppContext = new ApplicationContext(); + $sParams = $oAppContext->GetForLink(); + $sHtml .= '

        '; + if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY)) + { + $sHtml .= "".Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass))."
        \n"; + } + $sHtml .= "".Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass))."\n"; + $sHtml .= '

        '; + break; + case 'bare_details': while($oObj = $this->m_oSet->Fetch()) { From e6bdaf87e7d94d6c4bfb32e3e47e66b3867be401 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 29 Aug 2010 19:38:36 +0000 Subject: [PATCH 627/970] - Updated flash navigator: better objects placement, auto-zoom & pan at startup, improved tooltip with some details on each objects. SVN:trunk[710] --- navigator/iTop/GraphNode.as | 36 +++- navigator/iTop/Navigator.as | 363 ++++++++++++++++++++++++++++++++---- navigator/iTop/ToolTip.as | 88 +++++++-- navigator/navigator.fla | Bin 484864 -> 898048 bytes navigator/navigator.swf | Bin 24545 -> 29410 bytes pages/xml.navigator.php | 11 +- 6 files changed, 435 insertions(+), 63 deletions(-) diff --git a/navigator/iTop/GraphNode.as b/navigator/iTop/GraphNode.as index 7a51a0f710..f52d93dc49 100644 --- a/navigator/iTop/GraphNode.as +++ b/navigator/iTop/GraphNode.as @@ -5,6 +5,7 @@ import flash.net.*; import flash.events.*; import flash.text.*; + import flash.xml.*; import flash.ui.ContextMenu; import flash.ui.ContextMenuItem; import iTop.ToolTip; @@ -14,27 +15,46 @@ public class GraphNode extends Sprite { private var m_oIcon:Loader; - private var m_sClass;String; + private var m_sClass:String; + private var m_sClassName:String; private var m_iId:Number; private var m_sParentKey:String; private var m_oToolTip:ToolTip; private var m_fZoom:Number; private var m_oParent:Navigator; + private static const ROUND:Number = 20; + private static const PADDING:Number = 5; + public var m_speed_x:Number = 0; + public var m_speed_y:Number = 0; + public var m_bInDrag:Boolean = false; - public function GraphNode(oParent:Navigator, oPt:Point, sClass:String, iId:Number, sLabel:String, sIconPath:String, sParentKey:String, fZoom:Number) + public function GraphNode(oParent:Navigator, oPt:Point, sClass:String, sClassName:String, iId:Number, sLabel:String, sIconPath:String, sParentKey:String, fZoom:Number, oDetails:Object) { x = oPt.x; y = oPt.y; m_fZoom = fZoom; m_sClass = sClass; + m_sClassName = sClassName; m_iId = iId; + m_sLabel.autoSize = TextFieldAutoSize.LEFT; + m_sLabel.multiline = false; m_sLabel.text = sLabel; - m_sLabel.autoSize = TextFieldAutoSize.CENTER; m_sLabel.width = m_sLabel.textWidth; m_sLabel.x = -m_sLabel.width/2; m_sLabel.height = m_sLabel.textHeight; + // Draw the background + graphics.beginFill( 0xf1f1f6, 0.8 ); + graphics.drawRoundRect( m_sLabel.x -PADDING, m_sLabel.y - PADDING, m_sLabel.width+PADDING*2, m_sLabel.height+PADDING*2, ROUND ); + graphics.endFill(); + m_sParentKey = sParentKey; - m_oToolTip = new ToolTip( "

        "+m_sClass+"

        "+sLabel+"

        "); + var sTooltipText:String = "

        "+m_sClassName+"

        "+sLabel+"

        "; + for (var s:String in oDetails) + { + sTooltipText += '

        '+s+': '+oDetails[s]+'

        '; + } + trace('Tooltip text: '+sTooltipText); + m_oToolTip = new ToolTip(sTooltipText); m_oToolTip.scaleX = 1 / m_fZoom; m_oToolTip.scaleY = 1 / m_fZoom; m_oParent = oParent; @@ -94,11 +114,14 @@ { trace("Click in Node"); m_oParent.m_bChildDragging = true; + m_bInDrag = true; + m_oToolTip.timer.stop(); // Don't show the tooltip while dragging startDrag(); } function mouseReleased(event:MouseEvent):void { + m_bInDrag = false; stopDrag(); m_oParent.m_bChildDragging = false; } @@ -130,5 +153,10 @@ return sDefaultValue; } } + + public function GetLabelWidth():Number + { + return m_sLabel.width; + } } } \ No newline at end of file diff --git a/navigator/iTop/Navigator.as b/navigator/iTop/Navigator.as index 327f272206..b2c08ad45f 100644 --- a/navigator/iTop/Navigator.as +++ b/navigator/iTop/Navigator.as @@ -1,4 +1,4 @@ -package iTop +package iTop { import flash.display.*; import flash.geom.*; @@ -8,7 +8,7 @@ import fl.controls.Slider; import fl.events.SliderEvent; import fl.controls.Label; - + // The main canvas public class Navigator extends MovieClip { @@ -28,8 +28,14 @@ protected var m_sObjId:String; // Constants - protected var m_RADIUS = 120; - protected var m_ITEMS_PER_ROW = 8; + protected var m_RADIUS = 150; + protected var m_Q = 0.9; // Electrostatic forces coeff + protected var m_K = 1.0; // Elastic forces coeff + protected var m_Kf = 0.7; // Fluid friction coeff + protected var m_Ks = 30; // Solid friction coeff + protected var m_deltaT = 0.1; // Interval of time between updates + protected var m_MAX_ITEMS_PER_ROW = 8; + protected var m_FOCUS_DELAY_COUNTDOWN = 50; // 50 images to zoom & pan correctly protected var m_fZoom:Number; // Constructor @@ -41,6 +47,8 @@ initParameters(); doLoadData(); addEventListener(Event.ENTER_FRAME, initGraphics); + //Stop scaling the flash content + stage.scaleMode = StageScaleMode.NO_SCALE; } protected function initParameters():void @@ -62,11 +70,6 @@ m_oCanvas.scaleY = m_fZoom; // Handle listeners... removeEventListener(Event.ENTER_FRAME,initGraphics); - addEventListener(Event.ENTER_FRAME, drawLines); - m_oZoomSlider.value = 100; - m_oZoomSlider.addEventListener(SliderEvent.CHANGE, onZoomChange); - stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown) - stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased); } function mouseDown(event:MouseEvent):void { @@ -87,14 +90,24 @@ function onZoomChange(event:SliderEvent):void { - m_fZoom = event.value/100; + SetZoomLevel(event.value/100); + } + + function SetZoomLevel(fZoomLevel:Number):void + { + m_fZoom = fZoomLevel; m_oCanvas.scaleX = m_fZoom; m_oCanvas.scaleY = m_fZoom; } function doLoadData() { - var myString:String = m_sDataUrl+'?relation='+m_sRelation+'&class='+m_sObjClass+'&id='+m_sObjId; + var sSeparator:String = '?'; + if (m_sDataUrl.indexOf(sSeparator) != -1) + { + sSeparator = '&'; + } + var myString:String = m_sDataUrl+sSeparator+'relation='+m_sRelation+'&class='+m_sObjClass+'&id='+m_sObjId; trace("Requesting:"+myString); var myXMLURL:URLRequest = new URLRequest(myString); m_oLoader = new URLLoader(); @@ -109,10 +122,17 @@ var myXML:XML = XML(m_oLoader.data); //trace("Data loaded." + myXML); //trace("==========================="); - parseXMLData(null, myXML, 0); + parseXMLData(null, myXML, 0, 0); m_sTitle.text = myXML.attribute("title"); m_oZoomSlider.enabled = true; removeChild(m_oPreloader); + addEventListener(Event.ENTER_FRAME, drawLines); + m_oZoomSlider.value = 100; + m_oZoomSlider.addEventListener(SliderEvent.CHANGE, onZoomChange); + stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown) + stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased); + //trace('======= Initial Posistions ========='); + //DumpPositions(); } function onXMLLoadError(event:IOErrorEvent):void @@ -120,11 +140,11 @@ trace("An error occured:" + Event); } - function parseXMLData(oParentNode:GraphNode, oXMLData:XML, iChildIndex:Number) + function parseXMLData(oParentNode:GraphNode, oXMLData:XML, iChildIndex:Number, iChildCount:Number) { //trace(oXMLData.child("node").length()); var oNode:GraphNode; - oNode = addNode(oParentNode, oXMLData.child("node")[0], iChildIndex); + oNode = addNode(oParentNode, oXMLData.child("node")[0], iChildIndex, iChildCount); if (oParentNode != null) { AddLink(oParentNode.GetKey(), oNode.GetKey()); @@ -138,30 +158,43 @@ var oLink = oLinks.link; for each(var oChild:XML in oLink) { - parseXMLData(oNode, oChild, iChildIndex); + parseXMLData(oNode, oChild, iChildIndex, oLinks.link.length()); iChildIndex++; } } } - function addNode(oParent:GraphNode, oXMLData:XML, iChildIndex) + function addNode(oParent:GraphNode, oXMLData:XML, iChildIndex:Number, iChildCount:Number) { - var sClass = oXMLData.@obj_class; + var sClass:String = oXMLData.@obj_class; + var sClassName:String = oXMLData.@obj_class_name; var iId = oXMLData.@id; - var sLabel = oXMLData.@name; - var sIcon = oXMLData.@icon; + var sLabel:String = oXMLData.@name; + var sIcon:String = oXMLData.@icon; + var oDetails:Object = new Object; + var sZlist:String = oXMLData.@zlist; var oNode:GraphNode = GetNode(sClass+'/'+iId); if (oNode == null) { // If the node does not already exist, let's create it - var oPt:Point = GetNextFreePosition(oParent, iChildIndex); + var oPt:Point = GetNextFreePosition(oParent, iChildIndex, iChildCount); var sParentKey = null; if (oParent != null) { sParentKey = oParent.GetKey(); } - oNode = new GraphNode(this, oPt, sClass, iId, sLabel, sIcon, sParentKey, m_fZoom); + // Read the details + var aDetails:Array; + aDetails = sZlist.split(','); + for(var i:String in aDetails) + { + //if (oXMLData.hasOwnProperty('att_'+i)) + //{ + oDetails[aDetails[i]] = oXMLData.attribute('att_'+i).toString(); + //} + } + oNode = new GraphNode(this, oPt, sClass, sClassName, iId, sLabel, sIcon, sParentKey, m_fZoom, oDetails); this.m_aNodes[oNode.GetKey()] = oNode; //Keep it referenced if (oParent == null) { @@ -185,7 +218,7 @@ } } - function GetNextFreePosition(oParent:GraphNode, iChildIndex:Number):Point + function GetNextFreePosition(oParent:GraphNode, iChildIndex:Number, iChildCount:Number):Point { var oPt:Point = GetInitialPosition(); var angle:Number = GetInitialAngle(); @@ -208,13 +241,43 @@ angle = Math.atan2(dy, dx); } } - var radius = m_RADIUS * Math.floor(iChildIndex / m_ITEMS_PER_ROW); - angle += iChildIndex*(2*Math.PI) / m_ITEMS_PER_ROW; + var nbItemsOnRow:Number = 0; + var nbRows:Number = 0; + // Determines the position of this element + // The elements are placed on circles of maximum m_MAX_ITEMS_PER_ROW elements per row + // The last row containing potentially less items + // nbRows indicates on which row (first row = 0) the item is to be placed + if (iChildCount > m_MAX_ITEMS_PER_ROW) + { + nbRows = Math.floor(iChildIndex / m_MAX_ITEMS_PER_ROW); + if ( iChildIndex > (Math.floor(iChildCount / m_MAX_ITEMS_PER_ROW)*m_MAX_ITEMS_PER_ROW)) + { + // node is on the last (incomplete) row + nbItemsOnRow = (iChildCount % m_MAX_ITEMS_PER_ROW); + } + else + { + nbItemsOnRow = m_MAX_ITEMS_PER_ROW; + } + } + else + { + if (iChildCount == 2) + { + nbItemsOnRow = 4; // Nicer display than everything aligned at 180 deg. + } + else + { + nbItemsOnRow = iChildCount; + } + } + var radius = this.m_RADIUS * (1 + nbRows); + angle += (1 - 2*((1+iChildIndex) % 2))*(Math.floor((1+iChildIndex) / 2))*(2*Math.PI) / nbItemsOnRow; - oPt.x += m_RADIUS * Math.cos(angle); - oPt.y += m_RADIUS * Math.sin(angle); + oPt.x += radius * Math.cos(angle); + oPt.y += radius * 0.7 * Math.sin(angle); // Ellipse because the labels are written horizontally ! - trace("iChildIndex: "+iChildIndex+" x: "+oPt.x+" y: "+oPt.y+" sGdParentKey: "+sGrandParentKey); + //trace("iChildIndex: "+iChildIndex+" (iChildCount: "+iChildCount+") x: "+oPt.x+" y: "+oPt.y+" sGdParentKey: "+sGrandParentKey); } return oPt; } @@ -280,8 +343,10 @@ function drawLines(event:Event):void { + var color:uint = 0x666666; m_oCanvas.graphics.clear(); m_oCanvas.graphics.lineStyle(2,0x666666,100); + UpdatePositions(); for (var index:String in m_aLinks) { var oStartNode:GraphNode = GetNode(m_aLinks[index].GetStart()); @@ -289,18 +354,28 @@ m_oCanvas.graphics.moveTo(oStartNode.x, oStartNode.y); m_oCanvas.graphics.lineTo(oEndNode.x, oEndNode.y); var oMiddlePoint:Point = new Point((oEndNode.x+oStartNode.x)/2, (oEndNode.y+oStartNode.y)/2); - drawArrow(oMiddlePoint, oEndNode.x - oStartNode.x, oEndNode.y - oStartNode.y); + drawArrow(oMiddlePoint, oEndNode.x - oStartNode.x, oEndNode.y - oStartNode.y, color); + } + + if (this.m_FOCUS_DELAY_COUNTDOWN > 0) + { + this.m_FOCUS_DELAY_COUNTDOWN--; + trace('FOCUS_DELAY:'+this.m_FOCUS_DELAY_COUNTDOWN); + UpdatePanAndZoom(m_FOCUS_DELAY_COUNTDOWN / 30); } } - function drawArrow(oPt:Point, dx:Number, dy:Number):void + function drawArrow(oPt:Point, dx:Number, dy:Number, color:uint):void { var l:Number = Math.sqrt(dx*dx+dy*dy); var arrowSize:Number = 5; - m_oCanvas.graphics.lineStyle(2,0x666666,100,false,"normal",CapsStyle.ROUND); - m_oCanvas.graphics.moveTo(oPt.x, oPt.y); - m_oCanvas.graphics.lineTo(oPt.x + arrowSize*(dy-dx)/l, oPt.y - arrowSize*(dx+dy)/l); - m_oCanvas.graphics.moveTo(oPt.x, oPt.y); - m_oCanvas.graphics.lineTo(oPt.x - arrowSize*(dx+dy)/l, oPt.y - arrowSize*(dy-dx)/l); + if (l > 0) + { + m_oCanvas.graphics.lineStyle(2,color,100,false,"normal",CapsStyle.ROUND); + m_oCanvas.graphics.moveTo(oPt.x, oPt.y); + m_oCanvas.graphics.lineTo(oPt.x + arrowSize*(dy-dx)/l, oPt.y - arrowSize*(dx+dy)/l); + m_oCanvas.graphics.moveTo(oPt.x, oPt.y); + m_oCanvas.graphics.lineTo(oPt.x - arrowSize*(dx+dy)/l, oPt.y - arrowSize*(dy-dx)/l); + } } public function ReadParam(sName:String, sDefaultValue:String) @@ -316,6 +391,226 @@ return sDefaultValue; } } + + public function ComputeElectrostaticForces():Array + { + var aForces:Array = new Array; + //trace('====== BEGIN ComputeElectrostaticForces() ======='); + + for (var i:String in this.m_aNodes) + { + aForces[i] = new Object; + aForces[i].FxTotal = 0; + aForces[i].FyTotal = 0; + var oCurrentNode:GraphNode = GetNode(i); + for (var j:String in this.m_aNodes) + { + if (i != j) + { + var oRemoteNode:GraphNode = GetNode(j); + var dx:Number = oRemoteNode.x - oCurrentNode.x; + var dy:Number = oRemoteNode.y - oCurrentNode.y; + var d2:Number = (dx*dx + dy*dy) / (this.m_RADIUS * this.m_RADIUS); + var d:Number = Math.sqrt(d2); + var Fx:Number = 0; + var Fy:Number = 0; + if (d2 < 0.05) + { + d2 = 0.05; + } + if (d2 < 2 ) // Full influence under 2 * m_RADIUS px + { + Fx = -this.m_Q * dx / d2; + Fy = -this.m_Q * dy / d2; + aForces[i].FxTotal += Fx; + aForces[i].FyTotal += Fy; + } + else if (d2 < 4 ) // Decrease the influence to between 4 and 2 * m_RADIUS px + { + Fx = -this.m_Q * (4 - d2) * dx / d2; + Fy = -this.m_Q * (4 - d2) * dy / d2; + aForces[i].FxTotal += Fx; + aForces[i].FyTotal += Fy; + } + } + } + } + //for (i in this.m_aNodes) + //{ + // trace('ELECTROSTATIC forces on '+i+': Fx='+aForces[i].FxTotal+', Fy='+aForces[i].FyTotal); + // if (Math.abs(aForces[i].FyTotal) > 1) + // { + // for (i in this.m_aNodes) + // { + // var oNode:GraphNode = GetNode(i); + // trace('node: '+i+' (x='+oNode.x+', y='+oNode.y+')'); + // } + // } + //} + //trace('====== END ComputeElectrostaticForces() ======='); + return aForces; + } + + + function ComputeElasticForces() + { + //trace('====== BEGIN ComputeElasticForces() ======='); + var aForces:Array = new Array; + + for (var i:String in this.m_aNodes) + { + aForces[i] = new Object; + aForces[i].FxTotal = 0; + aForces[i].FyTotal = 0; + } + // Elastic forces: each link applies a force proportional to its length (F = - K * x) + for(i in this.m_aLinks) + { + var oStartNode:GraphNode = GetNode(m_aLinks[i].GetStart()); + var oEndNode = GetNode(m_aLinks[i].GetEnd()); + var dx = oStartNode.x - oEndNode.x; + var dy = oStartNode.y - oEndNode.y; + //d = Math.sqrt(dx*dx + dy*dy); + //Fx = -K * d * dx / d; + //Fy = -K * d * dy / d; + // Links with more weight attached are more rigid ! + //weightCoeff = (aWeights[aLinks[l].start] + aWeights[aLinks[l].end])/2; + var Fx = -this.m_K * dx; + var Fy = -this.m_K * dy; + aForces[oStartNode.GetKey()].FxTotal += Fx; + aForces[oStartNode.GetKey()].FyTotal += Fy; + aForces[oEndNode.GetKey()].FxTotal -= Fx; + aForces[oEndNode.GetKey()].FyTotal -= Fy; + } + //for (i in this.m_aNodes) + //{ + // trace('Elastic forces on '+i+': Fx='+aForces[i].FxTotal+', Fy='+aForces[i].FyTotal); + // if (Math.abs(aForces[i].FyTotal) > 1) + // { + // for (i in this.m_aNodes) + // { + // var oNode:GraphNode = GetNode(i); + // trace('node: '+i+' (x='+oNode.x+', y='+oNode.y+')'); + // } + // } + //} + //trace('====== END ComputeElasticForces() ======='); + return aForces; + } + + /** + * Update the nodes' position based on their current movement and the forces applied + */ + function UpdatePositions() + { + //trace('====== BEGIN UpdatePositions() ======='); + var aElasticForces:Array = ComputeElasticForces(); + var aElectrostaticForces:Array = ComputeElectrostaticForces(); + //DrawForces(aElectrostaticForces, 0xcc0000); + //DrawForces(aElectrostaticForces, 0x0000cc); + for (var i:String in this.m_aNodes) + { + var oNode:GraphNode = GetNode(i); + if (!oNode.m_bInDrag) + { + var Fx:Number = aElasticForces[i].FxTotal + aElectrostaticForces[i].FxTotal; + var Fy:Number = aElasticForces[i].FyTotal + aElectrostaticForces[i].FyTotal; + + if ( (Fx*Fx + Fy*Fy) < (this.m_Ks*this.m_Ks)) + { + // Movement is less than minimum level (solid friction) => object is stopped + // otherwise let's keep it moving + oNode.m_speed_x = 0; + oNode.m_speed_y = 0; + //trace('object '+i+' stopped ! (x='+oNode.x+', y='+oNode.y+')'); + } + else + { + oNode.m_speed_x = oNode.m_speed_x*this.m_Kf + this.m_deltaT*Fx; + oNode.m_speed_y = oNode.m_speed_y*this.m_Kf + this.m_deltaT*Fy; + var dx:Number = oNode.m_speed_x * this.m_deltaT; + var dy:Number = oNode.m_speed_y * this.m_deltaT; + oNode.x = Math.round(oNode.x + dx); + oNode.y = Math.round(oNode.y + dy); + //trace('object '+i+' moves (Force: Fx='+Fx+', Fy='+Fy+')! '); + } + } + } + //trace('======= Updated Positions ========='); + //DumpPositions(); + //trace('====== END UpdatePositions() ======='); + } + + public function DrawForces(aForces:Array, color:uint) + { + for (var i:String in aForces) + { + var oNode:GraphNode = GetNode(i); + var oForce:Object = aForces[i]; + m_oCanvas.graphics.lineStyle(2,color,100,false,"normal",CapsStyle.ROUND); + m_oCanvas.graphics.moveTo(oNode.x, oNode.y); + var oEndPoint:Point = new Point; + oEndPoint.x = Math.round(oNode.x + oForce.FxTotal); + oEndPoint.y = Math.round(oNode.y + oForce.FyTotal); + m_oCanvas.graphics.lineTo(oEndPoint.x, oEndPoint.y); + drawArrow(oEndPoint, oForce.FxTotal, oForce.FyTotal, color); + //trace('Drawinf vector '+i+': (x='+oNode.x+', y='+oNode.y+') to (x='+oEndPoint.x+', y='+oEndPoint.y+')'); + } + } + + public function UpdatePanAndZoom(countDownRatio:Number) + { + var sceneRect:Rectangle = null; + for(var i:String in this.m_aNodes) + { + if (sceneRect == null) + { + sceneRect = GetNode(i).getBounds(m_oCanvas); + } + else + { + sceneRect = sceneRect.union(GetNode(i).getBounds(m_oCanvas)); + } + } + if (sceneRect != null) + { + var idealZoomLevel:Number = 1; + trace('Stage dimensions: width:'+stage.stageWidth+' height:'+stage.stageHeight); + + if ((sceneRect.width > stage.stageWidth) || (sceneRect.height > (stage.stageHeight - 50))) + { + var wRatio:Number = stage.stageWidth / sceneRect.width; + var hRatio:Number = (stage.stageHeight - 50) / sceneRect.height; + idealZoomLevel = Math.min(wRatio, hRatio); + SetZoomLevel(idealZoomLevel); + m_oZoomSlider.value = Math.round(100*idealZoomLevel); + + } + var xOffset:Number = 0; + var yOffset:Number = 50; + if (stage.stageWidth > sceneRect.width) + { + xOffset = (stage.stageWidth-sceneRect.width)/2 + } + if (stage.stageHeight > sceneRect.height) + { + yOffset = 50 + (stage.stageHeight-50-sceneRect.height)/2 + } + m_oCanvas.x = xOffset-sceneRect.x; + m_oCanvas.y = yOffset-sceneRect.y; + + trace('Scene bounding rect: x:'+sceneRect.x+' y:'+sceneRect.y+' width:'+sceneRect.width+' height:'+sceneRect.height+' zoomLevel:'+idealZoomLevel); + } + } + + public function DumpPositions() + { + for (var i:String in this.m_aNodes) + { + var oNode:GraphNode = GetNode(i); + trace(i+' Position: (x='+oNode.x+', y='+oNode.y+')'); + } + } } } diff --git a/navigator/iTop/ToolTip.as b/navigator/iTop/ToolTip.as index 3cb79ccbce..898a4b79a9 100644 --- a/navigator/iTop/ToolTip.as +++ b/navigator/iTop/ToolTip.as @@ -1,6 +1,7 @@ -package iTop +package iTop { - import flash.display.Sprite; + import flash.display.*; + import flash.geom.*; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFieldAutoSize; @@ -15,17 +16,18 @@ // You'll need this for proper text formatting private var _tf:TextField = new TextField(); private var _format:TextFormat = new TextFormat(); - private static const ROUND:Number = 2; + private static const ROUND:Number = 10; private static const HEIGHT:Number = 25; private static const FONT_SIZE:uint = 12; private static const FONT:String = 'Arial'; private static const PADDING:Number = 5; - private static const MIN_ALPHA:Number = 0; - private static const ALPHA_INC:Number = .1; + private static const MIN_ALPHA:Number = 0.0; + private static const ALPHA_INC:Number = 0.1; private static const MAX_ALPHA:Number = 1; - private static const REFRESH:Number = MAX_ALPHA / ALPHA_INC; - // For fading in and out - private var _timer:Timer; + private static const REFRESH:Number = (MAX_ALPHA - MIN_ALPHA) / ALPHA_INC; + private static const APPEAR_TIMEOUT = 1000; // ms + // For appearence, fading in and out + public var timer:Timer; public function ToolTip( tip:String ) { // Hold onto the tip for posterity @@ -51,7 +53,7 @@ _tf.y += PADDING; addChild( _tf ); // Draw the background - graphics.beginFill( 0xF6F6F1, 1 ); + graphics.beginFill( 0xEEEE99, 0.95 ); graphics.drawRoundRect( 0, 0, _tf.textWidth+PADDING*4, _tf.textHeight+PADDING*4, ROUND ); graphics.endFill(); this.alpha = MIN_ALPHA; @@ -65,28 +67,74 @@ } public function mouse_over( e:MouseEvent ):void { - this.parent.setChildIndex( this, this.parent.numChildren-1 ) - // Move the tool tip to the top! - var fadeSpeed:Number = 500 / REFRESH; - _timer = new Timer( fadeSpeed, REFRESH ); - _timer.addEventListener( "timer", fadeIn ); - _timer.start(); + // Make the tooltip appear smoothly after a delay + if (this.timer != null) + { + this.timer.stop(); + } + this.timer = new Timer( APPEAR_TIMEOUT, 1 ); + this.timer.addEventListener( "timer", appear ); + this.timer.start(); this.parent.addEventListener( MouseEvent.MOUSE_OUT, mouse_out ); } public function mouse_out( e:MouseEvent ):void { var fadeSpeed:Number = 500 / REFRESH; - _timer = new Timer( fadeSpeed, REFRESH ); - _timer.addEventListener( "timer", fadeOut ); - _timer.start(); + if (this.timer != null) + { + this.timer.stop(); + } + this.timer = new Timer( fadeSpeed, REFRESH ); + this.timer.addEventListener( "timer", fadeOut ); + this.timer.start(); + this.parent.removeEventListener( MouseEvent.MOUSE_OUT, mouse_out ); } + + private function appear(i:uint):void + { + // The delay has elapsed, show (smoothly) the tooltip + if (this.timer != null) + { + this.timer.stop(); + } + // Reuse the time for the fadeIn + this.parent.setChildIndex( this, this.parent.numChildren-1 ) + // Move the tool tip to the top! + var fadeSpeed:Number = 500 / REFRESH; + this.alpha = MIN_ALPHA; + if (this.timer != null) + { + this.timer.stop(); + } + this.timer = new Timer( fadeSpeed, REFRESH ); + this.timer.addEventListener( "timer", fadeIn ); + this.timer.start(); + this.parent.addEventListener( MouseEvent.MOUSE_OUT, mouse_out ); + } + private function fadeIn( i:uint ):void { - this.alpha += ALPHA_INC; + if (this.alpha < (1.0 - ALPHA_INC)) + { + this.alpha += ALPHA_INC; + } + else + { + this.alpha = 1.0; + } + //trace("++ Tooltip alpha: "+this.alpha+" ALPHA_INC:"+ALPHA_INC); } private function fadeOut( i:uint ):void { - this.alpha -= ALPHA_INC; + if (this.alpha > ALPHA_INC) + { + this.alpha -= ALPHA_INC; + } + else + { + this.alpha = 0.0; + } + //trace("-- Tooltip alpha: "+this.alpha+" ALPHA_INC:"+ALPHA_INC); } } } diff --git a/navigator/navigator.fla b/navigator/navigator.fla index 10ddd55dc788a826252e64a8eeec51626d15151c..8049e4cd1afa63cd596a728c1fda42355770e309 100755 GIT binary patch delta 4814 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 GweT-6xs!YV delta 1726 zcmbu8Yfuwc6vy{&9t22aA+aH#29Y`e0SVD4ib9)!qP%^KGxAVdr%*+UGF3n=FDn&P zHW%ds2+=}1A|kLFIxy3M#DWy1_(o8RT9H0xv>KJ7p=V=#O=-V)XMVFM_nw@)=fD4x zbx)nuHL=JK5wy~ECL#zT8$b|b1BiqtfH(xfg?&C?0|)?n;5|TFpZy!+6UI#gs)=|L znS+*MGntES<8;n0v;-sW5}6dzo{x@>S^##%!f#g~1gCgQah_lTve2{!-{B@98IJIo z&RqqEILpTk>v>)LpGJI}tfgacTaA+M31`Am{1>@{y~Ac7a>llT#nwC6+Uy-NNRa?< z-N9u~aHSv+CF23XIy!#^uC(>w8lg3so}sfdAq#^h%(ZQhbU4v^in%&9pH%ch$Jc;9 zpdYvn+yHI@KLZ26AVAX+Tvp)Cdk8V~qSASpgyY~1*f(MqruiQDkyn(6U?m1cttad* zlQyl8+fc0~g(BExg?*XNq}6mIkyUfMx3+7)M~q5+R-~)Tpx74SFP2e)a3ma@5#C+* z7!S8lcyB-;(^Wr%)U^zgTOIL8%V8=!LE!dXKv3lIs>myfJ(L+AYmK2y{kATTQ-d@| z7u0NOlH>*Bn7CP39_NJ1;#{b6Wy=FB)76WQ-QAecNvLHw`&0x~Yq=cQRIiUR#O|!n zp72?QC&!0TBD43Uw7adE@}BicPg+Ofa7KJQwfSPJZb%E4pfHZH}ErVz^fO?l~~P~*aqGd z+u*{@yJc*jtblJW)|;#Vd4ag6U5syLo<}O|=sFqye%eIxD10bg&VC63@&1)J)0%g$ z-9+YkH^wb3NlNRtHWaqGh+Y}* zmBk);3DMyi@_*ZLcsO4<(wk}47K9HnYvgC}gO$W}r%y=Fgf$=7;=Vs|i}aOs@amaCOnBl6fi6Ki zmKR^w9n+Jt|McX`!cGPunqc4}^6+IR;~dl+LX8(Et?%#(U$lQH>=*B~2isxrUie|i z#*)*pLbCXXNb6zOo+q-ZXe`pp^HRDORIR=`^;t&!$=52HduIR8xu!jA-*E60al+*Y ztBS>osy5UWS8IwR^Fy>Ui`1$je*Y{H+qf#C_GWrT?q=%23l%Lx1w%F;Jq#{c8_Fi_d|DJ6ylp%|=pA5^VUQa$ I4~h8bZymLeR@9%xT@BVr3?OI)@YjvHf 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

        8yGzHrobx6u>9vsG*@h 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 if (!$oAttDef->IsLinkSet()) { - $oNode->SetAttribute('att_'.$index, $oObj->Get($sAttCode)); + $oNode->SetAttribute('att_'.$index, $oObj->GetAsHTML($sAttCode)); } $index++; } @@ -78,6 +78,7 @@ function GetRelatedObjects(DBObject $oObj, $sRelationName, &$oLinks, &$oXmlDoc, $oLinkedNode = $oXmlDoc->CreateElement('node'); $oLinkedNode->SetAttribute('id', $oTargetObj->GetKey()); $oLinkedNode->SetAttribute('obj_class', get_class($oTargetObj)); + $oLinkedNode->SetAttribute('obj_class_name', MetaModel::GetName(get_class($oTargetObj))); $oLinkedNode->SetAttribute('name', $oTargetObj->GetName()); $oLinkedNode->SetAttribute('icon', BuildIconPath($oTargetObj->GetIcon(false /* No IMG tag */))); AddNodeDetails($oLinkedNode, $oTargetObj); @@ -104,11 +105,10 @@ function BuildIconPath($sIconPath) require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -//// For developping the Navigator +// For developping the Navigator from within Flash //session_start(); //$_SESSION['auth_user'] = 'admin'; -//$_SESSION['auth_pwd'] = 'admin2'; -//UserRights::Login($_SESSION['auth_user'], $_SESSION['auth_pwd']); // Set the user's language +//UserRights::Login($_SESSION['auth_user']); // Set the user's language LoginWebPage::DoLogin(); // Check user rights and prompt if needed $oPage = new ajax_page(""); @@ -134,6 +134,7 @@ if ($id != 0) $oXmlNode = $oXmlDoc->CreateElement('node'); $oXmlNode->SetAttribute('id', $oObj->GetKey()); $oXmlNode->SetAttribute('obj_class', get_class($oObj)); + $oXmlNode->SetAttribute('obj_class_name', MetaModel::GetName(get_class($oObj))); $oXmlNode->SetAttribute('name', $oObj->GetName()); $oXmlNode->SetAttribute('icon', BuildIconPath($oObj->GetIcon(false /* No IMG tag */))); // Hard coded for the moment AddNodeDetails($oXmlNode, $oObj); From 75bbad57fac78299f476d34c6b535aa8affb7b6a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sun, 29 Aug 2010 19:49:17 +0000 Subject: [PATCH 628/970] Optimized roughly the load of user management data, and added a mean for quick profiling (to enable, add the setting log_duration) SVN:trunk[711] --- .../userrightsprofile.class.inc.php | 100 +++++++++++------- core/cmdbobject.class.inc.php | 1 + core/cmdbsource.class.inc.php | 32 +++--- core/config.class.inc.php | 9 ++ core/dbobjectsearch.class.php | 42 ++++++++ core/duration.class.inc.php | 82 ++++++++++++++ core/metamodel.class.php | 9 ++ core/test.class.inc.php | 1 + core/userrights.class.inc.php | 11 ++ pages/UI.php | 2 + setup/ajax.dataloader.php | 1 + setup/index.php | 2 + setup/xmldataloader.class.inc.php | 1 + 13 files changed, 241 insertions(+), 52 deletions(-) create mode 100644 core/duration.class.inc.php diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 2b41ece367..75e64031b8 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -412,7 +412,7 @@ class UserRightsProfile extends UserRightsAddOnAPI public function Init() { - MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'LoadCache')); + MetaModel::RegisterPlugin('userrights', 'ACbyProfile'); } @@ -422,8 +422,12 @@ class UserRightsProfile extends UserRightsAddOnAPI protected $m_aUserProfiles; // userid,profileid -> object protected $m_aUserOrgs; // userid,orgid -> object - protected $m_aClassActionGrants; // profile, class, action -> permission - protected $m_aClassStimulusGrants; // profile, class, stimulus -> permission + // Those arrays could be completed on demand (inheriting parent permissions) + protected $m_aClassActionGrants = null; // profile, class, action -> actiongrantid (or false if NO, or null/missing if undefined) + protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission + + // Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read) + protected $m_aObjectActionGrants = array(); public function ResetCache() { @@ -434,9 +438,30 @@ class UserRightsProfile extends UserRightsAddOnAPI $this->m_aAdmins = null; - // Loaded on demand - $this->m_aClassActionGrants = array(); - $this->m_aClassStimulusGrants = array(); + // Loaded on demand (time consuming as compared to the others) + $this->m_aClassActionGrants = null; + $this->m_aClassStimulusGrants = null; + + $this->m_aObjectActionGrants = array(); + } + + // Separate load: this cache is much more time consuming while loading + // Thus it is loaded iif required + // Could be improved by specifying the profile id + public function LoadActionGrantCache() + { + if (!is_null($this->m_aClassActionGrants)) return; + + $oDuration = new Duration(); + + $oFilter = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant AS p WHERE p.permission = 'yes'"); + $aGrants = $oFilter->ToDataArray(); + foreach($aGrants as $aGrant) + { + $this->m_aClassActionGrants[$aGrant['profileid']][$aGrant['class']][strtolower($aGrant['action'])] = $aGrant['id']; + } + + $oDuration->Scratch('Load of action grants'); } public function LoadCache() @@ -444,6 +469,8 @@ class UserRightsProfile extends UserRightsAddOnAPI if (!is_null($this->m_aProfiles)) return; // Could be loaded in a shared memory (?) + $oDuration = new Duration(); + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); $this->m_aProfiles = array(); while ($oProfile = $oProfileSet->Fetch()) @@ -469,11 +496,24 @@ class UserRightsProfile extends UserRightsAddOnAPI { $this->m_aUserOrgs[$oUserOrg->Get('userid')][$oUserOrg->Get('allowed_org_id')] = $oUserOrg; } + + $this->m_aClassStimulusGrants = array(); + $oStimGrantSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant")); + $this->m_aStimGrants = array(); + while ($oStimGrant = $oStimGrantSet->Fetch()) + { + $this->m_aClassStimulusGrants[$oStimGrant->Get('profileid')][$oStimGrant->Get('class')][$oStimGrant->Get('stimulus')] = $oStimGrant; + } + + $oDuration->Scratch('Load of user management cache (excepted Action Grants)'); + /* echo "

        \n";
         		print_r($this->m_aProfiles);
         		print_r($this->m_aUserProfiles);
         		print_r($this->m_aUserOrgs);
        +		print_r($this->m_aClassActionGrants);
        +		print_r($this->m_aClassStimulusGrants);
         		echo "
        \n"; exit; */ @@ -544,36 +584,29 @@ exit; // This verb has been made public to allow the development of an accurate feedback for the current configuration public function GetProfileActionGrant($iProfile, $sClass, $sAction) { - $this->LoadCache(); + $this->LoadActionGrantCache(); + // Note: action is forced lowercase to be more flexible (historical bug) + $sAction = strtolower($sAction); if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) { return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; } - // Get the permission for this profile/class/action - $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile AND permission = 'yes'"); - $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile)); - if ($oSet->Count() >= 1) + // Recursively look for the grant record in the class hierarchy + $sParentClass = MetaModel::GetParentPersistentClass($sClass); + if (empty($sParentClass)) { - $oGrantRecord = $oSet->Fetch(); + $iGrant = null; } else { - $sParentClass = MetaModel::GetParentPersistentClass($sClass); - if (empty($sParentClass)) - { - $oGrantRecord = null; - } - else - { - // Recursively look for the grant record in the class hierarchy - $oGrantRecord = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction); - } + // Recursively look for the grant record in the class hierarchy + $iGrant = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction); } - $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $oGrantRecord; - return $oGrantRecord; + $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $iGrant; + return $iGrant; } protected function GetUserActionGrant($oUser, $sClass, $iActionCode) @@ -594,8 +627,8 @@ exit; { foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) { - $oGrantRecord = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); - if (is_null($oGrantRecord)) + $iGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); + if (is_null($iGrant) || !$iGrant) { continue; // loop to the next profile } @@ -606,7 +639,7 @@ exit; // update the list of attributes with those allowed for this profile // $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); - $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey())); + $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $iGrant)); $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); if (count($aProfileAttributes) == 0) { @@ -735,21 +768,10 @@ exit; { return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]; } - - // Get the permission for this profile/class/stimulus - $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile AND permission = 'yes'"); - $oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile)); - if ($oSet->Count() >= 1) - { - $oGrantRecord = $oSet->Fetch(); - } else { - $oGrantRecord = null; + return null; } - - $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode] = $oGrantRecord; - return $oGrantRecord; } public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null) diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 50ba69a6b5..ecb58ce24b 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -35,6 +35,7 @@ require_once('coreexception.class.inc.php'); require_once('config.class.inc.php'); require_once('log.class.inc.php'); +require_once('duration.class.inc.php'); require_once('dict.class.inc.php'); diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 499c501173..e42d66b8d0 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -265,19 +265,7 @@ class CMDBSource throw new MySQLException('Failed to issue SQL query', array('query' => $sSql)); } - $aNames = array(); - for ($i = 0; $i < mysql_num_fields($result) ; $i++) - { - $meta = mysql_fetch_field($result, $i); - if (!$meta) - { - throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i)); - } - else - { - $aNames[] = $meta->name; - } - } + $aNames = self::GetColumns($result); $aData[] = $aNames; while ($aRow = mysql_fetch_array($result, MYSQL_ASSOC)) @@ -310,6 +298,24 @@ class CMDBSource return mysql_fetch_array($result, MYSQL_ASSOC); } + public static function GetColumns($result) + { + $aNames = array(); + for ($i = 0; $i < mysql_num_fields($result) ; $i++) + { + $meta = mysql_fetch_field($result, $i); + if (!$meta) + { + throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i)); + } + else + { + $aNames[] = $meta->name; + } + } + return $aNames; + } + public static function Seek($result, $iRow) { return mysql_data_seek($result, $iRow); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 1afedc700a..f11c35fa01 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -37,6 +37,7 @@ define ('DEFAULT_LOG_GLOBAL', true); define ('DEFAULT_LOG_NOTIFICATION', true); define ('DEFAULT_LOG_ISSUE', true); define ('DEFAULT_LOG_WEB_SERVICE', true); +define ('DEFAULT_LOG_DURATION', false); define ('DEFAULT_MIN_DISPLAY_LIMIT', 10); define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); @@ -79,6 +80,7 @@ class Config protected $m_bLogNotification; protected $m_bLogIssue; protected $m_bLogWebService; + protected $m_bLogDuration; // private setting /** * @var integer Number of elements to be displayed when there are more than m_iMaxDisplayLimit elements @@ -176,6 +178,7 @@ class Config $this->m_bLogNotification = DEFAULT_LOG_NOTIFICATION; $this->m_bLogIssue = DEFAULT_LOG_ISSUE; $this->m_bLogWebService = DEFAULT_LOG_WEB_SERVICE; + $this->m_bLogDuration = DEFAULT_LOG_DURATION; $this->m_iMinDisplayLimit = DEFAULT_MIN_DISPLAY_LIMIT; $this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT; $this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL; @@ -275,6 +278,7 @@ class Config $this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION; $this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE; $this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE; + $this->m_bLogDuration = isset($MySettings['log_duration']) ? (bool) trim($MySettings['log_duration']) : DEFAULT_LOG_DURATION; $this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT; $this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT; $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; @@ -405,6 +409,11 @@ class Config return $this->m_bLogWebService; } + public function GetLogDuration() + { + return $this->m_bLogDuration; + } + public function GetMinDisplayLimit() { return $this->m_iMinDisplayLimit; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 47253927b5..e1cb27358e 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -558,6 +558,48 @@ class DBObjectSearch return $retValue; } + // Alternative to object mapping: the data are transfered directly into an array + // This is 10 times faster than creating a set of objects, and makes sense when optimization is required + public function ToDataArray($aColumns = array(), $aOrderBy = array(), $aArgs = array()) + { + $sSQL = MetaModel::MakeSelectQuery($this, $aOrderBy, $aArgs); + $resQuery = CMDBSource::Query($sSQL); + if (!$resQuery) return; + + if (count($aColumns) == 0) + { + $aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass())); + // Add the standard id (as first column) + array_unshift($aColumns, 'id'); + } + + $aQueryCols = CMDBSource::GetColumns($resQuery); + + $sClassAlias = $this->GetClassAlias(); + $aColMap = array(); + foreach ($aColumns as $sAttCode) + { + $sColName = $sClassAlias.$sAttCode; + if (in_array($sColName, $aQueryCols)) + { + $aColMap[$sAttCode] = $sColName; + } + } + + $aRes = array(); + while ($aRow = CMDBSource::FetchArray($resQuery)) + { + $aMappedRow = array(); + foreach ($aColMap as $sAttCode => $sColName) + { + $aMappedRow[$sAttCode] = $aRow[$sColName]; + } + $aRes[] = $aMappedRow; + } + CMDBSource::FreeResult($resQuery); + return $aRes; + } + public function ToOQL(&$aParams = null) { // Currently unused, but could be useful later diff --git a/core/duration.class.inc.php b/core/duration.class.inc.php new file mode 100644 index 0000000000..c027455262 --- /dev/null +++ b/core/duration.class.inc.php @@ -0,0 +1,82 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +class Duration +{ + static $m_bEnabled = false; + + static public function Enable() + { + self::$m_bEnabled = true; + } + + protected $m_fStarted = null; + + public function __construct() + { + if (!self::$m_bEnabled) return; + + $this->m_fStarted = MyHelpers::getmicrotime(); + } + + // Get the duration since startup, and reset the counter for the next measure + // + public function Scratch($sMeasure) + { + if (!self::$m_bEnabled) return; + + $fStopped = MyHelpers::getmicrotime(); + $fDuration = $fStopped - $this->m_fStarted; + $this->Report($sMeasure.': '.round($fDuration, 3)); + + $this->m_fStarted = MyHelpers::getmicrotime(); + } + + protected function Report($sText) + { + echo "DURATION... $sText
        \n"; + } +} + +// Prototype, to be finalized later +// Reports the function duration +// One single thing to do: construct it +class FunctionDuration +{ + protected $m_sFunction = null; + + public function __construct() + { + $this->m_sFunction = 'my_function_name_in_call_stack'; + $this->m_fStarted = MyHelpers::getmicrotime(); + } + + public function __destruct() + { + $this->Scratch('Exiting '); + } +} + +?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 4c242009d0..d509707281 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3169,6 +3169,11 @@ abstract class MetaModel self::$m_bLogWebService = false; } + if (self::$m_oConfig->GetLogDuration()) + { + Duration::Enable(); + } + // Note: load the dictionary as soon as possible, because it might be // needed when some error occur foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude) @@ -3201,10 +3206,14 @@ abstract class MetaModel $sSource = self::$m_oConfig->GetDBName(); $sTablePrefix = self::$m_oConfig->GetDBSubname(); + $oDuration = new Duration(); + // The include have been included, let's browse the existing classes and // develop some data based on the proposed model self::InitClasses($sTablePrefix); + $oDuration->Scratch('Initialization of Data model structures'); + self::$m_sDBName = $sSource; self::$m_sTablePrefix = $sTablePrefix; diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 30949936c3..015eb78773 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -35,6 +35,7 @@ require_once('cmdbsource.class.inc.php'); require_once('sqlquery.class.inc.php'); require_once('log.class.inc.php'); +require_once('duration.class.inc.php'); require_once('dbobject.class.php'); require_once('dbobjectsearch.class.php'); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 6502085a16..9521a2418f 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -131,6 +131,8 @@ abstract class User extends cmdbAbstractObject return; } + $oDuration = new Duration(); + $aDisplayData = array(); foreach (MetaModel::GetClasses($sClassCategory) as $sClass) { @@ -165,6 +167,8 @@ abstract class User extends cmdbAbstractObject ); } + $oDuration->Scratch('Computation of user rights'); + $aDisplayConfig = array(); $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); @@ -596,6 +600,13 @@ class UserRights return self::$m_aAdmins[$iUser]; } + /** + * Reset cached data + * @param Bool Reset admin cache as well + * @return void + */ + // Reset cached data + // public static function FlushPrivileges($bResetAdminCache = false) { if ($bResetAdminCache) diff --git a/pages/UI.php b/pages/UI.php index 1162235946..c2570fb5ee 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -455,6 +455,7 @@ try require_once('../application/itopwebpage.class.inc.php'); require_once('../application/wizardhelper.class.inc.php'); + $oDuration = new Duration(); require_once('../application/startup.inc.php'); $oAppContext = new ApplicationContext(); $currentOrganization = utils::ReadParam('org_id', ''); @@ -1389,6 +1390,7 @@ EOF $oP->set_title($oMenuNode->GetLabel()); } } + $oDuration->Scratch('Total page execution time'); ////MetaModel::ShowQueryTrace(); $oP->output(); } diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 4a8667b761..c1db84a5de 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -89,6 +89,7 @@ ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it throug require_once('../core/config.class.inc.php'); require_once('../core/log.class.inc.php'); +require_once('../core/duration.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); require_once('./xmldataloader.class.inc.php'); diff --git a/setup/index.php b/setup/index.php index 7eb710b534..309ce4144c 100644 --- a/setup/index.php +++ b/setup/index.php @@ -26,6 +26,7 @@ require_once('../application/utils.inc.php'); require_once('../core/config.class.inc.php'); require_once('../core/log.class.inc.php'); +require_once('../core/duration.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); require_once('./setuppage.class.inc.php'); @@ -375,6 +376,7 @@ function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd) function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bModelOnly = true) { require_once('../core/log.class.inc.php'); + require_once('../core/duration.class.inc.php'); require_once('../core/coreexception.class.inc.php'); require_once('../core/dict.class.inc.php'); require_once('../core/attributedef.class.inc.php'); diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 8e416a8f91..f874d9e5fe 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -88,6 +88,7 @@ class XMLDataLoader protected function InitDataModel($sConfigFileName) { require_once('../core/log.class.inc.php'); + require_once('../core/duration.class.inc.php'); require_once('../core/coreexception.class.inc.php'); require_once('../core/dict.class.inc.php'); require_once('../core/attributedef.class.inc.php'); From 388951c95144f90a90bce1a819262dc5518877f6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 30 Aug 2010 06:44:14 +0000 Subject: [PATCH 629/970] - First spanish localization (Many thanks to Randall Badilla Castro from Costa Rica) SVN:trunk[712] --- .../es_cr.dict.itop-tickets.php | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php diff --git a/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php new file mode 100644 index 0000000000..e964a1f935 --- /dev/null +++ b/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php @@ -0,0 +1,239 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Ticket +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Ticket' => 'Tiquete', + 'Class:Ticket+' => '', + 'Class:Ticket/Attribute:ref' => 'Referencia', + 'Class:Ticket/Attribute:ref+' => '', + 'Class:Ticket/Attribute:title' => 'Título', + 'Class:Ticket/Attribute:title+' => '', + 'Class:Ticket/Attribute:ticket_log' => 'Bitácora', + 'Class:Ticket/Attribute:ticket_log+' => '', + 'Class:Ticket/Attribute:start_date' => 'Fecha de Reporte', + 'Class:Ticket/Attribute:start_date+' => '', + 'Class:Ticket/Attribute:document_list' => 'Documentos', + 'Class:Ticket/Attribute:document_list+' => 'Documentos relacionados con el tiquete', + 'Class:Ticket/Attribute:ci_list' => 'I.C.s', + 'Class:Ticket/Attribute:ci_list+' => 'I.C.s afectados por el incidente', + 'Class:Ticket/Attribute:contact_list' => 'Contactos', + 'Class:Ticket/Attribute:contact_list+' => 'Equipos y personas envueltas', + 'Class:Ticket/Attribute:finalclass' => 'Tipo', + 'Class:Ticket/Attribute:finalclass+' => '', +)); + +// +// Class: lnkTicketToDoc +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkTicketToDoc' => 'Tiquete/Documentación', + 'Class:lnkTicketToDoc+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_id' => 'Tiquete', + 'Class:lnkTicketToDoc/Attribute:ticket_id+' => 'Identificación del Tiquete', + 'Class:lnkTicketToDoc/Attribute:ticket_ref' => '# de Tiquete', + 'Class:lnkTicketToDoc/Attribute:ticket_ref+' => 'Número de Tiquete', + 'Class:lnkTicketToDoc/Attribute:document_id' => 'Documento', + 'Class:lnkTicketToDoc/Attribute:document_id+' => 'Identificación del Documento', + 'Class:lnkTicketToDoc/Attribute:document_name' => 'Documento', + 'Class:lnkTicketToDoc/Attribute:document_name+' => 'Nombre del Documento', +)); + +// +// Class: lnkTicketToContact +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkTicketToContact' => 'Tiquete/Contacto', + 'Class:lnkTicketToContact+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_id' => 'Tiquete', + 'Class:lnkTicketToContact/Attribute:ticket_id+' => 'Identificación del Tiquete', + 'Class:lnkTicketToContact/Attribute:ticket_ref' => '# de Tiquete', + 'Class:lnkTicketToContact/Attribute:ticket_ref+' => 'Número de Tiquete', + 'Class:lnkTicketToContact/Attribute:contact_id' => 'Contacto', + 'Class:lnkTicketToContact/Attribute:contact_id+' => 'Identificación del Contacto', + 'Class:lnkTicketToContact/Attribute:contact_name' => 'Contacto', + 'Class:lnkTicketToContact/Attribute:contact_name+' => 'Nombre del Contacto', + 'Class:lnkTicketToContact/Attribute:contact_email' => 'Correo Electrónico', + 'Class:lnkTicketToContact/Attribute:contact_email+' => '', + 'Class:lnkTicketToContact/Attribute:role' => 'Rol', + 'Class:lnkTicketToContact/Attribute:role+' => '', +)); + +// +// Class: lnkTicketToCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkTicketToCI' => 'Tiquete/I.C.s', + 'Class:lnkTicketToCI+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_id' => 'Tiquete', + 'Class:lnkTicketToCI/Attribute:ticket_id+' => 'Identificación del Tiquete', + 'Class:lnkTicketToCI/Attribute:ticket_ref' => '# de Tiquete', + 'Class:lnkTicketToCI/Attribute:ticket_ref+' => 'Número de Tiquete', + 'Class:lnkTicketToCI/Attribute:ci_id' => 'I.C.s', + 'Class:lnkTicketToCI/Attribute:ci_id+' => '', + 'Class:lnkTicketToCI/Attribute:ci_name' => 'I.C.s', + 'Class:lnkTicketToCI/Attribute:ci_name+' => '', + 'Class:lnkTicketToCI/Attribute:ci_status' => 'Estado de los I.C.s', + 'Class:lnkTicketToCI/Attribute:ci_status+' => '', +)); + +// +// Class: ResponseTicket +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:ResponseTicket' => 'Tiquete de Respuesta', + 'Class:ResponseTicket+' => '', + 'Class:ResponseTicket/Attribute:status' => 'Estado', + 'Class:ResponseTicket/Attribute:status+' => '', + 'Class:ResponseTicket/Attribute:status/Value:new' => 'Nuevo', + 'Class:ResponseTicket/Attribute:status/Value:new+' => 'Nuevamente Abierta', + 'Class:ResponseTicket/Attribute:status/Value:frozen' => 'Supendida', + 'Class:ResponseTicket/Attribute:status/Value:frozen+' => '', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto' => 'Escalación/T.P.A(TTO)', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto+' => '', + 'Class:ResponseTicket/Attribute:status/Value:assigned' => 'Asignada', + 'Class:ResponseTicket/Attribute:status/Value:assigned+' => '', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr' => 'Escalación/T.P.R(TTR)', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr+' => '', + 'Class:ResponseTicket/Attribute:status/Value:resolved' => 'Resuelto', + 'Class:ResponseTicket/Attribute:status/Value:resolved+' => '', + 'Class:ResponseTicket/Attribute:status/Value:closed' => 'Cerrado', + 'Class:ResponseTicket/Attribute:status/Value:closed+' => '', + 'Class:ResponseTicket/Attribute:caller_id' => 'Comunicador', + 'Class:ResponseTicket/Attribute:caller_id+' => '', + 'Class:ResponseTicket/Attribute:workgroup_name' => 'Grupo de Trabajo', + 'Class:ResponseTicket/Attribute:workgroup_name+' => '', + 'Class:ResponseTicket/Attribute:org_id' => 'Cliente', + 'Class:ResponseTicket/Attribute:org_id+' => '', + 'Class:ResponseTicket/Attribute:org_name' => 'Cliente', + 'Class:ResponseTicket/Attribute:org_name+' => '', + 'Class:ResponseTicket/Attribute:service_id' => 'Servicio', + 'Class:ResponseTicket/Attribute:service_id+' => 'Identificación del Servicio', + 'Class:ResponseTicket/Attribute:servicesubcategory_id' => 'Elemento de Servicio', + 'Class:ResponseTicket/Attribute:servicesubcategory_id+' => '', + 'Class:ResponseTicket/Attribute:product' => 'Producto', + 'Class:ResponseTicket/Attribute:product+' => '', + 'Class:ResponseTicket/Attribute:impact' => 'Impacto', + 'Class:ResponseTicket/Attribute:impact+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Una Persona', + 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:2' => 'Un Servicio', + 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Un Departamento', + 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:4' => 'Una División', + 'Class:ResponseTicket/Attribute:impact/Value:4+' => '', + 'Class:ResponseTicket/Attribute:urgency' => 'Urgencia', + 'Class:ResponseTicket/Attribute:urgency+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Bajo', + 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Medio', + 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Alto', + 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', + 'Class:ResponseTicket/Attribute:priority' => 'Priority', + 'Class:ResponseTicket/Attribute:priority+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Bajo', + 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Medio', + 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Alto', + 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', + 'Class:ResponseTicket/Attribute:workgroup_id' => 'Grupo de Trabajo', + 'Class:ResponseTicket/Attribute:workgroup_id+' => 'Identificación de Grupo de Trabajo', + 'Class:ResponseTicket/Attribute:agent_id' => 'Agent', + 'Class:ResponseTicket/Attribute:agent_id+' => '', + 'Class:ResponseTicket/Attribute:agent_name' => 'Agent', + 'Class:ResponseTicket/Attribute:agent_name+' => '', + 'Class:ResponseTicket/Attribute:agent_email' => 'Agent email', + 'Class:ResponseTicket/Attribute:agent_email+' => '', + 'Class:ResponseTicket/Attribute:related_change_id' => 'Modificación Relacionada', + 'Class:ResponseTicket/Attribute:related_change_id+' => 'Identificación de Modificación Relacionada', + 'Class:ResponseTicket/Attribute:related_change_ref' => 'Modificación Relacionada', + 'Class:ResponseTicket/Attribute:related_change_ref+' => 'Referencia de Modificación Relacionada', + 'Class:ResponseTicket/Attribute:close_date' => 'Cerrado', + 'Class:ResponseTicket/Attribute:close_date+' => '', + 'Class:ResponseTicket/Attribute:last_update' => 'Última Actualización', + 'Class:ResponseTicket/Attribute:last_update+' => '', + 'Class:ResponseTicket/Attribute:assignment_date' => 'Asignada', + 'Class:ResponseTicket/Attribute:assignment_date+' => '', + 'Class:ResponseTicket/Attribute:escalation_deadline' => 'Plazo de Escalación', + 'Class:ResponseTicket/Attribute:escalation_deadline+' => 'Fecha Límite para Escalar', + 'Class:ResponseTicket/Attribute:closure_deadline' => 'Plazo de Cierre', + 'Class:ResponseTicket/Attribute:closure_deadline+' => 'Fecha Límite para Cierre', + 'Class:ResponseTicket/Attribute:resolution_code' => 'Código de Resolución', + 'Class:ResponseTicket/Attribute:resolution_code+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce' => 'No puede ser reproducido', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate' => 'Tiquete Duplicado', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed' => 'Arreglado', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant' => 'Irrelevante', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant+' => '', + 'Class:ResponseTicket/Attribute:solution' => 'Solución', + 'Class:ResponseTicket/Attribute:solution+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction' => 'Satisfacción del Usuario', + 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => '1', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => '1', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => '2', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => '2', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => '3', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => '3', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => '4', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => '4', + 'Class:ResponseTicket/Attribute:user_commment' => 'Comentario del Usuario', + 'Class:ResponseTicket/Attribute:user_commment+' => '', + 'Class:ResponseTicket/Stimulus:ev_assign' => 'Asignar', + 'Class:ResponseTicket/Stimulus:ev_assign+' => '', + 'Class:ResponseTicket/Stimulus:ev_reassign' => 'Re-asignar', + 'Class:ResponseTicket/Stimulus:ev_reassign+' => '', + 'Class:ResponseTicket/Stimulus:ev_timeout' => 'Tiempo Fuera del incidente', + 'Class:ResponseTicket/Stimulus:ev_timeout+' => '', + 'Class:ResponseTicket/Stimulus:ev_resolve' => 'Marcar como Resuelto', + 'Class:ResponseTicket/Stimulus:ev_resolve+' => '', + 'Class:ResponseTicket/Stimulus:ev_close' => 'Cerrar', + 'Class:ResponseTicket/Stimulus:ev_close+' => '', +)); +?> From 89be9163e4e48392efe160a75ac9aaf633037096 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 30 Aug 2010 11:03:15 +0000 Subject: [PATCH 630/970] - Removed some debug output SVN:trunk[713] --- core/attributedef.class.inc.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 895ffec487..52eecbf769 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -2021,10 +2021,6 @@ class AttributeOneWayPassword extends AttributeDefinition $aValues = array(); $aValues[$this->GetCode().'_hash'] = ''; $aValues[$this->GetCode().'_salt'] = ''; - echo "Writing an empty password !!!"; - echo "
        \n";
        -			print_r($value);
        -			echo "
        \n"; } return $aValues; } From d2bad7d0fd828a388d6d56b7821d9805d8bbebb9 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 30 Aug 2010 11:23:02 +0000 Subject: [PATCH 631/970] - Fixed regression #230: Mandatory fields no longer shown as mandatory. - Fixed bug #139: input of strings is now limited to 255 characters in the user interface SVN:trunk[714] --- application/cmdbabstract.class.inc.php | 6 +- application/templates/welcome_menu.html | 87 +++++++++++++++++---- application/ui.passwordwidget.class.inc.php | 4 +- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 511eca0bd7..4ba470e8fa 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1029,7 +1029,7 @@ abstract class cmdbAbstractObject extends CMDBObject { // too many choices, use an autocomplete // The input for the auto complete - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); @@ -1061,14 +1061,14 @@ abstract class cmdbAbstractObject extends CMDBObject } else { - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; $aEventsList[] ='keyup'; $aEventsList[] ='change'; } break; } $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; - if (!empty($aEventlist)) + if (!empty($aEventsList)) { $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate } diff --git a/application/templates/welcome_menu.html b/application/templates/welcome_menu.html index 5e6b8e506e..3f914b3682 100644 --- a/application/templates/welcome_menu.html +++ b/application/templates/welcome_menu.html @@ -22,34 +22,93 @@ td.dashboard li { margin-top: 5px; display: list-item; } +td div.display_block { +padding:0; +}

        UI:WelcomeMenu:Title

        - - - - - +
        -UI:WelcomeMenu:LeftBlock - -UI:WelcomeMenu:RightBlock -
        - + + +
        -

        UI:WelcomeMenu:MyCalls

        -SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id + +

        Request Management

        +SELECT UserRequest +

        UI:WelcomeMenu:MyCalls

        +SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id
        -

        UI:WelcomeMenu:MyIncidents

        -SELECT Incident AS i WHERE i.caller_id = :current_contact_id + +

        Incident Management

        +SELECT Incident +

        UI:WelcomeMenu:MyIncidents

        +SELECT Incident AS i WHERE i.agent_id = :current_contact_id
        +

        Configuration Management

        + + + + + + + + + + + +
        + + +

        Class:BusinessProcess

        +SELECT BusinessProcess

        +SELECT BusinessProcess +
        +
        + + +

        Class:Contact

        +SELECT Contact

        +SELECT Contact +
        +
        + + +

        Class:Location

        +SELECT Location

        +SELECT Location +
        +
        + + +

        Class:Server +SELECT Server

        +SELECT Server +
        +
        + + +

        Class:DatabaseInstance

        +SELECT DatabaseInstance

        +SELECT DatabaseInstance +
        +
        + + +

        Class:NetworkDevice

        +SELECT NetworkDevice

        +SELECT NetworkDevice +
        +
        +
        diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php index 43f240ba46..37353b73ae 100644 --- a/application/ui.passwordwidget.class.inc.php +++ b/application/ui.passwordwidget.class.inc.php @@ -53,8 +53,8 @@ class UIPasswordWidget $sCode = $this->sAttCode.$this->sNameSuffix; $iWidgetIndex = self::$iWidgetIndex; $sHtmlValue = ''; - $sHtmlValue = ' 
        '; - $sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' '; + $sHtmlValue = ' 
        '; + $sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' '; $sHtmlValue .= ''; $oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate From 1d0f0231a4ddc8cf039dacfbc46734be195f0032 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 30 Aug 2010 12:02:36 +0000 Subject: [PATCH 632/970] - Fixed bug #225: improve friendliness of auto-complete SVN:trunk[715] --- application/cmdbabstract.class.inc.php | 4 ++++ css/light-grey.css | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 4ba470e8fa..b241cbe97c 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1029,6 +1029,10 @@ abstract class cmdbAbstractObject extends CMDBObject { // too many choices, use an autocomplete // The input for the auto complete + if ($sDisplayValue == $oAttDef->GetNullValue()) // Null or zero is displayed as '' + { + $sDisplayValue = ''; + } $sHTMLValue = " {$sValidationField}"; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; diff --git a/css/light-grey.css b/css/light-grey.css index 5c6413a0f9..d39ce73da2 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -205,7 +205,7 @@ input.textSearch { .ac_input { border: 1px solid #7f9db9; - background: url(../images/ac-background.gif) no-repeat right; + background: #fff url(../images/ac-background.gif) no-repeat right; } /* By Rom */ From ed1abec39d9497b8e70d5a120c64a9f398a8fcf8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 30 Aug 2010 13:32:21 +0000 Subject: [PATCH 633/970] Reordered class declaration (not supported on some systems) SVN:trunk[716] --- core/coreexception.class.inc.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/coreexception.class.inc.php b/core/coreexception.class.inc.php index b11da90032..039a388141 100644 --- a/core/coreexception.class.inc.php +++ b/core/coreexception.class.inc.php @@ -25,10 +25,6 @@ -class SecurityException extends CoreException -{ -} - class CoreException extends Exception { public function __construct($sIssue, $aContextData = null, $sImpact = '') @@ -111,4 +107,8 @@ class CoreUnexpectedValue extends CoreException { } +class SecurityException extends CoreException +{ +} + ?> From 97f26c05e44f2708fbd36e86652a270d170ffbfa Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 30 Aug 2010 14:04:16 +0000 Subject: [PATCH 634/970] Fixed isssue with final class recall, discovered when reintroducing the field Ticket::related_change_id SVN:trunk[717] --- core/attributedef.class.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 52eecbf769..c299b7cfe4 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -745,6 +745,7 @@ class AttributeClass extends AttributeString public function GetAsHTML($sValue) { + if (empty($sValue)) return ''; return MetaModel::GetName($sValue); } @@ -822,6 +823,7 @@ class AttributeFinalClass extends AttributeString public function GetAsHTML($sValue) { + if (empty($sValue)) return ''; return MetaModel::GetName($sValue); } } From 8fc34a9fd5015e004615db1ebcd0bb143ec469d1 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 30 Aug 2010 14:26:40 +0000 Subject: [PATCH 635/970] #229 History data was limited (administrators could not see any attribute!) SVN:trunk[718] --- core/userrights.class.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 9521a2418f..91c63a65af 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -570,6 +570,8 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; + if (self::IsAdministrator($oUser)) return true; + // this module is forbidden for non admins if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false; From ab808ebf93d52503e61a2f278e1ccaa9dc5e9b7d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 30 Aug 2010 15:18:14 +0000 Subject: [PATCH 636/970] #223 Trim spaces in CSV parsing SVN:trunk[719] --- core/csvparser.class.inc.php | 45 ++++++++++++++++++++++++++++++------ core/test.class.inc.php | 2 ++ pages/testlist.inc.php | 5 +++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php index 9c64cd3f43..64cb15e71f 100644 --- a/core/csvparser.class.inc.php +++ b/core/csvparser.class.inc.php @@ -33,6 +33,7 @@ define('stRAW', 2); //building a non-qualified string define('stQUALIFIED', 3); //building qualified string define('stESCAPED', 4); //just encountered an escape char +define('evBLANK', 0); define('evSEPARATOR', 1); define('evNEWLINE', 2); define('evTEXTQUAL', 3); // used for escaping as well @@ -70,23 +71,32 @@ class CSVParser { $this->m_sCurrCell = ''; } - protected function __AddCell($c = null, $aFieldMap = null) + protected function __AddCell($c = null, $aFieldMap = null, $bTrimSpaces = false) { + if ($bTrimSpaces) + { + $sCell = trim($this->m_sCurrCell); + } + else + { + $sCell = $this->m_sCurrCell; + } + if (!is_null($aFieldMap)) { $iNextCol = count($this->m_aCurrRow); $iNextName = $aFieldMap[$iNextCol]; - $this->m_aCurrRow[$iNextName] = $this->m_sCurrCell; + $this->m_aCurrRow[$iNextName] = $sCell; } else { - $this->m_aCurrRow[] = $this->m_sCurrCell; + $this->m_aCurrRow[] = $sCell; } $this->m_sCurrCell = ''; } - protected function __AddRow($c = null, $aFieldMap = null) + protected function __AddRow($c = null, $aFieldMap = null, $bTrimSpaces = false) { - $this->__AddCell($c, $aFieldMap); + $this->__AddCell($c, $aFieldMap, $bTrimSpaces); if ($this->m_iToSkip > 0) { @@ -112,26 +122,39 @@ class CSVParser } $this->m_aCurrRow = array(); } + protected function __AddCellTrimmed($c = null, $aFieldMap = null) + { + $this->__AddCell($c, $aFieldMap, true); + } + + protected function __AddRowTrimmed($c = null, $aFieldMap = null) + { + $this->__AddRow($c, $aFieldMap, true); + } function ToArray($iToSkip = 1, $aFieldMap = null, $iMax = 0) { $aTransitions = array(); + $aTransitions[stSTARTING][evBLANK] = array('', stSTARTING); $aTransitions[stSTARTING][evSEPARATOR] = array('__AddCell', stSTARTING); $aTransitions[stSTARTING][evNEWLINE] = array('__AddRow', stSTARTING); $aTransitions[stSTARTING][evTEXTQUAL] = array('', stQUALIFIED); $aTransitions[stSTARTING][evOTHERCHAR] = array('__AddChar', stRAW); - $aTransitions[stRAW][evSEPARATOR] = array('__AddCell', stSTARTING); - $aTransitions[stRAW][evNEWLINE] = array('__AddRow', stSTARTING); + $aTransitions[stRAW][evBLANK] = array('__AddChar', stRAW); + $aTransitions[stRAW][evSEPARATOR] = array('__AddCellTrimmed', stSTARTING); + $aTransitions[stRAW][evNEWLINE] = array('__AddRowTrimmed', stSTARTING); $aTransitions[stRAW][evTEXTQUAL] = array('__AddChar', stRAW); $aTransitions[stRAW][evOTHERCHAR] = array('__AddChar', stRAW); + $aTransitions[stQUALIFIED][evBLANK] = array('__AddChar', stQUALIFIED); $aTransitions[stQUALIFIED][evSEPARATOR] = array('__AddChar', stQUALIFIED); $aTransitions[stQUALIFIED][evNEWLINE] = array('__AddChar', stQUALIFIED); $aTransitions[stQUALIFIED][evTEXTQUAL] = array('', stESCAPED); $aTransitions[stQUALIFIED][evOTHERCHAR] = array('__AddChar', stQUALIFIED); + $aTransitions[stESCAPED][evBLANK] = array('', stESCAPED); $aTransitions[stESCAPED][evSEPARATOR] = array('__AddCell', stSTARTING); $aTransitions[stESCAPED][evNEWLINE] = array('__AddRow', stSTARTING); $aTransitions[stESCAPED][evTEXTQUAL] = array('__AddChar', stQUALIFIED); @@ -155,6 +178,14 @@ class CSVParser { $iEvent = evSEPARATOR; } + elseif ($c == ' ') + { + $iEvent = evBLANK; + } + elseif ($c == "\t") + { + $iEvent = evBLANK; + } elseif ($c == "\n") { $iEvent = evNEWLINE; diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 015eb78773..eaebc80af2 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -41,6 +41,8 @@ require_once('dbobject.class.php'); require_once('dbobjectsearch.class.php'); require_once('dbobjectset.class.php'); +require_once('../application/cmdbabstract.class.inc.php'); + require_once('userrights.class.inc.php'); require_once('../webservices/webservices.class.inc.php'); diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 5d53f74aa9..c16b92da71 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -237,6 +237,8 @@ a2","b","c" $sDataFile = '?field1?;?field2?;?field3? ?a?;?b?;?c? a;b;c + ? a ? ; ? b ? ; ? c ? + a ; b ; c ??;??;?? ;; ?a"?;?b?;?c? @@ -256,6 +258,8 @@ a2?;?b?;?c? //array('field1', 'field2', 'field3'), array('a', 'b', 'c'), array('a', 'b', 'c'), + array(' a ', ' b ', ' c '), + array('a', 'b', 'c'), array('', '', ''), array('', '', ''), array('a"', 'b', 'c'), @@ -263,7 +267,6 @@ a2?;?b?;?c? array('a1,a2', 'b', 'c'), array('a', 'b', "c1,\",c2\n,c3"), array('a', 'b', 'ouf !'), - array('a', 'b', 'a'), ); $oCSVParser = new CSVParser($sDataFile, ';', '?'); From 0d499ef03ad3e26af4d06683d4dc6640c1f269a0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 30 Aug 2010 15:27:29 +0000 Subject: [PATCH 637/970] - Fixed issue #157: Data Admin menu allowed only to admins and configuration managers SVN:trunk[720] --- application/itopwebpage.class.inc.php | 2 +- application/menunode.class.inc.php | 81 ++++++++++++++++--- .../model.itop-config-mgmt.php | 2 +- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 9b276edf5b..37693acfb4 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -269,7 +269,7 @@ EOF $oWelcomeMenu = new MenuGroup('WelcomeMenu', 10 /* fRank */); new TemplateMenuNode('WelcomeMenuPage', '../application/templates/welcome_menu.html', $oWelcomeMenu->GetIndex() /* oParent */, 1 /* fRank */); - $oToolsMenu = new MenuGroup('DataAdministration', 70 /* fRank */); + $oToolsMenu = new MenuGroup('DataAdministration', 70 /* fRank */, 'Organization', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS); new WebPageMenuNode('CSVImportMenu', '../pages/csvimport.php', $oToolsMenu->GetIndex(), 1 /* fRank */); // Add the admin menus diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 1d66369f46..ed643a333b 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -99,6 +99,7 @@ class ApplicationMenu { $oMenuNode = self::GetMenuNode($aMenu['index']); if (($oMenuNode->GetMenuId() == 'AdminTools') && (!UserRights::IsAdministrator())) continue; // Don't display the admin menu for non admin users + if (!$oMenuNode->IsEnabled()) continue; // Don't display a non-enabled menu $oPage->AddToMenu('

        '.$oMenuNode->GetTitle().'

        '); $oPage->AddToMenu('
        '); $aChildren = self::GetChildren($aMenu['index']); @@ -260,18 +261,39 @@ class ApplicationMenu abstract class MenuNode { protected $sMenuId; - protected $index = null; + protected $index; + + /** + * Class of objects to check if the menu is enabled, null if none + */ + protected $m_sEnableClass; + + /** + * User Rights Action code to check if the menu is enabled, null if none + */ + protected $m_iEnableAction; + + /** + * User Rights allowed results (actually a bitmask) to check if the menu is enabled, null if none + */ + protected $m_iEnableActionResults; /** - * Create a menu item and inserts it into the application's main menu + * Create a menu item, sets the condition to have it displayed and inserts it into the application's main menu * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) * @param integer $iParentIndex ID of the parent menu, pass -1 for top level (group) items * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @param string $sEnableClass Name of class of object + * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE + * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuNode */ - public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0) + public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES) { $this->sMenuId = $sMenuId; + $this->m_sEnableClass = $sEnableClass; + $this->m_iEnableAction = $iActionCode; + $this->m_iEnableActionResults = $iAllowedResults; $this->index = ApplicationMenu::InsertMenu($this, $iParentIndex, $fRank); } @@ -307,6 +329,18 @@ abstract class MenuNode */ public function IsEnabled() { + if ($this->m_sEnableClass != null) + { + if (MetaModel::IsValidClass($this->m_sEnableClass)) + { + $iResult = UserRights::IsActionAllowed($this->m_sEnableClass, $this->m_iEnableAction); + if (($iResult & $this->m_iEnableActionResults)) + { + return true; + } + } + return false; + } return true; } @@ -342,11 +376,14 @@ class MenuGroup extends MenuNode * Create a top-level menu group and inserts it into the application's main menu * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) * @param float $fRank Number used to order the list, the groups are sorted based on this value + * @param string $sEnableClass Name of class of object + * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE + * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuGroup */ - public function __construct($sMenuId, $fRank) + public function __construct($sMenuId, $fRank, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES) { - parent::__construct($sMenuId, -1 /* no parent, groups are at root level */, $fRank); + parent::__construct($sMenuId, -1 /* no parent, groups are at root level */, $fRank, $sEnableClass, $iActionCode, $iAllowedResults); } public function RenderContent(WebPage $oPage, $aExtraParams = array()) @@ -369,11 +406,14 @@ class TemplateMenuNode extends MenuNode * @param string $sTemplateFile Path (or URL) to the file that will be used as a template for displaying the page's content * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @param string $sEnableClass Name of class of object + * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE + * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuNode */ - public function __construct($sMenuId, $sTemplateFile, $iParentIndex, $fRank = 0) + public function __construct($sMenuId, $sTemplateFile, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES) { - parent::__construct($sMenuId, $iParentIndex, $fRank); + parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults); $this->sTemplateFile = $sTemplateFile; } @@ -415,14 +455,19 @@ class OQLMenuNode extends MenuNode * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value * @param bool $bSearch Whether or not to display a (collapsed) search frame at the top of the page + * @param string $sEnableClass Name of class of object + * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE + * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuNode */ - public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false) + public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES) { - parent::__construct($sMenuId, $iParentIndex, $fRank); + parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults); $this->sPageTitle = "Menu:$sMenuId+"; $this->sOQL = $sOQL; $this->bSearch = $bSearch; + // Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects + // of the class specified by the OQL... } public function RenderContent(WebPage $oPage, $aExtraParams = array()) @@ -469,11 +514,14 @@ class SearchMenuNode extends MenuNode * @param string $sPageTitle Title displayed into the page's content (will be looked-up in the dictionnary for translation) * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @param string $sEnableClass Name of class of object + * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE + * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuNode */ - public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0) + public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0, $bSearch = false, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES) { - parent::__construct($sMenuId, $iParentIndex, $fRank); + parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults); $this->sPageTitle = "Menu:$sMenuId+"; $this->sClass = $sClass; } @@ -506,11 +554,14 @@ class WebPageMenuNode extends MenuNode * @param string $sHyperlink URL to the page to load. Use relative URL if you want to keep the application portable ! * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @param string $sEnableClass Name of class of object + * @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE + * @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them... * @return MenuNode */ - public function __construct($sMenuId, $sHyperlink, $iParentIndex, $fRank = 0) + public function __construct($sMenuId, $sHyperlink, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES) { - parent::__construct($sMenuId, $iParentIndex, $fRank); + parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults); $this->sHyperlink = $sHyperlink; } @@ -558,6 +609,10 @@ class NewObjectMenuNode extends MenuNode return $this->AddParams($sHyperlink, $aExtraParams); } + /** + * Overload the check of the "enable" state of this menu to take into account + * derived classes of objects + */ public function IsEnabled() { // Enable this menu, only if the current user has enough rights to create such an object, or an object of diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index dc984320a0..cbfb8fa8bf 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -1467,7 +1467,7 @@ class lnkProcessToSolution extends cmdbAbstractObject //////////////////////////////////////////////////////////////////////////////////// // Create the top-level group. fRank = 1, means it will be inserted after the group '0', which is usually 'Welcome' -$oAdminMenu = new MenuGroup('DataAdministration', 70); +$oAdminMenu = new MenuGroup('DataAdministration', 70 /* fRank */, 'Organization', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS); $iAdminGroup = $oAdminMenu->GetIndex(); new WebPageMenuNode('Audit', '../pages/audit.php', $iAdminGroup, 33 /* fRank */); From 8a788acb9a726619003604e4611f4cb918a8a51d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 30 Aug 2010 16:11:46 +0000 Subject: [PATCH 638/970] #139 Improved string size verification, the rule is guaranteed by the core SVN:trunk[721] --- core/attributedef.class.inc.php | 31 +++++++++++++++++++++++++++++++ core/dbobject.class.php | 10 +++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index c299b7cfe4..2d3ce055d6 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -213,6 +213,11 @@ abstract class AttributeDefinition return true; } + public function GetMaxSize() + { + return null; + } + public function MakeValue() { $sComputeFunc = $this->Get("compute_func"); @@ -636,6 +641,11 @@ class AttributeString extends AttributeDBField } } + public function GetMaxSize() + { + return 255; + } + public function GetBasicFilterOperators() { return array( @@ -844,6 +854,11 @@ class AttributePassword extends AttributeString public function GetEditClass() {return "Password";} protected function GetSQLCol() {return "VARCHAR(64)";} + public function GetMaxSize() + { + return 64; + } + public function GetFilterDefinitions() { // Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists) @@ -887,6 +902,11 @@ class AttributeEncryptedString extends AttributeString protected function GetSQLCol() {return "TINYBLOB";} + public function GetMaxSize() + { + return 255; + } + public function GetFilterDefinitions() { // Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists) @@ -936,6 +956,13 @@ class AttributeText extends AttributeString public function GetEditClass() {return "Text";} protected function GetSQLCol() {return "TEXT";} + public function GetMaxSize() + { + // Is there a way to know the current limitation for mysql? + // See mysql_field_len() + return 65535; + } + public function GetAsHTML($sValue) { return str_replace("\n", "
        \n", parent::GetAsHTML($sValue)); @@ -2082,6 +2109,10 @@ class AttributeTable extends AttributeText public function GetEditClass() {return "Text";} protected function GetSQLCol() {return "TEXT";} + public function GetMaxSize() + { + return null; + } // Facilitate things: allow the user to Set the value from a string public function MakeRealValue($proposedValue) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 9e587542ee..ae10c31300 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -636,7 +636,15 @@ abstract class DBObject return "Value not allowed [$toCheck]"; } } - elseif (!$oAtt->CheckFormat($toCheck)) + if (!is_null($iMaxSize = $oAtt->GetMaxSize())) + { + $iLen = strlen($toCheck); + if ($iLen > $iMaxSize) + { + return "String too long (found $iLen, limited to $iMaxSize)"; + } + } + if (!$oAtt->CheckFormat($toCheck)) { return "Wrong format [$toCheck]"; } From b7131ba4db0549a19fa6caa6cebad4410becb61f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 30 Aug 2010 22:14:04 +0000 Subject: [PATCH 639/970] Improved the internal KPI reporting tools SVN:trunk[722] --- .../userrightsprofile.class.inc.php | 8 +- core/cmdbobject.class.inc.php | 2 +- core/cmdbsource.class.inc.php | 5 +- core/config.class.inc.php | 21 ++- core/duration.class.inc.php | 82 --------- core/kpi.class.inc.php | 167 ++++++++++++++++++ core/metamodel.class.php | 16 +- core/test.class.inc.php | 2 +- core/userrights.class.inc.php | 4 +- pages/UI.php | 8 +- setup/ajax.dataloader.php | 2 +- setup/index.php | 4 +- setup/xmldataloader.class.inc.php | 2 +- 13 files changed, 213 insertions(+), 110 deletions(-) delete mode 100644 core/duration.class.inc.php create mode 100644 core/kpi.class.inc.php diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 75e64031b8..d2320abeb8 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -452,7 +452,7 @@ class UserRightsProfile extends UserRightsAddOnAPI { if (!is_null($this->m_aClassActionGrants)) return; - $oDuration = new Duration(); + $oKPI = new ExecutionKPI(); $oFilter = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant AS p WHERE p.permission = 'yes'"); $aGrants = $oFilter->ToDataArray(); @@ -461,7 +461,7 @@ class UserRightsProfile extends UserRightsAddOnAPI $this->m_aClassActionGrants[$aGrant['profileid']][$aGrant['class']][strtolower($aGrant['action'])] = $aGrant['id']; } - $oDuration->Scratch('Load of action grants'); + $oKPI->ComputeAndReport('Load of action grants'); } public function LoadCache() @@ -469,7 +469,7 @@ class UserRightsProfile extends UserRightsAddOnAPI if (!is_null($this->m_aProfiles)) return; // Could be loaded in a shared memory (?) - $oDuration = new Duration(); + $oKPI = new ExecutionKPI(); $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); $this->m_aProfiles = array(); @@ -505,7 +505,7 @@ class UserRightsProfile extends UserRightsAddOnAPI $this->m_aClassStimulusGrants[$oStimGrant->Get('profileid')][$oStimGrant->Get('class')][$oStimGrant->Get('stimulus')] = $oStimGrant; } - $oDuration->Scratch('Load of user management cache (excepted Action Grants)'); + $oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)'); /* echo "
        \n";
        diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
        index ecb58ce24b..dd3a474287 100644
        --- a/core/cmdbobject.class.inc.php
        +++ b/core/cmdbobject.class.inc.php
        @@ -35,7 +35,7 @@ require_once('coreexception.class.inc.php');
         
         require_once('config.class.inc.php');
         require_once('log.class.inc.php');
        -require_once('duration.class.inc.php');
        +require_once('kpi.class.inc.php');
         
         require_once('dict.class.inc.php');
         
        diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php
        index e42d66b8d0..b72c4bdd8c 100644
        --- a/core/cmdbsource.class.inc.php
        +++ b/core/cmdbsource.class.inc.php
        @@ -195,14 +195,13 @@ class CMDBSource
         		//$aTraceInf['file'] = __FILE__;
         		// $sSQLQuery .= MyHelpers::MakeSQLComment($aTraceInf);
         	  
        -		$mu_t1 = MyHelpers::getmicrotime();
        +		$oKPI = new ExecutionKPI();
         		$result = mysql_query($sSQLQuery, self::$m_resDBLink);
         		if (!$result) 
         		{
         			throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery));
         		}
        -		$mu_t2 = MyHelpers::getmicrotime();
        -		// #@# todo - query_trace($sSQLQuery, $mu_t2 - $mu_t1);
        +		$oKPI->ComputeStats('Query exec (mySQL)', $sSQLQuery);
         	
         		return $result;
         	}
        diff --git a/core/config.class.inc.php b/core/config.class.inc.php
        index f11c35fa01..4c8ea89b59 100644
        --- a/core/config.class.inc.php
        +++ b/core/config.class.inc.php
        @@ -37,7 +37,8 @@ define ('DEFAULT_LOG_GLOBAL', true);
         define ('DEFAULT_LOG_NOTIFICATION', true);
         define ('DEFAULT_LOG_ISSUE', true);
         define ('DEFAULT_LOG_WEB_SERVICE', true);
        -define ('DEFAULT_LOG_DURATION', false);
        +define ('DEFAULT_LOG_KPI_DURATION', false);
        +define ('DEFAULT_LOG_KPI_MEMORY', false);
         
         define ('DEFAULT_MIN_DISPLAY_LIMIT', 10);
         define ('DEFAULT_MAX_DISPLAY_LIMIT', 15);
        @@ -80,7 +81,8 @@ class Config
         	protected $m_bLogNotification;
         	protected $m_bLogIssue;
         	protected $m_bLogWebService;
        -	protected $m_bLogDuration; // private setting
        +	protected $m_bLogKpiDuration; // private setting
        +	protected $m_bLogKpiMemory; // private setting
         
         	/**
         	 * @var integer Number of elements to be displayed when there are more than m_iMaxDisplayLimit elements
        @@ -178,7 +180,8 @@ class Config
         		$this->m_bLogNotification = DEFAULT_LOG_NOTIFICATION;
         		$this->m_bLogIssue = DEFAULT_LOG_ISSUE;
         		$this->m_bLogWebService = DEFAULT_LOG_WEB_SERVICE;
        -		$this->m_bLogDuration = DEFAULT_LOG_DURATION;
        +		$this->m_bLogKPIDuration = DEFAULT_LOG_KPI_DURATION;
        +		$this->m_bLogKPIDuration = DEFAULT_LOG_KPI_DURATION;
         		$this->m_iMinDisplayLimit = DEFAULT_MIN_DISPLAY_LIMIT;
         		$this->m_iMaxDisplayLimit = DEFAULT_MAX_DISPLAY_LIMIT;
         		$this->m_iStandardReloadInterval = DEFAULT_STANDARD_RELOAD_INTERVAL;
        @@ -278,7 +281,8 @@ class Config
         		$this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION;
         		$this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE;
         		$this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE;
        -		$this->m_bLogDuration = isset($MySettings['log_duration']) ? (bool) trim($MySettings['log_duration']) : DEFAULT_LOG_DURATION;
        +		$this->m_bLogKPIDuration = isset($MySettings['log_kpi_duration']) ? (bool) trim($MySettings['log_kpi_duration']) : DEFAULT_LOG_KPI_DURATION;
        +		$this->m_bLogKPIMemory = isset($MySettings['log_kpi_memory']) ? (bool) trim($MySettings['log_kpi_memory']) : DEFAULT_LOG_KPI_MEMORY;
         		$this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT;
         		$this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT;
         		$this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL;
        @@ -409,9 +413,14 @@ class Config
         		return $this->m_bLogWebService;
         	}
         
        -	public function GetLogDuration()
        +	public function GetLogKPIDuration()
         	{
        -		return $this->m_bLogDuration;
        +		return $this->m_bLogKPIDuration;
        +	}
        +
        +	public function GetLogKPIMemory()
        +	{
        +		return $this->m_bLogKPIMemory;
         	}
         
         	public function GetMinDisplayLimit()
        diff --git a/core/duration.class.inc.php b/core/duration.class.inc.php
        deleted file mode 100644
        index c027455262..0000000000
        --- a/core/duration.class.inc.php
        +++ /dev/null
        @@ -1,82 +0,0 @@
        -
        - * @author      Romain Quetiez 
        - * @author      Denis Flaven 
        - * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
        - */
        -
        -class Duration
        -{
        -	static $m_bEnabled = false;
        -
        -	static public function Enable()
        -	{
        -		self::$m_bEnabled = true;
        -	}
        -
        -	protected $m_fStarted = null;
        -
        -	public function __construct()
        -	{
        -		if (!self::$m_bEnabled) return;
        -
        -		$this->m_fStarted = MyHelpers::getmicrotime();
        -	}
        -
        -	// Get the duration since startup, and reset the counter for the next measure
        -	//
        -	public function Scratch($sMeasure)
        -	{
        -		if (!self::$m_bEnabled) return;
        -
        -		$fStopped = MyHelpers::getmicrotime();
        -		$fDuration = $fStopped - $this->m_fStarted;
        -		$this->Report($sMeasure.': '.round($fDuration, 3));
        -
        -		$this->m_fStarted = MyHelpers::getmicrotime();
        -	}
        -
        -	protected function Report($sText)
        -	{
        -		echo "DURATION... $sText
        \n"; - } -} - -// Prototype, to be finalized later -// Reports the function duration -// One single thing to do: construct it -class FunctionDuration -{ - protected $m_sFunction = null; - - public function __construct() - { - $this->m_sFunction = 'my_function_name_in_call_stack'; - $this->m_fStarted = MyHelpers::getmicrotime(); - } - - public function __destruct() - { - $this->Scratch('Exiting '); - } -} - -?> diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php new file mode 100644 index 0000000000..4f3dec5528 --- /dev/null +++ b/core/kpi.class.inc.php @@ -0,0 +1,167 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +class ExecutionKPI +{ + static protected $m_bEnabled_Duration = false; + static protected $m_bEnabled_Memory = false; + + static protected $m_aStats = array(); + + protected $m_fStarted = null; + protected $m_iInitialMemory = null; + + static public function EnableDuration() + { + self::$m_bEnabled_Duration = true; + } + + static public function EnableMemory() + { + self::$m_bEnabled_Memory = true; + } + + static public function ReportStats() + { + foreach (self::$m_aStats as $sOperation => $aOpStats) + { + echo "====================
        \n"; + echo "KPIs for $sOperation
        \n"; + echo "====================
        \n"; + $fTotalOp = 0; + $iTotalOp = 0; + foreach ($aOpStats as $sArguments => $aEvents) + { + $fTotalInter = 0; + $iTotalInter = 0; + foreach ($aEvents as $fDuration) + { + $fTotalInter += $fDuration; + $iTotalInter++; + } + $fTotalOp += $fTotalInter; + $iTotalOp++; + echo "$sArguments: $iTotalInter (".round($fTotalInter, 3).")
        \n"; + } + echo "Total: $iTotalOp (".round($fTotalOp, 3).")
        \n"; + echo "====================
        \n"; + } + } + + + public function __construct() + { + $this->ResetCounters(); + } + + // Get the duration since startup, and reset the counter for the next measure + // + public function ComputeAndReport($sOperationDesc) + { + if (self::$m_bEnabled_Duration) + { + $fStopped = MyHelpers::getmicrotime(); + $fDuration = $fStopped - $this->m_fStarted; + $this->Report($sOperationDesc.' / duration: '.round($fDuration, 3)); + } + + if (self::$m_bEnabled_Memory) + { + $iMemory = self::memory_get_usage(); + $iMemoryUsed = $iMemory - $this->m_iInitialMemory; + $this->Report($sOperationDesc.' / memory: '.self::MemStr($iMemoryUsed).' (Total: '.self::MemStr($iMemory).')'); + } + + $this->ResetCounters(); + } + + public function ComputeStats($sOperation, $sArguments) + { + if (self::$m_bEnabled_Duration) + { + $fStopped = MyHelpers::getmicrotime(); + $fDuration = $fStopped - $this->m_fStarted; + self::$m_aStats[$sOperation][$sArguments][] = $fDuration; + } + } + + protected function ResetCounters() + { + if (self::$m_bEnabled_Duration) + { + $this->m_fStarted = MyHelpers::getmicrotime(); + } + + if (self::$m_bEnabled_Memory) + { + $this->m_iInitialMemory = self::memory_get_usage(); + } + } + + protected function Report($sText) + { + echo "$sText
        \n"; + } + + static protected function MemStr($iMemory) + { + return round($iMemory / 1024).' Kb'; + } + + static protected function memory_get_usage() + { + if (function_exists('memory_get_usage')) + { + return memory_get_usage(true); + } + + // Copied from the PHP manual + // + //If its Windows + //Tested on Win XP Pro SP2. Should work on Win 2003 Server too + //Doesn't work for 2000 + //If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642 + if (substr(PHP_OS,0,3) == 'WIN') + { + $output = array(); + exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output); + + return preg_replace( '/[\D]/', '', $output[5] ) * 1024; + } + else + { + //We now assume the OS is UNIX + //Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4 + //This should work on most UNIX systems + $pid = getmypid(); + exec("ps -eo%mem,rss,pid | grep $pid", $output); + $output = explode(" ", $output[0]); + //rss is given in 1024 byte units + return $output[1] * 1024; + } + } +} + +?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index d509707281..763ca5ffeb 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1534,9 +1534,9 @@ abstract class MetaModel // Query caching // $bQueryCacheEnabled = true; - $sOqlQuery = $oFilter->ToOql(); if ($bQueryCacheEnabled) { + $sOqlQuery = $oFilter->ToOql(); // Warning: using directly the query string as the key to the hash array can FAIL if the string // is long and the differences are only near the end... so it's safer (but not bullet proof?) // to use a hash (like md5) of the string as the key ! @@ -1565,7 +1565,9 @@ abstract class MetaModel $aTableAliases = array(); $oConditionTree = $oFilter->GetCriteria(); + $oKPI = new ExecutionKPI(); $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + $oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery); self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; } @@ -3169,9 +3171,13 @@ abstract class MetaModel self::$m_bLogWebService = false; } - if (self::$m_oConfig->GetLogDuration()) + if (self::$m_oConfig->GetLogKPIDuration()) { - Duration::Enable(); + ExecutionKPI::EnableDuration(); + } + if (self::$m_oConfig->GetLogKPIMemory()) + { + ExecutionKPI::EnableMemory(); } // Note: load the dictionary as soon as possible, because it might be @@ -3206,13 +3212,13 @@ abstract class MetaModel $sSource = self::$m_oConfig->GetDBName(); $sTablePrefix = self::$m_oConfig->GetDBSubname(); - $oDuration = new Duration(); + $oKPI = new ExecutionKPI(); // The include have been included, let's browse the existing classes and // develop some data based on the proposed model self::InitClasses($sTablePrefix); - $oDuration->Scratch('Initialization of Data model structures'); + $oKPI->ComputeAndReport('Initialization of Data model structures'); self::$m_sDBName = $sSource; self::$m_sTablePrefix = $sTablePrefix; diff --git a/core/test.class.inc.php b/core/test.class.inc.php index eaebc80af2..67e8e184fe 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -35,7 +35,7 @@ require_once('cmdbsource.class.inc.php'); require_once('sqlquery.class.inc.php'); require_once('log.class.inc.php'); -require_once('duration.class.inc.php'); +require_once('kpi.class.inc.php'); require_once('dbobject.class.php'); require_once('dbobjectsearch.class.php'); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 91c63a65af..e247ea685a 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -131,7 +131,7 @@ abstract class User extends cmdbAbstractObject return; } - $oDuration = new Duration(); + $oKPI = new ExecutionKPI(); $aDisplayData = array(); foreach (MetaModel::GetClasses($sClassCategory) as $sClass) @@ -167,7 +167,7 @@ abstract class User extends cmdbAbstractObject ); } - $oDuration->Scratch('Computation of user rights'); + $oKPI->ComputeAndReport('Computation of user rights'); $aDisplayConfig = array(); $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); diff --git a/pages/UI.php b/pages/UI.php index c2570fb5ee..66698c9ee3 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -455,15 +455,18 @@ try require_once('../application/itopwebpage.class.inc.php'); require_once('../application/wizardhelper.class.inc.php'); - $oDuration = new Duration(); require_once('../application/startup.inc.php'); $oAppContext = new ApplicationContext(); $currentOrganization = utils::ReadParam('org_id', ''); $operation = utils::ReadParam('operation', ''); + $oKPI = new ExecutionKPI(); + require_once('../application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed + $oKPI->ComputeAndReport('User login'); + $oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $currentOrganization); switch($operation) @@ -1390,7 +1393,8 @@ EOF $oP->set_title($oMenuNode->GetLabel()); } } - $oDuration->Scratch('Total page execution time'); + $oKPI->ComputeAndReport('GUI creation before output'); + ExecutionKPI::ReportStats(); ////MetaModel::ShowQueryTrace(); $oP->output(); } diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index c1db84a5de..6ddf3b35d8 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -89,7 +89,7 @@ ob_start('FatalErrorCatcher'); // Start capturing the output, and pass it throug require_once('../core/config.class.inc.php'); require_once('../core/log.class.inc.php'); -require_once('../core/duration.class.inc.php'); +require_once('../core/kpi.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); require_once('./xmldataloader.class.inc.php'); diff --git a/setup/index.php b/setup/index.php index 309ce4144c..59b8fa6a4e 100644 --- a/setup/index.php +++ b/setup/index.php @@ -26,7 +26,7 @@ require_once('../application/utils.inc.php'); require_once('../core/config.class.inc.php'); require_once('../core/log.class.inc.php'); -require_once('../core/duration.class.inc.php'); +require_once('../core/kpi.class.inc.php'); require_once('../core/cmdbsource.class.inc.php'); require_once('./setuppage.class.inc.php'); @@ -376,7 +376,7 @@ function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd) function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bModelOnly = true) { require_once('../core/log.class.inc.php'); - require_once('../core/duration.class.inc.php'); + require_once('../core/kpi.class.inc.php'); require_once('../core/coreexception.class.inc.php'); require_once('../core/dict.class.inc.php'); require_once('../core/attributedef.class.inc.php'); diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index f874d9e5fe..de28bfb4f6 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -88,7 +88,7 @@ class XMLDataLoader protected function InitDataModel($sConfigFileName) { require_once('../core/log.class.inc.php'); - require_once('../core/duration.class.inc.php'); + require_once('../core/kpi.class.inc.php'); require_once('../core/coreexception.class.inc.php'); require_once('../core/dict.class.inc.php'); require_once('../core/attributedef.class.inc.php'); From b726430ba1e70955d7a4717c57a0cfac1d2d8f1b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 31 Aug 2010 07:25:43 +0000 Subject: [PATCH 640/970] Cosmetic on trigger action link (in lists) SVN:trunk[723] --- core/trigger.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 4bfb1cbc7c..dc23d1b4cd 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -244,7 +244,7 @@ class lnkTriggerAction extends cmdbAbstractObject // Display lists MetaModel::Init_SetZListItems('details', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list - MetaModel::Init_SetZListItems('list', array('action_name', 'trigger_name', 'order')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('action_id', 'trigger_id', 'order')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('action_id', 'trigger_id', 'order')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form From e75b6f8dbbb8e1c97605168e04ff84bb7318fe83 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 31 Aug 2010 07:34:53 +0000 Subject: [PATCH 641/970] Added KPI for send mail SVN:trunk[724] --- core/action.class.inc.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index adbe7f564c..97fb1d2aef 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -277,7 +277,9 @@ class ActionEmail extends ActionNotification } else { + $oKPI = new ExecutionKPI(); $this->m_aMailErrors = array_merge($this->m_aMailErrors, $oEmail->Send()); + $oKPI->ComputeStats('Send mail', $sTo); } } } From a17c123f086bd7ed96f0dd263d07d134d9760512 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Tue, 31 Aug 2010 07:51:26 +0000 Subject: [PATCH 642/970] Update KEDB and Problem Mgmt Modules Review other modules SVN:trunk[725] --- .../model.itop-change-mgmt.php | 46 +-- .../en.dict.itop-config-mgmt.php | 212 +++++++----- .../model.itop-config-mgmt.php | 20 +- .../model.itop-incident-mgmt.php | 40 ++- .../en.dict.itop-knownerror-mgmt.php | 91 +++++ .../model.itop-knownerror-mgmt.php | 144 ++++++++ .../module.itop-knownerror-mgmt.php | 1 - .../en.dict.itop-problem-mgmt.php | 109 ++++++ .../model.itop-problem-mgmt.php | 209 +++++++++++ .../en.dict.itop-request-mgmt.php | 45 ++- .../model.itop-request-mgmt.php | 9 +- .../en.dict.itop-service-mgmt.php | 29 +- .../model.itop-service-mgmt.php | 83 ++++- .../en.dict.itop-tickets.php | 326 ++++++++++-------- .../itop-tickets-1.0.0/model.itop-tickets.php | 40 ++- .../module.itop-tickets.php | 2 +- 16 files changed, 1085 insertions(+), 321 deletions(-) diff --git a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php index 3d7c8cda06..4d7c9bfc3f 100644 --- a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php @@ -71,12 +71,11 @@ abstract class Change extends Ticket MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"Person", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Person AS p JOIN lnkTeamToContact AS l ON l.contact_id=p.id JOIN Team AS t ON l.team_id=t.id WHERE t.id = :this->manager_group_id'), "sql"=>"manager_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("manager_group_id")))); MetaModel::Init_AddAttribute(new AttributeExternalField("manager_email", array("allowed_values"=>null, "extkey_attcode"=>"manager_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"outage", "default_value"=>"no", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("change_request", array("allowed_values"=>null, "sql"=>"change_request", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("fallback", array("allowed_values"=>null, "sql"=>"fallback", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('advanced_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); + MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'description','ticket_log', 'start_date','end_date', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'fallback')); + MetaModel::Init_SetZListItems('advanced_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'end_date','status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage')); + MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'end_date','status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage')); MetaModel::Init_SetZListItems('list', array('finalclass', 'title', 'start_date', 'status')); @@ -118,7 +117,9 @@ abstract class Change extends Ticket 'workgroup_id' => OPT_ATT_MANDATORY, 'supervisor_group_id' => OPT_ATT_MANDATORY, 'manager_group_id' => OPT_ATT_MANDATORY, - 'change_request' => OPT_ATT_READONLY, + 'description' => OPT_ATT_READONLY, + 'requestor_id' => OPT_ATT_READONLY, + 'title' => OPT_ATT_MANDATORY, ), ) ); @@ -139,6 +140,8 @@ abstract class Change extends Ticket 'agent_id' => OPT_ATT_MUSTCHANGE, 'supervisor_id' => OPT_ATT_MUSTCHANGE, 'manager_id' => OPT_ATT_MUSTCHANGE, + 'description' => OPT_ATT_READONLY, + 'requestor_id' => OPT_ATT_READONLY, ), ) ); @@ -208,7 +211,6 @@ abstract class Change extends Ticket "attribute_list" => array( 'end_date' => OPT_ATT_READONLY, 'agent_id' => OPT_ATT_READONLY, - 'change_request' => OPT_ATT_READONLY, 'fallback' => OPT_ATT_READONLY, ), ) @@ -316,10 +318,10 @@ class RoutineChange extends Change MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritLifecycle(); - MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); + MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'description','ticket_log', 'start_date', 'end_date','document_list', 'ci_list', 'contact_list','incident_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'fallback')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'end_date','status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date','end_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id')); MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("assigned", "ev_plan", array("target_state"=>"plannedscheduled", "actions"=>array(), "user_restriction"=>null)); @@ -353,10 +355,10 @@ abstract class ApprovedChange extends Change MetaModel::Init_AddAttribute(new AttributeDateTime("approval_date", array("allowed_values"=>null, "sql"=>"approval_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("approval_comment", array("allowed_values"=>null, "sql"=>"approval_comment", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); - MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); - MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); + MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'description','ticket_log', 'start_date', 'end_date','document_list', 'ci_list', 'contact_list','incident_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'fallback', 'approval_date', 'approval_comment')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date','end_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage','approval_date')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date','end_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'approval_date')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id')); MetaModel::Init_OverloadStateAttribute('new', 'approval_date', OPT_ATT_HIDDEN); MetaModel::Init_OverloadStateAttribute('new', 'approval_comment', OPT_ATT_HIDDEN); @@ -404,10 +406,10 @@ class NormalChange extends ApprovedChange MetaModel::Init_AddAttribute(new AttributeDateTime("acceptance_date", array("allowed_values"=>null, "sql"=>"acceptance_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("acceptance_comment", array("allowed_values"=>null, "sql"=>"acceptance_comment", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment', 'acceptance_date', 'acceptance_comment')); - MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); - MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback')); + MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'description','ticket_log', 'start_date','end_date', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update','close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'fallback', 'approval_date', 'approval_comment', 'acceptance_date', 'acceptance_comment')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'end_date','status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date','end_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id')); MetaModel::Init_OverloadStateAttribute('new', 'acceptance_date', OPT_ATT_HIDDEN); MetaModel::Init_OverloadStateAttribute('new', 'acceptance_comment', OPT_ATT_HIDDEN); @@ -463,10 +465,10 @@ class EmergencyChange extends ApprovedChange MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritLifecycle(); - MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); - MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); - MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'end_date', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'change_request', 'fallback', 'approval_date', 'approval_comment')); + MetaModel::Init_SetZListItems('details', array('title', 'org_id', 'description','ticket_log', 'start_date', 'end_date','document_list', 'ci_list', 'contact_list','incident_list', 'status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'fallback', 'approval_date', 'approval_comment')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date','end_date', 'status', 'reason', 'requestor_id', 'workgroup_id', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'approval_date')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'end_date','status', 'reason', 'requestor_id', 'workgroup_id', 'creation_date', 'last_update', 'close_date', 'impact', 'agent_id', 'agent_email', 'supervisor_group_id', 'supervisor_id', 'manager_group_id', 'manager_id', 'outage', 'approval_date')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'requestor_id')); MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("assigned", "ev_plan", array("target_state"=>"plannedscheduled", "actions"=>array(), "user_restriction"=>null)); diff --git a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php index 4b6de2b018..d78c9d120c 100644 --- a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php @@ -299,28 +299,33 @@ Dict::Add('EN US', 'English', 'English', array( // Class: Licence // -Dict::Add('EN US', 'English', 'English', array( - 'Class:Licence' => 'Licence', - 'Class:Licence+' => '', - 'Class:Licence/Attribute:provider' => 'Provider', - 'Class:Licence/Attribute:provider+' => '', - 'Class:Licence/Attribute:product' => 'Product', - 'Class:Licence/Attribute:product+' => '', - 'Class:Licence/Attribute:name' => 'Name', - 'Class:Licence/Attribute:name+' => '', - 'Class:Licence/Attribute:start' => 'Start date', - 'Class:Licence/Attribute:start+' => '', - 'Class:Licence/Attribute:end' => 'End date', - 'Class:Licence/Attribute:end+' => '', - 'Class:Licence/Attribute:licence_key' => 'Key', - 'Class:Licence/Attribute:licence_key+' => '', - 'Class:Licence/Attribute:scope' => 'Scope', - 'Class:Licence/Attribute:scope+' => '', - 'Class:Licence/Attribute:usage_limit' => 'Usage limit', - 'Class:Licence/Attribute:usage_limit+' => '', - 'Class:Licence/Attribute:usage_list' => 'Usage', - 'Class:Licence/Attribute:usage_list+' => 'Application instances using this licence', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:Licence' => 'Licence', + 'Class:Licence+' => '', + 'Class:Licence/Attribute:provider' => 'Provider', + 'Class:Licence/Attribute:provider+' => '', + 'Class:Licence/Attribute:org_id' => 'Owner', + 'Class:Licence/Attribute:org_id+' => '', + 'Class:Licence/Attribute:org_name' => 'Name', + 'Class:Licence/Attribute:org_name+' => 'Common name', + 'Class:Licence/Attribute:product' => 'Product', + 'Class:Licence/Attribute:product+' => '', + 'Class:Licence/Attribute:name' => 'Name', + 'Class:Licence/Attribute:name+' => '', + 'Class:Licence/Attribute:start' => 'Start date', + 'Class:Licence/Attribute:start+' => '', + 'Class:Licence/Attribute:end' => 'End date', + 'Class:Licence/Attribute:end+' => '', + 'Class:Licence/Attribute:licence_key' => 'Key', + 'Class:Licence/Attribute:licence_key+' => '', + 'Class:Licence/Attribute:scope' => 'Scope', + 'Class:Licence/Attribute:scope+' => '', + 'Class:Licence/Attribute:usage_limit' => 'Usage limit', + 'Class:Licence/Attribute:usage_limit+' => '', + 'Class:Licence/Attribute:usage_list' => 'Usage', + 'Class:Licence/Attribute:usage_list+' => 'Application instances using this licence', +)); + // // Class: Subnet @@ -490,8 +495,6 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SoftwareInstance/Attribute:licence_id+' => '', 'Class:SoftwareInstance/Attribute:licence_name' => 'Licence', 'Class:SoftwareInstance/Attribute:licence_name+' => '', - 'Class:SoftwareInstance/Attribute:software_id' => 'Software', - 'Class:SoftwareInstance/Attribute:software_id+' => '', 'Class:SoftwareInstance/Attribute:software_name' => 'Software', 'Class:SoftwareInstance/Attribute:software_name+' => '', 'Class:SoftwareInstance/Attribute:version' => 'Version', @@ -504,21 +507,31 @@ Dict::Add('EN US', 'English', 'English', array( // Class: ApplicationInstance // -Dict::Add('EN US', 'English', 'English', array( - 'Class:ApplicationInstance' => 'Application Instance', - 'Class:ApplicationInstance+' => '', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:ApplicationInstance' => 'Application Instance', + 'Class:ApplicationInstance+' => '', + 'Class:ApplicationInstance/Attribute:software_id' => 'Software', + 'Class:ApplicationInstance/Attribute:software_id+' => '', + 'Class:ApplicationInstance/Attribute:software_name' => 'Name', + 'Class:ApplicationInstance/Attribute:software_name+' => '', +)); + // // Class: DBServerInstance // -Dict::Add('EN US', 'English', 'English', array( - 'Class:DBServerInstance' => 'DB Server Instance', - 'Class:DBServerInstance+' => '', - 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Databases', - 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Database sources', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:DBServerInstance' => 'DB Server Instance', + 'Class:DBServerInstance+' => '', + 'Class:DBServerInstance/Attribute:software_id' => 'Software', + 'Class:DBServerInstance/Attribute:software_id+' => '', + 'Class:DBServerInstance/Attribute:software_name' => 'Name', + 'Class:DBServerInstance/Attribute:software_name+' => '', + 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Databases', + 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Database sources', +)); + // // Class: DatabaseInstance @@ -584,64 +597,70 @@ Dict::Add('EN US', 'English', 'English', array( // Class: NetworkInterface // -Dict::Add('EN US', 'English', 'English', array( - 'Class:NetworkInterface' => 'Network Interface', - 'Class:NetworkInterface+' => '', - 'Class:NetworkInterface/Attribute:device_id' => 'Device', - 'Class:NetworkInterface/Attribute:device_id+' => '', - 'Class:NetworkInterface/Attribute:device_name' => 'Device', - 'Class:NetworkInterface/Attribute:device_name+' => '', - 'Class:NetworkInterface/Attribute:logical_type' => 'Logical Type', - 'Class:NetworkInterface/Attribute:logical_type+' => '', - 'Class:NetworkInterface/Attribute:logical_type/Value:backup' => 'Backup', - 'Class:NetworkInterface/Attribute:logical_type/Value:backup+' => '', - 'Class:NetworkInterface/Attribute:logical_type/Value:logical' => 'Logical', - 'Class:NetworkInterface/Attribute:logical_type/Value:logical+' => '', - 'Class:NetworkInterface/Attribute:logical_type/Value:port' => 'Port', - 'Class:NetworkInterface/Attribute:logical_type/Value:port+' => '', - 'Class:NetworkInterface/Attribute:logical_type/Value:primary' => 'Primary', - 'Class:NetworkInterface/Attribute:logical_type/Value:primary+' => '', - 'Class:NetworkInterface/Attribute:logical_type/Value:secondary' => 'Secondary', - 'Class:NetworkInterface/Attribute:logical_type/Value:secondary+' => '', - 'Class:NetworkInterface/Attribute:physical_type' => 'Physical Type', - 'Class:NetworkInterface/Attribute:physical_type+' => '', - 'Class:NetworkInterface/Attribute:physical_type/Value:atm' => 'ATM', - 'Class:NetworkInterface/Attribute:physical_type/Value:atm+' => '', - 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet' => 'Ethernet', - 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet+' => '', - 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay' => 'Frame Relay', - 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay+' => '', - 'Class:NetworkInterface/Attribute:physical_type/Value:vlan' => 'VLAN', - 'Class:NetworkInterface/Attribute:physical_type/Value:vlan+' => '', - 'Class:NetworkInterface/Attribute:ip_address' => 'IP Address', - 'Class:NetworkInterface/Attribute:ip_address+' => '', - 'Class:NetworkInterface/Attribute:ip_mask' => 'IP Mask', - 'Class:NetworkInterface/Attribute:ip_mask+' => '', - 'Class:NetworkInterface/Attribute:mac_address' => 'MAC Address', - 'Class:NetworkInterface/Attribute:mac_address+' => '', - 'Class:NetworkInterface/Attribute:speed' => 'Speed', - 'Class:NetworkInterface/Attribute:speed+' => '', - 'Class:NetworkInterface/Attribute:duplex' => 'Duplex', - 'Class:NetworkInterface/Attribute:duplex+' => '', - 'Class:NetworkInterface/Attribute:duplex/Value:full' => 'Full', - 'Class:NetworkInterface/Attribute:duplex/Value:full+' => '', - 'Class:NetworkInterface/Attribute:duplex/Value:half' => 'Half', - 'Class:NetworkInterface/Attribute:duplex/Value:half+' => '', - 'Class:NetworkInterface/Attribute:duplex/Value:unknown' => 'Unknown', - 'Class:NetworkInterface/Attribute:duplex/Value:unknown+' => '', - 'Class:NetworkInterface/Attribute:connected_if' => 'Connected to', - 'Class:NetworkInterface/Attribute:connected_if+' => 'Connected interface', - 'Class:NetworkInterface/Attribute:connected_name' => 'Connected to', - 'Class:NetworkInterface/Attribute:connected_name+' => '', - 'Class:NetworkInterface/Attribute:connected_if_device_id' => 'Connected device', - 'Class:NetworkInterface/Attribute:connected_if_device_id+' => '', - 'Class:NetworkInterface/Attribute:link_type' => 'Link type', - 'Class:NetworkInterface/Attribute:link_type+' => '', - 'Class:NetworkInterface/Attribute:link_type/Value:uplink' => 'Up link', - 'Class:NetworkInterface/Attribute:link_type/Value:uplink+' => '', - 'Class:NetworkInterface/Attribute:link_type/Value:downlink' => 'Down link', - 'Class:NetworkInterface/Attribute:link_type/Value:downlink+' => '', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:NetworkInterface' => 'Network Interface', + 'Class:NetworkInterface+' => '', + 'Class:NetworkInterface/Attribute:device_id' => 'Device', + 'Class:NetworkInterface/Attribute:device_id+' => '', + 'Class:NetworkInterface/Attribute:device_name' => 'Device', + 'Class:NetworkInterface/Attribute:device_name+' => '', + 'Class:NetworkInterface/Attribute:logical_type' => 'Logical Type', + 'Class:NetworkInterface/Attribute:logical_type+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup' => 'Backup', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical' => 'Logical', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:port' => 'Port', + 'Class:NetworkInterface/Attribute:logical_type/Value:port+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary' => 'Primary', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary' => 'Secondary', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary+' => '', + 'Class:NetworkInterface/Attribute:physical_type' => 'Physical Type', + 'Class:NetworkInterface/Attribute:physical_type+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm' => 'ATM', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet' => 'Ethernet', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay' => 'Frame Relay', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan' => 'VLAN', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan+' => '', + 'Class:NetworkInterface/Attribute:ip_address' => 'IP Address', + 'Class:NetworkInterface/Attribute:ip_address+' => '', + 'Class:NetworkInterface/Attribute:ip_mask' => 'IP Mask', + 'Class:NetworkInterface/Attribute:ip_mask+' => '', + 'Class:NetworkInterface/Attribute:mac_address' => 'MAC Address', + 'Class:NetworkInterface/Attribute:mac_address+' => '', + 'Class:NetworkInterface/Attribute:speed' => 'Speed', + 'Class:NetworkInterface/Attribute:speed+' => '', + 'Class:NetworkInterface/Attribute:duplex' => 'Duplex', + 'Class:NetworkInterface/Attribute:duplex+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:auto' => 'Auto', + 'Class:NetworkInterface/Attribute:duplex/Value:auto+' => 'Auto', + 'Class:NetworkInterface/Attribute:duplex/Value:full' => 'Full', + 'Class:NetworkInterface/Attribute:duplex/Value:full+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:half' => 'Half', + 'Class:NetworkInterface/Attribute:duplex/Value:half+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown' => 'Unknown', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown+' => '', + 'Class:NetworkInterface/Attribute:connected_if' => 'Connected to', + 'Class:NetworkInterface/Attribute:connected_if+' => 'Connected interface', + 'Class:NetworkInterface/Attribute:connected_name' => 'Connected to', + 'Class:NetworkInterface/Attribute:connected_name+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id' => 'Connected device', + 'Class:NetworkInterface/Attribute:connected_if_device_id+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name' => 'Device', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name+' => '', + 'Class:NetworkInterface/Attribute:link_type' => 'Link type', + 'Class:NetworkInterface/Attribute:link_type+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink' => 'Down link', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink' => 'Up link', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink+' => '', +)); + + // // Class: Device @@ -955,5 +974,14 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:MobilePhone+' => 'All Mobile Phones', 'Menu:PC' => 'Personal Computers', 'Menu:PC+' => 'All Personal Computers', +'Menu:NewContact' => 'New Contact', +'Menu:NewContact+' => 'New Contact', +'Menu:SearchContacts' => 'Search for contacts', +'Menu:SearchContacts+' => 'Search for contacts', +'Menu:NewCI' => 'New CI', +'Menu:NewCI+' => 'New CI', +'Menu:SearchCIs' => 'Search for CIs', +'Menu:SearchCIs+' => 'Search for CIs', + )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index cbfb8fa8bf..daefa36f49 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -164,7 +164,7 @@ class Person extends Contact MetaModel::Init_AddAttribute(new AttributeString("first_name", array("allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("employee_id", array("allowed_values"=>null, "sql"=>"employee_id", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('name','first_name', 'org_id', 'status', 'location_id', 'email', 'phone', 'employee_id', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'team_list')); + MetaModel::Init_SetZListItems('details', array('name','first_name', 'org_id', 'status', 'location_id', 'email', 'phone', 'employee_id','team_list', 'contract_list', 'service_list', 'ticket_list', 'ci_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'email', 'phone', 'location_id', 'first_name', 'employee_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'email', 'phone', 'location_id', 'first_name', 'employee_id')); MetaModel::Init_SetZListItems('list', array('first_name','status', 'org_id', 'email', 'phone', 'location_id')); @@ -1048,6 +1048,24 @@ class NetworkInterface extends ConnectableCI { return $this->Get('device_name').' - '.$this->Get('name'); } + + + + public static function GetRelationQueries($sRelCode) + { + switch ($sRelCode) + { + case "impacts": + $aRels = array( + "connected_devices" => array("sQuery"=>"SELECT Device AS dev JOIN NetworkInterface AS if1 ON if1.device_id = dev.id JOIN NetworkInterface AS if2 ON if2.connected_if = if1.id WHERE if2.id = :this->id AND if2.link_type='downlink'", "bPropagate"=>true, "iDistance"=>5), + ); + return array_merge($aRels, parent::GetRelationQueries($sRelCode)); + break; + + default: + return parent::GetRelationQueries($sRelCode); + } + } } abstract class Device extends ConnectableCI { diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index 2db0f4a156..a2b2133c37 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -43,9 +43,9 @@ class Incident extends ResponseTicket MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritLifecycle(); - MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); } @@ -77,6 +77,7 @@ class Incident extends ResponseTicket { $oNewLink = new lnkTicketToCI(); $oNewLink->Set('ci_id', $iKey); + $oNewLink->Set('impact', 'potentially impacted (automatically computed)'); $oToImpact->AddObject($oNewLink); } } @@ -94,6 +95,39 @@ class Incident extends ResponseTicket } } +class lnkTicketToIncident extends cmdbAbstractObject +{ + + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable,incidentmgmt,requestmgmt", + "key_type" => "autoincrement", + "name_attcode" => "ticket_id", + "state_attcode" => "", + "reconc_keys" => array("ticket_id","incident_id"), + "db_table" => "lnktickettoincident", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"Ticket", "jointype"=>null, "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_ref", array("allowed_values"=>null, "extkey_attcode"=>"ticket_id", "target_attcode"=>"ref", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("incident_id", array("targetclass"=>"Incident", "jointype"=>null, "allowed_values"=>null, "sql"=>"incident_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("incident_ref", array("allowed_values"=>null, "extkey_attcode"=>"incident_id", "target_attcode"=>"ref", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_SetZListItems('details', array('ticket_id', 'incident_id','reason')); + MetaModel::Init_SetZListItems('advanced_search', array('ticket_id', 'incident_id')); + MetaModel::Init_SetZListItems('standard_search', array('ticket_id', 'incident_id')); + MetaModel::Init_SetZListItems('list', array('ticket_id', 'incident_id','reason')); + + } +} + + $oMyMenuGroup = new MenuGroup('IncidentManagement', 40 /* fRank */); new TemplateMenuNode('Incident:Overview', '../modules/itop-incident-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); new NewObjectMenuNode('NewIncident', 'Incident', $oMyMenuGroup->GetIndex(), 1 /* fRank */); diff --git a/modules/itop-knownerror-mgmt-1.0.0/en.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/en.dict.itop-knownerror-mgmt.php index c24bd674be..e16258e124 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/en.dict.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/en.dict.itop-knownerror-mgmt.php @@ -48,4 +48,95 @@ // Class:/Stimulus: // Class:/Stimulus:+ +// +// Class: KnownError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:KnownError' => 'Known Error', + 'Class:KnownError+' => 'Error documented for a known issue', + 'Class:KnownError/Attribute:name' => 'Name', + 'Class:KnownError/Attribute:name+' => '', + 'Class:KnownError/Attribute:org_id' => 'Customer', + 'Class:KnownError/Attribute:org_id+' => '', + 'Class:KnownError/Attribute:cust_name' => 'Customer Name', + 'Class:KnownError/Attribute:cust_name+' => '', + 'Class:KnownError/Attribute:problem_id' => 'Related Problem', + 'Class:KnownError/Attribute:problem_id+' => '', + 'Class:KnownError/Attribute:problem_ref' => 'Ref', + 'Class:KnownError/Attribute:problem_ref+' => '', + 'Class:KnownError/Attribute:symptom' => 'Symptom', + 'Class:KnownError/Attribute:symptom+' => '', + 'Class:KnownError/Attribute:root_cause' => 'Root Cause', + 'Class:KnownError/Attribute:root_cause+' => '', + 'Class:KnownError/Attribute:workaround' => 'Workaround', + 'Class:KnownError/Attribute:workaround+' => '', + 'Class:KnownError/Attribute:solution' => 'Solution', + 'Class:KnownError/Attribute:solution+' => '', + 'Class:KnownError/Attribute:error_code' => 'Error Code', + 'Class:KnownError/Attribute:error_code+' => '', + 'Class:KnownError/Attribute:domain' => 'Domain', + 'Class:KnownError/Attribute:domain+' => '', + 'Class:KnownError/Attribute:domain/Value:Application' => 'Application', + 'Class:KnownError/Attribute:domain/Value:Application+' => 'Application', + 'Class:KnownError/Attribute:domain/Value:Desktop' => 'Desktop', + 'Class:KnownError/Attribute:domain/Value:Desktop+' => 'Desktop', + 'Class:KnownError/Attribute:domain/Value:Network' => 'Network', + 'Class:KnownError/Attribute:domain/Value:Network+' => 'Network', + 'Class:KnownError/Attribute:domain/Value:Server' => 'Server', + 'Class:KnownError/Attribute:domain/Value:Server+' => 'Server', + 'Class:KnownError/Attribute:vendor' => 'Vendor', + 'Class:KnownError/Attribute:vendor+' => '', + 'Class:KnownError/Attribute:model' => 'Model', + 'Class:KnownError/Attribute:model+' => '', + 'Class:KnownError/Attribute:version' => 'Version', + 'Class:KnownError/Attribute:version+' => '', + 'Class:KnownError/Attribute:ci_list' => 'CIs', + 'Class:KnownError/Attribute:ci_list+' => '', + 'Class:KnownError/Attribute:document_list' => 'Documents', + 'Class:KnownError/Attribute:document_list+' => '', +)); + + +// +// Class: lnkInfraError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraError' => 'InfraErrorLinks', + 'Class:lnkInfraError+' => 'Infra related to a known error', + 'Class:lnkInfraError/Attribute:infra_id' => 'CI', + 'Class:lnkInfraError/Attribute:infra_id+' => '', + 'Class:lnkInfraError/Attribute:infra_name' => 'CI Name', + 'Class:lnkInfraError/Attribute:infra_name+' => '', + 'Class:lnkInfraError/Attribute:infra_status' => 'CI Status', + 'Class:lnkInfraError/Attribute:infra_status+' => '', + 'Class:lnkInfraError/Attribute:error_id' => 'Error', + 'Class:lnkInfraError/Attribute:error_id+' => '', + 'Class:lnkInfraError/Attribute:error_name' => 'Error name', + 'Class:lnkInfraError/Attribute:error_name+' => '', + 'Class:lnkInfraError/Attribute:reason' => 'Reason', + 'Class:lnkInfraError/Attribute:reason+' => '', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkDocumentError' => 'DocumentsErrorLinks', + 'Class:lnkDocumentError+' => 'A link between a document and a known error', + 'Class:lnkDocumentError/Attribute:doc_id' => 'Document', + 'Class:lnkDocumentError/Attribute:doc_id+' => '', + 'Class:lnkDocumentError/Attribute:doc_name' => 'Document Name', + 'Class:lnkDocumentError/Attribute:doc_name+' => '', + 'Class:lnkDocumentError/Attribute:error_id' => 'Error', + 'Class:lnkDocumentError/Attribute:error_id+' => '', + 'Class:lnkDocumentError/Attribute:error_name' => 'Error Name', + 'Class:lnkDocumentError/Attribute:error_name+' => '', + 'Class:lnkDocumentError/Attribute:link_type' => 'Information', + 'Class:lnkDocumentError/Attribute:link_type+' => '', +)); + + ?> diff --git a/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php index 8b2dd15d08..96965b936e 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php @@ -23,4 +23,148 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ +//////////////////////////////////////////////////////////////////////////////////// +/** +* Description of known error +*/ +//////////////////////////////////////////////////////////////////////////////////// +class KnownError extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "Known Error", + "description" => "Error documented for a known issue", + "key_type" => "autoincrement", + "key_label" => "id", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("org_id", "name"), // inherited attributes + "db_table" => "known_error", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "allowed_values"=>null, "sql"=>"cust_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("cust_name", array("allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("problem_id", array("targetclass"=>"Problem", "allowed_values"=>null, "sql"=>"problem_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("problem_ref", array("allowed_values"=>null, "extkey_attcode"=> 'problem_id', "target_attcode"=>"ref"))); + MetaModel::Init_AddAttribute(new AttributeText("symptom", array("allowed_values"=>null, "sql"=>"symptom", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("root_cause", array("allowed_values"=>null, "sql"=>"rootcause", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("workaround", array("allowed_values"=>null, "sql"=>"workaround", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("solution", array("allowed_values"=>null, "sql"=>"solution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeString("error_code", array("allowed_values"=>null, "sql"=>"error_code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("domain", array("allowed_values"=>new ValueSetEnum("Network, Server, Application, Desktop"), "sql"=>"domain", "default_value"=>"Application", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("vendor", array("allowed_values"=>null, "sql"=>"vendor", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("model", array("allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkInfraError", "ext_key_to_me"=>"error_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("document_list", array("linked_class"=>"lnkDocumentError", "ext_key_to_me"=>"error_id", "ext_key_to_remote"=>"doc_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + + + + + MetaModel::Init_SetZListItems('details', array('name', 'org_id','problem_id','error_code','domain','vendor','model','version', 'symptom','root_cause','workaround','solution','ci_list','document_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('org_id','problem_id','error_code', 'symptom')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id','problem_id','error_code','domain','symptom')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id','problem_id', 'error_code','domain','symptom')); // Criteria of the advanced search form + + } +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Infra and a Known Error +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkInfraError extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "InfraErrorLinks", + "description" => "Infra related to a known error", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "", // ???? + "state_attcode" => "", + "reconc_keys" => array("infra_id","error_id"), // ???? + "db_table" => "infra_error_links", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"FunctionalCI", "jointype"=> '',"allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"KnownError", "jointype"=> '', "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array( "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"dummy", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + + // Display lists + MetaModel::Init_SetZListItems('details', array('infra_id', 'error_id','reason')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('infra_id', 'infra_status','error_id','reason')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'error_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'error_id')); // Criteria of the advanced search form + } + + +} + +//////////////////////////////////////////////////////////////////////////////////// +/** +* n-n link between any Contract and a Document +*/ +//////////////////////////////////////////////////////////////////////////////////// +class lnkDocumentError extends cmdbAbstractObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable", + "name" => "DocumentsErrorLinks", + "description" => "A link between a document and a known error", + "key_type" => "autoincrement", + "key_label" => "link_id", + "name_attcode" => "link_type", + "state_attcode" => "", + "reconc_keys" => array("doc_id", "error_id"), + "db_table" => "documents_error_link", + "db_key_field" => "link_id", + "db_finalclass_field" => "", + "display_template" => "../business/templates/default.html", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"Document","allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"KnownError","allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeString("link_type", array("allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + + // Display lists + MetaModel::Init_SetZListItems('details', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for a list + } +} + + +$oMyMenuGroup = new MenuGroup('ProblemManagement', 42 /* fRank */); +new OQLMenuNode('Problem:KnownErrors', 'SELECT KnownError', $oMyMenuGroup->GetIndex(), 3 /* fRank */); + ?> diff --git a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php index 50c43aeaa4..3414805fa4 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php @@ -27,7 +27,6 @@ SetupWebPage::AddModule( ), 'dictionary' => array( 'en.dict.itop-knownerror-mgmt.php', - 'es_cr.dict.itop-knownerror-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-knownerror-mgmt.xml', diff --git a/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php index c24bd674be..b9fd7481d4 100644 --- a/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php @@ -48,4 +48,113 @@ // Class:/Stimulus: // Class:/Stimulus:+ + + + +Dict::Add('EN US', 'English', 'English', array( + 'Menu:ProblemManagement' => 'Problem Management', + 'Menu:ProblemManagement+' => 'Problem Management', + 'Menu:Problem:Overview' => 'Overview', + 'Menu:Problem:Overview+' => 'Overview', + 'Menu:NewProblem' => 'New Problem', + 'Menu:NewProblem+' => 'New Problem', + 'Menu:SearchProblems' => 'Search for Problems', + 'Menu:SearchProblems+' => 'Search for Problems', + 'Menu:Problem:KnownErrors' => 'All Known Errors', + 'Menu:Problem:KnownErrors+' => 'All Known Errors', + 'Menu:Problem:Shortcuts' => 'Shortcuts', + 'Menu:Problem:MyProblems' => 'My Problems', + 'Menu:Problem:MyProblems+' => 'My Problems', + 'Menu:Problem:OpenProblems' => 'All Open problems', + 'Menu:Problem:OpenProblems+' => 'All Open problems', +)); +// +// Class: Problem +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:Problem' => 'Problem', + 'Class:Problem+' => '', + 'Class:Problem/Attribute:status' => 'Status', + 'Class:Problem/Attribute:status+' => '', + 'Class:Problem/Attribute:status/Value:new' => 'New', + 'Class:Problem/Attribute:status/Value:new+' => '', + 'Class:Problem/Attribute:status/Value:assigned' => 'Assigned', + 'Class:Problem/Attribute:status/Value:assigned+' => '', + 'Class:Problem/Attribute:status/Value:resolved' => 'Resolved', + 'Class:Problem/Attribute:status/Value:resolved+' => '', + 'Class:Problem/Attribute:status/Value:closed' => 'Closed', + 'Class:Problem/Attribute:status/Value:closed+' => '', + 'Class:Problem/Attribute:org_id' => 'Customer', + 'Class:Problem/Attribute:org_id+' => '', + 'Class:Problem/Attribute:org_name' => 'Name', + 'Class:Problem/Attribute:org_name+' => 'Common name', + 'Class:Problem/Attribute:service_id' => 'Service', + 'Class:Problem/Attribute:service_id+' => '', + 'Class:Problem/Attribute:service_name' => 'Name', + 'Class:Problem/Attribute:service_name+' => '', + 'Class:Problem/Attribute:servicesubcategory_id' => 'Service Category', + 'Class:Problem/Attribute:servicesubcategory_id+' => '', + 'Class:Problem/Attribute:servicesubcategory_name' => 'Name', + 'Class:Problem/Attribute:servicesubcategory_name+' => '', + 'Class:Problem/Attribute:product' => 'Product', + 'Class:Problem/Attribute:product+' => '', + 'Class:Problem/Attribute:impact' => 'Impact', + 'Class:Problem/Attribute:impact+' => '', + 'Class:Problem/Attribute:impact/Value:1' => 'A Person', + 'Class:Problem/Attribute:impact/Value:1+' => '', + 'Class:Problem/Attribute:impact/Value:2' => 'A Service', + 'Class:Problem/Attribute:impact/Value:2+' => '', + 'Class:Problem/Attribute:impact/Value:3' => 'A Department', + 'Class:Problem/Attribute:impact/Value:3+' => '', + 'Class:Problem/Attribute:urgency' => 'urgency', + 'Class:Problem/Attribute:urgency+' => '', + 'Class:Problem/Attribute:urgency/Value:1' => 'Low', + 'Class:Problem/Attribute:urgency/Value:1+' => 'Low', + 'Class:Problem/Attribute:urgency/Value:2' => 'Medium', + 'Class:Problem/Attribute:urgency/Value:2+' => 'Medium', + 'Class:Problem/Attribute:urgency/Value:3' => 'High', + 'Class:Problem/Attribute:urgency/Value:3+' => 'High', + 'Class:Problem/Attribute:priority' => 'priority', + 'Class:Problem/Attribute:priority+' => '', + 'Class:Problem/Attribute:priority/Value:1' => 'Low', + 'Class:Problem/Attribute:priority/Value:1+' => '', + 'Class:Problem/Attribute:priority/Value:2' => 'Medium', + 'Class:Problem/Attribute:priority/Value:2+' => '', + 'Class:Problem/Attribute:priority/Value:3' => 'High', + 'Class:Problem/Attribute:priority/Value:3+' => '', + 'Class:Problem/Attribute:workgroup_id' => 'WorkGroup', + 'Class:Problem/Attribute:workgroup_id+' => '', + 'Class:Problem/Attribute:workgroup_name' => 'Name', + 'Class:Problem/Attribute:workgroup_name+' => '', + 'Class:Problem/Attribute:agent_id' => 'Agent', + 'Class:Problem/Attribute:agent_id+' => '', + 'Class:Problem/Attribute:agent_name' => 'Agent Name', + 'Class:Problem/Attribute:agent_name+' => '', + 'Class:Problem/Attribute:agent_email' => 'Agent Email', + 'Class:Problem/Attribute:agent_email+' => '', + 'Class:Problem/Attribute:related_change_id' => 'Related Change', + 'Class:Problem/Attribute:related_change_id+' => '', + 'Class:Problem/Attribute:related_change_ref' => 'Ref', + 'Class:Problem/Attribute:related_change_ref+' => '', + 'Class:Problem/Attribute:close_date' => 'Close Date', + 'Class:Problem/Attribute:close_date+' => '', + 'Class:Problem/Attribute:last_update' => 'Last Update', + 'Class:Problem/Attribute:last_update+' => '', + 'Class:Problem/Attribute:assignment_date' => 'Assignment Date', + 'Class:Problem/Attribute:assignment_date+' => '', + 'Class:Problem/Attribute:resolution_date' => 'Resolution Date', + 'Class:Problem/Attribute:resolution_date+' => '', + 'Class:Problem/Attribute:knownerrors_list' => 'Known Errors', + 'Class:Problem/Attribute:knownerrors_list+' => '', + 'Class:Problem/Stimulus:ev_assign' => 'Assign', + 'Class:Problem/Stimulus:ev_assign+' => '', + 'Class:Problem/Stimulus:ev_reassign' => 'Reaassign', + 'Class:Problem/Stimulus:ev_reassign+' => '', + 'Class:Problem/Stimulus:ev_resolve' => 'Resolve', + 'Class:Problem/Stimulus:ev_resolve+' => '', + 'Class:Problem/Stimulus:ev_close' => 'Close', + 'Class:Problem/Stimulus:ev_close+' => '', +)); + ?> diff --git a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php index 8b2dd15d08..753e687187 100644 --- a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php @@ -23,4 +23,213 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ +class Problem extends Ticket +{ + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable,problemmgmt", + "key_type" => "autoincrement", + "name_attcode" => "ref", + "state_attcode" => "status", + "reconc_keys" => array("ref"), + "db_table" => "ticket_problem", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); +// MetaModel::Init_InheritLifecycle(); + + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,assigned,resolved,closed'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"Service", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Service AS s JOIN SLA AS sla ON sla.service_id=s.id JOIN lnkContractToSLA AS ln ON ln.sla_id=sla.id JOIN CustomerContract AS cc ON ln.contract_id=cc.id WHERE cc.org_id =:this->org_id'), "sql"=>"service_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("allowed_values"=>null, "extkey_attcode"=>"service_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("servicesubcategory_id", array("targetclass"=>"ServiceSubcategory", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT ServiceSubcategory WHERE service_id = :this->service_id'), "sql"=>"servicesubcategory_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("service_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("servicesubcategory_name", array("allowed_values"=>null, "extkey_attcode"=>"servicesubcategory_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("product", array("allowed_values"=>null, "sql"=>"product", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("impact", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"impact", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("urgency", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"urgency", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("priority", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"priority", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"Team", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Team AS t JOIN CustomerContract AS cc ON cc.support_team_id=t.id JOIN lnkContractToSLA AS ln ON ln.contract_id=cc.id JOIN SLA AS sla ON ln.sla_id=sla.id WHERE sla.service_id = :this->service_id AND cc.org_id = :this->org_id'), "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id","service_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=>"workgroup_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"Person", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Person AS p JOIN lnkTeamToContact AS l ON l.contact_id=p.id JOIN Team AS t ON l.team_id=t.id WHERE t.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_name", array("allowed_values"=>null, "extkey_attcode"=>"agent_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_email", array("allowed_values"=>null, "extkey_attcode"=>"agent_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("related_change_id", array("targetclass"=>"Change", "jointype"=>null, "allowed_values"=>null, "sql"=>"related_change_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("related_change_ref", array("allowed_values"=>null, "extkey_attcode"=>"related_change_id", "target_attcode"=>"ref", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("close_date", array("allowed_values"=>null, "sql"=>"close_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("assignment_date", array("allowed_values"=>null, "sql"=>"assignment_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("resolution_date", array("allowed_values"=>null, "sql"=>"resolution_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSet("knownerrors_list", array("linked_class"=>"KnownError", "ext_key_to_me"=>"problem_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + + + MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date','knownerrors_list', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'service_id', 'servicesubcategory_id','product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date', 'last_update', 'assignment_date')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date')); + MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority')); + + // Lifecycle + MetaModel::Init_DefineState( + "new", + array( + "attribute_inherit" => null, + "attribute_list" => array( + 'ref' => OPT_ATT_READONLY, + 'ticket_log' => OPT_ATT_HIDDEN, + 'related_change_id' => OPT_ATT_HIDDEN, + 'description' => OPT_ATT_MUSTCHANGE, + 'contact_list' => OPT_ATT_READONLY, + 'start_date' => OPT_ATT_READONLY, + 'last_update' => OPT_ATT_READONLY, + 'assignment_date' => OPT_ATT_HIDDEN, + 'resolution_date' => OPT_ATT_HIDDEN, + 'close_date' => OPT_ATT_HIDDEN, + 'org_id' => OPT_ATT_MUSTCHANGE, + 'service_id' => OPT_ATT_MUSTCHANGE, + 'servicesubcategory_id' => OPT_ATT_MUSTCHANGE, + 'product' => OPT_ATT_MUSTPROMPT, + 'impact' => OPT_ATT_MUSTCHANGE, + 'urgency' => OPT_ATT_MUSTCHANGE, + 'priority' => OPT_ATT_READONLY, + 'workgroup_id' => OPT_ATT_MUSTCHANGE, + 'agent_id' => OPT_ATT_HIDDEN, + 'agent_email' => OPT_ATT_HIDDEN, + ), + ) + ); + MetaModel::Init_DefineState( + "assigned", + array( + "attribute_inherit" => 'new', + "attribute_list" => array( + 'title' => OPT_ATT_READONLY, + 'org_id' => OPT_ATT_READONLY, + 'ticket_log' => OPT_ATT_NORMAL, + 'description' => OPT_ATT_READONLY, + 'agent_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, + 'agent_email' => OPT_ATT_READONLY, + 'workgroup_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, +// 'related_change_id' => OPT_ATT_NORMAL, + ), + ) + ); + MetaModel::Init_DefineState( + "resolved", + array( + "attribute_inherit" => 'assigned', + "attribute_list" => array( + 'service_id' => OPT_ATT_READONLY, + 'servicesubcategory_id' => OPT_ATT_READONLY, + 'product' => OPT_ATT_READONLY, + 'impact' => OPT_ATT_READONLY, + 'workgroup_id' => OPT_ATT_READONLY, + 'agent_id' => OPT_ATT_READONLY, + 'urgency' => OPT_ATT_READONLY, + ), + ) + ); + MetaModel::Init_DefineState( + "closed", + array( + "attribute_inherit" => 'resolved', + "attribute_list" => array( + 'ticket_log' => OPT_ATT_READONLY, + 'close_date' => OPT_ATT_READONLY, + ), + ) + ); + + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_assign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reassign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_resolve", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_close", array())); + + MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array('SetAssignedDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("assigned", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("assigned", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetResolveDate'), "user_restriction"=>null)); + + MetaModel::Init_DefineTransition("resolved", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("resolved", "ev_close", array("target_state"=>"closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); + } + + // Lifecycle actions + // + public function SetAssignedDate($sStimulusCode) + { + $this->Set('assignment_date', time()); + return true; + } + public function SetResolveDate($sStimulusCode) + { + $this->Set('resolution_date', time()); + return true; + } + public function SetClosureDate($sStimulusCode) + { + $this->Set('close_date', time()); + return true; + } + + /** Compute the priority of the ticket based on its impact and urgency + * @return integer The priority of the ticket 1(high) .. 3(low) + */ + public function ComputePriority() + { + // priority[impact][urgency] + $aPriorities = array( + // single person + 1 => array( + 1 => 1, + 2 => 1, + 3 => 2, + ), + // a group + 2 => array( + 1 => 1, + 2 => 2, + 3 => 3, + ), + // a departement! + 3 => array( + 1 => 2, + 2 => 3, + 3 => 3, + ), + ); + $iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')]; + return $iPriority; + } + + + public function ComputeValues() + { + // Compute the priority of the ticket + $this->Set('priority', $this->ComputePriority()); + + $iKey = $this->GetKey(); + if ($iKey < 0) + { + // Object not yet in the Database + $iKey = MetaModel::GetNextKey(get_class($this)); + } + $sName = sprintf('P-%06d', $iKey); + $this->Set('ref', $sName); + } + +} + + +$iIndex = ApplicationMenu::GetMenuIndexById('ProblemManagement'); +new TemplateMenuNode('Problem:Overview', '../modules/itop-problem-mgmt-1.0.0/overview.html', $iIndex /* oParent */, 0 /* fRank */); +new NewObjectMenuNode('NewProblem', 'Problem', $iIndex, 1 /* fRank */); +new SearchMenuNode('SearchProblems', 'Problem', $iIndex, 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('Problem:Shortcuts', '', $iIndex, 4 /* fRank */); +new OQLMenuNode('Problem:MyProblems', 'SELECT Problem WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('Problem:OpenProblems', 'SELECT Problem WHERE status IN ("new", "assigned", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); + ?> diff --git a/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php index e62a916050..8ac76994c3 100644 --- a/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php @@ -56,23 +56,32 @@ Dict::Add('EN US', 'English', 'English', array( // Class: UserRequest // -Dict::Add('EN US', 'English', 'English', array( - 'Class:UserRequest' => 'User Request', - 'Class:UserRequest+' => '', - 'Class:UserRequest/Attribute:freeze_reason' => 'Pending reason', - 'Class:UserRequest/Attribute:freeze_reason+' => '', - 'Class:UserRequest/Stimulus:ev_assign' => 'Assign', - 'Class:UserRequest/Stimulus:ev_assign+' => '', - 'Class:UserRequest/Stimulus:ev_freeze' => 'Mark as pending', - 'Class:UserRequest/Stimulus:ev_freeze+' => '', - 'Class:UserRequest/Stimulus:ev_reassign' => 'Reassign', - 'Class:UserRequest/Stimulus:ev_reassign+' => '', - 'Class:UserRequest/Stimulus:ev_timeout' => 'ev_timeout', - 'Class:UserRequest/Stimulus:ev_timeout+' => '', - 'Class:UserRequest/Stimulus:ev_resolve' => 'Mark as resolved', - 'Class:UserRequest/Stimulus:ev_resolve+' => '', - 'Class:UserRequest/Stimulus:ev_close' => 'Close', - 'Class:UserRequest/Stimulus:ev_close+' => '', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:UserRequest' => 'User Request', + 'Class:UserRequest+' => '', + 'Class:UserRequest/Attribute:request_type' => 'Request Type', + 'Class:UserRequest/Attribute:request_type+' => '', + 'Class:UserRequest/Attribute:request_type/Value:information' => 'Information', + 'Class:UserRequest/Attribute:request_type/Value:information+' => 'Information', + 'Class:UserRequest/Attribute:request_type/Value:issue' => 'Issue', + 'Class:UserRequest/Attribute:request_type/Value:issue+' => 'Issue', + 'Class:UserRequest/Attribute:request_type/Value:service request' => 'Service Request', + 'Class:UserRequest/Attribute:request_type/Value:service request+' => 'Service Request', + 'Class:UserRequest/Attribute:freeze_reason' => 'Pending reason', + 'Class:UserRequest/Attribute:freeze_reason+' => '', + 'Class:UserRequest/Stimulus:ev_assign' => 'Assign', + 'Class:UserRequest/Stimulus:ev_assign+' => '', + 'Class:UserRequest/Stimulus:ev_reassign' => 'Reassign', + 'Class:UserRequest/Stimulus:ev_reassign+' => '', + 'Class:UserRequest/Stimulus:ev_timeout' => 'ev_timeout', + 'Class:UserRequest/Stimulus:ev_timeout+' => '', + 'Class:UserRequest/Stimulus:ev_resolve' => 'Mark as resolved', + 'Class:UserRequest/Stimulus:ev_resolve+' => '', + 'Class:UserRequest/Stimulus:ev_close' => 'Close', + 'Class:UserRequest/Stimulus:ev_close+' => '', + 'Class:UserRequest/Stimulus:ev_freeze' => 'Mark as pending', + 'Class:UserRequest/Stimulus:ev_freeze+' => '', +)); + ?> diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index 8f6602881b..555d199e6d 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -43,22 +43,25 @@ class UserRequest extends ResponseTicket MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritLifecycle(); + MetaModel::Init_AddAttribute(new AttributeEnum("request_type", array("allowed_values"=>new ValueSetEnum('service request,issue,information'), "sql"=>"request_type", "default_value"=>"service request", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("freeze_reason", array("allowed_values"=>null, "sql"=>"freeze_reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment', 'freeze_reason')); - MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'request_type','ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment', 'freeze_reason')); + MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'request_type','start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); MetaModel::Init_OverloadStateAttribute('frozen', 'freeze_reason', OPT_ATT_MANDATORY); // The freeze reason remains hidden in all other states + MetaModel::Init_OverloadStateAttribute('new', 'request_type', OPT_ATT_MANDATORY); MetaModel::Init_OverloadStateAttribute('new', 'freeze_reason', OPT_ATT_HIDDEN); MetaModel::Init_OverloadStateAttribute('assigned', 'freeze_reason', OPT_ATT_HIDDEN); MetaModel::Init_OverloadStateAttribute('frozen', 'freeze_reason', OPT_ATT_MANDATORY | OPT_ATT_MUSTPROMPT); MetaModel::Init_OverloadStateAttribute('escalated_tto', 'freeze_reason', OPT_ATT_HIDDEN); MetaModel::Init_OverloadStateAttribute('escalated_ttr', 'freeze_reason', OPT_ATT_HIDDEN); MetaModel::Init_OverloadStateAttribute('resolved', 'freeze_reason', OPT_ATT_HIDDEN); + MetaModel::Init_OverloadStateAttribute('closed', 'request_type', OPT_ATT_READONLY); MetaModel::Init_OverloadStateAttribute('closed', 'freeze_reason', OPT_ATT_HIDDEN); MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_freeze", array())); diff --git a/modules/itop-service-mgmt-1.0.0/en.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/en.dict.itop-service-mgmt.php index 1328dfd18d..9685e7ecfa 100644 --- a/modules/itop-service-mgmt-1.0.0/en.dict.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/en.dict.itop-service-mgmt.php @@ -121,7 +121,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:ProviderContract/Attribute:provider_name+' => '', 'Class:ProviderContract/Attribute:sla' => 'SLA', 'Class:ProviderContract/Attribute:sla+' => 'Service Level Agreement', - 'Class:ProviderContract/Attribute:coverage' => 'Coverage', + 'Class:ProviderContract/Attribute:coverage' => 'Service hours', 'Class:ProviderContract/Attribute:coverage+' => '', )); @@ -148,7 +148,30 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:CustomerContract/Attribute:provider_list+' => '', 'Class:CustomerContract/Attribute:sla_list' => 'SLAs', 'Class:CustomerContract/Attribute:sla_list+' => 'List of SLA related to the contract', + 'Class:CustomerContract/Attribute:provider_list' => 'Underpinning Contracts', + 'Class:CustomerContract/Attribute:sla_list+' => '', )); +// +// Class: lnkCustomerContractToProviderContract +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkCustomerContractToProviderContract' => 'lnkCustomerContractToProviderContract', + 'Class:lnkCustomerContractToProviderContract+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id' => 'Customer Contract', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name' => 'Name', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id' => 'Provider Contract', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name' => 'Name', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla' => 'Provider SLA', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla+' => 'Service Level Agreement', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage' => 'Service hours', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage+' => '', +)); + // // Class: lnkContractToSLA @@ -165,7 +188,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:lnkContractToSLA/Attribute:sla_id+' => '', 'Class:lnkContractToSLA/Attribute:sla_name' => 'SLA', 'Class:lnkContractToSLA/Attribute:sla_name+' => '', - 'Class:lnkContractToSLA/Attribute:coverage' => 'Coverage', + 'Class:lnkContractToSLA/Attribute:coverage' => 'Service Hours', 'Class:lnkContractToSLA/Attribute:coverage+' => '', )); @@ -267,6 +290,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Service/Attribute:document_list+' => 'Documents attached to the service', 'Class:Service/Attribute:contact_list' => 'Contacts', 'Class:Service/Attribute:contact_list+' => 'Contacts having a role for this service', + 'Class:Service/Tab:Related_Contracts' => 'Related Contracts', + 'Class:Service/Tab:Related_Contracts+' => 'Contracts signed for this service', )); // diff --git a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php index 8e0595875c..f0b7542ac6 100644 --- a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php @@ -73,7 +73,7 @@ class ProviderContract extends Contract "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","provider_id"), "db_table" => "providercontract", "db_key_field" => "id", "db_finalclass_field" => "", @@ -104,7 +104,7 @@ class CustomerContract extends Contract "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id","provider_id"), "db_table" => "customercontract", "db_key_field" => "id", "db_finalclass_field" => "", @@ -120,13 +120,47 @@ class CustomerContract extends Contract MetaModel::Init_AddAttribute(new AttributeExternalKey("support_team_id", array("targetclass"=>"Team", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Team WHERE Team.org_id = :this->provider_id'), "sql"=>"support_team_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array('provider_id')))); MetaModel::Init_AddAttribute(new AttributeExternalField("support_team_name", array("allowed_values"=>null, "extkey_attcode"=>"support_team_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("sla_list", array("linked_class"=>"lnkContractToSLA", "ext_key_to_me"=>"contract_id", "ext_key_to_remote"=>"sla_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("provider_list", array("linked_class"=>"lnkCustomerContractToProviderContract", "ext_key_to_me"=>"customer_contract_id", "ext_key_to_remote"=>"provider_contract_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'contact_list', 'document_list', 'ci_list', 'provider_id', 'support_team_id', 'sla_list')); + MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'contact_list', 'document_list', 'ci_list', 'provider_list','provider_id', 'support_team_id', 'sla_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'org_id', 'support_team_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'org_id', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'provider_id', 'support_team_id')); MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'start_date', 'end_date', 'provider_id', 'support_team_id')); } } +class lnkCustomerContractToProviderContract extends cmdbAbstractObject +{ + + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable,servicemgmt", + "key_type" => "autoincrement", + "name_attcode" => "customer_contract_id", + "state_attcode" => "", + "reconc_keys" => array("customer_contract_id","provider_contract_id"), + "db_table" => "lnkcustomercontracttoprovider", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_contract_id", array("targetclass"=>"CustomerContract", "jointype"=>null, "allowed_values"=>null, "sql"=>"customer_contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("customer_contract_name", array("allowed_values"=>null, "extkey_attcode"=>"customer_contract_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_contract_id", array("targetclass"=>"ProviderContract", "jointype"=>null, "allowed_values"=>null, "sql"=>"provider_contract_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("provider_contract_name", array("allowed_values"=>null, "extkey_attcode"=>"provider_contract_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("provider_sla", array("allowed_values"=>null, "extkey_attcode"=>"provider_contract_id", "target_attcode"=>"sla", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("provider_coverage", array("allowed_values"=>null, "extkey_attcode"=>"provider_contract_id", "target_attcode"=>"coverage", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_SetZListItems('details', array('customer_contract_id', 'provider_contract_id','provider_sla','provider_coverage')); + MetaModel::Init_SetZListItems('advanced_search', array('customer_contract_id', 'provider_contract_id')); + MetaModel::Init_SetZListItems('standard_search', array('customer_contract_id', 'provider_contract_id')); + MetaModel::Init_SetZListItems('list', array('customer_contract_id', 'provider_contract_id','provider_sla','provider_coverage')); + } +} class lnkContractToSLA extends cmdbAbstractObject { @@ -138,7 +172,7 @@ class lnkContractToSLA extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "contract_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("contract_id","sla_id"), "db_table" => "lnkcontracttosla", "db_key_field" => "id", "db_finalclass_field" => "", @@ -151,12 +185,13 @@ class lnkContractToSLA extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("allowed_values"=>null, "extkey_attcode"=>"contract_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("sla_id", array("targetclass"=>"SLA", "jointype"=>null, "allowed_values"=>null, "sql"=>"sla_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("sla_name", array("allowed_values"=>null, "extkey_attcode"=>"sla_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("sla_service_name", array("allowed_values"=>null, "extkey_attcode"=>"sla_id", "target_attcode"=>"service_name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("coverage", array("allowed_values"=>null, "sql"=>"coverage", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('contract_id', 'sla_id', 'coverage')); + MetaModel::Init_SetZListItems('details', array('contract_id', 'sla_id','sla_service_name', 'coverage')); MetaModel::Init_SetZListItems('advanced_search', array('contract_id', 'sla_id', 'coverage')); MetaModel::Init_SetZListItems('standard_search', array('contract_id', 'sla_id', 'coverage')); - MetaModel::Init_SetZListItems('list', array('contract_id', 'sla_id', 'coverage')); + MetaModel::Init_SetZListItems('list', array('contract_id', 'sla_id', 'sla_service_name','coverage')); } } class lnkContractToDoc extends cmdbAbstractObject @@ -170,7 +205,7 @@ class lnkContractToDoc extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "contract_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("contract_id","document_id"), "db_table" => "lnkcontracttodoc", "db_key_field" => "id", "db_finalclass_field" => "", @@ -203,7 +238,7 @@ class lnkContractToContact extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "contract_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("contract_id","contact_id"), "db_table" => "lnkcontracttocontact", "db_key_field" => "id", "db_finalclass_field" => "", @@ -236,7 +271,7 @@ class lnkContractToCI extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "contract_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("contract_id","ci_id"), "db_table" => "lnkcontracttoci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -268,7 +303,7 @@ class Service extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","org_id"), "db_table" => "service", "db_key_field" => "id", "db_finalclass_field" => "", @@ -292,6 +327,22 @@ class Service extends cmdbAbstractObject MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'org_id', 'type', 'status')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'org_id', 'type', 'status')); MetaModel::Init_SetZListItems('list', array('name', 'description', 'org_id', 'type', 'status')); + + } + function DisplayBareRelations(WebPage $oPage, $bEditMode = false) + { + parent::DisplayBareRelations($oPage, $bEditMode); + $aExtraParam = array ('menu' => false); + $ServiceID=$this->GetKey(); + if (!$bEditMode) + { + $oPage->SetCurrentTab(Dict::S('Class:Service/Tab:Related_Contracts')); + $oCustomerContracts=new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT CustomerContract AS cc JOIN lnkContractToSLA AS ln ON ln.contract_id=cc.id JOIN SLA AS sla ON ln.sla_id=sla.id WHERE sla.service_id=$ServiceID")); + self::DisplaySet($oPage,$oCustomerContracts,$aExtraParam); + + + } + } } class ServiceSubcategory extends cmdbAbstractObject @@ -305,7 +356,7 @@ class ServiceSubcategory extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","service_id"), "db_table" => "servicesubcategory", "db_key_field" => "id", "db_finalclass_field" => "", @@ -336,7 +387,7 @@ class SLA extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","service_id"), "db_table" => "sla", "db_key_field" => "id", "db_finalclass_field" => "", @@ -400,7 +451,7 @@ class lnkSLTToSLA extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "sla_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("sla_id","slt_id"), "db_table" => "lnkslttosla", "db_key_field" => "id", "db_finalclass_field" => "", @@ -435,7 +486,7 @@ class lnkServiceToDoc extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "service_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("service_id","document_id"), "db_table" => "lnkservicetodoc", "db_key_field" => "id", "db_finalclass_field" => "", @@ -468,7 +519,7 @@ class lnkServiceToContact extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "service_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("service_id","contact_id"), "db_table" => "lnkservicetocontact", "db_key_field" => "id", "db_finalclass_field" => "", @@ -501,7 +552,7 @@ class lnkServiceToCI extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "service_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("service_id","ci_id"), "db_table" => "lnkservicetoci", "db_key_field" => "id", "db_finalclass_field" => "", diff --git a/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php index 533b2e8842..50d11e41d1 100644 --- a/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php @@ -38,26 +38,35 @@ // Class: Ticket // -Dict::Add('EN US', 'English', 'English', array( - 'Class:Ticket' => 'Ticket', - 'Class:Ticket+' => '', - 'Class:Ticket/Attribute:ref' => 'Ref', - 'Class:Ticket/Attribute:ref+' => '', - 'Class:Ticket/Attribute:title' => 'Title', - 'Class:Ticket/Attribute:title+' => '', - 'Class:Ticket/Attribute:ticket_log' => 'Log', - 'Class:Ticket/Attribute:ticket_log+' => '', - 'Class:Ticket/Attribute:start_date' => 'Started', - 'Class:Ticket/Attribute:start_date+' => '', - 'Class:Ticket/Attribute:document_list' => 'Documents', - 'Class:Ticket/Attribute:document_list+' => 'Documents related to the ticket', - 'Class:Ticket/Attribute:ci_list' => 'CIs', - 'Class:Ticket/Attribute:ci_list+' => 'CIs concerned by the incident', - 'Class:Ticket/Attribute:contact_list' => 'Contacts', - 'Class:Ticket/Attribute:contact_list+' => 'Team and persons involved', - 'Class:Ticket/Attribute:finalclass' => 'Type', - 'Class:Ticket/Attribute:finalclass+' => '', -)); +// +// Class: Ticket +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:Ticket' => 'Ticket', + 'Class:Ticket+' => '', + 'Class:Ticket/Attribute:ref' => 'Ref', + 'Class:Ticket/Attribute:ref+' => '', + 'Class:Ticket/Attribute:title' => 'Title', + 'Class:Ticket/Attribute:title+' => '', + 'Class:Ticket/Attribute:description' => 'description', + 'Class:Ticket/Attribute:description+' => '', + 'Class:Ticket/Attribute:ticket_log' => 'Log', + 'Class:Ticket/Attribute:ticket_log+' => '', + 'Class:Ticket/Attribute:start_date' => 'Started', + 'Class:Ticket/Attribute:start_date+' => '', + 'Class:Ticket/Attribute:document_list' => 'Documents', + 'Class:Ticket/Attribute:document_list+' => 'Documents related to the ticket', + 'Class:Ticket/Attribute:ci_list' => 'CIs', + 'Class:Ticket/Attribute:ci_list+' => 'CIs concerned by the incident', + 'Class:Ticket/Attribute:contact_list' => 'Contacts', + 'Class:Ticket/Attribute:contact_list+' => 'Team and persons involved', + 'Class:Ticket/Attribute:incident_list' => 'Related Incidents', + 'Class:Ticket/Attribute:incident_list+' => '', + 'Class:Ticket/Attribute:finalclass' => 'Type', + 'Class:Ticket/Attribute:finalclass+' => '', +)); + // // Class: lnkTicketToDoc @@ -101,141 +110,158 @@ Dict::Add('EN US', 'English', 'English', array( // Class: lnkTicketToCI // -Dict::Add('EN US', 'English', 'English', array( - 'Class:lnkTicketToCI' => 'Ticket/CI', - 'Class:lnkTicketToCI+' => '', - 'Class:lnkTicketToCI/Attribute:ticket_id' => 'Ticket', - 'Class:lnkTicketToCI/Attribute:ticket_id+' => '', - 'Class:lnkTicketToCI/Attribute:ticket_ref' => 'Ticket #', - 'Class:lnkTicketToCI/Attribute:ticket_ref+' => '', - 'Class:lnkTicketToCI/Attribute:ci_id' => 'CI', - 'Class:lnkTicketToCI/Attribute:ci_id+' => '', - 'Class:lnkTicketToCI/Attribute:ci_name' => 'CI', - 'Class:lnkTicketToCI/Attribute:ci_name+' => '', - 'Class:lnkTicketToCI/Attribute:ci_status' => 'CI status', - 'Class:lnkTicketToCI/Attribute:ci_status+' => '', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkTicketToCI' => 'Ticket/CI', + 'Class:lnkTicketToCI+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_id' => 'Ticket', + 'Class:lnkTicketToCI/Attribute:ticket_id+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_ref' => 'Ticket #', + 'Class:lnkTicketToCI/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToCI/Attribute:ci_id' => 'CI', + 'Class:lnkTicketToCI/Attribute:ci_id+' => '', + 'Class:lnkTicketToCI/Attribute:ci_name' => 'CI', + 'Class:lnkTicketToCI/Attribute:ci_name+' => '', + 'Class:lnkTicketToCI/Attribute:ci_status' => 'CI status', + 'Class:lnkTicketToCI/Attribute:ci_status+' => '', + 'Class:lnkTicketToCI/Attribute:impact' => 'Impact', + 'Class:lnkTicketToCI/Attribute:impact+' => '', +)); + // // Class: ResponseTicket // -Dict::Add('EN US', 'English', 'English', array( - 'Class:ResponseTicket' => 'ResponseTicket', - 'Class:ResponseTicket+' => '', - 'Class:ResponseTicket/Attribute:status' => 'Status', - 'Class:ResponseTicket/Attribute:status+' => '', - 'Class:ResponseTicket/Attribute:status/Value:new' => 'New', - 'Class:ResponseTicket/Attribute:status/Value:new+' => 'newly opened', - 'Class:ResponseTicket/Attribute:status/Value:frozen' => 'Pending', - 'Class:ResponseTicket/Attribute:status/Value:frozen+' => '', - 'Class:ResponseTicket/Attribute:status/Value:escalated_tto' => 'Escalation/TTO', - 'Class:ResponseTicket/Attribute:status/Value:escalated_tto+' => '', - 'Class:ResponseTicket/Attribute:status/Value:assigned' => 'Assigned', - 'Class:ResponseTicket/Attribute:status/Value:assigned+' => '', - 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr' => 'Escalation/TTR', - 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr+' => '', - 'Class:ResponseTicket/Attribute:status/Value:resolved' => 'Resolved', - 'Class:ResponseTicket/Attribute:status/Value:resolved+' => '', - 'Class:ResponseTicket/Attribute:status/Value:closed' => 'Closed', - 'Class:ResponseTicket/Attribute:status/Value:closed+' => '', - 'Class:ResponseTicket/Attribute:caller_id' => 'Caller', - 'Class:ResponseTicket/Attribute:caller_id+' => '', - 'Class:ResponseTicket/Attribute:workgroup_name' => 'Workgroup', - 'Class:ResponseTicket/Attribute:workgroup_name+' => '', - 'Class:ResponseTicket/Attribute:org_id' => 'Customer', - 'Class:ResponseTicket/Attribute:org_id+' => '', - 'Class:ResponseTicket/Attribute:org_name' => 'Customer', - 'Class:ResponseTicket/Attribute:org_name+' => '', - 'Class:ResponseTicket/Attribute:service_id' => 'Service', - 'Class:ResponseTicket/Attribute:service_id+' => '', - 'Class:ResponseTicket/Attribute:servicesubcategory_id' => 'Service element', - 'Class:ResponseTicket/Attribute:servicesubcategory_id+' => '', - 'Class:ResponseTicket/Attribute:product' => 'Product', - 'Class:ResponseTicket/Attribute:product+' => '', - 'Class:ResponseTicket/Attribute:impact' => 'Impact', - 'Class:ResponseTicket/Attribute:impact+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:1' => 'A person', - 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:2' => 'A service', - 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:3' => 'A department', - 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', - 'Class:ResponseTicket/Attribute:urgency' => 'Urgency', - 'Class:ResponseTicket/Attribute:urgency+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Low', - 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Medium', - 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'High', - 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', - 'Class:ResponseTicket/Attribute:priority' => 'Priority', - 'Class:ResponseTicket/Attribute:priority+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Low', - 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Medium', - 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:3' => 'High', - 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', - 'Class:ResponseTicket/Attribute:workgroup_id' => 'Workgroup', - 'Class:ResponseTicket/Attribute:workgroup_id+' => '', - 'Class:ResponseTicket/Attribute:agent_id' => 'Agent', - 'Class:ResponseTicket/Attribute:agent_id+' => '', - 'Class:ResponseTicket/Attribute:agent_name' => 'Agent', - 'Class:ResponseTicket/Attribute:agent_name+' => '', - 'Class:ResponseTicket/Attribute:agent_email' => 'Agent email', - 'Class:ResponseTicket/Attribute:agent_email+' => '', - 'Class:ResponseTicket/Attribute:related_change_id' => 'Related change', - 'Class:ResponseTicket/Attribute:related_change_id+' => '', - 'Class:ResponseTicket/Attribute:related_change_ref' => 'Related change', - 'Class:ResponseTicket/Attribute:related_change_ref+' => '', - 'Class:ResponseTicket/Attribute:close_date' => 'Closed', - 'Class:ResponseTicket/Attribute:close_date+' => '', - 'Class:ResponseTicket/Attribute:last_update' => 'Last update', - 'Class:ResponseTicket/Attribute:last_update+' => '', - 'Class:ResponseTicket/Attribute:assignment_date' => 'Assigned', - 'Class:ResponseTicket/Attribute:assignment_date+' => '', - 'Class:ResponseTicket/Attribute:tto_escalation_deadline' => 'TTO Escalation deadline', - 'Class:ResponseTicket/Attribute:tto_escalation_deadline+' => '', - 'Class:ResponseTicket/Attribute:ttr_escalation_deadline' => 'TTR Escalation deadline', - 'Class:ResponseTicket/Attribute:ttr_escalation_deadline+' => '', - 'Class:ResponseTicket/Attribute:closure_deadline' => 'Closure deadline', - 'Class:ResponseTicket/Attribute:closure_deadline+' => '', - 'Class:ResponseTicket/Attribute:resolution_code' => 'Resolution code', - 'Class:ResponseTicket/Attribute:resolution_code+' => '', - 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce' => 'Could not be reproduced', - 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce+' => '', - 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate' => 'Duplicate ticket', - 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate+' => '', - 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed' => 'Fixed', - 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed+' => '', - 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant' => 'Irrelevant', - 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant+' => '', - 'Class:ResponseTicket/Attribute:solution' => 'Solution', - 'Class:ResponseTicket/Attribute:solution+' => '', - 'Class:ResponseTicket/Attribute:user_satisfaction' => 'User satisfaction', - 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => '1', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => '1', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => '2', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => '2', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => '3', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => '3', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => '4', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => '4', - 'Class:ResponseTicket/Attribute:user_commment' => 'User comment', - 'Class:ResponseTicket/Attribute:user_commment+' => '', - 'Class:ResponseTicket/Stimulus:ev_assign' => 'Assign', - 'Class:ResponseTicket/Stimulus:ev_assign+' => '', - 'Class:ResponseTicket/Stimulus:ev_reassign' => 'Reassign', - 'Class:ResponseTicket/Stimulus:ev_reassign+' => '', - 'Class:ResponseTicket/Stimulus:ev_timeout' => 'ev_timeout', - 'Class:ResponseTicket/Stimulus:ev_timeout+' => '', - 'Class:ResponseTicket/Stimulus:ev_resolve' => 'Mark a resolved', - 'Class:ResponseTicket/Stimulus:ev_resolve+' => '', - 'Class:ResponseTicket/Stimulus:ev_close' => 'Close', - 'Class:ResponseTicket/Stimulus:ev_close+' => '', -)); +Dict::Add('EN US', 'English', 'English', array( + 'Class:ResponseTicket' => 'ResponseTicket', + 'Class:ResponseTicket+' => '', + 'Class:ResponseTicket/Attribute:status' => 'Status', + 'Class:ResponseTicket/Attribute:status+' => '', + 'Class:ResponseTicket/Attribute:status/Value:new' => 'New', + 'Class:ResponseTicket/Attribute:status/Value:new+' => 'newly opened', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto' => 'Escalation/TTO', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto+' => '', + 'Class:ResponseTicket/Attribute:status/Value:assigned' => 'Assigned', + 'Class:ResponseTicket/Attribute:status/Value:assigned+' => '', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr' => 'Escalation/TTR', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr+' => '', + 'Class:ResponseTicket/Attribute:status/Value:frozen' => 'Pending', + 'Class:ResponseTicket/Attribute:status/Value:frozen+' => '', + 'Class:ResponseTicket/Attribute:status/Value:resolved' => 'Resolved', + 'Class:ResponseTicket/Attribute:status/Value:resolved+' => '', + 'Class:ResponseTicket/Attribute:status/Value:closed' => 'Closed', + 'Class:ResponseTicket/Attribute:status/Value:closed+' => '', + 'Class:ResponseTicket/Attribute:caller_id' => 'Caller', + 'Class:ResponseTicket/Attribute:caller_id+' => '', + 'Class:ResponseTicket/Attribute:caller_email' => 'Email', + 'Class:ResponseTicket/Attribute:caller_email+' => '', + 'Class:ResponseTicket/Attribute:org_id' => 'Customer', + 'Class:ResponseTicket/Attribute:org_id+' => '', + 'Class:ResponseTicket/Attribute:org_name' => 'Customer', + 'Class:ResponseTicket/Attribute:org_name+' => '', + 'Class:ResponseTicket/Attribute:service_id' => 'Service', + 'Class:ResponseTicket/Attribute:service_id+' => '', + 'Class:ResponseTicket/Attribute:service_name' => 'Name', + 'Class:ResponseTicket/Attribute:service_name+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_id' => 'Service element', + 'Class:ResponseTicket/Attribute:servicesubcategory_id+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_name' => 'Name', + 'Class:ResponseTicket/Attribute:servicesubcategory_name+' => '', + 'Class:ResponseTicket/Attribute:product' => 'Product', + 'Class:ResponseTicket/Attribute:product+' => '', + 'Class:ResponseTicket/Attribute:impact' => 'Impact', + 'Class:ResponseTicket/Attribute:impact+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'A person', + 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:2' => 'A service', + 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'A department', + 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', + 'Class:ResponseTicket/Attribute:urgency' => 'Urgency', + 'Class:ResponseTicket/Attribute:urgency+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Low', + 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Medium', + 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'High', + 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', + 'Class:ResponseTicket/Attribute:priority' => 'Priority', + 'Class:ResponseTicket/Attribute:priority+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Low', + 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Medium', + 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'High', + 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', + 'Class:ResponseTicket/Attribute:workgroup_id' => 'Workgroup', + 'Class:ResponseTicket/Attribute:workgroup_id+' => '', + 'Class:ResponseTicket/Attribute:workgroup_name' => 'Workgroup', + 'Class:ResponseTicket/Attribute:workgroup_name+' => '', + 'Class:ResponseTicket/Attribute:agent_id' => 'Agent', + 'Class:ResponseTicket/Attribute:agent_id+' => '', + 'Class:ResponseTicket/Attribute:agent_name' => 'Agent', + 'Class:ResponseTicket/Attribute:agent_name+' => '', + 'Class:ResponseTicket/Attribute:agent_email' => 'Agent email', + 'Class:ResponseTicket/Attribute:agent_email+' => '', + 'Class:ResponseTicket/Attribute:related_problem_id' => 'Related Problem', + 'Class:ResponseTicket/Attribute:related_problem_id+' => '', + 'Class:ResponseTicket/Attribute:related_problem_ref' => 'Ref', + 'Class:ResponseTicket/Attribute:related_problem_ref+' => '', + 'Class:ResponseTicket/Attribute:related_change_id' => 'Related change', + 'Class:ResponseTicket/Attribute:related_change_id+' => '', + 'Class:ResponseTicket/Attribute:related_change_ref' => 'Related change', + 'Class:ResponseTicket/Attribute:related_change_ref+' => '', + 'Class:ResponseTicket/Attribute:close_date' => 'Closed', + 'Class:ResponseTicket/Attribute:close_date+' => '', + 'Class:ResponseTicket/Attribute:last_update' => 'Last update', + 'Class:ResponseTicket/Attribute:last_update+' => '', + 'Class:ResponseTicket/Attribute:assignment_date' => 'Assignment Date ', + 'Class:ResponseTicket/Attribute:assignment_date+' => '', + 'Class:ResponseTicket/Attribute:resolution_date' => 'Resolution Date', + 'Class:ResponseTicket/Attribute:resolution_date+' => '', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline' => 'TTO Escalation deadline', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline' => 'TTR Escalation deadline', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:closure_deadline' => 'Closure deadline', + 'Class:ResponseTicket/Attribute:closure_deadline+' => '', + 'Class:ResponseTicket/Attribute:resolution_code' => 'Resolution code', + 'Class:ResponseTicket/Attribute:resolution_code+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce' => 'Could not be reproduced', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate' => 'Duplicate ticket', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed' => 'Fixed', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant' => 'Irrelevant', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant+' => '', + 'Class:ResponseTicket/Attribute:solution' => 'Solution', + 'Class:ResponseTicket/Attribute:solution+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction' => 'User satisfaction', + 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Bad', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => 'Bad', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Average', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => 'Average', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Good', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => 'Good', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Very Good', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => 'Very Good', + 'Class:ResponseTicket/Attribute:user_commment' => 'User comment', + 'Class:ResponseTicket/Attribute:user_commment+' => '', + 'Class:ResponseTicket/Stimulus:ev_assign' => 'Assign', + 'Class:ResponseTicket/Stimulus:ev_assign+' => '', + 'Class:ResponseTicket/Stimulus:ev_reassign' => 'Reassign', + 'Class:ResponseTicket/Stimulus:ev_reassign+' => '', + 'Class:ResponseTicket/Stimulus:ev_timeout' => 'Escalation', + 'Class:ResponseTicket/Stimulus:ev_timeout+' => '', + 'Class:ResponseTicket/Stimulus:ev_resolve' => 'Mark a resolved', + 'Class:ResponseTicket/Stimulus:ev_resolve+' => '', + 'Class:ResponseTicket/Stimulus:ev_close' => 'Close', + 'Class:ResponseTicket/Stimulus:ev_close+' => '', +)); + + ?> diff --git a/modules/itop-tickets-1.0.0/model.itop-tickets.php b/modules/itop-tickets-1.0.0/model.itop-tickets.php index be6b3e3ff1..d85e38da73 100644 --- a/modules/itop-tickets-1.0.0/model.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/model.itop-tickets.php @@ -51,8 +51,9 @@ abstract class Ticket extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("document_list", array("linked_class"=>"lnkTicketToDoc", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"document_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkTicketToCI", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"ci_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("contact_list", array("linked_class"=>"lnkTicketToContact", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"contact_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("incident_list", array("linked_class"=>"lnkTicketToIncident", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"incident_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('ref', 'title', 'description', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list')); + MetaModel::Init_SetZListItems('details', array('ref', 'title', 'description', 'ticket_log', 'start_date', 'document_list', 'ci_list', 'contact_list','incident_list')); MetaModel::Init_SetZListItems('advanced_search', array('finalclass', 'ref', 'title', 'ticket_log', 'start_date')); MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'ticket_log', 'start_date')); MetaModel::Init_SetZListItems('list', array('finalclass', 'ref', 'title', 'ticket_log', 'start_date')); @@ -69,7 +70,7 @@ class lnkTicketToDoc extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "ticket_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("ticket_id","document_id"), "db_table" => "lnktickettodoc", "db_key_field" => "id", "db_finalclass_field" => "", @@ -100,7 +101,7 @@ class lnkTicketToContact extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "ticket_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("ticket_id","contact_id"), "db_table" => "lnktickettocontact", "db_key_field" => "id", "db_finalclass_field" => "", @@ -133,7 +134,7 @@ class lnkTicketToCI extends cmdbAbstractObject "key_type" => "autoincrement", "name_attcode" => "ticket_id", "state_attcode" => "", - "reconc_keys" => array(), + "reconc_keys" => array("ticket_id","ci_id"), "db_table" => "lnktickettoci", "db_key_field" => "id", "db_finalclass_field" => "", @@ -147,11 +148,12 @@ class lnkTicketToCI extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalKey("ci_id", array("targetclass"=>"FunctionalCI", "jointype"=>null, "allowed_values"=>null, "sql"=>"ci_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("ci_name", array("allowed_values"=>null, "extkey_attcode"=>"ci_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("ci_status", array("allowed_values"=>null, "extkey_attcode"=>"ci_id", "target_attcode"=>"status", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("impact", array("allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('ticket_id', 'ci_id', 'ci_status')); + MetaModel::Init_SetZListItems('details', array('ticket_id', 'ci_id', 'impact','ci_status')); MetaModel::Init_SetZListItems('advanced_search', array('ticket_id', 'ci_id', 'ci_status')); MetaModel::Init_SetZListItems('standard_search', array('ticket_id', 'ci_id', 'ci_status')); - MetaModel::Init_SetZListItems('list', array('ticket_id', 'ci_id', 'ci_status')); + MetaModel::Init_SetZListItems('list', array('ticket_id', 'ci_id', 'impact','ci_status')); } } @@ -201,6 +203,7 @@ abstract class ResponseTicket extends Ticket MetaModel::Init_AddAttribute(new AttributeDateTime("close_date", array("allowed_values"=>null, "sql"=>"close_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDateTime("assignment_date", array("allowed_values"=>null, "sql"=>"assignment_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("resolution_date", array("allowed_values"=>null, "sql"=>"resolution_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDeadline("tto_escalation_deadline", array("allowed_values"=>null, "sql"=>"tto_escalation_deadline", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDeadline("ttr_escalation_deadline", array("allowed_values"=>null, "sql"=>"ttr_escalation_deadline", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeDeadline("closure_deadline", array("allowed_values"=>null, "sql"=>"closure_deadline", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); @@ -211,8 +214,8 @@ abstract class ResponseTicket extends Ticket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'document_list', 'ci_list', 'contact_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('advanced_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('list', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('list', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'priority', 'workgroup_id', 'agent_id', 'last_update')); // Lifecycle MetaModel::Init_DefineState( @@ -229,6 +232,7 @@ abstract class ResponseTicket extends Ticket 'start_date' => OPT_ATT_READONLY, 'last_update' => OPT_ATT_READONLY, 'assignment_date' => OPT_ATT_HIDDEN, + 'resolution_date' => OPT_ATT_HIDDEN, 'tto_escalation_deadline' => OPT_ATT_READONLY, 'ttr_escalation_deadline' => OPT_ATT_HIDDEN, 'closure_deadline' => OPT_ATT_HIDDEN, @@ -273,6 +277,8 @@ abstract class ResponseTicket extends Ticket 'workgroup_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, 'tto_escalation_deadline' => OPT_ATT_HIDDEN, 'ttr_escalation_deadline' => OPT_ATT_READONLY, + 'related_problem_id' => OPT_ATT_MUSTPROMPT, +// 'related_change_id' => OPT_ATT_MUSTPROMPT, ), ) ); @@ -336,17 +342,17 @@ abstract class ResponseTicket extends Ticket MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_resolve", array())); MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_close", array())); - MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array('SetAssignedDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("new", "ev_timeout", array("target_state"=>"escalated_tto", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("escalated_tto", "ev_assign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("escalated_tto", "ev_assign", array("target_state"=>"assigned", "actions"=>array('SetAssignedDate'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("assigned", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("assigned", "ev_timeout", array("target_state"=>"escalated_ttr", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("assigned", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetClosureDeadline'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("assigned", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetResolveDate','SetClosureDeadline'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("escalated_ttr", "ev_reassign", array("target_state"=>"escalated_ttr", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("escalated_ttr", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetClosureDeadline'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("escalated_ttr", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetResolveDate','SetClosureDeadline'), "user_restriction"=>null)); MetaModel::Init_DefineTransition("resolved", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); MetaModel::Init_DefineTransition("resolved", "ev_close", array("target_state"=>"closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); @@ -360,6 +366,16 @@ abstract class ResponseTicket extends Ticket $this->Set('closure_deadline', time() + $iMaxWaitHours * 3600); return true; } + public function SetAssignedDate($sStimulusCode) + { + $this->Set('assignment_date', time()); + return true; + } + public function SetResolveDate($sStimulusCode) + { + $this->Set('resolution_date', time()); + return true; + } public function SetClosureDate($sStimulusCode) { $this->Set('close_date', time()); diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index 310cb5adcf..47283913b9 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -26,7 +26,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-tickets.php', 'fr.dict.itop-tickets.php', - 'es_cr.dict.itop-tickets.php', +// 'es_cr.dict.itop-tickets.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', From ce02a39157ebbb99a83b80ed0d592325679e2fc2 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 31 Aug 2010 08:15:35 +0000 Subject: [PATCH 643/970] - Fixed Trac #215: support several characters encoding for the interactive CSV import. SVN:trunk[726] --- core/config.class.inc.php | 25 ++++++++ dictionaries/dictionary.itop.ui.php | 2 +- dictionaries/fr.dictionary.itop.ui.php | 1 + pages/ajax.csvimport.php | 3 +- pages/csvimport.php | 89 +++++++++++++++++++++----- 5 files changed, 103 insertions(+), 17 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 4c8ea89b59..e0c95bfc77 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -138,6 +138,12 @@ class Config */ protected $m_sEncryptionKey; + /** + * @var array Additional character sets to be supported by the interactive CSV import + * 'iconv_code' => 'display name' + */ + protected $m_aCharsets; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; @@ -192,6 +198,7 @@ class Config $this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE; $this->m_sEncryptionKey = DEFAULT_ENCRYPTION_KEY; + $this->m_aCharsets = array(); $this->m_aModuleSettings = array(); @@ -296,6 +303,7 @@ class Config $this->m_sAllowedLoginTypes = isset($MySettings['allowed_login_types']) ? trim($MySettings['allowed_login_types']) : DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = isset($MySettings['ext_auth_variable']) ? trim($MySettings['ext_auth_variable']) : DEFAULT_EXT_AUTH_VARIABLE; $this->m_sEncryptionKey = isset($MySettings['encryption_key']) ? trim($MySettings['encryption_key']) : DEFAULT_ENCRYPTION_KEY; + $this->m_aCharsets = isset($MySettings['csv_import_charsets']) ? $MySettings['csv_import_charsets'] : array(); } protected function Verify() @@ -473,6 +481,11 @@ class Config return $this->m_sExtAuthVariable; } + public function GetCSVImportCharsets() + { + return $this->m_aCharsets; + } + public function SetDBHost($sDBHost) { $this->m_sDBHost = $sDBHost; @@ -568,6 +581,15 @@ class Config $this->m_sEncryptionKey = $sKey; } + public function SetCSVImportCharsets($aCharsets) + { + $this->m_aCharsets = $aCharsets; + } + + public function AddCSVImportCharset($sIconvCode, $sDisplayName) + { + $this->m_aCharsets[$sIconvCode] = $sDisplayName; + } public function FileIsWritable() { return is_writable($this->m_sFile); @@ -618,6 +640,9 @@ class Config fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n"); fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n"); fwrite($hFile, "\t'encryption_key' => '{$this->m_sEncryptionKey}',\n"); + $sExport = var_export($this->m_aCharsets, true); + fwrite($hFile, "\t'csv_import_charsets' => $sExport,\n"); + fwrite($hFile, ");\n"); fwrite($hFile, "\n"); diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index ac399abf0a..d7fb9d5ea0 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -496,7 +496,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:CSVImport:HeaderSearch' => 'Search?', 'UI:CSVImport:AlertIncompleteMapping' => 'Please select a mapping for every field.', 'UI:CSVImport:AlertNoSearchCriteria' => 'Please select at least one search criteria', - + 'UI:CSVImport:Encoding' => 'Character encoding', 'UI:UniversalSearchTitle' => 'iTop - Universal Search', 'UI:UniversalSearch:Error' => 'Error: %1$s', 'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index cbf576f001..1827373181 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -499,6 +499,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:CSVImport:HeaderSearch' => 'Recherche ?', 'UI:CSVImport:AlertIncompleteMapping' => 'Veuillez choisir le correspondance de chacun des champs.', 'UI:CSVImport:AlertNoSearchCriteria' => 'Veuillez choisir au moins une clef de recherche.', + 'UI:CSVImport:Encoding' => 'Encodage des caractères', 'UI:UniversalSearchTitle' => 'iTop - Recherche Universelle', 'UI:UniversalSearch:Error' => 'Erreur : %1$s', diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index 58594d6b86..5841f12b20 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -185,6 +185,7 @@ switch($sOperation) $sTextQualifier = utils::ReadParam('qualifier', '"'); $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0); $bFirstLineAsHeader = utils::ReadParam('header_line', true); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $sData = stripslashes(utils::ReadParam('csvdata', true)); $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier); $aData = $oCSVParser->ToArray($iLinesToSkip); @@ -246,7 +247,7 @@ switch($sOperation) $sData = stripslashes(utils::ReadParam('csvdata', true)); $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', false); - + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier); $aData = $oCSVParser->ToArray($iLinesToSkip); $iTarget = count($aData); diff --git a/pages/csvimport.php b/pages/csvimport.php index 14bc4a0101..36106081a3 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -232,6 +232,7 @@ function ProcessCSVData(WebPage $oPage, $bSimulate = true) $aSearchFields = utils::ReadParam('search_field', array()); $iCurrentStep = $bSimulate ? 4 : 5; $bAdvanced = utils::ReadParam('advanced', 0); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); // Parse the data set $oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier); @@ -476,6 +477,7 @@ function ProcessCSVData(WebPage $oPage, $bSimulate = true) $oPage->add(''); $oPage->add(''); $oPage->add(''); + $oPage->add(''); foreach($aFieldsMapping as $iNumber => $sAttCode) { $oPage->add(''); @@ -603,6 +605,7 @@ function SelectMapping(WebPage $oPage) } $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', 0); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep3').'

        '); $oPage->add('
        '); @@ -619,7 +622,7 @@ function SelectMapping(WebPage $oPage) $oPage->add(''); $oPage->add(''); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add('

          '); $oPage->add('

        '); $oPage->add(''); @@ -670,6 +673,7 @@ EOF var header_line = $('input[name=header_line]').val(); var nb_lines_skipped = $('input[name=nb_skipped_lines]').val(); var csv_data = $('input[name=csvdata]').val(); + var encoding = $('input[name=encoding]').val(); if (advanced != 1) { advanced = 0; @@ -687,7 +691,7 @@ EOF ajax_request = $.post('ajax.csvimport.php', { operation: 'display_mapping_form', enctype: 'multipart/form-data', csvdata: csv_data, separator: separator, qualifier: text_qualifier, nb_lines_skipped: nb_lines_skipped, header_line: header_line, class_name: class_name, - advanced: advanced }, + advanced: advanced, encoding: encoding }, function(data) { $('#mapping').empty(); $('#mapping').append(data); @@ -823,8 +827,19 @@ function SelectOptions(WebPage $oPage) default: $sCSVData = utils::ReadParam('csvdata', '', 'post'); } - - $aGuesses = GuessParameters($sCSVData); // Try to predict the parameters, based on the input data + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); + + // Compute a subset of the data set, now that we know the charset + if ($sEncoding == 'UTF-8') + { + $sUTF8Data = $sCSVData; + } + else + { + $sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); + } + + $aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data $sSeparator = utils::ReadParam('separator', ''); if ($sSeparator == '') // May be set to an empty value by the previous page @@ -848,23 +863,23 @@ function SelectOptions(WebPage $oPage) // Create a truncated version of the data used for the fast preview // Take about 20 lines of data... knowing that some lines may contain carriage returns $iMaxLines = 20; - $iMaxLen = strlen($sCSVData); + $iMaxLen = strlen($sUTF8Data); $iCurPos = true; while ( ($iCurPos > 0) && ($iMaxLines > 0)) { - $pos = strpos($sCSVData, "\n", $iCurPos); + $pos = strpos($sUTF8Data, "\n", $iCurPos); if ($pos !== false) { $iCurPos = 1+$pos; } else { - $iCurPos = strlen($sCSVData); + $iCurPos = strlen($sUTF8Data); $iMaxLines = 1; } $iMaxLines--; } - $sCSVDataTruncated = substr($sCSVData, 0, $iCurPos); + $sCSVDataTruncated = substr($sUTF8Data, 0, $iCurPos); $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep2').'

        '); $oPage->add('
        '); @@ -888,7 +903,7 @@ function SelectOptions(WebPage $oPage) $oPage->add('

        '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '').'

        '); $oPage->add('

        '); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); @@ -933,6 +948,7 @@ function SelectOptions(WebPage $oPage) { header_line = 1; } + var encoding = $('input[name=encoding]').val(); $('#preview').block(); @@ -945,7 +961,7 @@ function SelectOptions(WebPage $oPage) } ajax_request = $.post('ajax.csvimport.php', - { operation: 'parser_preview', enctype: 'multipart/form-data', csvdata: $("#csvdata_truncated").val(), separator: separator, qualifier: text_qualifier, nb_lines_skipped: nb_lines_skipped, header_line: header_line }, + { operation: 'parser_preview', enctype: 'multipart/form-data', csvdata: $("#csvdata_truncated").val(), separator: separator, qualifier: text_qualifier, nb_lines_skipped: nb_lines_skipped, header_line: header_line, encoding: encoding }, function(data) { $('#preview').empty(); $('#preview').append(data); @@ -965,6 +981,23 @@ EOF */ function Welcome(iTopWebPage $oPage) { + // Encodings supported: + // ICONV_CODE => Display Name + // Each iconv installation supports different encodings + // Some reasonably common and useful encodnings are listed here + $aPossibleEncodings = array( + 'UTF-8' => 'Unicode (UTF-8)', + 'ISO-8859-1' => 'Western (ISO-8859-1)', + 'WINDOWS-1251' => 'Cyrilic (Windows 1251)', + 'WINDOWS-1252' => 'Western (Windows 1252)', + 'ISO-8859-15' => 'Western (ISO-8859-15)', + ); + // Some more encodings can be specified in the config file + $aExtraCharsets = utils::GetConfig()->GetCSVImportCharsets(); + + $aPossibleEncodings = array_merge($aPossibleEncodings, $aExtraCharsets); + asort($aPossibleEncodings); + $oPage->add("

        ".Dict::S('UI:Title:BulkImport+')."

        \n"); $oPage->AddTabContainer('tabs1'); @@ -974,12 +1007,26 @@ function Welcome(iTopWebPage $oPage) $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', 0); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $sFileLoadHtml = '

        '.Dict::S('UI:CSVImport:SelectFile').'

        '. - '

        '. - '

        '. - '

        '. - '

        '. + '

        '; + + $sFileLoadHtml .= '

        '.Dict::S('UI:CSVImport:Encoding').': '; + $sFileLoadHtml .= '

        '; + $sFileLoadHtml .= '

        '. + ''. + ''. ''. ''. ''. @@ -989,7 +1036,19 @@ function Welcome(iTopWebPage $oPage) $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:LoadFromFile'), $sFileLoadHtml); $sCSVData = utils::ReadParam('csvdata', ''); $sPasteDataHtml = '

        '.Dict::S('UI:CSVImport:PasteData').'

        '. - '

        '. + '

        '; + $sPasteDataHtml .= '

        '.Dict::S('UI:CSVImport:Encoding').': '; + $sPasteDataHtml .= '

        '. '

        '. ''. ''. From ac0b302e75ca8646688a1e23490f063b676872e8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 31 Aug 2010 08:19:26 +0000 Subject: [PATCH 644/970] #213 Reset external keys upon object deletion SVN:trunk[727] --- pages/UI.php | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/UI.php b/pages/UI.php index 66698c9ee3..91dd035430 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -88,6 +88,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed) } $aResetedObjs[$sRemoteClass][$iId]['attributes_list'] = implode(', ', $aExtKeyLabels); $aTotalResetedObjs[$sRemoteClass][$iId]['attributes_list'] = $aResetedObjs[$sRemoteClass][$iId]['attributes_list']; + $aTotalResetedObjs[$sRemoteClass][$iId]['attributes'] = $aResetedObjs[$sRemoteClass][$iId]['attributes']; if (count($aForbiddenKeys) > 0) { $aTotalResetedObjs[$sRemoteClass][$iId]['issue'] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenKeys)); From 3c7af6d37f1f3bb386919a49b94875a51543a965 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 31 Aug 2010 09:34:08 +0000 Subject: [PATCH 645/970] -Fixed menu loading order bug. SVN:trunk[728] --- modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php index 753e687187..50f00ff805 100644 --- a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php @@ -224,7 +224,8 @@ class Problem extends Ticket } -$iIndex = ApplicationMenu::GetMenuIndexById('ProblemManagement'); +$oMyMenuGroup = new MenuGroup('ProblemManagement', 42 /* fRank */); // Will create if it does not exist +$iIndex = $oMyMenuGroup->GetIndex(); new TemplateMenuNode('Problem:Overview', '../modules/itop-problem-mgmt-1.0.0/overview.html', $iIndex /* oParent */, 0 /* fRank */); new NewObjectMenuNode('NewProblem', 'Problem', $iIndex, 1 /* fRank */); new SearchMenuNode('SearchProblems', 'Problem', $iIndex, 2 /* fRank */); From 8fe9d25341ff8446be69df4d96e1e5b16e7dbd0a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 31 Aug 2010 13:35:16 +0000 Subject: [PATCH 646/970] Improved benchmark tools SVN:trunk[729] --- core/kpi.class.inc.php | 8 ++++++ .../data.sample.Location.xml | 4 +-- setup/benchmark.php | 25 +++++++++++++------ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php index 4f3dec5528..ade2c64642 100644 --- a/core/kpi.class.inc.php +++ b/core/kpi.class.inc.php @@ -52,6 +52,8 @@ class ExecutionKPI echo "====================
        \n"; $fTotalOp = 0; $iTotalOp = 0; + $fMinOp = null; + $fMaxOp = 0; foreach ($aOpStats as $sArguments => $aEvents) { $fTotalInter = 0; @@ -60,12 +62,18 @@ class ExecutionKPI { $fTotalInter += $fDuration; $iTotalInter++; + + $fMinOp = is_null($fMinOp) ? $fDuration : min($fMinOp, $fDuration); + $fMaxOp = max($fMaxOp, $fDuration); } $fTotalOp += $fTotalInter; $iTotalOp++; echo "$sArguments: $iTotalInter (".round($fTotalInter, 3).")
        \n"; } echo "Total: $iTotalOp (".round($fTotalOp, 3).")
        \n"; + echo "Min: ".round($fMinOp, 3)."
        \n"; + echo "Max: ".round($fMaxOp, 3)."
        \n"; + echo "Avg: ".round($fTotalOp / $iTotalOp, 3)."
        \n"; echo "====================
        \n"; } } diff --git a/modules/itop-config-mgmt-1.0.0/data.sample.Location.xml b/modules/itop-config-mgmt-1.0.0/data.sample.Location.xml index 5bca5e4be3..d840bd3241 100644 --- a/modules/itop-config-mgmt-1.0.0/data.sample.Location.xml +++ b/modules/itop-config-mgmt-1.0.0/data.sample.Location.xml @@ -17,7 +17,7 @@
        5, rue du Sentier
        75002 Paris -Franche +France 0 - \ No newline at end of file + diff --git a/setup/benchmark.php b/setup/benchmark.php index 58285a0d77..c87f658f42 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -218,7 +218,9 @@ class BenchmarkDataCreation } elseif ($iCount == 1) { - $aSample = array(array_rand($aData)); + // array_rand() for one item returns only the key + $key = array_rand($aData); + $aSample = array($key); } elseif ($iCount <= count($aData)) { @@ -226,7 +228,7 @@ class BenchmarkDataCreation } else { - $aSample = array_merge(array_rand($aData, count($aData)), self::my_array_rand($aData, $iCount - count($aData))); + $aSample = array_merge(array_keys($aData), self::my_array_rand($aData, $iCount - count($aData))); } return $aSample; } @@ -239,11 +241,11 @@ class BenchmarkDataCreation $aTargets = self::GetClassIds($sToClass); $aSample = self::my_array_rand($aTargets, $iCount); - for($iLinked = 0 ; $iLinked < $iCount ; $iLinked++) + foreach($aSample as $key) { $aData = array( $sAttCodeFrom => $iFrom, - $sAttCodeTo => $aSample[$iLinked], + $sAttCodeTo => $aTargets[$key], ); $this->CreateObject($sLinkClass, $aData); } @@ -284,7 +286,7 @@ class BenchmarkDataCreation 'location_id' => self::FindId('Location'), 'first_name' => 'Jesus', 'name' => 'Deus', - 'email' => '', + 'email' => 'guru@combodo.com', ); $iPerson = $this->CreateObject('Person', $aData); $aData = array( @@ -312,7 +314,7 @@ class BenchmarkDataCreation 'location_id' => self::FindId('Location'), 'first_name' => 'Little ze', 'name' => 'Foo', - 'email' => '', + 'email' => 'foo@combodo.com', ); $iPerson = $this->CreateObject('Person', $aData); $aData = array( @@ -362,7 +364,7 @@ class BenchmarkDataCreation 'org_id' => $iOrg, 'location_id' => $iLoc, 'name' => 'Fluminense', - 'email' => 'fluminense@nowhere.fr', + 'email' => 'fluminense@combodo.com', ); $iTeam = $this->CreateObject('Team', $aData); @@ -398,7 +400,7 @@ class BenchmarkDataCreation 'org_id' => $iOrg, 'name' => 'My Service', ); - $iOrg = $this->CreateObject('Service', $aData); + $iService = $this->CreateObject('Service', $aData); ///////////////////////// // @@ -406,6 +408,7 @@ class BenchmarkDataCreation // $aData = array( 'name' => 'My subcategory', + 'service_id' => $iService, ); $iOrg = $this->CreateObject('ServiceSubcategory', $aData); @@ -617,6 +620,7 @@ class BenchmarkDataCreation 'service_id' => $this->RandomId('Service'), 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), 'title' => 'Incident #'.$i, + 'description' => 'O que aconteceu?', 'ticket_log' => 'Testing...', ); $iTicket = $this->CreateObject('Incident', $aData); @@ -642,6 +646,7 @@ class BenchmarkDataCreation 'service_id' => $this->RandomId('Service'), 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), 'title' => 'Big ticket', + 'description' => 'O que aconteceu?', 'ticket_log' => 'Testing...', ); $iTicket = $this->CreateObject('Incident', $aData); @@ -777,6 +782,9 @@ LoginWebPage::DoLogin(); // Check user rights and prompt if needed $sOperation = Utils::ReadParam('operation', 'step1'); $oP = new SetupWebPage('iTop benchmark utility'); +ExecutionKPI::EnableDuration(); +$oKPI = new ExecutionKPI(); + try { switch($sOperation) @@ -862,5 +870,6 @@ catch(ZZCoreException $e) { $oP->error("Error: '".$e->getHtmlDesc()."'"); } +$oKPI->ComputeAndReport('Total execution'); $oP->output(); ?> From 8cd804c2eca8ae7afa95deb6c61799187f49bbf2 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 31 Aug 2010 16:05:00 +0000 Subject: [PATCH 647/970] - Bug fix: the "global search" can be launched from any page... SVN:trunk[730] --- application/itopwebpage.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 37693acfb4..18d656f07b 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -527,7 +527,7 @@ EOF echo '
        '; echo '
        '; - echo ' '; From 7b82f17eed6e2f7fadbb11a1d57fcaa13e4fa617 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 31 Aug 2010 16:39:40 +0000 Subject: [PATCH 648/970] - Cosmetics: aligned screen titles with the "Next" label mentioned in the previous screen. - All hyperlinks (in the setup) now point to a new tab/window so that you can continue to run the setup in the original window after reading what was displayed. SVN:trunk[731] --- setup/index.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/setup/index.php b/setup/index.php index 59b8fa6a4e..d3782551ac 100644 --- a/setup/index.php +++ b/setup/index.php @@ -184,7 +184,7 @@ function CheckPHPVersion(SetupWebPage $oP) else { $aMissingExtensions[] = $sExtension; - $aMissingExtensionsLinks[] = "$sExtension"; + $aMissingExtensionsLinks[] = "$sExtension"; } } if (count($aExtensionsOk) > 0) @@ -647,8 +647,8 @@ function LicenceAcknowledgement($oP, $aParamValues, $iCurrentStep) { $sNextOperation = 'step'.($iCurrentStep+1); - $oP->set_title('License Agreement'); - $oP->add('

        iTop is released by Combodo SARL under the terms of the GPL V3 license. In order to use iTop you must accept the terms of this license.

        '); + $oP->set_title('License agreement'); + $oP->add('

        iTop is released by Combodo SARL under the terms of the GPL V3 license. In order to use iTop you must accept the terms of this license.

        '); $oP->add("\n"); $oP->add("
        \n"); $oP->add("\n"); @@ -679,7 +679,7 @@ function DatabaseServerSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep) if ($aParamValues['licence_ok'] == 1) { $sRedStar = '*'; - $oP->set_title("Configuration of the database connection\n"); + $oP->set_title("Database server selection\n"); $oP->add("

        Please enter the name of the MySQL database server you want to use for iTop and supply valid credentials to connect to it

        \n"); // Form goes here $oP->add("
        Database connection\n"); @@ -691,7 +691,7 @@ function DatabaseServerSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep) $aForm[] = array('label' => 'Password:', 'input' => ""); $oP->form($aForm); $oP->add("
        \n"); - $oP->add("

        Next: Database Instance Selection

        \n"); + $oP->add("

        Next: Database instance Selection

        \n"); $oP->add("\n"); $oP->add("\n"); $oP->add("\n"); @@ -713,7 +713,7 @@ function DatabaseServerSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep) function DatabaseInstanceSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConfig) { $sNextOperation = 'step'.($iCurrentStep+1); - $oP->set_title("Database Instance Selection\n"); + $oP->set_title("Database instance selection\n"); $oP->add("\n"); $oP->add("\n"); AddParamsToForm($oP, $aParamValues, array('db_name', 'db_prefix', 'new_db_name')); @@ -772,7 +772,7 @@ function DatabaseInstanceSelection(SetupWebPage $oP, $aParamValues, $iCurrentSte $aForm[] = array('label' => "Add a prefix to all the tables: "); $oP->form($aForm); - $oP->add("

        Next: iTop Modules Selection

        \n"); + $oP->add("

        Next: iTop modules selection

        \n"); $oP->add("
        \n"); $oP->add("\n"); $oP->add("\n"); @@ -803,7 +803,7 @@ function ModulesSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConf $oP->add("\n"); AddParamsToForm($oP, $aParamValues, array('module')); $sRedStar = '*'; - $oP->set_title("Selection of the iTop Modules\n"); + $oP->set_title("iTop modules selection"); $oP->add("

        Customize your iTop installation to fit your needs

        \n"); $aAvailableModules = GetAvailableModules($oP); @@ -828,7 +828,7 @@ function ModulesSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConf $sModuleHelp = $aModule['doc.more_information']; $sClass = ($aModule['mandatory']) ? 'class="read-only"' : ''; $sChecked = ($aModule['mandatory'] || in_array($sModuleId, $aSelectedModules) ) ? 'checked' : ''; - $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": ''; + $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": ''; if ($aModule['category'] == 'authentication') { // For now authentication modules are always on and hidden @@ -849,7 +849,7 @@ function ModulesSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConf } $oP->add(""); $oP->add("\n"); - $oP->add("

        Next: Administrator Account Creation

        \n"); + $oP->add("

        Next: Administrator account creation

        \n"); $oP->add("
        \n"); $oP->add("\n"); $oP->add("\n"); @@ -868,7 +868,7 @@ function ModulesSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConf function AdminAccountDefinition(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) { $sNextOperation = 'step'.($iCurrentStep+1); - $oP->set_title("Configuration of the admin account"); + $oP->set_title("Administrator account creation"); $oP->add("

        Creation of the database structure

        "); $oP->add("\n"); $oP->add("\n"); @@ -910,7 +910,7 @@ function AdminAccountDefinition(SetupWebPage $oP, $aParamValues, $iCurrentStep, $aForm[] = array('label' => "Retype password$sRedStar:", 'input' => ""); $oP->form($aForm); $oP->add("\n"); - $oP->add("

        Next: Administrator Account Creation

        \n"); + $oP->add("

        Next: Application initialization

        \n"); $oP->add("
        \n"); $oP->add("\n"); $oP->add("\n"); @@ -934,7 +934,7 @@ function SampleDataSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, Con { $sNextOperation = 'step'.($iCurrentStep+1); - $oP->set_title("Application Initialization"); + $oP->set_title("Application initialization"); $sAdminUser = $aParamValues['auth_user']; $sAdminPwd = $aParamValues['auth_pwd']; $sLanguage = $aParamValues['language']; @@ -952,7 +952,7 @@ function SampleDataSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, Con $oP->p("\n"); $oP->p("\n"); $oP->p("\n"); - $oP->add("

        Next: Application Initialization

        \n"); + $oP->add("

        Next: Setup complete

        \n"); $oP->add("
        \n"); $oP->add("\n"); $oP->add("\n"); @@ -1007,7 +1007,7 @@ function SetupFinished(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $o // try to make the final config file read-only @chmod(FINAL_CONFIG_FILE, 0440); // Read-only for owner and group, nothing for others - $oP->set_title("Configuration completed"); + $oP->set_title("Setup complete"); $oP->add("\n"); // Check if there are some manual steps required: @@ -1037,12 +1037,12 @@ function SetupFinished(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $o // Form goes here.. No back button since the job is done ! $oP->add("

        Let us know what you think about iTop

        "); $oP->add('
        '); + $oP->add(''); $oP->add('
        '); - $oP->add(''); $oP->add("Combodo built iTop because Combodo believes that modern ITIL tools should be at the center of any IT department."); $oP->p("Combodo invested a lot of time and effort in iTop, but you can help us improve it even further by providing your feedbacks

        "); $oP->p("Register online to get informed about all iTop related events (new versions, webinars, etc...)"); - $oP->p("Check out the support options available for iTop."); + $oP->p("Check out the support options available for iTop."); $oP->add('
        '); $oP->add("

        \n"); $oP->add("
        \n"); From b9d168bb04448ae07dcf31e25238589492e701bc Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 31 Aug 2010 16:42:22 +0000 Subject: [PATCH 649/970] - Display the actual code of each state next to its label. This is useful for writing OQL queries and also triggers. SVN:trunk[732] --- pages/schema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/schema.php b/pages/schema.php index 1914bd31b9..ee4f2df000 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -186,7 +186,7 @@ function DisplayLifecycle($oPage, $sClass) { $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); - $oPage->add("
      • $sStateLabel ($sStateDescription)
      • \n"); + $oPage->add("
      • $sStateLabel ($sStateCode) $sStateDescription
      • \n"); $oPage->add("
          \n"); foreach(MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) { @@ -212,7 +212,7 @@ function DisplayLifecycle($oPage, $sClass) { $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); - $oPage->add("
        • $sStateLabel ($sStateDescription)
        • \n"); + $oPage->add("
        • $sStateLabel ($sStateCode) $sStateDescription
        • \n"); if (count($aStates[$sStateCode]['attribute_list']) > 0) { $oPage->add("
            \n"); From 1f6473947c409461b0c35c770087125f40d2d961 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 31 Aug 2010 16:43:31 +0000 Subject: [PATCH 650/970] - Fix: the menu "Helpdesk / Requests assigned to me" no longer shows the resolved or closed requests assigned to me. SVN:trunk[733] --- modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index 555d199e6d..a816370170 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -92,7 +92,7 @@ new TemplateMenuNode('UserRequest:Overview', '../modules/itop-request-mgmt-1.0.0 new NewObjectMenuNode('NewUserRequest', 'UserRequest', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchUserRequests', 'UserRequest', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('UserRequest:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); From 77159e876602e7db74d0216cb59f0f70167bd85f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 31 Aug 2010 21:11:11 +0000 Subject: [PATCH 651/970] Notification were not working well with class hierarchy (not in Trac at that time) SVN:trunk[734] --- application/cmdbabstract.class.inc.php | 5 ++-- core/dbobject.class.php | 5 ++-- core/metamodel.class.php | 35 ++++++++++++++++++++++++-- pages/schema.php | 3 ++- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index b241cbe97c..f25cfefe22 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -238,14 +238,15 @@ abstract class cmdbAbstractObject extends CMDBObject // If any trigger has been found then display a tab with notifications // $sClass = get_class($this); - $oTriggerSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject AS T WHERE T.target_class = '$sClass'")); + $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); + $oTriggerSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject AS T WHERE T.target_class IN ('$sClassList')")); if ($oTriggerSet->Count() > 0) { $oPage->SetCurrentTab(Dict::S('UI:NotificationsTab')); // Display notifications regarding the object $iId = $this->GetKey(); - $oNotifSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT EventNotificationEmail AS Ev JOIN TriggerOnObject AS T ON Ev.trigger_id = T.id WHERE T.target_class = '$sClass' AND Ev.object_id = $iId")); + $oNotifSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT EventNotificationEmail AS Ev JOIN TriggerOnObject AS T ON Ev.trigger_id = T.id WHERE T.target_class IN ('$sClassList') AND Ev.object_id = $iId")); self::DisplaySet($oPage, $oNotifSet); } } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index ae10c31300..331a0e9333 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1079,13 +1079,14 @@ abstract class DBObject // Change state triggers... $sClass = get_class($this); - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class='$sClass' AND t.state='$sPreviousState'")); + $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN ('$sClassList') AND t.state='$sPreviousState'")); while ($oTrigger = $oSet->Fetch()) { $oTrigger->DoActivate($this->ToArgs('this')); } - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class='$sClass' AND t.state='$sNewState'")); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN ('$sClassList') AND t.state='$sNewState'")); while ($oTrigger = $oSet->Fetch()) { $oTrigger->DoActivate($this->ToArgs('this')); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 763ca5ffeb..35d0e9ecc3 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -47,6 +47,18 @@ define('ENUM_CHILD_CLASSES_EXCLUDETOP', 1); * @package iTopORM */ define('ENUM_CHILD_CLASSES_ALL', 2); +/** + * add some description here... + * + * @package iTopORM + */ +define('ENUM_PARENT_CLASSES_EXCLUDELEAF', 1); +/** + * add some description here... + * + * @package iTopORM + */ +define('ENUM_PARENT_CLASSES_ALL', 2); /** * Specifies that this attribute is visible/editable.... normal (default config) @@ -1209,6 +1221,19 @@ abstract class MetaModel self::$m_aAttribOrigins[$sTargetClass] = array(); } self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); + // Note: while investigating on some issues related to attribute inheritance, + // I found out that the notion of "host class" is unclear + // For stability reasons, and also because a workaround has been found + // I leave it unchanged, but later it could be a good thing to force + // attribute host class to the new class (See code below) + // In that case, we will have to review the attribute labels + // (currently relying on host class => the original declaration + // of the attribute) + // See TRAC #148 + // foreach(self::$m_aAttribDefs[$sTargetClass] as $sAttCode => $oAttDef) + // { + // $oAttDef->SetHostClass($sTargetClass); + // } self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); } // Build root class information @@ -1417,10 +1442,16 @@ abstract class MetaModel { return array_unique(self::$m_aRootClasses); } - public static function EnumParentClasses($sClass) + public static function EnumParentClasses($sClass, $iOption = ENUM_PARENT_CLASSES_EXCLUDELEAF) { self::_check_subclass($sClass); - return self::$m_aParentClasses[$sClass]; + if ($iOption == ENUM_PARENT_CLASSES_EXCLUDELEAF) + { + return self::$m_aParentClasses[$sClass]; + } + $aRes = self::$m_aParentClasses[$sClass]; + $aRes[] = $sClass; + return $aRes; } public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) { diff --git a/pages/schema.php b/pages/schema.php index ee4f2df000..8713cae6af 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -255,7 +255,8 @@ function DisplayLifecycle($oPage, $sClass) */ function DisplayTriggers($oPage, $sClass) { - $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateChange WHERE target_class = '$sClass'")); + $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); + $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject WHERE target_class IN ('$sClassList')")); cmdbAbstractObject::DisplaySet($oPage, $oSet); } From 4894dd2b4c1e9e0e9ad62d220a6893d19f458da5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 1 Sep 2010 09:07:01 +0000 Subject: [PATCH 652/970] New placeholders in templates: this->html(attcode) and this->label(attcode) SVN:trunk[735] --- application/utils.inc.php | 12 +++++++++++- core/dbobject.class.php | 9 +++++++++ .../itop-tickets-1.0.0/data.struct.ta-actions.xml | 6 +++--- .../itop-tickets-1.0.0/data.struct.ta-triggers.xml | 4 ++-- setup/ajax.dataloader.php | 2 ++ setup/index.php | 4 ++++ 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 5268e6c41e..68c7d40b6a 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -39,6 +39,7 @@ class FileUploadException extends Exception */ class utils { + private static $m_sConfigFile = ITOP_CONFIG_FILE; private static $m_oConfig = null; public static function ReadParam($sName, $defaultValue = "") @@ -136,6 +137,15 @@ class utils return file_get_contents($sFileName); } + /** + * Specify the application config file + * @param string path to the config file + * @return void + */ + public static function SpecifyConfigFile($sFilePath) + { + self::$m_sConfigFile = $sFilePath; + } /** * Get access to the application config file * @param none @@ -145,7 +155,7 @@ class utils { if (self::$m_oConfig == null) { - self::$m_oConfig = new Config(ITOP_CONFIG_FILE); + self::$m_oConfig = new Config(self::$m_sConfigFile); } return self::$m_oConfig; } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 331a0e9333..932823bf4a 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1096,6 +1096,10 @@ abstract class DBObject } // Make standard context arguments + // Note: Needs to be reviewed because it is currently called once per attribute when an object is written (CheckToWrite / CheckValue) + // Several options here: + // 1) cache the result + // 2) set only the object ref and resolve the values iif needed from contextual templates and queries (easy for the queries, not for the templates) public function ToArgs($sArgName = 'this') { $aScalarArgs = array(); @@ -1109,6 +1113,11 @@ abstract class DBObject foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { $aScalarArgs[$sArgName.'->'.$sAttCode] = $this->Get($sAttCode); + if ($oAttDef->IsScalar()) + { + $aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $this->GetAsHtml($sAttCode); + $aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = strip_tags($this->GetAsHtml($sAttCode)); + } } return $aScalarArgs; } diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml index 3d776a69f6..e42d0557b7 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml @@ -10,7 +10,7 @@ SELECT Team WHERE id=:this->workgroup_id -The ticket $this->name()$, priority $this->priority$ has been assigned to the workgroup $this->workgroup_name$ +The ticket $this->name()$, priority $this->label(priority)$ has been assigned to the workgroup $this->workgroup_name$ <html> <body> <p>The incident ticket $this->name()$ has been assigned to the workgroup $this->workgroup_name$.</p> @@ -32,7 +32,7 @@ SELECT Person WHERE id=:this->agent_id -The ticket $this->name()$, priority $this->priority$ has been assigned to you +The ticket $this->name()$, priority $this->label(priority)$ has been assigned to you <html> <body> <p>The incident ticket $this->name()$ has been assigned to you.</p> @@ -54,7 +54,7 @@ SELECT Person WHERE id=:this->caller_id -Ticket $this->name()$, priority $this->priority$ - $this->ticket_status$ +Ticket $this->name()$, priority $this->label(priority)$ - $this->ticket_status$ <html> <body> <p>The incident ticket $this->name()$ has changed to status $this->ticket_status$</p> diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-triggers.xml b/modules/itop-tickets-1.0.0/data.struct.ta-triggers.xml index eda86b24dc..249cbe1ed8 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-triggers.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-triggers.xml @@ -8,12 +8,12 @@ Incident ticket assigned to agent Incident -Assigned +assigned Incident ticket resolved Incident -Resolved +resolved diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 6ddf3b35d8..fa01e90373 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -100,6 +100,8 @@ define('TMP_CONFIG_FILE', '../tmp-config-itop.php'); header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past +Utils::SpecifyConfigFile(TMP_CONFIG_FILE); + /** * Main program */ diff --git a/setup/index.php b/setup/index.php index d3782551ac..589da15e48 100644 --- a/setup/index.php +++ b/setup/index.php @@ -1082,6 +1082,8 @@ function SetupFinished(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $o clearstatcache(); // Make sure we know what we are doing ! if (file_exists(FINAL_CONFIG_FILE)) { + Utils::SpecifyConfigFile(FINAL_CONFIG_FILE); + // The configuration file already exists if (is_writable(FINAL_CONFIG_FILE)) { @@ -1099,6 +1101,8 @@ if (file_exists(FINAL_CONFIG_FILE)) } else { + Utils::SpecifyConfigFile(TMP_CONFIG_FILE); + // No configuration file yet // Check that the wizard can write into the root dir to create the configuration file if (!is_writable(dirname(FINAL_CONFIG_FILE))) From 79ab5d6b755d0be34ee0f2f0c4b1354e2ec739b7 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 1 Sep 2010 10:48:38 +0000 Subject: [PATCH 653/970] - Add the field 'to' to a list of EmailNotificationEvents. SVN:trunk[736] --- core/event.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/event.class.inc.php b/core/event.class.inc.php index 92f6edaae5..9fe51d49e6 100644 --- a/core/event.class.inc.php +++ b/core/event.class.inc.php @@ -120,7 +120,7 @@ class EventNotificationEmail extends EventNotification // Display lists MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'message', 'subject')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'message', 'to', 'subject')); // Attributes to be displayed for a list // Search criteria // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form From 22aef82b830fb413ef5a6a1cfb5366f8a85bd9f2 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 1 Sep 2010 10:52:08 +0000 Subject: [PATCH 654/970] - Display the full list of attributes (details ZList) when exporting objects in HTML format (useful for Excel Web Queries). SVN:trunk[737] --- application/cmdbabstract.class.inc.php | 3 ++- webservices/export.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index f25cfefe22..574840b8f8 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -381,7 +381,8 @@ abstract class cmdbAbstractObject extends CMDBObject $oAppContext = new ApplicationContext(); $sClassName = $oSet->GetFilter()->GetClass(); $aAttribs = array(); - $aList = MetaModel::GetZListItems($sClassName, 'list'); + $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; + $aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, '$sZListName')); $aList = array_merge($aList, $aExtraFields); if (!empty($sLinkageAttribute)) { diff --git a/webservices/export.php b/webservices/export.php index 8469754d93..e6743ba61c 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -70,7 +70,7 @@ if (!empty($sExpression)) } $sUrl = "$sProtocol://{$sServerName}{$sPort}/pages/"; $oP->set_base($sUrl); - cmdbAbstractObject::DisplaySet($oP, $oSet, array('menu' => false, 'display_limit' => false)); // no menu, no truncated list + cmdbAbstractObject::DisplaySet($oP, $oSet, array('menu' => false, 'display_limit' => false, 'zlist' => 'details')); // no menu, no truncated list, "details" zlist break; case 'csv': From b0d89dece3cdbbd83fc157719c228f8c280f3ddf Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 1 Sep 2010 17:27:18 +0000 Subject: [PATCH 655/970] - Sepcial corner case when loading objects with external keys and these externla keys themsleves have reconciliation keys that are also externla keys. Example: On a person the field location_id Can be specified by Location->Name and Location->Owner Organization (The 2 reconciliation keys for the class Location). The latter is also an external key, and therefore only an Id can be supplied for this field. Note that Location->Owner Organization->Name is not yet supported. SVN:trunk[738] --- core/bulkchange.class.inc.php | 1 + pages/ajax.csvimport.php | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 7abffa53a2..b7519bb67e 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -274,6 +274,7 @@ class BulkChange if ($oExtKey->IsNullAllowed()) { $oTargetObj->Set($sAttCode, $oExtKey->GetNullValue()); + $aResults[$sAttCode]= new CellStatus_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found'); } else { diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index 5841f12b20..b24caa3ca1 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -58,9 +58,11 @@ function IsIdField($sClassName, $sFieldCode) /** * Get all the fields xxx->yyy based on the field xxx which is an external key * @param string $sExtKeyAttCode Attribute code of the external key + * @param AttributeDefinition $oExtKeyAttDef Attribute definition of the external key + * @param bool $bAdvanced True if advanced mode * @return Ash List of codes=>display name: xxx->yyy where yyy are the reconciliation keys for the object xxx */ -function GetMappingsForExtKey($sAttCode, AttributeDefinition $oExtKeyAttDef) +function GetMappingsForExtKey($sAttCode, AttributeDefinition $oExtKeyAttDef, $bAdvanced) { $aResult = array(); $sTargetClass = $oExtKeyAttDef->GetTargetClass(); @@ -68,7 +70,11 @@ function GetMappingsForExtKey($sAttCode, AttributeDefinition $oExtKeyAttDef) { if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode)) { - $aResult[$sAttCode.'->'.$sTargetAttCode] = $oExtKeyAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); + if ($bAdvanced || !$oTargetAttDef->IsExternalKey()) + { + // When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys ! + $aResult[$sAttCode.'->'.$sTargetAttCode] = $oExtKeyAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); + } } } return $aResult; @@ -118,10 +124,15 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMo { if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode)) { - $aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); - if ((strcasecmp($sFieldName, $aChoices[$sAttCode.'->'.$sTargetAttCode]) == 0) || (strcasecmp($sFieldName, ($sAttCode.'->'.$sTargetAttCode)) == 0) ) + if ($bAdvancedMode || (!$oTargetAttDef->IsExternalKey())) { - $sFieldCode = $sAttCode.'->'.$sTargetAttCode; + + // When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys ! + $aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); + if ((strcasecmp($sFieldName, $aChoices[$sAttCode.'->'.$sTargetAttCode]) == 0) || (strcasecmp($sFieldName, ($sAttCode.'->'.$sTargetAttCode)) == 0) ) + { + $sFieldCode = $sAttCode.'->'.$sTargetAttCode; + } } } } @@ -303,7 +314,7 @@ switch($sOperation) $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); if ($oAttDef->IsExternalKey()) { - $aMoreReconciliationKeys = array_keys(GetMappingsForExtKey($sAttCode, $oAttDef)); + $aMoreReconciliationKeys = array_keys(GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced)); } } $sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys,$aMoreReconciliationKeys)).'"'; From e31e23df7a931d0dc3aafe2da7f37a1667ba24f7 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 05:50:40 +0000 Subject: [PATCH 656/970] Could not search on Contacts while being allowed to search on persons/teams SVN:trunk[739] --- addons/userrights/userrightsprofile.class.inc.php | 3 --- core/userrights.class.inc.php | 3 --- 2 files changed, 6 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index d2320abeb8..3913f173e5 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -923,9 +923,6 @@ class SetupProfiles // foreach (MetaModel::GetClasses('bizmodel') as $sClass) { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - self::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass); self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass); } diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index e247ea685a..75322d5250 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -136,9 +136,6 @@ abstract class User extends cmdbAbstractObject $aDisplayData = array(); foreach (MetaModel::GetClasses($sClassCategory) as $sClass) { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - $aClassStimuli = MetaModel::EnumStimuli($sClass); if (count($aClassStimuli) > 0) { From 2a3bddf454df706a9657e7c4fa6be9857b99a090 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 05:52:14 +0000 Subject: [PATCH 657/970] Usability: trim spaces in search forms SVN:trunk[740] --- application/displayblock.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index ef466f2ff0..9bb120041f 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -303,11 +303,11 @@ class DisplayBlock $sExternalFilterValue = utils::ReadParam($sFilterCode, ''); if (isset($aExtraParams[$sFilterCode])) { - $this->m_oFilter->AddCondition($sFilterCode, $aExtraParams[$sFilterCode]); // Use the default 'loose' operator + $this->m_oFilter->AddCondition($sFilterCode, trim($aExtraParams[$sFilterCode])); // Use the default 'loose' operator } else if ($bDoSearch && $sExternalFilterValue != "") { - $this->m_oFilter->AddCondition($sFilterCode, $sExternalFilterValue); // Use the default 'loose' operator + $this->m_oFilter->AddCondition($sFilterCode, trim($sExternalFilterValue)); // Use the default 'loose' operator } } } From a36f50d490c90a3fa0e3c95165c19edf3f555f9e Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 05:54:38 +0000 Subject: [PATCH 658/970] Cosmetic: changed the name of views (removed the underscore prefix) SVN:trunk[741] --- core/metamodel.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 35d0e9ecc3..6ecd52ad14 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -423,7 +423,7 @@ abstract class MetaModel final static public function DBGetView($sClass) { - return self::$m_sTablePrefix."_view_".$sClass; + return self::$m_sTablePrefix."view_".$sClass; } final static protected function DBEnumTables() From 3695475e9f0d2107cc2adde63a149de85945bf6f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 05:56:14 +0000 Subject: [PATCH 659/970] Fixed bug: CSV import (interactive or web service) reporting change "on behalf of" SVN:trunk[742] --- pages/csvimport.php | 4 ++-- webservices/import.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 36106081a3..08ef30528b 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -314,9 +314,9 @@ function ProcessCSVData(WebPage $oPage, $bSimulate = true) // We're doing it for real, let's create a change $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) + if (UserRights::IsImpersonated()) { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { diff --git a/webservices/import.php b/webservices/import.php index a151a424e7..ff531be182 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -112,9 +112,9 @@ try $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::GetUser() != UserRights::GetRealUser()) + if (UserRights::IsImpersonated()) { - $sUserString = UserRights::GetRealUser()." on behalf of ".UserRights::GetUser(); + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { From d813a7b10dc3d1ce635127dae5e2403b4473cf4f Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 06:26:11 +0000 Subject: [PATCH 660/970] Usability: reviewed navigation into the data model SVN:trunk[743] --- pages/schema.php | 74 ++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/pages/schema.php b/pages/schema.php index 8713cae6af..c1471a9284 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -269,34 +269,21 @@ function DisplayClassesList($oPage) $oPage->add("

            ".Dict::S('UI:Schema:Title')."

            \n"); $oPage->add("
              \n"); - foreach(MetaModel::EnumCategories() as $sCategory) + foreach(MetaModel::GetClasses() as $sClassName) { - if (empty($sCategory)) continue; // means 'all' -> skip - - $sClosed = ($sCategory == 'bizmodel') ? '' : ' class="closed"'; - $oPage->add("".Dict::Format('UI:Schema:CategoryMenuItem', $sCategory)."\n"); - - $oPage->add("
                \n"); - foreach(MetaModel::GetClasses($sCategory) as $sClassName) + if (MetaModel::IsRootClass($sClassName)) { - if (MetaModel::IsStandaloneClass($sClassName)) - { - $oPage->add("
              • ".MakeClassHLink($sClassName)."
              • \n"); - } - else if (MetaModel::IsRootClass($sClassName)) - { - $oPage->add("
              • ".MakeClassHLink($sClassName)."\n"); - DisplaySubclasses($oPage, $sClassName); - $oPage->add("
              • \n"); - } + $oPage->add("
              • ".MakeClassHLink($sClassName)."\n"); + DisplaySubclasses($oPage, $sClassName); + $oPage->add("
              • \n"); + } + elseif (MetaModel::IsStandaloneClass($sClassName)) + { + $oPage->add("
              • ".MakeClassHLink($sClassName)."
              • \n"); } - $oPage->add("
              \n"); - - $oPage->add("\n"); } $oPage->add("
            \n"); - $oPage->add("

            ".Dict::S('UI:Schema:Relationships')."

            \n"); $oPage->add("
              \n"); @@ -320,7 +307,7 @@ function DisplayClassesList($oPage) */ function DisplayClassDetails($oPage, $sClass) { - $oPage->p("

              $sClass


              \n".MetaModel::GetClassDescription($sClass)."
              \n"); + $oPage->add("

              $sClass - ".MetaModel::GetClassDescription($sClass)."

              \n"); if (MetaModel::IsAbstract($sClass)) { $oPage->p(Dict::S('UI:Schema:AbstractClass')); @@ -329,32 +316,33 @@ function DisplayClassDetails($oPage, $sClass) { $oPage->p(Dict::S('UI:Schema:NonAbstractClass')); } - $oPage->p("

              ".Dict::S('UI:Schema:ClassHierarchyTitle')."

              "); - $oPage->p("[".Dict::S('UI:Schema:AllClasses')."]"); - // List the parent classes - $sParent = MetaModel::GetParentPersistentClass($sClass); - $aParents = array(); - $aParents[] = $sClass; - while($sParent != "" && $sParent != 'cmdbAbstractObject') + +// $oPage->p("

              ".Dict::S('UI:Schema:ClassHierarchyTitle')."

              "); + + $aParentClasses = array(); + foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass) { - $aParents[] = $sParent; - $sParent = MetaModel::GetParentPersistentClass($sParent); + $aParentClasses[] = MakeClassHLink($sParentClass); } - $iIndex = count($aParents); - $sSpace =""; - $oPage->add("
                "); - while ($iIndex > 0) + if (count($aParentClasses) > 0) { - $iIndex--; - $oPage->add("
              • ".MakeClassHLink($aParents[$iIndex])."\n"); - $oPage->add("
                  \n"); + $sParents = implode(' >> ', $aParentClasses)." >> $sClass"; } - for($iIndex = 0; $iIndex < count($aParents); $iIndex++) + else { - $oPage->add("
                \n
              • \n"); + $sParents = ''; + } + $oPage->p("[".Dict::S('UI:Schema:AllClasses')."] $sParents"); + + if (MetaModel::HasChildrenClasses($sClass)) + { + $oPage->add("
                  "); + $oPage->add("
                • ".$sClass."\n"); + DisplaySubclasses($oPage, $sClass); + $oPage->add("
                • \n"); + $oPage->add("
                \n"); + $oPage->add_ready_script('$("#ClassHierarchy").treeview();'); } - $oPage->add("
              \n"); - $oPage->add_ready_script('$("#ClassHierarchy").treeview();'); $oPage->p(''); $oPage->AddTabContainer('details'); $oPage->SetCurrentTabContainer('details'); From 16baa35ad4175f8c14a0056ea9efc4785f84b03e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 2 Sep 2010 11:21:38 +0000 Subject: [PATCH 661/970] - Fixed a regression introduced by revision [737] SVN:trunk[744] --- application/cmdbabstract.class.inc.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 574840b8f8..3e47fdc3bd 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -382,7 +382,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sClassName = $oSet->GetFilter()->GetClass(); $aAttribs = array(); $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; - $aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, '$sZListName')); + $aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); $aList = array_merge($aList, $aExtraFields); if (!empty($sLinkageAttribute)) { @@ -390,6 +390,7 @@ abstract class cmdbAbstractObject extends CMDBObject // and other objects... // The display will then group all the attributes related to the link itself: // | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n | + $aDisplayList = array(); $aAttDefs = MetaModel::ListAttributeDefs($sClassName); assert(isset($aAttDefs[$sLinkageAttribute])); $oAttDef = $aAttDefs[$sLinkageAttribute]; From 400e2916d0d773e1ea52939a5a80ca50f995ee37 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 2 Sep 2010 11:24:31 +0000 Subject: [PATCH 662/970] - Fixed the (cosmetic) bug #237: hyperlinks color. SVN:trunk[745] --- css/light-grey.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/light-grey.css b/css/light-grey.css index d39ce73da2..09bcc619af 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -136,7 +136,7 @@ td.label { font-weight:bold; } -p a, p a:visited, td a, td a:visited { +.ui-widget-content td a, .ui-widget-content td a:visited, p a, p a:visited, td a, td a:visited { text-decoration:none; color: #1C94C4; padding-left:14px; From 66c7d99c466c5ed76e453532277c88ad85f26326 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 15:25:57 +0000 Subject: [PATCH 663/970] #239 Issue with character set (impacting searches with accents) SVN:trunk[746] --- core/cmdbsource.class.inc.php | 15 +++++++++++++++ core/config.class.inc.php | 32 ++++++++++++++++++++++++++++++++ core/metamodel.class.php | 3 +++ core/test.class.inc.php | 1 + 4 files changed, 51 insertions(+) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index b72c4bdd8c..925d9aa00c 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -69,6 +69,21 @@ class CMDBSource } } + public static function SetCharacterSet($sCharset = 'utf8', $sCollation = 'utf8_general_ci') + { + if (strlen($sCharset) > 0) + { + if (strlen($sCollation) > 0) + { + self::Query("SET NAMES '$sCharset' COLLATE '$sCollation'"); + } + else + { + self::Query("SET NAMES '$sCharset'"); + } + } + } + public static function ListDB() { $aDBs = self::QueryToCol('SHOW DATABASES', 'Database'); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index e0c95bfc77..09597a4dc7 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -33,6 +33,9 @@ class ConfigException extends CoreException { } +define ('DEFAULT_CHARACTER_SET', 'utf8'); +define ('DEFAULT_COLLATION', 'utf8_general_ci'); + define ('DEFAULT_LOG_GLOBAL', true); define ('DEFAULT_LOG_NOTIFICATION', true); define ('DEFAULT_LOG_ISSUE', true); @@ -73,6 +76,8 @@ class Config protected $m_sDBPwd; protected $m_sDBName; protected $m_sDBSubname; + protected $m_sDBCharacterSet; + protected $m_sDBCollation; /** * @var integer Event log options (see LOG_... definition) @@ -182,6 +187,8 @@ class Config $this->m_sDBPwd = ''; $this->m_sDBName = ''; $this->m_sDBSubname = ''; + $this->m_sDBCharacterSet = DEFAULT_CHARACTER_SET; + $this->m_sDBCollation = DEFAULT_COLLATION; $this->m_bLogGlobal = DEFAULT_LOG_GLOBAL; $this->m_bLogNotification = DEFAULT_LOG_NOTIFICATION; $this->m_bLogIssue = DEFAULT_LOG_ISSUE; @@ -284,6 +291,9 @@ class Config $this->m_sDBName = trim($MySettings['db_name']); $this->m_sDBSubname = trim($MySettings['db_subname']); + $this->m_sDBCharacterSet = isset($MySettings['db_character_set']) ? trim($MySettings['db_character_set']) : DEFAULT_CHARACTER_SET; + $this->m_sDBCollation = isset($MySettings['db_collation']) ? trim($MySettings['db_collation']) : DEFAULT_COLLATION; + $this->m_bLogGlobal = isset($MySettings['log_global']) ? (bool) trim($MySettings['log_global']) : DEFAULT_LOG_GLOBAL; $this->m_bLogNotification = isset($MySettings['log_notification']) ? (bool) trim($MySettings['log_notification']) : DEFAULT_LOG_NOTIFICATION; $this->m_bLogIssue = isset($MySettings['log_issue']) ? (bool) trim($MySettings['log_issue']) : DEFAULT_LOG_ISSUE; @@ -391,6 +401,16 @@ class Config return $this->m_sDBSubname; } + public function GetDBCharacterSet() + { + return $this->m_sDBCharacterSet; + } + + public function GetDBCollation() + { + return $this->m_sDBCollation; + } + public function GetDBUser() { return $this->m_sDBUser; @@ -501,6 +521,16 @@ class Config $this->m_sDBSubname = $sDBSubName; } + public function SetDBCharacterSet($sDBCharacterSet) + { + $this->m_sDBCharacterSet = $sDBCharacterSet; + } + + public function SetDBCollation($sDBCollation) + { + $this->m_sDBCollation = $sDBCollation; + } + public function SetDBUser($sUser) { $this->m_sDBUser = $sUser; @@ -626,6 +656,8 @@ class Config fwrite($hFile, "\t'db_pwd' => '".addslashes($this->m_sDBPwd)."',\n"); fwrite($hFile, "\t'db_name' => '{$this->m_sDBName}',\n"); fwrite($hFile, "\t'db_subname' => '{$this->m_sDBSubname}',\n"); + fwrite($hFile, "\t'db_character_set' => '{$this->m_sDBCharacterSet}',\n"); + fwrite($hFile, "\t'db_collation' => '{$this->m_sDBCollation}',\n"); fwrite($hFile, "\n"); fwrite($hFile, "\t'log_global' => {$this->m_bLogGlobal},\n"); fwrite($hFile, "\t'log_notification' => {$this->m_bLogNotification},\n"); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6ecd52ad14..ab6b47854f 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3242,6 +3242,8 @@ abstract class MetaModel $sPwd = self::$m_oConfig->GetDBPwd(); $sSource = self::$m_oConfig->GetDBName(); $sTablePrefix = self::$m_oConfig->GetDBSubname(); + $sCharacterSet = self::$m_oConfig->GetDBCharacterSet(); + $sCollation = self::$m_oConfig->GetDBCollation(); $oKPI = new ExecutionKPI(); @@ -3255,6 +3257,7 @@ abstract class MetaModel self::$m_sTablePrefix = $sTablePrefix; CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) + CMDBSource::SetCharacterSet($sCharacterSet, $sCollation); } public static function GetModuleSetting($sModule, $sProperty, $defaultvalue = null) diff --git a/core/test.class.inc.php b/core/test.class.inc.php index 67e8e184fe..b716ad086d 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -361,6 +361,7 @@ abstract class TestScenarioOnDB extends TestHandler $sDBName = $this->GetDBName(); CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd); + CMDBSource::SetCharacterSet(); if (CMDBSource::IsDB($sDBName)) { CMDBSource::DropDB($sDBName); From b1f877f845d1314a09c83f277043dbfde1983a2b Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 2 Sep 2010 15:57:08 +0000 Subject: [PATCH 664/970] Fixed bug #231: distinguish between external key (id) and external key (name) for loading fields that are external keys pointing to externla keys! Example: Connected To->Device is to be mapped to the Connected Interface's device NAME whereas Connected To->Device->id is to be mapped to the connected interface's device id SVN:trunk[747] --- application/cmdbabstract.class.inc.php | 9 ++++++++- pages/ajax.csvimport.php | 22 +++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3e47fdc3bd..9bb4756913 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -661,7 +661,14 @@ abstract class cmdbAbstractObject extends CMDBObject { $sExtKeyLabel = MetaModel::GetLabel($sClassName, $oAttDef->GetKeyAttCode()); $sRemoteAttLabel = MetaModel::GetLabel($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode()); - $aHeader[] = $sExtKeyLabel.'->'.$sRemoteAttLabel; + $oTargetAttDef = MetaModel::GetAttributeDef($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode()); + $sSuffix = ''; + if ($oTargetAttDef->IsExternalKey()) + { + $sSuffix = '->id'; + } + + $aHeader[] = $sExtKeyLabel.'->'.$sRemoteAttLabel.$sSuffix; } else { diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index b24caa3ca1..6b402014d8 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -70,10 +70,16 @@ function GetMappingsForExtKey($sAttCode, AttributeDefinition $oExtKeyAttDef, $bA { if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode)) { - if ($bAdvanced || !$oTargetAttDef->IsExternalKey()) + $bExtKey = $oTargetAttDef->IsExternalKey(); + $sSuffix = ''; + if ($bExtKey) + { + $sSuffix = '->id'; + } + if ($bAdvanced || !$bExtKey) { // When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys ! - $aResult[$sAttCode.'->'.$sTargetAttCode] = $oExtKeyAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); + $aResult[$sAttCode.'->'.$sTargetAttCode] = $oExtKeyAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix; } } } @@ -124,12 +130,18 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMo { if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode)) { - if ($bAdvancedMode || (!$oTargetAttDef->IsExternalKey())) + $bExtKey = $oTargetAttDef->IsExternalKey(); + $sSuffix = ''; + if ($bExtKey) + { + $sSuffix = '->id'; + } + if ($bAdvancedMode || !$bExtKey) { // When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys ! - $aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); - if ((strcasecmp($sFieldName, $aChoices[$sAttCode.'->'.$sTargetAttCode]) == 0) || (strcasecmp($sFieldName, ($sAttCode.'->'.$sTargetAttCode)) == 0) ) + $aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix; + if ((strcasecmp($sFieldName, $aChoices[$sAttCode.'->'.$sTargetAttCode]) == 0) || (strcasecmp($sFieldName, ($sAttCode.'->'.$sTargetAttCode.$sSuffix)) == 0) ) { $sFieldCode = $sAttCode.'->'.$sTargetAttCode; } From 15b805fed6678ab701c7d52b865077533d1f162d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 16:51:41 +0000 Subject: [PATCH 665/970] #242 loosing linked objects when modifying name attribute SVN:trunk[748] --- core/dbobject.class.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 932823bf4a..6c910ae7a7 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -790,10 +790,14 @@ abstract class DBObject { $aOriginalList = $oOriginalSet->ToArray(); $aNewSet = $oLinks->ToArray(); - $aToDelete = array_diff($aOriginalList, $aNewSet); - foreach ($aToDelete as $iKey => $oObject) + + foreach($aOriginalList as $iId => $oObject) { - $oObject->DBDelete(); + if (!array_key_exists($iId, $aNewSet)) + { + // It disappeared from the list + $oObject->DBDelete(); + } } } } From 368b863aa356dfaaa0b072cf1d74ce770feb9672 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Thu, 2 Sep 2010 16:53:12 +0000 Subject: [PATCH 666/970] - When sending email the "From" field is mandatory otherwise the mails are not sent. SVN:trunk[749] --- core/action.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 97fb1d2aef..f47c0043b5 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -149,7 +149,7 @@ class ActionEmail extends ActionNotification MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeOQL("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); From 8c82b098c09df9ad9495697b43b830d944f130ee Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 2 Sep 2010 17:09:10 +0000 Subject: [PATCH 667/970] - Fixed bug #232: changing password was not working without the Javascript debugger !!! SVN:trunk[750] --- js/forms-json-utils.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index f0f0597685..cad353a868 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -223,7 +223,6 @@ function ResetPwd(id) function PasswordFieldChanged(id) { // Set the flag, to tell that the password changed - console.log('Password changed'); $('#'+id+'_changed').val(1); } @@ -248,4 +247,4 @@ function ValidatePasswordField(id, sFormId) } $('#v_'+id).html(''); //'); return true; -} \ No newline at end of file +} From 6b728bbb501b45d96df49292d0c761aa075454f3 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 3 Sep 2010 09:57:26 +0000 Subject: [PATCH 668/970] From field is now mandatory SVN:trunk[751] --- modules/itop-tickets-1.0.0/data.struct.ta-actions.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml index e42d0557b7..64e3c84ee5 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml @@ -5,7 +5,7 @@ This action informs a team that a ticket has been assigned their workgroup disabled - +test@test.com SELECT Team WHERE id=:this->workgroup_id @@ -27,7 +27,7 @@ This action informs an agent that a ticket has been assigned to her/him disabled - +test@test.com SELECT Person WHERE id=:this->agent_id @@ -49,7 +49,7 @@ This action is used to inform the caller disabled - +test@test.com SELECT Person WHERE id=:this->caller_id From 22b096de28a05603b0835efb52b68ebd5fd1c100 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 3 Sep 2010 10:36:57 +0000 Subject: [PATCH 669/970] - Fixed bug#236: current value is now properly displayed in autcomplete fields, and the validation works properly too. SVN:trunk[752] --- application/cmdbabstract.class.inc.php | 31 ++++++-------------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 9bb4756913..b9453ebf8a 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -959,14 +959,11 @@ abstract class cmdbAbstractObject extends CMDBObject if (!$oAttDef->IsExternalField()) { - $aCSSClasses = array(); $bMandatory = 0; if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { - $aCSSClasses[] = 'mandatory'; $bMandatory = 1; } - $sCSSClasses = self::GetCSSClasses($aCSSClasses); $sValidationField = ""; $sHelpText = $oAttDef->GetHelpOnEdition(); $aEventsList = array('validate'); @@ -988,7 +985,7 @@ abstract class cmdbAbstractObject extends CMDBObject case 'Text': $aEventsList[] ='keyup'; $aEventsList[] ='change'; - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = "
              {$sValidationField}
              "; break; case 'LinkedSet': @@ -1027,27 +1024,23 @@ abstract class cmdbAbstractObject extends CMDBObject default: // #@# todo - add context information (depending on dimensions) $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); + $iFieldSize = $oAttDef->GetMaxSize(); if ($aAllowedValues !== null) { - //Enum field or external key, display a combo - //if (count($aAllowedValues) == 0) - //{ - // $sHTMLValue = ""; - //} - //else if (count($aAllowedValues) > 50) if (count($aAllowedValues) > 50) { // too many choices, use an autocomplete // The input for the auto complete - if ($sDisplayValue == $oAttDef->GetNullValue()) // Null or zero is displayed as '' + if ($oAttDef->IsNull($value)) // Null values are displayed as '' { $sDisplayValue = ''; } - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; // another hidden input to store & pass the object's Id $sHTMLValue .= "\n"; $oPage->add_ready_script("\$('#label_$iId').autocomplete('./ajax.render.php', { scroll:true, minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#$iId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});"); - $oPage->add_ready_script("\$('#label_$iId').result( function(event, data, formatted) { if (data) { $('#{$iId}').val(data[1]); } } );"); + $oPage->add_ready_script("\$('#label_$iId').blur(function() { $(this).search(); } );"); + $oPage->add_ready_script("\$('#label_$iId').result( function(event, data, formatted) { if (data) { $('#{$iId}').val(data[1]); $('#{$iId}').trigger('change'); } else { $('#{$iId}').val(''); $('#{$iId}').trigger('change');} } );"); $aEventsList[] ='change'; } else @@ -1075,7 +1068,7 @@ abstract class cmdbAbstractObject extends CMDBObject } else { - $sHTMLValue = " {$sValidationField}"; + $sHTMLValue = " {$sValidationField}"; $aEventsList[] ='keyup'; $aEventsList[] ='change'; } @@ -1274,16 +1267,6 @@ EOF return $oObj->DisplayModifyForm( $oPage, $aExtraParams = array()); } - protected static function GetCSSClasses($aCSSClasses) - { - $sCSSClasses = ''; - if (!empty($aCSSClasses)) - { - $sCSSClasses = ' class="'.implode(' ', $aCSSClasses).'" '; - } - return $sCSSClasses; - } - protected static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet) { //echo "
              ZList: ";
              
              From d4be10528b722ac65f1db5b53d0f42b78a36488a Mon Sep 17 00:00:00 2001
              From: Denis Flaven 
              Date: Fri, 3 Sep 2010 11:31:17 +0000
              Subject: [PATCH 670/970] - Added an icon for Network Devices - Added more
               reconciliation keys (ext key of ext key!) - Added an "organization" field to
               the Documents objects
              
              SVN:trunk[753]
              ---
               .../en.dict.itop-config-mgmt.php              |   5 +
               .../es_cr.dict.itop-config-mgmt.php           |   5 +
               .../fr.dict.itop-config-mgmt.php              |   4 +
               .../itop-config-mgmt-1.0.0/images/switch.png  | Bin 0 -> 3343 bytes
               .../model.itop-config-mgmt.php                |  87 +++++++++---------
               5 files changed, 59 insertions(+), 42 deletions(-)
               create mode 100644 modules/itop-config-mgmt-1.0.0/images/switch.png
              
              diff --git a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php
              index d78c9d120c..3413f4669a 100644
              --- a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php
              +++ b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php
              @@ -227,6 +227,11 @@ Dict::Add('EN US', 'English', 'English', array(
               	'Class:Document+' => '',
               	'Class:Document/Attribute:name' => 'Name',
               	'Class:Document/Attribute:name+' => '',
              +	'Class:Document/Attribute:org_id' => 'Organization',
              +	'Class:Document/Attribute:description+' => '',
              +	'Class:Document/Attribute:org_name' => 'Organization Name',
              +	'Class:Document/Attribute:org_name+' => '',
              +	'Class:Document/Attribute:description+' => '',
               	'Class:Document/Attribute:description' => 'Description',
               	'Class:Document/Attribute:description+' => '',
               	'Class:Document/Attribute:type' => 'Type',
              diff --git a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php
              index 9e3db24fc1..a9ba3a1d00 100644
              --- a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php
              +++ b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php
              @@ -227,6 +227,11 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
               	'Class:Document+' => '',
               	'Class:Document/Attribute:name' => 'Nombre',
               	'Class:Document/Attribute:name+' => '',
              +	'Class:Document/Attribute:org_id' => 'Organización',
              +	'Class:Document/Attribute:org_id+' => '',
              +	'Class:Document/Attribute:org_name' => 'Nombre de la organización',
              +	'Class:Document/Attribute:org_name+' => '',
              +	'Class:Document/Attribute:description+' => '',
               	'Class:Document/Attribute:description' => 'Descripción',
               	'Class:Document/Attribute:description+' => '',
               	'Class:Document/Attribute:type' => 'Tipo',
              diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php
              index bc041280ab..a28ee79a8d 100644
              --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php
              +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php
              @@ -226,6 +226,10 @@ Dict::Add('FR FR', 'French', 'Français', array(
               	'Class:Document+' => '',
               	'Class:Document/Attribute:name' => 'Nom',
               	'Class:Document/Attribute:name+' => '',
              +	'Class:Document/Attribute:org_id' => 'Organisation',
              +	'Class:Document/Attribute:org_name' => 'Nom de l\'organization',
              +	'Class:Document/Attribute:org_name+' => '',
              +	'Class:Document/Attribute:description+' => '',
               	'Class:Document/Attribute:description' => 'Description',
               	'Class:Document/Attribute:description+' => '',
               	'Class:Document/Attribute:type' => 'Type',
              diff --git a/modules/itop-config-mgmt-1.0.0/images/switch.png b/modules/itop-config-mgmt-1.0.0/images/switch.png
              new file mode 100644
              index 0000000000000000000000000000000000000000..b32274acd24f6ce1d03a1fc6a8b159a02fc2a3a9
              GIT binary patch
              literal 3343
              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
              Z1_0CHicerQjB5Y@002ovPDHLkV1mMlU(f&m
              
              literal 0
              HcmV?d00001
              
              diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php
              index daefa36f49..16f6452554 100644
              --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php
              +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php
              @@ -72,7 +72,7 @@ class Location extends cmdbAbstractObject
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name", "org_id"),
              +			"reconc_keys" => array("name", "org_id", "org_name"),
               			"db_table" => "location",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -112,7 +112,7 @@ abstract class Contact extends cmdbAbstractObject
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id","email"),
              +			"reconc_keys" => array("name","org_id", "org_name", "email"),
               			"db_table" => "contact",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -186,7 +186,7 @@ class Team extends Contact
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name"),
              +			"reconc_keys" => array("name", "org_id", "org_name"),
               			"db_table" => "team",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -250,7 +250,7 @@ abstract class Document extends cmdbAbstractObject
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name"),
              +			"reconc_keys" => array("name","org_id","org_name"),
               			"db_table" => "document",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -260,6 +260,8 @@ abstract class Document extends cmdbAbstractObject
               		MetaModel::Init_InheritAttributes();
               
               		MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
              +		MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
              +		MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array())));
               		MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
               		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('contract,networkmap,presentation,training,whitePaper,workinginstructions'), "sql"=>"type", "default_value"=>"presentation", "is_null_allowed"=>true, "depends_on"=>array())));
               		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('draft,published,obsolete'), "sql"=>"status", "default_value"=>"draft", "is_null_allowed"=>false, "depends_on"=>array())));
              @@ -268,10 +270,10 @@ abstract class Document extends cmdbAbstractObject
               		MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ticket_list", array("linked_class"=>"lnkTicketToDoc", "ext_key_to_me"=>"document_id", "ext_key_to_remote"=>"ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
               		MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkCIToDoc", "ext_key_to_me"=>"document_id", "ext_key_to_remote"=>"ci_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
               
              -		MetaModel::Init_SetZListItems('details', array('name', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list'));
              -		MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'type', 'status'));
              -		MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'type', 'status'));
              -		MetaModel::Init_SetZListItems('list', array('type', 'status'));
              +		MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list'));
              +		MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status'));
              +		MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status'));
              +		MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status'));
               	}
               }
               class ExternalDoc extends Document
              @@ -285,7 +287,7 @@ class ExternalDoc extends Document
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name"),
              +			"reconc_keys" => array("name","org_id","org_name"),
               			"db_table" => "externaldoc",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -296,10 +298,10 @@ class ExternalDoc extends Document
               
               		MetaModel::Init_AddAttribute(new AttributeURL("url", array("target"=>"_blank", "allowed_values"=>null, "sql"=>"url", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
               
              -		MetaModel::Init_SetZListItems('details', array('name', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'url'));
              -		MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'type', 'status', 'url'));
              -		MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'type', 'status', 'url'));
              -		MetaModel::Init_SetZListItems('list', array('type', 'status', 'url'));
              +		MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'url'));
              +		MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status', 'url'));
              +		MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status', 'url'));
              +		MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status', 'url'));
               	}
               }
               class Note extends Document
              @@ -313,7 +315,7 @@ class Note extends Document
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name"),
              +			"reconc_keys" => array("name","org_id","org_name"),
               			"db_table" => "note",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -324,10 +326,10 @@ class Note extends Document
               
               		MetaModel::Init_AddAttribute(new AttributeWikiText("note", array("allowed_values"=>null, "sql"=>"note", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
               
              -		MetaModel::Init_SetZListItems('details', array('name', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'note'));
              -		MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'type', 'status', 'note'));
              -		MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'type', 'status', 'note'));
              -		MetaModel::Init_SetZListItems('list', array('type', 'status', 'note'));
              +		MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'note'));
              +		MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status', 'note'));
              +		MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status', 'note'));
              +		MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status', 'note'));
               	}
               }
               class FileDoc extends Document
              @@ -341,7 +343,7 @@ class FileDoc extends Document
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name"),
              +			"reconc_keys" => array("name","org_id","org_name"),
               			"db_table" => "filedoc",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -352,10 +354,10 @@ class FileDoc extends Document
               
               		MetaModel::Init_AddAttribute(new AttributeBlob("contents", array("depends_on"=>array())));
               
              -		MetaModel::Init_SetZListItems('details', array('name', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'contents'));
              -		MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'type', 'status'));
              -		MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'type', 'status'));
              -		MetaModel::Init_SetZListItems('list', array('type', 'status', 'contents'));
              +		MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'contents'));
              +		MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status'));
              +		MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status'));
              +		MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status', 'contents'));
               	}
               	
               	/**
              @@ -385,7 +387,7 @@ class Licence extends cmdbAbstractObject
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id", "org_name"),
               			"db_table" => "licence",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -423,7 +425,7 @@ class Subnet extends cmdbAbstractObject
               			"key_type" => "autoincrement",
               			"name_attcode" => "ip",
               			"state_attcode" => "",
              -			"reconc_keys" => array("ip", "ip_mask","org_id"),
              +			"reconc_keys" => array("ip", "ip_mask","org_id", "org_name"),
               			"db_table" => "subnet",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -656,7 +658,7 @@ abstract class FunctionalCI extends cmdbAbstractObject
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id", "owner_name"),
               			"db_table" => "functionalci",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -710,7 +712,7 @@ abstract class SoftwareInstance extends FunctionalCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name", "device_id","org_id"),
              +			"reconc_keys" => array("name", "device_id", "device_name", "org_id", "owner_name"),
               			"db_table" => "softwareinstance",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -775,7 +777,7 @@ class DBServerInstance extends SoftwareInstance
               			"key_type" => "autoincrement",
               			"name_attcode" => "software_name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","software_id", "device_id","org_id"),
              +			"reconc_keys" => array("name","software_id","software_name","device_id","device_name","org_id","owner_name"),
               			"db_table" => "softwareinstance_dbserver",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -804,7 +806,7 @@ class ApplicationInstance extends SoftwareInstance
               			"key_type" => "autoincrement",
               			"name_attcode" => "software_name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","software_id", "device_id","org_id"),
              +			"reconc_keys" => array("name","software_id","software_name","device_id","device_name","org_id","owner_name"),
               			"db_table" => "softwareinstance_application",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -834,7 +836,7 @@ class DatabaseInstance extends FunctionalCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id","db_server_instance_id"),
              +			"reconc_keys" => array("name","org_id","owner_name","db_server_instance_id","db_server_instance_name"),
               			"db_table" => "databaseinstance",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -885,7 +887,7 @@ class ApplicationSolution extends FunctionalCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "applicationsolution",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -938,7 +940,7 @@ class BusinessProcess extends FunctionalCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "businessprocess",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -983,7 +985,7 @@ abstract class ConnectableCI extends FunctionalCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "connectableci",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1014,7 +1016,7 @@ class NetworkInterface extends ConnectableCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","device_id","org_id"),
              +			"reconc_keys" => array("name","device_id","org_id", "device_name", "owner_name"),
               			"db_table" => "networkinterface",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1078,7 +1080,7 @@ abstract class Device extends ConnectableCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "device",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1130,7 +1132,7 @@ class PC extends Device
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "pc",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1164,7 +1166,7 @@ abstract class MobileCI extends Device
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "mobileci",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1191,7 +1193,7 @@ class MobilePhone extends MobileCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id"),
              +			"reconc_keys" => array("name","org_id","owner_name"),
               			"db_table" => "mobilephone",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1221,7 +1223,7 @@ abstract class InfrastructureCI extends Device
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id","location_id"),
              +			"reconc_keys" => array("name","org_id","owner_name","location_id","location_name"),
               			"db_table" => "infrastructureci",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1254,11 +1256,12 @@ class NetworkDevice extends InfrastructureCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id","location_id"),
              +			"reconc_keys" => array("name","org_id","owner_name","location_id","location_name"),
               			"db_table" => "networkdevice",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
               			"display_template" => "",
              +			"icon" => "../modules/itop-config-mgmt-1.0.0/images/switch.png",
               		);
               		MetaModel::Init_Params($aParams);
               		MetaModel::Init_InheritAttributes();
              @@ -1286,7 +1289,7 @@ class Server extends InfrastructureCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id","location_id"),
              +			"reconc_keys" => array("name","org_id","owner_name","location_id","location_name"),
               			"db_table" => "server",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              @@ -1320,7 +1323,7 @@ class Printer extends InfrastructureCI
               			"key_type" => "autoincrement",
               			"name_attcode" => "name",
               			"state_attcode" => "",
              -			"reconc_keys" => array("name","org_id","location_id"),
              +			"reconc_keys" => array("name","org_id","owner_name","location_id","location_name"),
               			"db_table" => "printer",
               			"db_key_field" => "id",
               			"db_finalclass_field" => "",
              
              From 66ea947bc30cbc19a670f653901da8508630e81f Mon Sep 17 00:00:00 2001
              From: Denis Flaven 
              Date: Fri, 3 Sep 2010 11:49:48 +0000
              Subject: [PATCH 671/970] - Prevent menu itemshyperlinks  to get highlighted
              
              SVN:trunk[754]
              ---
               css/light-grey.css | 2 +-
               1 file changed, 1 insertion(+), 1 deletion(-)
              
              diff --git a/css/light-grey.css b/css/light-grey.css
              index 09bcc619af..cb72c5d9f7 100644
              --- a/css/light-grey.css
              +++ b/css/light-grey.css
              @@ -136,7 +136,7 @@ td.label {
                   font-weight:bold;	
               }
               
              -.ui-widget-content td a, .ui-widget-content td a:visited, p a, p a:visited, td a, td a:visited {
              +.ui-widget-content td a, p a, p a:visited, td a, td a:visited {
               	text-decoration:none;
               	color: #1C94C4;
               	padding-left:14px;
              
              From 65b669f27fe57955bcb92d0e3d9a9babc507c56f Mon Sep 17 00:00:00 2001
              From: Denis Flaven 
              Date: Fri, 3 Sep 2010 13:16:12 +0000
              Subject: [PATCH 672/970] - Simplified Notifications page (Trac #163)
              
              SVN:trunk[755]
              ---
               application/templates/notifications_menu.html | 19 ++-----------------
               1 file changed, 2 insertions(+), 17 deletions(-)
              
              diff --git a/application/templates/notifications_menu.html b/application/templates/notifications_menu.html
              index fb10b38d47..76f8f5ac41 100644
              --- a/application/templates/notifications_menu.html
              +++ b/application/templates/notifications_menu.html
              @@ -2,7 +2,7 @@
               

              UI:NotificationsMenu:Title

        -
        +
        UI:NotificationsMenu:HelpContent
        @@ -11,22 +11,7 @@

        UI:NotificationsMenu:AvailableTriggers

        - - - - - - -
        -

        UI:NotificationsMenu:OnCreate

        - SELECT TriggerOnObjectCreate -
        -

        UI:NotificationsMenu:OnStateEnter

        - SELECT TriggerOnStateEnter -
        -

        UI:NotificationsMenu:OnStateLeave

        - SELECT TriggerOnStateLeave -
        + SELECT Trigger

        UI:NotificationsMenu:AvailableActions

        From a9c0ba63df068ce65e162aa046834cf5a0363b2e Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 3 Sep 2010 21:23:37 +0000 Subject: [PATCH 673/970] Optimizations: DBObjectSet::Count does it without loading all the data + possibility to specify LIMIT + restored the query cache (was inoperant) + improved the debug reports + added two settings (query_cache_enabled and debug_queries) SVN:trunk[756] --- application/cmdbabstract.class.inc.php | 3 +- core/config.class.inc.php | 19 +++++ core/dbobjectset.class.php | 39 ++++++++-- core/kpi.class.inc.php | 38 +++++++--- core/metamodel.class.php | 100 +++++++++++++++++-------- core/sqlquery.class.inc.php | 28 +++++-- 6 files changed, 174 insertions(+), 53 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index b9453ebf8a..183ab29e74 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -438,7 +438,6 @@ abstract class cmdbAbstractObject extends CMDBObject $aAttribs[$sAttCode] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode)); } $aValues = array(); - $oSet->Seek(0); $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; $iMaxObjects = -1; if ($bDisplayLimit) @@ -446,8 +445,10 @@ abstract class cmdbAbstractObject extends CMDBObject if ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit()) { $iMaxObjects = utils::GetConfig()->GetMinDisplayLimit(); + $oSet->SetLimit($iMaxObjects); } } + $oSet->Seek(0); while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0)) { $aRow = array(); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 09597a4dc7..46733fcd22 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -42,6 +42,10 @@ define ('DEFAULT_LOG_ISSUE', true); define ('DEFAULT_LOG_WEB_SERVICE', true); define ('DEFAULT_LOG_KPI_DURATION', false); define ('DEFAULT_LOG_KPI_MEMORY', false); +define ('DEFAULT_DEBUG_QUERIES', false); + +define ('DEFAULT_QUERY_CACHE_ENABLED', true); + define ('DEFAULT_MIN_DISPLAY_LIMIT', 10); define ('DEFAULT_MAX_DISPLAY_LIMIT', 15); @@ -88,6 +92,8 @@ class Config protected $m_bLogWebService; protected $m_bLogKpiDuration; // private setting protected $m_bLogKpiMemory; // private setting + protected $m_bDebugQueries; // private setting + protected $m_bQueryCacheEnabled; // private setting /** * @var integer Number of elements to be displayed when there are more than m_iMaxDisplayLimit elements @@ -300,6 +306,9 @@ class Config $this->m_bLogWebService = isset($MySettings['log_web_service']) ? (bool) trim($MySettings['log_web_service']) : DEFAULT_LOG_WEB_SERVICE; $this->m_bLogKPIDuration = isset($MySettings['log_kpi_duration']) ? (bool) trim($MySettings['log_kpi_duration']) : DEFAULT_LOG_KPI_DURATION; $this->m_bLogKPIMemory = isset($MySettings['log_kpi_memory']) ? (bool) trim($MySettings['log_kpi_memory']) : DEFAULT_LOG_KPI_MEMORY; + $this->m_bDebugQueries = isset($MySettings['debug_queries']) ? (bool) trim($MySettings['debug_queries']) : DEFAULT_DEBUG_QUERIES; + $this->m_bQueryCacheEnabled = isset($MySettings['query_cache_enabled']) ? (bool) trim($MySettings['query_cache_enabled']) : DEFAULT_QUERY_CACHE_ENABLED; + $this->m_iMinDisplayLimit = isset($MySettings['min_display_limit']) ? trim($MySettings['min_display_limit']) : DEFAULT_MIN_DISPLAY_LIMIT; $this->m_iMaxDisplayLimit = isset($MySettings['max_display_limit']) ? trim($MySettings['max_display_limit']) : DEFAULT_MAX_DISPLAY_LIMIT; $this->m_iStandardReloadInterval = isset($MySettings['standard_reload_interval']) ? trim($MySettings['standard_reload_interval']) : DEFAULT_STANDARD_RELOAD_INTERVAL; @@ -451,6 +460,16 @@ class Config return $this->m_bLogKPIMemory; } + public function GetDebugQueries() + { + return $this->m_bDebugQueries; + } + + public function GetQueryCacheEnabled() + { + return $this->m_bQueryCacheEnabled; + } + public function GetMinDisplayLimit() { return $this->m_iMinDisplayLimit; diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 077cc4be31..979826c5b5 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -38,11 +38,13 @@ class DBObjectSet private $m_aId2Row; private $m_iCurrRow; - public function __construct(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) + public function __construct(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0) { $this->m_oFilter = $oFilter; $this->m_aOrderBy = $aOrderBy; $this->m_aArgs = $aArgs; + $this->m_iLimitCount = $iLimitCount; + $this->m_iLimitStart = $iLimitStart; $this->m_bLoaded = false; $this->m_aData = array(); // array of (row => array of (classalias) => object) @@ -194,11 +196,33 @@ class DBObjectSet return MetaModel::GetRootClass($this->GetClass()); } + public function SetLimit($iLimitCount, $iLimitStart = 0) + { + $this->m_iLimitCount = $iLimitCount; + $this->m_iLimitStart = $iLimitStart; + } + + public function GetLimitCount() + { + return $this->m_iLimitCount; + } + + public function GetLimitStart() + { + return $this->m_iLimitStart; + } + public function Load() { if ($this->m_bLoaded) return; - - $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs); + if ($this->m_iLimitCount > 0) + { + $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_iLimitCount, $this->m_iLimitStart); + } + else + { + $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs); + } $resQuery = CMDBSource::Query($sSQL); if (!$resQuery) return; @@ -220,8 +244,13 @@ class DBObjectSet public function Count() { - if (!$this->m_bLoaded) $this->Load(); - return count($this->m_aData); + $sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, 0, 0, true); + $resQuery = CMDBSource::Query($sSQL); + if (!$resQuery) return 0; + + $aRow = CMDBSource::FetchArray($resQuery); + CMDBSource::FreeResult($resQuery); + return $aRow['COUNT']; } public function Fetch($sClassAlias = '') diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php index ade2c64642..6822c94e99 100644 --- a/core/kpi.class.inc.php +++ b/core/kpi.class.inc.php @@ -47,34 +47,50 @@ class ExecutionKPI { foreach (self::$m_aStats as $sOperation => $aOpStats) { - echo "====================
        \n"; - echo "KPIs for $sOperation
        \n"; - echo "====================
        \n"; + echo "

        KPIs for $sOperation

        \n"; $fTotalOp = 0; $iTotalOp = 0; $fMinOp = null; $fMaxOp = 0; + echo "
          \n"; foreach ($aOpStats as $sArguments => $aEvents) { $fTotalInter = 0; - $iTotalInter = 0; + $fMinInter = null; + $fMaxInter = 0; foreach ($aEvents as $fDuration) { $fTotalInter += $fDuration; - $iTotalInter++; + $fMinInter = is_null($fMinInter) ? $fDuration : min($fMinInter, $fDuration); + $fMaxInter = max($fMaxInter, $fDuration); $fMinOp = is_null($fMinOp) ? $fDuration : min($fMinOp, $fDuration); $fMaxOp = max($fMaxOp, $fDuration); } $fTotalOp += $fTotalInter; $iTotalOp++; - echo "$sArguments: $iTotalInter (".round($fTotalInter, 3).")
          \n"; + + $iCountInter = count($aEvents); + $sTotalInter = round($fTotalInter, 3)."s"; + if ($iCountInter > 1) + { + $sMinInter = round($fMinInter, 3)."s"; + $sMaxInter = round($fMaxInter, 3)."s"; + $sTimeDesc = "$sTotalInter (from $sMinInter to $sMaxInter) in $iCountInter times"; + } + else + { + $sTimeDesc = "$sTotalInter"; + } + echo "
        • Spent $sTimeDesc, on: $sArguments
        • \n"; } - echo "Total: $iTotalOp (".round($fTotalOp, 3).")
          \n"; - echo "Min: ".round($fMinOp, 3)."
          \n"; - echo "Max: ".round($fMaxOp, 3)."
          \n"; - echo "Avg: ".round($fTotalOp / $iTotalOp, 3)."
          \n"; - echo "====================
          \n"; + echo "
        \n"; + echo "
          Sumary for $sOperation\n"; + echo "
        • Total: $iTotalOp (".round($fTotalOp, 3).")
        • \n"; + echo "
        • Min: ".round($fMinOp, 3)."
        • \n"; + echo "
        • Max: ".round($fMaxOp, 3)."
        • \n"; + echo "
        • Avg: ".round($fTotalOp / $iTotalOp, 3)."
        • \n"; + echo "
        \n"; } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index ab6b47854f..be10e33312 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -202,7 +202,8 @@ abstract class MetaModel private static $m_oConfig = null; - private static $m_bTraceQueries = true; + private static $m_bQueryCacheEnabled = false; + private static $m_bTraceQueries = false; private static $m_aQueriesLog = array(); private static $m_bLogIssue = false; @@ -1539,7 +1540,7 @@ abstract class MetaModel return $aScalarArgs; } - public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false) { // Hide objects that are not visible to the current user // @@ -1562,12 +1563,23 @@ abstract class MetaModel } } + if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries) + { + // Need to identify the query + $sOqlQuery = $oFilter->ToOql(); + $sOqlId = md5($sOqlQuery); + } + else + { + $sOqlQuery = "SELECTING... ".$oFilter->GetClass(); + $sOqlId = "query id ? n/a"; + } + + // Query caching // - $bQueryCacheEnabled = true; - if ($bQueryCacheEnabled) + if (self::$m_bQueryCacheEnabled) { - $sOqlQuery = $oFilter->ToOql(); // Warning: using directly the query string as the key to the hash array can FAIL if the string // is long and the differences are only near the end... so it's safer (but not bullet proof?) // to use a hash (like md5) of the string as the key ! @@ -1578,16 +1590,12 @@ abstract class MetaModel // SELECT SLT JOIN lnkSLTToSLA AS L1 ON L1.slt_id=SLT.id JOIN SLA ON L1.sla_id = SLA.id JOIN lnkContractToSLA AS L2 ON L2.sla_id = SLA.id JOIN CustomerContract ON L2.contract_id = CustomerContract.id WHERE SLT.ticket_priority = 1 AND SLA.service_id = 3 AND SLT.metric = 'TTR' AND CustomerContract.customer_id = 2 // the only difference is R instead or O at position 285 (TTR instead of TTO)... // - if (array_key_exists(md5($sOqlQuery), self::$m_aQueryStructCache)) + if (array_key_exists($sOqlId, self::$m_aQueryStructCache)) { // hit! - $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; + $oSelect = clone self::$m_aQueryStructCache[$sOqlId]; } } - else - { - $sOqlQuery = "dummy"; - } if (!isset($oSelect)) { @@ -1600,7 +1608,7 @@ abstract class MetaModel $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); $oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery); - self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; + self::$m_aQueryStructCache[$sOqlId] = clone $oSelect; } // Check the order by specification, and prefix with the class alias @@ -1636,7 +1644,7 @@ abstract class MetaModel try { - $sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs); + $sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount); } catch (MissingQueryArgument $e) { @@ -1647,16 +1655,25 @@ abstract class MetaModel if (self::$m_bTraceQueries) { - $aParams = array(); - if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog)) + $sQueryId = md5($sRes); + if(!isset(self::$m_aQueriesLog[$sOqlId])) { - self::$m_aQueriesLog[$sOqlQuery] = array( - 'sql' => array(), - 'count' => 0, - ); + self::$m_aQueriesLog[$sOqlId]['oql'] = $sOqlQuery; + self::$m_aQueriesLog[$sOqlId]['hits'] = 1; + } + else + { + self::$m_aQueriesLog[$sOqlId]['hits']++; + } + if(!isset(self::$m_aQueriesLog[$sOqlId]['queries'][$sQueryId])) + { + self::$m_aQueriesLog[$sOqlId]['queries'][$sQueryId]['sql'] = $sRes; + self::$m_aQueriesLog[$sOqlId]['queries'][$sQueryId]['count'] = 1; + } + else + { + self::$m_aQueriesLog[$sOqlId]['queries'][$sQueryId]['count']++; } - self::$m_aQueriesLog[$sOqlQuery]['count']++; - self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes; } return $sRes; @@ -1664,17 +1681,37 @@ abstract class MetaModel public static function ShowQueryTrace() { - $iTotal = 0; - foreach (self::$m_aQueriesLog as $sOql => $aQueryData) + if (!self::$m_bTraceQueries) return; + + $iOqlCount = count(self::$m_aQueriesLog); + if ($iOqlCount == 0) { - echo "

        $sOql

        \n"; - $iTotal += $aQueryData['count']; - echo '

        '.$aQueryData['count'].'

        '; - echo '

        Example: '.$aQueryData['sql'][0].'

        '; + echo "

        Trace activated, but no query found

        \n"; + return; + } + + $iSqlCount = 0; + foreach (self::$m_aQueriesLog as $aOqlData) + { + $iSqlCount += $aOqlData['hits']; + } + echo "

        Stats on SELECT queries: OQL=$iOqlCount, SQL=$iSqlCount

        \n"; + + foreach (self::$m_aQueriesLog as $aOqlData) + { + $sOql = $aOqlData['oql']; + $sHits = $aOqlData['hits']; + + echo "

        $sHits hits for OQL query: $sOql

        \n"; + echo "
          \n"; + foreach($aOqlData['queries'] as $aSqlData) + { + $sQuery = $aSqlData['sql']; + $sSqlHits = $aSqlData['count']; + echo "
        • $sSqlHits hits for SQL: $sQuery
        • \n"; + } + echo "
        \n"; } - echo "

        Total

        \n"; - echo "

        Count of executed queries: $iTotal

        "; - echo "

        Count of built queries: ".count(self::$m_aQueriesLog)."

        "; } public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) @@ -3211,6 +3248,9 @@ abstract class MetaModel ExecutionKPI::EnableMemory(); } + self::$m_bTraceQueries = self::$m_oConfig->GetDebugQueries(); + self::$m_bQueryCacheEnabled = self::$m_oConfig->GetQueryCacheEnabled(); + // Note: load the dictionary as soon as possible, because it might be // needed when some error occur foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude) diff --git a/core/sqlquery.class.inc.php b/core/sqlquery.class.inc.php index 36daf2c50b..e7b9ae5a9f 100644 --- a/core/sqlquery.class.inc.php +++ b/core/sqlquery.class.inc.php @@ -238,7 +238,7 @@ class SQLQuery } // Interface, build the SQL query - public function RenderSelect($aOrderBy = array(), $aArgs = array()) + public function RenderSelect($aOrderBy = array(), $aArgs = array(), $iLimitCount = 0, $iLimitStart = 0, $bGetCount = false) { // The goal will be to complete the lists as we build the Joins $aFrom = array(); @@ -248,15 +248,31 @@ class SQLQuery $aSetValues = array(); $this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues); - $sSelect = self::ClauseSelect($aFields); $sFrom = self::ClauseFrom($aFrom); $sWhere = self::ClauseWhere($oCondition, $aArgs); - $sOrderBy = self::ClauseOrderBy($aOrderBy); - if (!empty($sOrderBy)) + if ($bGetCount) { - $sOrderBy = "ORDER BY $sOrderBy"; + $sSQL = "SELECT COUNT(*) AS COUNT FROM $sFrom WHERE $sWhere"; } - return "SELECT DISTINCT $sSelect FROM $sFrom WHERE $sWhere $sOrderBy"; + else + { + $sSelect = self::ClauseSelect($aFields); + $sOrderBy = self::ClauseOrderBy($aOrderBy); + if (!empty($sOrderBy)) + { + $sOrderBy = "ORDER BY $sOrderBy"; + } + if ($iLimitCount > 0) + { + $sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount; + } + else + { + $sLimit = ''; + } + $sSQL = "SELECT DISTINCT $sSelect FROM $sFrom WHERE $sWhere $sOrderBy $sLimit"; + } + return $sSQL; } private static function ClauseSelect($aFields) From 1432f1ffe596a718ce966b78077ee921a212e9b3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 3 Sep 2010 21:29:27 +0000 Subject: [PATCH 674/970] Updated the benchmark page to the last change in data model SVN:trunk[757] --- setup/benchmark.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/benchmark.php b/setup/benchmark.php index c87f658f42..bef9b9d8ba 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -458,7 +458,7 @@ class BenchmarkDataCreation for($i = 0 ; $i < $this->m_aPlanned['Documents'] ; $i++) { $aData = array( - //'org_id' => $iOrg, + 'org_id' => $iOrg, 'name' => "document$i", 'contents' => $oRefDoc, ); From d1a84bfbc15ec474e6c36ffbe1b22df55695f413 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 3 Sep 2010 21:42:37 +0000 Subject: [PATCH 675/970] #247 Missing query argument (CSV export failing, or Emailed URL not working for lists) SVN:trunk[758] --- core/dbobjectsearch.class.php | 44 +++++++++++++++++++++------------- core/valuesetdef.class.inc.php | 4 ++-- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index e1cb27358e..8011cff155 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -507,19 +507,21 @@ class DBObjectSearch return $this->m_oSearchCondition->Render($this->m_aParams, false); } - public function serialize() + public function serialize($bDevelopParams = false, $aContextParams = null) { - $sOql = $this->ToOql(); - return base64_encode($sOql); + $sOql = $this->ToOql($bDevelopParams, $aContextParams); + return base64_encode(serialize(array($sOql, $this->m_aParams))); } static public function unserialize($sValue) { - $sOql = base64_decode($sValue); + $aData = unserialize(base64_decode($sValue)); + $sOql = $aData[0]; + $aParams = $aData[1]; // We've tried to use gzcompress/gzuncompress, but for some specific queries // it was not working at all (See Trac #193) // gzuncompress was issuing a warning "data error" and the return object was null - return self::FromOQL($sOql); + return self::FromOQL($sOql, $aParams); } // SImple BUt Structured Query Languag - SubuSQL @@ -600,23 +602,28 @@ class DBObjectSearch return $aRes; } - public function ToOQL(&$aParams = null) + public function ToOQL($bDevelopParams = false, $aContextParams = null) { // Currently unused, but could be useful later $bRetrofitParams = false; - if (is_null($aParams)) + if ($bDevelopParams) { - // Leave it as is, the rendering will be made with parameters in clear + if (is_null($aContextParams)) + { + $aParams = array_merge($this->m_aParams); + } + else + { + $aParams = array_merge($aContextParams, $this->m_aParams); + } } else { - if (count($this->m_aParams) > 0) - { - $aParams = array_merge($aParams, $this->m_aParams); - } + // Leave it as is, the rendering will be made with parameters in clear + $aParams = null; } - + $sSelectedClasses = implode(', ', array_keys($this->m_aSelectedClasses)); $sRes = 'SELECT '.$sSelectedClasses.' FROM'; @@ -739,14 +746,14 @@ class DBObjectSearch // Do not filter out depending on user rights // In particular when we are currently in the process of evaluating the user rights... - static public function FromOQL_AllData($sQuery) + static public function FromOQL_AllData($sQuery, $aParams = null) { - $oRes = self::FromOQL($sQuery); + $oRes = self::FromOQL($sQuery, $aParams); $oRes->AllowAllData(); return $oRes; } - static public function FromOQL($sQuery) + static public function FromOQL($sQuery, $aParams = null) { if (empty($sQuery)) return null; @@ -861,6 +868,11 @@ class DBObjectSearch $oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases); } + if (!is_null($aParams)) + { + $oResultFilter->m_aParams = $aParams; + } + if ($bOQLCacheEnabled) { self::$m_aOQLQueries[$sQuery] = clone $oResultFilter; diff --git a/core/valuesetdef.class.inc.php b/core/valuesetdef.class.inc.php index 0797d185eb..626489a90e 100644 --- a/core/valuesetdef.class.inc.php +++ b/core/valuesetdef.class.inc.php @@ -109,11 +109,11 @@ class ValueSetObjects extends ValueSetDefinition if ($this->m_bAllowAllData) { - $oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr, $aArgs); + $oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr); } else { - $oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr, $aArgs); + $oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr); } if (!$oFilter) return false; From 81cd84060421ad84da286685d39057aa135dd17b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 3 Sep 2010 22:20:16 +0000 Subject: [PATCH 676/970] #234 Removed the error_reporting directive and implemented a workaround to reduce the potential warnings issued by PHP SVN:trunk[759] --- core/cmdbobject.class.inc.php | 76 ----------------------------------- core/metamodel.class.php | 9 +++-- pages/UI.php | 54 ++++++++++++++++--------- 3 files changed, 41 insertions(+), 98 deletions(-) diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index dd3a474287..d722a9eefb 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -75,82 +75,6 @@ require_once('cmdbchangeop.class.inc.php'); require_once('csvparser.class.inc.php'); require_once('bulkchange.class.inc.php'); - -// -// Error handling -// To be finalized... or removed ? -// -function cmdbErrorHandler($errno, $errstr, $errfile, $errline) -{ -// font-family: Courier-New, Courier, Arial, Helevtica; - $sErrorStyle = " - background-color: #ffaaaa; - color: #000000; - border: 1px dashed #000000; - padding: 0.25em; - margin-top: 1em; - "; - $sCallStackStyle = " - font-size: smaller; - background-color: #ffcccc; - color: #000000; - border: 1px dashed #000000; - padding: 0.25em; - margin-top: 1em; - "; - - switch ($errno) - { - case E_USER_ERROR: - case E_ERROR: - echo "
        \n"; - echo "Error [$errno] $errstr
        \n"; - echo "
        \n"; - MyHelpers::dump_callstack(1); - echo "
        \n"; - echo "Hereafter the biz model internals:
        \n"; - echo "
        \n";
        -		MetaModel::static_var_dump();
        -		echo "
        \n"; - echo "Aborting...
        \n"; - echo "
        \n"; - exit(1); - break; - case E_USER_WARNING: - case E_WARNING: - echo "
        \n"; - echo "Warning [$errno] $errstr
        \n"; - echo "
        \n"; - MyHelpers::dump_callstack(1); - echo "
        \n"; - echo "
        \n"; - break; - case E_USER_NOTICE: - case E_NOTICE: - echo "
        \n"; - echo "Notice [$errno] $errstr
        \n"; - echo "
        \n"; - MyHelpers::dump_callstack(1); - echo "
        \n"; - echo "
        \n"; - break; - default: - echo "Unknown error type: [$errno] $errstr
        \n"; - MyHelpers::dump_callstack(1); - break; - } -} - -error_reporting(E_ALL | E_STRICT); -//set_error_handler("cmdbErrorHandler"); - - - -// -// -// - - /** * A persistent object, which changes are accurately recorded * diff --git a/core/metamodel.class.php b/core/metamodel.class.php index be10e33312..c6f5247f7c 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2080,7 +2080,8 @@ abstract class MetaModel self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); $oSelectExtKey = self::MakeQuery($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); - $sLocalKeyField = current($oKeyAttDef->GetSQLExpressions()); // get the first column for an external key + $aCols = $oKeyAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc()) + $sLocalKeyField = current($aCols); // get the first column for an external key $sExternalKeyField = self::DBGetKey($sKeyClass); self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); if ($oKeyAttDef->IsNullAllowed()) @@ -2992,7 +2993,8 @@ abstract class MetaModel $sRemoteTable = self::DBGetTable($sRemoteClass); $sRemoteKey = self::DBGetKey($sRemoteClass); - $sExtKeyField = current($oAttDef->GetSQLExpressions()); // get the first column for an external key + $aCols = $oKeyAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc()) + $sExtKeyField = current($aCols); // get the first column for an external key // Note: a class/table may have an external key on itself $sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`"; @@ -3038,7 +3040,8 @@ abstract class MetaModel { $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); - $sMyAttributeField = current($oAttDef->GetSQLExpressions()); // get the first column for the moment + $aCols = $oKeyAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc()) + $sMyAttributeField = current($aCols); // get the first column for the moment $sDefaultValue = $oAttDef->GetDefaultValue(); $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)"; self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); diff --git a/pages/UI.php b/pages/UI.php index 91dd035430..00d23d9849 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1395,8 +1395,10 @@ EOF } } $oKPI->ComputeAndReport('GUI creation before output'); + ExecutionKPI::ReportStats(); - ////MetaModel::ShowQueryTrace(); + MetaModel::ShowQueryTrace(); + $oP->output(); } catch(CoreException $e) @@ -1411,15 +1413,22 @@ catch(CoreException $e) { if (MetaModel::IsValidClass('EventIssue')) { - $oLog = new EventIssue(); - - $oLog->Set('message', $e->getMessage()); - $oLog->Set('userinfo', ''); - $oLog->Set('issue', $e->GetIssue()); - $oLog->Set('impact', 'Page could not be displayed'); - $oLog->Set('callstack', $e->getTrace()); - $oLog->Set('data', $e->getContextData()); - $oLog->DBInsertNoReload(); + try + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', $e->GetIssue()); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', $e->getContextData()); + $oLog->DBInsertNoReload(); + } + catch(Exception $e) + { + IssueLog::Error("Failed to log issue into the DB"); + } } IssueLog::Error($e->getMessage()); @@ -1440,15 +1449,22 @@ catch(Exception $e) { if (MetaModel::IsValidClass('EventIssue')) { - $oLog = new EventIssue(); - - $oLog->Set('message', $e->getMessage()); - $oLog->Set('userinfo', ''); - $oLog->Set('issue', 'PHP Exception'); - $oLog->Set('impact', 'Page could not be displayed'); - $oLog->Set('callstack', $e->getTrace()); - $oLog->Set('data', array()); - $oLog->DBInsertNoReload(); + try + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', 'PHP Exception'); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', array()); + $oLog->DBInsertNoReload(); + } + catch(Exception $e) + { + IssueLog::Error("Failed to log issue into the DB"); + } } IssueLog::Error($e->getMessage()); From 8926e64bc285cca10e231ad91d9267df75f5615e Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sun, 5 Sep 2010 07:02:13 +0000 Subject: [PATCH 677/970] #223 Trim spaces on CSV import was not handling well the last line SVN:trunk[760] --- core/csvparser.class.inc.php | 63 ++++++++++++++++++++---------------- core/test.class.inc.php | 24 +++++++------- pages/testlist.inc.php | 17 ++-------- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/core/csvparser.class.inc.php b/core/csvparser.class.inc.php index 64cb15e71f..75b7b16a99 100644 --- a/core/csvparser.class.inc.php +++ b/core/csvparser.class.inc.php @@ -38,6 +38,7 @@ define('evSEPARATOR', 1); define('evNEWLINE', 2); define('evTEXTQUAL', 3); // used for escaping as well define('evOTHERCHAR', 4); +define('evEND', 5); /** @@ -141,24 +142,28 @@ class CSVParser $aTransitions[stSTARTING][evNEWLINE] = array('__AddRow', stSTARTING); $aTransitions[stSTARTING][evTEXTQUAL] = array('', stQUALIFIED); $aTransitions[stSTARTING][evOTHERCHAR] = array('__AddChar', stRAW); + $aTransitions[stSTARTING][evEND] = array('__AddRow', stSTARTING); $aTransitions[stRAW][evBLANK] = array('__AddChar', stRAW); $aTransitions[stRAW][evSEPARATOR] = array('__AddCellTrimmed', stSTARTING); $aTransitions[stRAW][evNEWLINE] = array('__AddRowTrimmed', stSTARTING); $aTransitions[stRAW][evTEXTQUAL] = array('__AddChar', stRAW); $aTransitions[stRAW][evOTHERCHAR] = array('__AddChar', stRAW); + $aTransitions[stRAW][evEND] = array('__AddRowTrimmed', stSTARTING); $aTransitions[stQUALIFIED][evBLANK] = array('__AddChar', stQUALIFIED); $aTransitions[stQUALIFIED][evSEPARATOR] = array('__AddChar', stQUALIFIED); $aTransitions[stQUALIFIED][evNEWLINE] = array('__AddChar', stQUALIFIED); $aTransitions[stQUALIFIED][evTEXTQUAL] = array('', stESCAPED); $aTransitions[stQUALIFIED][evOTHERCHAR] = array('__AddChar', stQUALIFIED); + $aTransitions[stQUALIFIED][evEND] = array('__AddRow', stSTARTING); $aTransitions[stESCAPED][evBLANK] = array('', stESCAPED); $aTransitions[stESCAPED][evSEPARATOR] = array('__AddCell', stSTARTING); $aTransitions[stESCAPED][evNEWLINE] = array('__AddRow', stSTARTING); $aTransitions[stESCAPED][evTEXTQUAL] = array('__AddChar', stQUALIFIED); $aTransitions[stESCAPED][evOTHERCHAR] = array('__AddChar', stSTARTING); + $aTransitions[stESCAPED][evEND] = array('__AddRow', stSTARTING); // Reset parser variables $this->m_sCurrCell = ''; @@ -166,37 +171,43 @@ class CSVParser $this->m_iToSkip = $iToSkip; $this->m_aDataSet = array(); + $iDataLength = strlen($this->m_sCSVData); + $iState = stSTARTING; - for($i = 0; $i < strlen($this->m_sCSVData) ; $i++) + for($i = 0; $i <= $iDataLength ; $i++) { - $c = $this->m_sCSVData[$i]; - -// // Note: I did that because the unit test was not working fine (file edited with notepad: \n chars padded :-( -// if (ord($c) == 0) continue; - - if ($c == $this->m_sSep) + if ($i == $iDataLength) { - $iEvent = evSEPARATOR; - } - elseif ($c == ' ') - { - $iEvent = evBLANK; - } - elseif ($c == "\t") - { - $iEvent = evBLANK; - } - elseif ($c == "\n") - { - $iEvent = evNEWLINE; - } - elseif ($c == $this->m_sTextQualifier) - { - $iEvent = evTEXTQUAL; + $iEvent = evEND; } else { - $iEvent = evOTHERCHAR; + $c = $this->m_sCSVData[$i]; + + if ($c == $this->m_sSep) + { + $iEvent = evSEPARATOR; + } + elseif ($c == ' ') + { + $iEvent = evBLANK; + } + elseif ($c == "\t") + { + $iEvent = evBLANK; + } + elseif ($c == "\n") + { + $iEvent = evNEWLINE; + } + elseif ($c == $this->m_sTextQualifier) + { + $iEvent = evTEXTQUAL; + } + else + { + $iEvent = evOTHERCHAR; + } } $sAction = $aTransitions[$iState][$iEvent][0]; @@ -218,8 +229,6 @@ class CSVParser $iLineCount = count($this->m_aDataSet); if (($iMax > 0) && ($iLineCount >= $iMax)) break; } - // Close the final line - $this->__AddRow(null, $aFieldMap); return $this->m_aDataSet; } diff --git a/core/test.class.inc.php b/core/test.class.inc.php index b716ad086d..b8baa4ebf6 100644 --- a/core/test.class.inc.php +++ b/core/test.class.inc.php @@ -88,8 +88,8 @@ abstract class TestHandler $this->m_aErrors = array(); } - abstract static public function GetName(); - abstract static public function GetDescription(); + static public function GetName() {return "fooname";} + static public function GetDescription(){return "foodesc";} protected function DoPrepare() {return true;} abstract protected function DoExecute(); @@ -278,8 +278,8 @@ abstract class TestSoapWebService extends TestHandler */ abstract class TestFunctionInOut extends TestFunction { - abstract static public function GetCallSpec(); // parameters to call_user_func - abstract static public function GetInOut(); // array of input => output +// abstract static public function GetCallSpec(); // parameters to call_user_func +// abstract static public function GetInOut(); // array of input => output protected function DoExecute() { @@ -316,9 +316,9 @@ abstract class TestFunctionInOut extends TestFunction */ abstract class TestUrl extends TestHandler { - abstract static public function GetUrl(); - abstract static public function GetErrorKeywords(); - abstract static public function GetWarningKeywords(); +// abstract static public function GetUrl(); +// abstract static public function GetErrorKeywords(); +// abstract static public function GetWarningKeywords(); protected function DoExecute() { @@ -348,10 +348,10 @@ abstract class TestUserRights extends TestHandler */ abstract class TestScenarioOnDB extends TestHandler { - abstract static public function GetDBHost(); - abstract static public function GetDBUser(); - abstract static public function GetDBPwd(); - abstract static public function GetDBName(); +// abstract static public function GetDBHost(); +// abstract static public function GetDBUser(); +// abstract static public function GetDBPwd(); +// abstract static public function GetDBName(); protected function DoPrepare() { @@ -385,7 +385,7 @@ abstract class TestBizModel extends TestHandler { // abstract static public function GetDBSubName(); // abstract static public function GetBusinessModelFile(); - abstract static public function GetConfigFile(); +// abstract static public function GetConfigFile(); protected function DoPrepare() { diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index c16b92da71..d263dd0646 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -220,20 +220,6 @@ class TestCSVParser extends TestFunction public function DoExecute() { - $sDataFile = '"field1","field2","field3" -"a","b","c" -a,b,c -"","","" -,, -"a""","b","c" -"a1 -a2","b","c" -"a1,a2","b","c" -"a","b","c1,"",c2 -,c3" -"a","b","ouf !" -'; - $sDataFile = '?field1?;?field2?;?field3? ?a?;?b?;?c? a;b;c @@ -248,7 +234,7 @@ a2?;?b?;?c? ?a?;?b?;?c1,",c2 ,c3? ?a?;?b?;?ouf !? -'; + Espace sur la fin ; 1234; e@taloc.com '; echo "
        \n";
         		print_r($sDataFile);
        @@ -267,6 +253,7 @@ a2?;?b?;?c?
         			array('a1,a2', 'b', 'c'),
         			array('a', 'b', "c1,\",c2\n,c3"),
         			array('a', 'b', 'ouf !'),
        +			array('Espace sur la fin', '1234', 'e@taloc.com'),
         		);
         	
         		$oCSVParser = new CSVParser($sDataFile, ';', '?');
        
        From b49373b959cd84addc1f703012438e05eb42e19d Mon Sep 17 00:00:00 2001
        From: Romain Quetiez 
        Date: Sun, 5 Sep 2010 07:29:33 +0000
        Subject: [PATCH 678/970] #153 SOAP Web service broken due to violated
         integrity rule
        
        SVN:trunk[761]
        ---
         core/attributedef.class.inc.php |  1 -
         core/event.class.inc.php        | 10 +++++-----
         2 files changed, 5 insertions(+), 6 deletions(-)
        
        diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
        index 2d3ce055d6..01bb7d801b 100644
        --- a/core/attributedef.class.inc.php
        +++ b/core/attributedef.class.inc.php
        @@ -603,7 +603,6 @@ class AttributeBoolean extends AttributeInteger
         
         	public function ScalarToSQL($value)
         	{
        -		assert(is_bool($value));
         		if ($value) return 1;
         		return 0;
         	}
        diff --git a/core/event.class.inc.php b/core/event.class.inc.php
        index 9fe51d49e6..d24b580ee7 100644
        --- a/core/event.class.inc.php
        +++ b/core/event.class.inc.php
        @@ -248,11 +248,11 @@ class EventWebService extends Event
         		MetaModel::Init_InheritAttributes();
         		MetaModel::Init_AddAttribute(new AttributeString("verb", array("allowed_values"=>null, "sql"=>"verb", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
         		//MetaModel::Init_AddAttribute(new AttributeStructure("arguments", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
        -		MetaModel::Init_AddAttribute(new AttributeBoolean("result", array("allowed_values"=>null, "sql"=>"result", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
        -		MetaModel::Init_AddAttribute(new AttributeText("log_info", array("allowed_values"=>null, "sql"=>"log_info", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
        -		MetaModel::Init_AddAttribute(new AttributeText("log_warning", array("allowed_values"=>null, "sql"=>"log_warning", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
        -		MetaModel::Init_AddAttribute(new AttributeText("log_error", array("allowed_values"=>null, "sql"=>"log_error", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
        -		MetaModel::Init_AddAttribute(new AttributeText("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
        +		MetaModel::Init_AddAttribute(new AttributeBoolean("result", array("allowed_values"=>null, "sql"=>"result", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
        +		MetaModel::Init_AddAttribute(new AttributeText("log_info", array("allowed_values"=>null, "sql"=>"log_info", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
        +		MetaModel::Init_AddAttribute(new AttributeText("log_warning", array("allowed_values"=>null, "sql"=>"log_warning", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
        +		MetaModel::Init_AddAttribute(new AttributeText("log_error", array("allowed_values"=>null, "sql"=>"log_error", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
        +		MetaModel::Init_AddAttribute(new AttributeText("data", array("allowed_values"=>null, "sql"=>"data", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
         
         		// Display lists
         		MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'verb', 'result', 'log_info', 'log_warning', 'log_error', 'data')); // Attributes to be displayed for the complete details
        
        From 573e7f4e319fa2f21e30548e7f488ff6f5cd0a2f Mon Sep 17 00:00:00 2001
        From: Denis Flaven 
        Date: Sun, 5 Sep 2010 09:13:28 +0000
        Subject: [PATCH 679/970] - Fixed Trac #177: Icons for everything ! For
         Incidents, User Requests and Changes the icon depends on the state of the
         object.
        
        SVN:trunk[762]
        ---
         .../images/change-approved.png                | Bin 0 -> 3268 bytes
         .../images/change-closed.png                  | Bin 0 -> 2107 bytes
         .../images/change-rejected.png                | Bin 0 -> 2815 bytes
         .../itop-change-mgmt-1.0.0/images/change.png  | Bin 0 -> 2472 bytes
         .../model.itop-change-mgmt.php                |  50 ++++++++++
         .../itop-config-mgmt-1.0.0/images/switch.png  | Bin 3343 -> 2509 bytes
         .../images/incident-closed.png                | Bin 0 -> 4770 bytes
         .../images/incident-deadline.png              | Bin 0 -> 2750 bytes
         .../images/incident-escalated.png             | Bin 0 -> 4182 bytes
         .../images/incident.png                       | Bin 0 -> 4944 bytes
         .../model.itop-incident-mgmt.php              |  88 ++++++++++++++++++
         .../images/known-error.png                    | Bin 0 -> 2738 bytes
         .../model.itop-knownerror-mgmt.php            |   1 +
         .../en.dict.itop-problem-mgmt.php             |   4 +-
         .../model.itop-problem-mgmt.php               |   1 +
         .../images/user-request-closed.png            | Bin 0 -> 2049 bytes
         .../images/user-request-deadline.png          | Bin 0 -> 2878 bytes
         .../images/user-request-escalated.png         | Bin 0 -> 4167 bytes
         .../images/user-request.png                   | Bin 0 -> 2211 bytes
         .../model.itop-request-mgmt.php               |  87 +++++++++++++++++
         .../en.dict.itop-tickets.php                  |   4 +-
         21 files changed, 231 insertions(+), 4 deletions(-)
         create mode 100644 modules/itop-change-mgmt-1.0.0/images/change-approved.png
         create mode 100644 modules/itop-change-mgmt-1.0.0/images/change-closed.png
         create mode 100644 modules/itop-change-mgmt-1.0.0/images/change-rejected.png
         create mode 100644 modules/itop-change-mgmt-1.0.0/images/change.png
         create mode 100644 modules/itop-incident-mgmt-1.0.0/images/incident-closed.png
         create mode 100644 modules/itop-incident-mgmt-1.0.0/images/incident-deadline.png
         create mode 100644 modules/itop-incident-mgmt-1.0.0/images/incident-escalated.png
         create mode 100644 modules/itop-incident-mgmt-1.0.0/images/incident.png
         create mode 100644 modules/itop-knownerror-mgmt-1.0.0/images/known-error.png
         create mode 100644 modules/itop-request-mgmt-1.0.0/images/user-request-closed.png
         create mode 100644 modules/itop-request-mgmt-1.0.0/images/user-request-deadline.png
         create mode 100644 modules/itop-request-mgmt-1.0.0/images/user-request-escalated.png
         create mode 100644 modules/itop-request-mgmt-1.0.0/images/user-request.png
        
        diff --git a/modules/itop-change-mgmt-1.0.0/images/change-approved.png b/modules/itop-change-mgmt-1.0.0/images/change-approved.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..a1014575b8386d85e369c579460b1b6ed0f278fb
        GIT binary patch
        literal 3268
        zcmV;#3_J6QP)T_RD1__H;0)f>k8ea8GcTfNC
        zd+&SKAI2~Y{G1OKKl9@>t{*hO`Aa<-oNetinI)uq5SoI#=|`Y2KA
        zd)Hw|?~>L`6UR>gfXtvcr*~Ps<^)vwt{JH6O~9lgUpb=enIvWnh+zqZ9U0^#_5Tcj
        zEXcBY`N+IzMNMl0+>R(lrTXzzvm4G~RUf}yLP;nv~DtAT}nnYr|bRJL{3{DO&
        zP!oA)d#3}1gL+Vs>_>!dEi5<|vvhUTOY`90)ZMt)EYuTuJ-ZY
        zO|q4QUV;smjzZ)CmrI%L$wXFNn+t=yLHr`41L1zJSeSnta0XE=ahO=-8%r7KW)N*E
        znzCV61(W~D^w|3(V@Q?_Tny+xLLwYWa|25c6Gccd%pTc{Bwm9SXIC#tsV9hv%nmfr
        z^R6;K5+vnesJwMtdj?ciOQlD)kd%`wBI&+#L2ULRyjGEwt4uo3>4YuGk69GFk+||E
        z2sM7bb#ND852HFgin7wKe1!P9!~IDT(Pxv%G?KSSHVteR;^jWEsNWAjWl=CZxb%c1
        zFfs+=RoSf=ZkJ>9;o@n;JO@aML>Z+?0W|OmY}Yhnl$C$X6_PgUj9x~!J4qfK@HAR-
        zA_ryEDQ0$)m#rZJL$);oj#`G8iFFJcTi82iuWSY54PahYJEC!=Kl;Lr6)$I__8UiR
        z0h4X>odnGm#l9pJ&mEmPIvOBn
        z>TUzVBC=A$3&TS-M|Y==ap^vcBLzdIvec2j_|6b~eb|kLb{p2N{E}*ejsn76ME9pH
        zUyi(gS0E$>%^=>c!+80k%mWdNLUd3tG?0h~tF8k_mY;DfAG-%1#p;3&oTWwn+pBo8
        zuQP->l>1Lw+Dbi+*R9}dn}()#D|`_aE&^b7`>UV11w+@&M%W#KLiOW`$s4vD^R{Tv
        zxgS)r1~boZ9_XXG2EWjk7>D;H6Qxpwtp%UWXN!+!{D&I1LB#FJWPOu99RVKg9acE)
        z8WNoV%ePIu?Q9Rj7G}R`=D7B@TvuM
        znaz0NnKv+PhkfMM?cUoVYA;=iZget9r6ER3ulUDo=bXnE!=BoM>6CfesF|zWn}|7A
        zw<5u#P_XLQS)GW3JuHOQM5HZP2_`~Ci~1yMl*elizJ)4B5Q*b^;dLkvP<3`*YyiI^
        z`4`DxvvcgVMR4bz=3&jFi;zSYn?f80r$kYh>xbV*EsaG-l#*6sod-kiTmilz9fmyu
        zE?`I3%za2%PzkiTQSD-=F7yneBDnZs0$54%28ncP_%a|9mnS|wAJW*3-%97p%cv*je|}g>Q}A9IID^rs@V124migL%<6nUdBJnB030Fd
        zI-^;oY!jy7bv$$@GIp21l@`Ef2T2FYIIxCsN8@v#ATC97H8O_wnj$faN7J$O_>^{e(2f7xUD<{Rxqo!
        zg1qJ|+*3IhnoW**i;;OmvYT-1q4lvEXm+akQUoI$G8(7_mkPtC7w)Az>>aQybl=q1
        z``f$9${TC{P<7oiOqUy~pq#ox<0JEVHoPy)gOaclnlFNIB8Js9(yg#Z7s4;}BGR
        zoQ$ikKY_gTUVPbu^#;MwpgAMk#*f%FnU-xwYV<
        z0<}e_xTIM$2t5*>+HxBz_LadGSDvBjEz^**`Q~?Mbkt+k-?}{&TQ?0xkDtem$}|*M
        zgR#c2Mw3uh@ceV5u#wJGxTscW=x(#2?VyN}Db%SpyCLOj@OtEBl#x;gY@nfJ
        zGd8E9YkD;mAV#rZF8!k8>2-+Ar~%h2p7xU{rx2t1b{NcnS5kbad-QdbjSix-(~5>e
        zp1!+md>6v1qh@y!as+C+H3l_d56TPu$&``qxo`mH9gW4Pp`p7whDR(RzZLnEzxm^K
        z?5!8^@gsjh#MKK{{r*Ob4~l45vI$yNGaAX#jdfH;9P0mMF#~}vD>`az7?I{j!wCb7^_U-ur
        zkU7g0#XYw+ScxJ5ODrVzbU0(QidoOJA{DCr3%B6S>Reifp-To0T|8=YzQMX#2VocO
        zu;(anQ+2j78d2V$1r1`35!)}KaRUa;3rdKELsY;}U1&V!jeDWfwdJFv2VgVqJ3j!l
        zhG@~bVdUZ}ljo+=loHcYvC>hpm?rbk2FPN=%{ka2hs_KS)NzX6K
        zjJ)z|cYyWNqfyc!WN-+RGBP!15sHftDf8~_m6-i$A>8Dl-9`1-HlEt200k{QV^Z-u
        zz&iV#bu>Z|A{vEaDln&Z`Yd|vLv6@|$wi&g#+}J4=)#h729Vt>K+~DOZ&;OkG|8^!
        z^P-l**tJw%QWFy#T9P1XUcsRyFp_4My^Xeg6Zbf#Z=A&+xn&b(4{D<7Em97kRH~am
        zEeKIyhscvr@?wO}ri95mGJV%HoC<6cGy(aFI@MV@C%}B77oQh}vRbVtnY3H);y=|5
        zMk4BpDs|4X0Vm}QCvo0I>pG$|@mH#iIjQMi%==(R;B97CmeV3?F3zsG1iR+s9jcTl
        zDfTo+#7^HO-mXg!4J%`ZVE!|=d{vIBHbdYjF-8gONN)sUKr<=0{RhY1v;C!h%9JUm
        zrF9Z}d?ql(oS^JvFOd+MNNK!h1W9#xeO#c8;(#?@s}nVJ9Td8ZrV(p&7zP=gIt+u+
        zIfFASh6RFdOY9i;*CBd$G1h1U(S&!{dAZU5kc&R#kp);}Be)>rKaWJOR
        zqG5s}513$77`86K*Ft*PG}hqzo-ULtBefQ5=GDwrfXrtV1fj^
        zmk@`2^h0-A6i848Zm5rknH-luzpjZ001GJ}S3D*{e4on_FPP?>))%Ub>6N}So%B;X
        zk%kG7hy;t>fSS`zb94uf^PR2&SL_0000NYlfba2U%B_p~i#bRJ!;C*ct2LgdV5)u+xN9*X3BS%IC
        zVx#~P6B9pQv0_Cd+XoIDsO#_VuN9ENS4K{~y}c>3XV3njySv*wdGf@Rl$5-yeIL9s
        zIYHz@p-^3Ze*W}p*RB~XH#gU$rKSBOc_qFQAo}|H%+#q1dF0PMUK`#eE84^h?z5Ij+ruLiYYEGHm$9#mJPA<
        zqs|Y;Wlq4t#EBEvDS^`-J$huF$5fR}RaKQyuP{A5Jw{Ti5E~!IrJMvJTvSxFfg~0q
        zX5PGcmW^l6o{{L5jkdNn%SK8{3T!0D6%)uwj@8M*nMf%+JKM~fHOsQ#mW2x!S~i|O
        zeF~^DvGHkKfuO9a3N^6C$+>gqns7Kglq-NiS|mDB1EfHIuj8S(0ihA2TFvck98?*l
        zLt9|J8OmBkMTM0f5}Q1Ea*57a9Ty;QRx;WW3v!h{J%$1FpR
        zjmM84+jG>}VIw6TN^FrdzLLIxvt?yvwo;MQXV0EByLRm|SFT*K$6#Z@f(4chw#CL9
        zI(N;uRwqEHtge^-Q>jWsT8`NqfT4Z+_Sx}y_wL=62~S2wRm=c-(xgeS@ty7y9yccB
        zXjG1GU|Mx$l?nvFFKsIoj!fqvO=YD*OMDCKqse*lN(}cn~qI!65mDL6uBOj2FWMy2p
        z)?1yG6vxShyLa!}==+1fQd(MS*UVHJO9};UD%@R7Jd`y
        zzmp^j0boIvl4OAZZys|HdU%Ga%UTy*oFL--2F&v1%dL#~jiVVE8Med@2jFspAW*ID
        z-MeSERUE$3@vJdRY#y$1_=)tNPZE;{G%lp!B;v|1Q!5G2HFxgZ89K)S@ILEUAdU^j
        z5#>p~_BD)IVoOR%iq!pxbI!4ie9FtqO?7p(xq0)Zb?(NE8@5`x1-TtM
        zbja-3vBO-wdX-mK3l>&11#H#*a>qnYdT@;rupAbMG9a9+Tx5|2PBV(r&`A7Mt5%sy
        zmoAx8r%pX@Oxkzs*fG=8)MRtv`0?ZRWr^!4x#}S~V(!bB%gL+lU%1yyWJGg=5%=Bz
        z3(QMYB=Q5NI1XDiH8s|GqTkrqXxA`<;W&*C(6?{j=3@gzqCsp-inW~PsX4vprd8e<
        zaMC|39X@>6POi1JwN$X7>#1CTsjRHD{o~@ri_ae%c>ZXvJ#*%a4T@t!xsj#7eH}A7
        z5n<82NwZKA^rKsxq8|`BnjG&E%t|;y_r`gCb1lb_A9;cP*>(lMfdL$BaBp&hN+mWn
        zi;aR&&raU7nyc|1_=ian
        zo&LgiD%jwIn{p%l=+UF!>ilh^0^!yAwQ?%!`t|Ep!xt`GuyR?vc=3=VTsOukMiKJH
        zO{}oj($Zpk2G?;MQ0~3M^%NN6Ji0ekjnQ7$Y!u5o^_*)X2BC)}YOY!1zFZJ-20G`k
        zGopvV21aBtm|;vOS=}*!4+rn2UV;RRw{G2XY@~^e?P6o=sN|#@2i%SQI(CWccZR4A
        zuWWAHf4&3a2M=i0lxe`QrV*;C@;&PpU3Tdo3($-H1dL>K!*!}Szv;XHPP%tw%r!nI
        z3%BhWplgD|E8g(+aIgU59!QI|D&Mco&CQ>UhzTAbegD3H|Na&=uu{n*4->E~QJgKy
        zlx665Sb7gBnjuAVf})(@*mC2BAFuC>f-FAp4
        z&-b3+dHl{fUr>mmNI&Hz&`*3l1Mm#M0GBH2Hws0MTRuUcd1J?tN24KCl;rR1>?9Dw
        zbcI5ZDG0*nxHj~qgjcDg9cRu^L34BD4ZRSJ>PgTtBtsxTL9I68Lz&UkYV})XWo6p}
        z*jHX&J}npwzN&~|jPMo2_w4}j)I&%e4AR^iHz*KAA-~^m%FD}J*V@`jUayz(^Yf?r
        ze7;xvQa;A-rv&UDk{oy*Zq@)uB+ulE48+^mabpFPEMzjOeWL2@Iuu0w88SF
        zckWPPd;8D;NP~5=ARU6x-TI1@l$5t{mGS^e6MG^vGjlw`X?oPK1n}otT855=lz8E|
        zh6X92AGxnoj!R8VeX#-G!RnNlm`MDYyVABv_y
        z$yrUXqy`j=M_IOGwSLj
        z{$>C+n{6?|#>4=S2U7U3S4NK>O{fJDBO$y-m?9h>#H9yxh{V*^!+5!oBrSe#!!fURE17sgJD{
        zyEmf9#U@<*OoxJy+o)8Y6}u*a9ujmo#J|j$BaWFeMf~IY?~A7{ToBJ3I3PAmm>@>`
        zO_=O~6!ds^G#w#|XvZp-&1UJ^0PCGba#mTb)TCBZC&D23jmgh`Vg4jkM%bJwWo+FMdIX3RJ3
        zC}i`G9ix(S=b}^(H8rA$*-oA6C4dFmla$nW-ehu5@_4Q@t=f?@J;v4kjFf^rv~ZHB
        zgzbL@gBheCAB@_3?~${HFSY52$ASddeI*lC-ckKkK=?ycECB_oe_N@wm!eYPj@^
        zM!RP_#>7}C{VV2m{x`P`+@!T5$#c-tEVzAJYG!%`kdQ!kCQhVID4yzaQ6d1q64~dYdb2T_5lUzTlhB4Ne=6-Xl2-#OE#+aIJI@@vSP2l$-MeWR6w;tzJ?^r9
        z_Z{V}TuIwEZK8{)+?eF(HoPZ$VKuxEJ179c!$+-Fmty7}-U3S*P!BSZWGP76oDV-F
        zMHpK=l6VN0tK9!YSShH)8@~OP&ZDJ>!9FGop5`zNxe%f&1~r7(>S%#~!{O12&*^Dt
        zG#a#-xMN4uhn_;5uf=3`63OZ*EseOxjWKgQK(t)GOb&Fax0sGGidwDKRq#UmpoI`X
        zjKok<(tyM>gF1it>8E7>^2^9xL;rIT)%P4iBnSnWxvBtysW#Fo#GYU8+(~H$0|h_^
        zji}b}LOi^%YEVLmdwWNG{4&94R4Gs;SC^Ml)(<~K+<#Z6qmvsql4ssLih<%m{I(gn
        zVS;jp7A>NGCP(g&Gr&S~Kl_Xl!2dsDe*!`(CMISDz`F)?I<4qBGD%3y%9`Ja0e;QF
        zgOrbCi?lAGS9%AuDq;?Bpc`f2T(u8<{WZ10a}_5~(p8L>ZMZkTu4glA|NeVg0(RR{
        zQbL}tT!^i#tXzYkXkEVnu=}kx+hT*=Zv7c76ixZ7Na*9MR?$lsT!QdYI}~OBJcTjk
        zUb=LN;*j`!TDJ>~W3Q^B(wdqc&&fw(FGWsln>?AG&&gpgEJKmoit2x(UjQ%)sxiwh
        z2%=Jc<#tm#ERzmTgs;=t*|hzYS4c!y>G@%3;M8Hl^@5KUc`8%!84~W*phvYy0g1xKN0-Sm?iyKTn%R%McQ7a5t|C^g-KUEdk7=
        zg2kn?W%-CaM8BcWp^)r)ql&80M!boc_{EDCd%Sl>bv4~ZNP1?^W=47gUf9On{SzlK
        zCMrK>+fM|gQXC0OCnCnbLeaWDemq?*E~Z1MzKQ6CoCpsTAU-;jjun-#G@r(W6b`TE
        zd}YAgxo(FiszAw+)^LPV3)+)Hx7&pTf_!ovI$Ggc!os-`O7M2vS0P{C-n*BQ<-~5q@Ai%v
        zLx+bABOTflHwxj-ojXrgR8$oA>rYIwBFDX6ZzYn`AT~7_F(nwGmC_x}{dLX4k3Xhnu+-6o3+W2-CKt0t
        z0=(-7Pyj#Nzd!Ol!l()r3-4DFX^2Io66A5vCto=Y8URk@juj-p-sKg9bjl)zJyVGm
        zLfr{OCq|4g)>|z4U{#ggEC>wNdc|OH+`M_S8phZ(Pyit%S?n`
        zY*Qs))iM~Rd{2<~g$^2mVb>srm`(|pAOz%nDj8_s;qmN0dprX$u=Rfd1^~dN9Nn4q
        RxS#+4002ovPDHLkV1nVyI|={*
        
        literal 0
        HcmV?d00001
        
        diff --git a/modules/itop-change-mgmt-1.0.0/images/change.png b/modules/itop-change-mgmt-1.0.0/images/change.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..68e0cb3f0a5824a64d6c0062b093f13d264b6c9a
        GIT binary patch
        literal 2472
        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
        mDP(8DcJ^NlPWSd-0R{jg$x;a^T$JMg0000 "id",
         			"db_finalclass_field" => "",
         			"display_template" => "",
        +			"icon" => "../modules/itop-change-mgmt-1.0.0/images/change.png",
         		);
         		MetaModel::Init_Params($aParams);
         		MetaModel::Init_InheritAttributes();
        @@ -296,6 +297,55 @@ abstract class Change extends Ticket
         		$sName = sprintf('C-%06d', $iKey);
         		$this->Set('ref', $sName);
         	}
        +
        +	/**
        +	 * Get the icon representing this object
        +	 * @param boolean $bImgTag If true the result is a full IMG tag (or an emtpy string if no icon is defined)
        +	 * @return string Either the full IMG tag ($bImgTag == true) or just the path to the icon file
        +	 */
        +	public function GetIcon($bImgTag = true)
        +	{
        +		$sStatus = $this->Get('status');
        +		switch($this->GetState())
        +		{
        +			case 'approved':
        +			case 'implemented':
        +			case 'monitored':
        +			$sIconName = self::MakeIconFromName('change-approved.png');
        +			break;
        +			
        +			case 'rejected':
        +			case 'notapproved':
        +				$sIconName = self::MakeIconFromName('change-rejected.png');
        +			break;
        +
        +			case 'closed':
        +			$sIcon = self::MakeIconFromName('change-closed.png');
        +			break;
        +
        +			default:
        +			$sIcon = MetaModel::GetClassIcon(get_class($this), $bImgTag);
        +		}
        +		return $sIcon;
        +	}
        +	
        +	protected static function MakeIconFromName($sIconName, $bImgTag = true)
        +	{
        +		$sIcon = '';
        +		if ($sIconName != '')
        +		{
        +			$sPath = '../modules/itop-change-mgmt-1.0.0/images/'.$sIconName;
        +			if ($bImgTag)
        +			{
        +				$sIcon = "";
        +			}
        +			else
        +			{
        +				$sIcon  = $sPath;
        +			}
        +		}
        +		return $sIcon;
        +	}
         }
         
         class RoutineChange extends Change
        diff --git a/modules/itop-config-mgmt-1.0.0/images/switch.png b/modules/itop-config-mgmt-1.0.0/images/switch.png
        index b32274acd24f6ce1d03a1fc6a8b159a02fc2a3a9..bca7e198860709954ead60c3a3bbae8271464fc1 100644
        GIT binary patch
        delta 2447
        zcmV;A32^q08qE`sSbqs$NklX0!R_1yEgGjl+izqrbmj45jg2US4o@b%l?Q57w?-i;Rp6golT}d;sd|
        z>TvMjL3DR_!+&Hl!NbEtjOVM1iwoS{JroFU0g{rEf(;usz}wsVQZ?*$@#CffXxO$oJ0;z`(!&jvhUVTeogahfgIfy{u`X>BCnV!s|44
        zVq!u7xPQ7#8^@sYJzqygM$psKgDLe#AYox)2nq_4wVx>f>ie-{$52&OHFHfHuQ^W;
        z8UQD$LLj`xL2ol6G|q0fi$b}&x=cIAHUJa<)OW+f!vcb9KNA3a*zx1XrC8}f!?eLh
        zqqX-nRXFK=zLN8!qoc5o+Tr5vqQICiIXR)$v46<@Xi~)4NvV*(r#yo>z-zo$sHLLg=kF)-DtTi7
        zIDcOx9t&Xa-n}?|`ZT<~yb&20iQtgnM>ubsvkf;IZv>z}(^%bV&^XeR*SLN_K!DuK
        zHFyuBKhYRn=7pZVy31Vmv}ZLT0?}h&o2&X&9$2W}F(Jj;J;?
        zFfeeY4dJ?^a7RanAYZ9C=5jElvfA<
        zD#dK-7`d;FH-gabjlbc&T6+0C>jRCyckiCmpalVFY-~hDMFonk7NK2*E?sqx@qh6N
        zaB+5dMz(X&^e1e9zXL{Z?~xvowp2
        zI#~03vZ>o6lYIoet<7*#3bb|GHmqK?dQOGBT~~{fCr{$TGarhVn3OcY5p|3aGDbj8
        zf}o7jXf}!u9z2j|QeqbjfCVMq8h?x5|Lq(S|CocDwow!p79cD<3>iDr#w6^~jmg;f
        z7(To784evfgbR5WU^ba&I?(yJKig=d_jS_Jcv6R8sDR6tFJBk{{(vJfIR)qMG~UrvXMZOxjVFy*
        zjS~_Q98$>X4{8`Ma3g-{(j~O2Mc#@D$Jm2f^nOu{Q6+I4T=eG6oAC1Y!nUp3q-UYG
        z7&SfGJKAyO$`y1y?1I&5MQCW~jEp)mo{?d9O3vT8b4Q}U`?-tb8jdRQoc#bJKq{O%
        zbqXGQ2p3je;!F>uy2zE$Z-1a|Lpz2F?AWpHZCKZ=LGizf@%a~@ix>GYzH+kP-?L{A
        zcJJOT`9Yw@dZA^Qj-l7<>+3~GsCH3NQP{F&iwK0HF((4_3i-wb+LRYs+8_Pr1B<5x
        zSy@@|?(l`Jtq(U!Z=j*E0o!-{1l!-UaVo0Zv
        zHr%4pcvfYC<^K6eJGMCh#7MpYgC7y1TpL>(Z}~o|cZzmQJ`OIU~R2
        zDhA7K_|?0=L`%F0UBawOn~ZziF)-;Qs}
        zzs2Uwo6&iv9@oFAKuc!_!k2|1DIpnwApz*>?vkE{lp;l0__l7{Dv8Y`CNQ4jrKhJa
        z_{im{P9-F<6@Mje-@Yvsl3JgWlY@uKsCP1Vsl9ToikdI#8X8r8q@wsrF?_5R^v6yh
        zv?Kr#kzrU7wF2W4FG@uRY1N_jKbK8CJ;cz`d(>;bvdCP!b`59Go>l5G
        zhTr}6*H{|ljsaEp*2G&8mmG(RhH8vh2GMKlMR!>@Hh*r~BuYc|p;L6Lz{GtT;}>+(E!QcOZ%|HH;QYvmcJ&>F8Peq{=1cv#^$ph~#`%f9-
        z;@67AQibRkDh^fext|Le2UQ|IK3+Ud)Wbh0
        zN;dF38-JRdnh-Ucnwk_E=~Ft&>I(&+ho7Gx)~#D7@<%YyZ$zUYzW{Z$bucUMX6*ba
        z>gpTN*Vl*Pp=sKTj*bZBSaV`xVq}dMax&-eUrLmIGYXVmc8{#M7xFHmD%PDHLkV1j8$wuS%z
        
        delta 3287
        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
        VicerQjB5Y@002ovPDHLkV1nPJSls{s
        
        diff --git a/modules/itop-incident-mgmt-1.0.0/images/incident-closed.png b/modules/itop-incident-mgmt-1.0.0/images/incident-closed.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..264ce583d62a275ad38821cb90e26e5b59bb8820
        GIT binary patch
        literal 4770
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/modules/itop-incident-mgmt-1.0.0/images/incident.png b/modules/itop-incident-mgmt-1.0.0/images/incident.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..237db0c76e1de66f653c05b1281abd4a1429bf68
        GIT binary patch
        literal 4944
        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 "id",
         			"db_finalclass_field" => "",
         			"display_template" => "",
        +			"icon" => "../modules/itop-incident-mgmt-1.0.0/images/incident.png",
         		);
         		MetaModel::Init_Params($aParams);
         		MetaModel::Init_InheritAttributes();
        @@ -93,6 +94,93 @@ class Incident extends ResponseTicket
         		}
         		parent::OnInsert();
         	}
        +
        +	/**
        +	 * Get the icon representing this object
        +	 * @param boolean $bImgTag If true the result is a full IMG tag (or an emtpy string if no icon is defined)
        +	 * @return string Either the full IMG tag ($bImgTag == true) or just the path to the icon file
        +	 */
        +	public function GetIcon($bImgTag = true)
        +	{
        +		$sStatus = $this->Get('status');
        +		switch($this->GetState())
        +		{
        +
        +			case 'escalated_tto':
        +			case 'escalated_ttr':
        +			$sIconName = self::MakeIconFromName('incident-escalated.png');
        +			break;
        +			
        +			case 'resolved':
        +			case 'closed':
        +			$sIcon = self::MakeIconFromName('incident-closed.png');
        +			break;
        +
        +			case 'new':
        +			$sIcon = self::MakeIconFromName('incident.png');
        +			$oEscalationDeadline = $this->Get('tto_escalation_deadline');
        +			if ($oEscalationDeadline != null)
        +			{
        +				// A SLA is running
        +				$iStartDate = AttributeDateTime::GetAsUnixSeconds($this->Get('start_date'));
        +				$iEscalationDeadline = AttributeDateTime::GetAsUnixSeconds($oEscalationDeadline);
        +				$ratio = ($iEscalationDeadline - time())/($iEscalationDeadline - $iStartDate);
        +				if ($ratio <= 0)
        +				{
        +					$sIcon = self::MakeIconFromName('incident-escalated.png');
        +				}
        +				else if ($ratio <= 0.25)
        +				{
        +					$sIcon = self::MakeIconFromName('incident-deadline.png');
        +				}
        +			}
        +			break;
        +			
        +			case 'assigned':
        +			$sIcon = self::MakeIconFromName('incident.png');
        +			$oEscalationDeadline = $this->Get('ttr_escalation_deadline');
        +			if ($oEscalationDeadline != null)
        +			{
        +				// A SLA is running
        +				$iStartDate = AttributeDateTime::GetAsUnixSeconds($this->Get('start_date'));
        +				$iEscalationDeadline = AttributeDateTime::GetAsUnixSeconds($oEscalationDeadline);
        +				$ratio = ($iEscalationDeadline - time())/($iEscalationDeadline - $iStartDate);
        +				if ($ratio <= 0)
        +				{
        +					$sIcon = self::MakeIconFromName('incident-escalated.png');
        +				}
        +				else if ($ratio <= 0.25)
        +				{
        +					$sIcon = self::MakeIconFromName('incident-deadline.png');
        +				}
        +			}
        +			break;
        +
        +			
        +			default:
        +			$sIcon = MetaModel::GetClassIcon(get_class($this), $bImgTag);
        +		}
        +		return $sIcon;
        +	}
        +	
        +	protected static function MakeIconFromName($sIconName, $bImgTag = true)
        +	{
        +		$sIcon = '';
        +		if ($sIconName != '')
        +		{
        +			$sPath = '../modules/itop-incident-mgmt-1.0.0/images/'.$sIconName;
        +			if ($bImgTag)
        +			{
        +				$sIcon = "";
        +			}
        +			else
        +			{
        +				$sIcon  = $sPath;
        +			}
        +		}
        +		return $sIcon;
        +	}
        +
         }
         
         class lnkTicketToIncident extends cmdbAbstractObject
        diff --git a/modules/itop-knownerror-mgmt-1.0.0/images/known-error.png b/modules/itop-knownerror-mgmt-1.0.0/images/known-error.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..0871dfbc06749a4df1f804cdfc4cbe84c3136c26
        GIT binary patch
        literal 2738
        zcmV;j3QhHiP)%6Uyu;0
        zcnU>Df(HahAR!P^P*Ex*q*6s~(>5ivkdW55wY^t+FSB#MaL!zI*K0RUvnx=gPI5f6
        z_>tJ03VUC5mD`a4>~7
        z1x*}TIps$RKvT7GDJor&B$5%*X)fnyy-w-37pohXBNo_l-JWnD8e-3OI0G4g7S*F>I
        zK09s(pV@rjBLKQW0L1+rX2%SR3d;0NH|WMz;CP?;pQB`RyUq_+L6VVFj0EENkg
        zz(vgT;m^9R1Faq68a1_evX}C3PmU*2NqjaxjgO_HAn&kD{qFBu+t{o($OEBA#V%dk
        z{K!;@=uZ~To5tW`%me^g;NDQy=dW~#x=TTna&a5
        z$sB5*`6Qm(UcwKqzYEkNSFAS~WpO2iMYSAfOS0jKGl
        zY2n-FmhjBQ5Q;aE#T9&i>GSyb`6qGVgrQI4ao
        z8yg()unrgq1K8CISc$mtd3-l;esLbpWN!g4U53@(hI9Np@RetQFZ~#5Y!WhMt$qD>
        zcw+Mxc>3WJevR~azi_WLAl^d_z#ss&?c7~Tw<{2sTBI6+7{R3n3EXQxM~ALc3@-}{3RQ`U*cL~kLJm1jHN0k1#0RA`Y;4_T
        zW?LTb7Z0UzR(lJL@@*)lPQs%@-gpC!QuDy+rV51ssJe`%(nNCk&p5v@<nw(M1w&VB%)l=8XmIAO3nI>=2V5pOR;Cf<9IrVMm;R-kR&^rTiJ$z=c@
        z2vb9=^fpeUeEtiR0ixj{+Ru2$?vOId8%2MEBTiC^K`9OCgf^*=rueiY-WrrKMQeG-
        zE;(VUE$saJc|@j;qSLMrEoA~jwPeVY5gm576MI02f9@>Z>h{n@Y>}g;Wfe(@oKX82
        z0M`a7R8NWIrr!%h0n0lrG{=t-uz>4iOrzH34K!}OfOziH=qMfUyeu1#rcdHuEvwg!
        z-GM?LjzWBiP$d96Os9S+_$VFs7
        z^%P~m@@N=~3XqpB;IHp)(#_Ea!Yzzs7IIyitx02bo$u8zrR+dgO8KF!ZGRkcO#JZ8
        z8d8t_2oWJh%rfB8leTYPMe)VU*nH`y-qhhx|2zG~OL(@HL%CY@o$1a-PX1n0=)f9a
        z`6Q}n)CZ>+518|l-y{L~*%zw#|qi?>jJ`x+`Yt`R6&
        zYtBw#@eALDbs%A0@o#
        z>W02-=J%JtT!}4*>Cfl{zv~sg&n1bAQiN+8_X?v1=hBMntcU?rd$~
        zPSNqti|~p)LRs=1cY6b1k3v~qdk+nO9rQTvxa*pxd7XDZDdlXqw6GBjem5j^hdLpo
        z1s_P_?s41Q*YR+lqMt{|)`ndV1XQw1v^wJtLRiFm7Wkbu1cjPeYOEL8&^fn%MTWaM
        z-hbWv&_zsRL|6-3nx@MHXo4xUuas&Xzo8|^`WleCk8|(o@Zdf_bSLh`&D|ynRhcqj
        z2KG#)ROBx8f^J{vt#kW3?B4Uvz4HLZDKimiJrW@0vzcVa|sD?ek(7+QrwIVBa^;sriSX$XLeHw5He%Wo*c!K_R1{dq3**Tw*q
        zNZ*jP)Z7!DcSiDR(VK}((^zG>v1mJcta6VN;
        zjx0-QRwHF-72;?Seb%Uu>3H{4%EkbE3ei|?o4$13tx)$XD4F|j2E@sN)#BIl8}~m>hyTb?0<%F=sq4LJlzb^=
        zAY8~H0G9wjRf0z4>|ekhm$0FAi74ed8_M8V~`$xbrs$zep>#U!W#`zj
        zW0;$p!}$0(vP(yhnKme*5)Q>BOeSK!rIMJSX_nUz$+2-nXlAlvNhneRb#mpO#FjPH
        z##-INYcdUvv1sO?uc!_LkjwYJFyV-=uB;aGg#z-098R7TkxVAN5u1&jPsO505VPY^
        zTDc`t5aLPC$+i^7He&dXEn?kpQS6u~S2UE`RkYi(tII7FN~@hv<;IvM*QTvbWwBGa
        zX-1;ep#V%}rgk;8?BVR@(UrXhYKicCTg;ct+I-Cr$Tit
        z(L#FS=>oUEtxPIb~$N~bJsGwGOHamU({T&F_)rcUM2a0NlOL`kE)HIh#3UCiTx
        z>nvZrdKI&?vnZ8H-uK5mPBfcMQ|riz^w7DM_~1u&67B8!xS^D@mcEs@&CR%Lm7<=5
        zs8K!bP;YKIf~b&#I!{2ZNRurhqyM6Na4uSypMU=L?b|=ObLY+{h(0y4^32RkDVNJ_
        zNzrIYw9P7t|DctP*mNk0 "id",
         			"db_finalclass_field" => "",
         			"display_template" => "",
        +			"icon" => "../modules/itop-knownerror-mgmt-1.0.0/images/known-error.png",
         		);
         		MetaModel::Init_Params($aParams);
         		MetaModel::Init_InheritAttributes();
        diff --git a/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php
        index b9fd7481d4..0c5e109669 100644
        --- a/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php
        +++ b/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php
        @@ -107,7 +107,7 @@ Dict::Add('EN US', 'English', 'English', array(
         	'Class:Problem/Attribute:impact/Value:2+' => '',
         	'Class:Problem/Attribute:impact/Value:3' => 'A Department',
         	'Class:Problem/Attribute:impact/Value:3+' => '',
        -	'Class:Problem/Attribute:urgency' => 'urgency',
        +	'Class:Problem/Attribute:urgency' => 'Urgency',
         	'Class:Problem/Attribute:urgency+' => '',
         	'Class:Problem/Attribute:urgency/Value:1' => 'Low',
         	'Class:Problem/Attribute:urgency/Value:1+' => 'Low',
        @@ -115,7 +115,7 @@ Dict::Add('EN US', 'English', 'English', array(
         	'Class:Problem/Attribute:urgency/Value:2+' => 'Medium',
         	'Class:Problem/Attribute:urgency/Value:3' => 'High',
         	'Class:Problem/Attribute:urgency/Value:3+' => 'High',
        -	'Class:Problem/Attribute:priority' => 'priority',
        +	'Class:Problem/Attribute:priority' => 'Priority',
         	'Class:Problem/Attribute:priority+' => '',
         	'Class:Problem/Attribute:priority/Value:1' => 'Low',
         	'Class:Problem/Attribute:priority/Value:1+' => '',
        diff --git a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php
        index 50f00ff805..3c1261fc25 100644
        --- a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php
        +++ b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php
        @@ -38,6 +38,7 @@ class Problem extends Ticket
         			"db_key_field" => "id",
         			"db_finalclass_field" => "",
         			"display_template" => "",
        +			"icon" => "../modules/itop-problem-mgmt-1.0.0/images/problem.png",
         		);
         		MetaModel::Init_Params($aParams);
         		MetaModel::Init_InheritAttributes();
        diff --git a/modules/itop-request-mgmt-1.0.0/images/user-request-closed.png b/modules/itop-request-mgmt-1.0.0/images/user-request-closed.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..77ac9138bbd094ffd5095ac64849f3c3087b19ac
        GIT binary patch
        literal 2049
        zcmV+c2>$npP)R6yFE{c
        zM8e=~YCPTA+FF{Dl5&6DW?*)9HtFcA;6JFUuBo@Bq;l;Y9bmOt;l%529^!dZ^Y!c3
        zh{VXcc!0npLqG#xT~qtDV773BgM;k;*_l~zxqIN1W5lVn*cR)^d7BjUZNpR2SgF_{Ui|md(XEYkgSyK-r5qXkLJ;`Qx*vrn_pMDBnd-XV^
        z*lY~=wd<{Lv%LdMI2#j_lkoft2aBVTNYjwV^EQ$mc_b1d@K%yd4ar6UZxRF$a2z>&
        zn1N>x#}xQtNeOJ-ycuFR6LcT?01EdXD9*{vZNk~fS$6_zPmT5q(mXlq9s
        z$_I-X`<)~u<4~Ui4o7}bdV0DOjh?gS9+=BEwHSF-HX3;Cz={iT0mUVz%KI0&lTc
        zD5Wyv3MMqPSpbXK!g?KdPcPPm2)kAzz{sexLJ0t8qKG%SoxYKgQbAbyObE&3`9!jz
        zgTOiLb~t|QWk$q!eDUR15DJADITn4DfHotNngAS*f?^ZTH{JDmOOcdsMJ6UgqS0!Z
        zY!(4eAn1Iz%LU*6a31>a48)NWu^MZ@C7lnnLBEMC#m<5we_!#g-MgD&(P;Lv3D85a
        zx2dhFsriOdkT{H=RXLuNXy?QmCr1U^8dJpKyuoR2gYbp?cJS4Ga?~|BfV>!zr
        zfDb!#`pnt-)U-`}BJlIfsEaC2KjwRBL897qqV8V(GqgH^W`=#w78d8`=AIM97@stO
        zjEsyo^78ToFI8BRDGt1BkieWVRS=63(1T7OCCz(bZ0Voav-cP_h1p3HK*RM-PftS{
        z=4mZ8DpHW*qG_B3S(Xy|L`onZ<1FaOQ!+p~`7nUfNfW>YO~a*zhQ=Ma+bM;{MKDf2
        z^M-5y4-1-Ce_lUZncKF(#Kc6Tueaw@Boa)T09MqQ&dz_%{CKIsNi8vXKr}`?QB+yK
        zBPBGktS7Cu*v6>S_wsLYu-R)|b{fSj6zb~gDsOf+Hm9YfvK%hzUaSYO
        z*bLADU6K>uV+4=hgJ>ii?PzbS!tWR9ohxeQki<-1UtjqzS1z}hu_d4f8nr7LfQh3K$;2ZJ5
        zPj~OKJg(IXYD0OHACc4~m_RnwBpWJ($?ixcwI;@#JQD~6%3R(5Uc|-uN$Te%Rr8^l
        z8Mu7qXJ%Mq!$yM^H0UYzNj}NU%p~w=*yLriqFo6j5t^Q!D(~%aH_ssGxfYqV)Ml_^
        z0kYs~%c>3nO+CL&T_-cytP=P|H?|hb8bo4hDp1zf<7!c|jcU-m2zVv(Sfohv*k@@T
        zqV}EMYaF!AxhfLUIz=KvgI-EAG2tICce}frQ&LlbBNILp{Da8T^9VQ%<18gsIH%2IkK%{ye+C?@*$n>Q+P#dThju^wF5
        zf8e5fUQ&PKbYQ_idc_B~@t-|5
        f2(0Y*Pk;dcW{7wf_D~o_00000NkvXXu0mjf7Dv`J
        
        literal 0
        HcmV?d00001
        
        diff --git a/modules/itop-request-mgmt-1.0.0/images/user-request-deadline.png b/modules/itop-request-mgmt-1.0.0/images/user-request-deadline.png
        new file mode 100644
        index 0000000000000000000000000000000000000000..1fb42ec00be23857b5f6165621944aa6684b692e
        GIT binary patch
        literal 2878
        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
        
        literal 0
        HcmV?d00001
        
        diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php
        index a816370170..6bd86d55f1 100644
        --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php
        +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php
        @@ -38,6 +38,7 @@ class UserRequest extends ResponseTicket
         			"db_key_field" => "id",
         			"db_finalclass_field" => "",
         			"display_template" => "",
        +			"icon" => "../modules/itop-request-mgmt-1.0.0/images/user-request.png",
         		);
         		MetaModel::Init_Params($aParams);
         		MetaModel::Init_InheritAttributes();
        @@ -84,6 +85,92 @@ class UserRequest extends ResponseTicket
         		$this->Set('ref', $sName);
         		return parent::ComputeValues();
         	}
        +
        +	/**
        +	 * Get the icon representing this object
        +	 * @param boolean $bImgTag If true the result is a full IMG tag (or an emtpy string if no icon is defined)
        +	 * @return string Either the full IMG tag ($bImgTag == true) or just the path to the icon file
        +	 */
        +	public function GetIcon($bImgTag = true)
        +	{
        +		$sStatus = $this->Get('status');
        +		switch($this->GetState())
        +		{
        +
        +			case 'escalated_tto':
        +			case 'escalated_ttr':
        +			$sIconName = self::MakeIconFromName('user-request-escalated.png');
        +			break;
        +			
        +			case 'resolved':
        +			case 'closed':
        +			$sIcon = self::MakeIconFromName('user-request-closed.png');
        +			break;
        +
        +			case 'new':
        +			$sIcon = self::MakeIconFromName('user-request.png');
        +			$oEscalationDeadline = $this->Get('tto_escalation_deadline');
        +			if ($oEscalationDeadline != null)
        +			{
        +				// A SLA is running
        +				$iStartDate = AttributeDateTime::GetAsUnixSeconds($this->Get('start_date'));
        +				$iEscalationDeadline = AttributeDateTime::GetAsUnixSeconds($oEscalationDeadline);
        +				$ratio = ($iEscalationDeadline - time())/($iEscalationDeadline - $iStartDate);
        +				if ($ratio <= 0)
        +				{
        +					$sIcon = self::MakeIconFromName('user-request-escalated.png');
        +				}
        +				else if ($ratio <= 0.25)
        +				{
        +					$sIcon = self::MakeIconFromName('user-request-deadline.png');
        +				}
        +			}
        +			break;
        +			
        +			case 'assigned':
        +			$sIcon = self::MakeIconFromName('user-request.png');
        +			$oEscalationDeadline = $this->Get('ttr_escalation_deadline');
        +			if ($oEscalationDeadline != null)
        +			{
        +				// A SLA is running
        +				$iStartDate = AttributeDateTime::GetAsUnixSeconds($this->Get('start_date'));
        +				$iEscalationDeadline = AttributeDateTime::GetAsUnixSeconds($oEscalationDeadline);
        +				$ratio = ($iEscalationDeadline - time())/($iEscalationDeadline - $iStartDate);
        +				if ($ratio <= 0)
        +				{
        +					$sIcon = self::MakeIconFromName('user-request-escalated.png');
        +				}
        +				else if ($ratio <= 0.25)
        +				{
        +					$sIcon = self::MakeIconFromName('user-request-deadline.png');
        +				}
        +			}
        +			break;
        +
        +			
        +			default:
        +			$sIcon = MetaModel::GetClassIcon(get_class($this), $bImgTag);
        +		}
        +		return $sIcon;
        +	}
        +	
        +	protected static function MakeIconFromName($sIconName, $bImgTag = true)
        +	{
        +		$sIcon = '';
        +		if ($sIconName != '')
        +		{
        +			$sPath = '../modules/itop-request-mgmt-1.0.0/images/'.$sIconName;
        +			if ($bImgTag)
        +			{
        +				$sIcon = "";
        +			}
        +			else
        +			{
        +				$sIcon  = $sPath;
        +			}
        +		}
        +		return $sIcon;
        +	}
         }
         
         $oMyMenuGroup = new MenuGroup('RequestManagement', 30 /* fRank */);
        diff --git a/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php
        index 50d11e41d1..0316201e77 100644
        --- a/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php
        +++ b/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php
        @@ -49,7 +49,7 @@ Dict::Add('EN US', 'English', 'English', array(
         	'Class:Ticket/Attribute:ref+' => '',
         	'Class:Ticket/Attribute:title' => 'Title',
         	'Class:Ticket/Attribute:title+' => '',
        -	'Class:Ticket/Attribute:description' => 'description',
        +	'Class:Ticket/Attribute:description' => 'Description',
         	'Class:Ticket/Attribute:description+' => '',
         	'Class:Ticket/Attribute:ticket_log' => 'Log',
         	'Class:Ticket/Attribute:ticket_log+' => '',
        @@ -255,7 +255,7 @@ Dict::Add('EN US', 'English', 'English', array(
         	'Class:ResponseTicket/Stimulus:ev_reassign+' => '',
         	'Class:ResponseTicket/Stimulus:ev_timeout' => 'Escalation',
         	'Class:ResponseTicket/Stimulus:ev_timeout+' => '',
        -	'Class:ResponseTicket/Stimulus:ev_resolve' => 'Mark a resolved',
        +	'Class:ResponseTicket/Stimulus:ev_resolve' => 'Mark as resolved',
         	'Class:ResponseTicket/Stimulus:ev_resolve+' => '',
         	'Class:ResponseTicket/Stimulus:ev_close' => 'Close',
         	'Class:ResponseTicket/Stimulus:ev_close+' => '',
        
        From 775524824ec82914a3a82c4b19d7a8b0c59214d6 Mon Sep 17 00:00:00 2001
        From: Denis Flaven 
        Date: Sun, 5 Sep 2010 09:43:26 +0000
        Subject: [PATCH 680/970] - Added a try/catch to nicely handle exceptions in
         these pages. - Fixed Trac #240: Validation of the fields when pressing the
         "Back" button - Fixed Trac #238: JS Error when pressin the "Next" button
        
        SVN:trunk[763]
        ---
         pages/audit.php     |  341 +++++----
         pages/csvimport.php | 1698 ++++++++++++++++++++++---------------------
         2 files changed, 1077 insertions(+), 962 deletions(-)
        
        diff --git a/pages/audit.php b/pages/audit.php
        index 2ba598f924..28310122f4 100644
        --- a/pages/audit.php
        +++ b/pages/audit.php
        @@ -22,158 +22,215 @@
          * @author      Denis Flaven 
          * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
          */
        -
        -require_once('../application/application.inc.php');
        -require_once('../application/itopwebpage.class.inc.php');
        -
        -require_once('../application/startup.inc.php');
        -$currentOrganization = utils::ReadParam('org_id', '');
        -$operation = utils::ReadParam('operation', '');
        -$oAppContext = new ApplicationContext();
        -
        -require_once('../application/loginwebpage.class.inc.php');
        -LoginWebPage::DoLogin(); // Check user rights and prompt if needed
        -
        -$oP = new iTopWebPage(Dict::S('UI:Audit:Title'), $currentOrganization);
        -
        -function GetRuleResultSet($iRuleId, $oDefinitionFilter)
        +try
         {
        -	$oRule = MetaModel::GetObject('AuditRule', $iRuleId);
        -	$sOql = $oRule->Get('query');
        -	$oRuleFilter = DBObjectSearch::FromOQL($sOql);
        -	if ($oRule->Get('valid_flag') == 'false')
        -	{
        -		// The query returns directly the invalid elements
        -		$oFilter = $oRuleFilter; 
        -		$oFilter->MergeWith($oDefinitionFilter);
        -		$oErrorObjectSet = new CMDBObjectSet($oFilter);
        -	}
        -	else
        -	{
        -		// The query returns only the valid elements, all the others are invalid
        -		$oFilter = $oRuleFilter; 
        -		$oErrorObjectSet = new CMDBObjectSet($oFilter);
        -		$aValidIds = array(0); // Make sure that we have at least one value in the list
        -		while($oObj = $oErrorObjectSet->Fetch())
        -		{
        -			$aValidIds[] = $oObj->GetKey(); 
        -		}
        -		$oFilter = $oDefinitionFilter;
        -		$oFilter->AddCondition('id', $aValidIds, 'NOTIN');
        -		$oErrorObjectSet = new CMDBObjectSet($oFilter);
        -	}
        -	return $oErrorObjectSet;
        -}
        -
        -function GetReportColor($iTotal, $iErrors)
        -{
        -	$sResult = 'red';
        -	if ( ($iTotal == 0) || ($iErrors / $iTotal) <= 0.05 )
        -	{
        -		$sResult = 'green';
        -	}
        -	else if ( ($iErrors / $iTotal) <= 0.25 )
        -	{
        -		$sResult = 'orange';
        -	}
        -	return $sResult;
        -}
        -
        -switch($operation)
        -{
        -	case 'errors':
        -	$iCategory = utils::ReadParam('category', '');
        -	$iRuleIndex = utils::ReadParam('rule', 0);
        -
        -	$oAuditCategory = MetaModel::GetObject('AuditCategory', $iCategory);
        -	$oDefinitionFilter = DBObjectSearch::FromOQL($oAuditCategory->Get('definition_set'));
        -	if (!empty($currentOrganization))
        -	{
        -		$oDefinitionFilter->AddCondition('org_id', $currentOrganization, '=');
        -	}
        -	$oDefinitionSet = new CMDBObjectSet($oDefinitionFilter);
        -	$oErrorObjectSet = GetRuleResultSet($iRuleIndex, $oDefinitionFilter);
        -	$oAuditRule = MetaModel::GetObject('AuditRule', $iRuleIndex);
        -	$oP->add('');
        -	$oP->p('[Back to audit results]');
        -    $sBlockId = 'audit_errors';
        -	$oP->p("
        \n"); - cmdbAbstractObject::DisplaySet($oP, $oErrorObjectSet, array('block_id' => $sBlockId)); - $oP->p("
        \n"); - break; + require_once('../application/application.inc.php'); + require_once('../application/itopwebpage.class.inc.php'); - case 'audit': - default: - $oP->add(''); - $oAuditFilter = new CMDBSearchFilter('AuditCategory'); - $oCategoriesSet = new DBObjectSet($oAuditFilter); - $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + foreach($aResults as $aRow) + { + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + } + } + $oP->add("
        \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - while($oAuditCategory = $oCategoriesSet->fetch()) + require_once('../application/startup.inc.php'); + $currentOrganization = utils::ReadParam('org_id', ''); + $operation = utils::ReadParam('operation', ''); + $oAppContext = new ApplicationContext(); + + require_once('../application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed + + $oP = new iTopWebPage(Dict::S('UI:Audit:Title'), $currentOrganization); + + function GetRuleResultSet($iRuleId, $oDefinitionFilter) { + $oRule = MetaModel::GetObject('AuditRule', $iRuleId); + $sOql = $oRule->Get('query'); + $oRuleFilter = DBObjectSearch::FromOQL($sOql); + if ($oRule->Get('valid_flag') == 'false') + { + // The query returns directly the invalid elements + $oFilter = $oRuleFilter; + $oFilter->MergeWith($oDefinitionFilter); + $oErrorObjectSet = new CMDBObjectSet($oFilter); + } + else + { + // The query returns only the valid elements, all the others are invalid + $oFilter = $oRuleFilter; + $oErrorObjectSet = new CMDBObjectSet($oFilter); + $aValidIds = array(0); // Make sure that we have at least one value in the list + while($oObj = $oErrorObjectSet->Fetch()) + { + $aValidIds[] = $oObj->GetKey(); + } + $oFilter = $oDefinitionFilter; + $oFilter->AddCondition('id', $aValidIds, 'NOTIN'); + $oErrorObjectSet = new CMDBObjectSet($oFilter); + } + return $oErrorObjectSet; + } + + function GetReportColor($iTotal, $iErrors) + { + $sResult = 'red'; + if ( ($iTotal == 0) || ($iErrors / $iTotal) <= 0.05 ) + { + $sResult = 'green'; + } + else if ( ($iErrors / $iTotal) <= 0.25 ) + { + $sResult = 'orange'; + } + return $sResult; + } + + switch($operation) + { + case 'errors': + $iCategory = utils::ReadParam('category', ''); + $iRuleIndex = utils::ReadParam('rule', 0); + + $oAuditCategory = MetaModel::GetObject('AuditCategory', $iCategory); $oDefinitionFilter = DBObjectSearch::FromOQL($oAuditCategory->Get('definition_set')); - $aObjectsWithErrors = array(); if (!empty($currentOrganization)) { - if (MetaModel::IsValidFilterCode($oDefinitionFilter->GetClass(), 'org_id')) - { - $oDefinitionFilter->AddCondition('org_id', $currentOrganization, '='); - } + $oDefinitionFilter->AddCondition('org_id', $currentOrganization, '='); } - $aResults = array(); $oDefinitionSet = new CMDBObjectSet($oDefinitionFilter); - $iCount = $oDefinitionSet->Count(); - $oRulesFilter = new CMDBSearchFilter('AuditRule'); - $oRulesFilter->AddCondition('category_id', $oAuditCategory->GetKey(), '='); - $oRulesSet = new DBObjectSet($oRulesFilter); - while($oAuditRule = $oRulesSet->fetch() ) - { - $aRow = array(); - $aRow['description'] = $oAuditRule->Get('name'); - if ($iCount == 0) - { - // nothing to check, really ! - $aRow['nb_errors'] = "GetKey()."&rule=".$oAuditRule->GetKey()."\">0"; - $aRow['percent_ok'] = '100.00'; - $aRow['class'] = GetReportColor($iCount, 0); - } - else - { - $oRuleFilter = DBObjectSearch::FromOQL($oAuditRule->Get('query')); - $oErrorObjectSet = GetRuleResultSet($oAuditRule->GetKey(), $oDefinitionFilter); - $iErrorsCount = $oErrorObjectSet->Count(); - while($oObj = $oErrorObjectSet->Fetch()) - { - $aObjectsWithErrors[$oObj->GetKey()] = true; - } - $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCount"; - $aRow['percent_ok'] = sprintf('%.2f', 100.0 * (($iCount - $iErrorsCount) / $iCount)); - $aRow['class'] = GetReportColor($iCount, $iErrorsCount); - } - $aResults[] = $aRow; - $iTotalErrors = count($aObjectsWithErrors); - $sOverallPercentOk = ($iCount == 0) ? '100.00' : sprintf('%.2f', 100.0 * (($iCount - $iTotalErrors) / $iCount)); - $sClass = GetReportColor($iCount, $iTotalErrors); - - } + $oErrorObjectSet = GetRuleResultSet($iRuleIndex, $oDefinitionFilter); + $oAuditRule = MetaModel::GetObject('AuditRule', $iRuleIndex); + $oP->add(''); + $oP->p('[Back to audit results]'); + $sBlockId = 'audit_errors'; + $oP->p("
        \n"); + cmdbAbstractObject::DisplaySet($oP, $oErrorObjectSet, array('block_id' => $sBlockId)); + $oP->p("
        \n"); + break; + + case 'audit': + default: + $oP->add(''); + $oAuditFilter = new CMDBSearchFilter('AuditCategory'); + $oCategoriesSet = new DBObjectSet($oAuditFilter); + $oP->add("
        ".Dict::S('UI:Audit:HeaderAuditRule')."".Dict::S('UI:Audit:HeaderNbObjects')."".Dict::S('UI:Audit:HeaderNbErrors')."".Dict::S('UI:Audit:PercentageOk')."
        \n"); + $oP->add("\n"); - $oP->add("
        \n"); + $oP->add("\n"); $oP->add("\n"); - $oP->add("\n"); + $oP->add("\n"); $oP->add("\n"); - foreach($aResults as $aRow) + while($oAuditCategory = $oCategoriesSet->fetch()) { - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - } - } - $oP->add("
        ".$oAuditCategory->GetName()."$iCount$iTotalErrors$sOverallPercentOk %".Dict::S('UI:Audit:HeaderAuditRule')."".Dict::S('UI:Audit:HeaderNbObjects')."".Dict::S('UI:Audit:HeaderNbErrors')."".Dict::S('UI:Audit:PercentageOk')."
         ".$aRow['description']."".$aRow['nb_errors']."".$aRow['percent_ok']." %
        \n"); - $oP->add("
        \n"); -} + $oDefinitionFilter = DBObjectSearch::FromOQL($oAuditCategory->Get('definition_set')); + $aObjectsWithErrors = array(); + if (!empty($currentOrganization)) + { + if (MetaModel::IsValidFilterCode($oDefinitionFilter->GetClass(), 'org_id')) + { + $oDefinitionFilter->AddCondition('org_id', $currentOrganization, '='); + } + } + $aResults = array(); + $oDefinitionSet = new CMDBObjectSet($oDefinitionFilter); + $iCount = $oDefinitionSet->Count(); + $oRulesFilter = new CMDBSearchFilter('AuditRule'); + $oRulesFilter->AddCondition('category_id', $oAuditCategory->GetKey(), '='); + $oRulesSet = new DBObjectSet($oRulesFilter); + while($oAuditRule = $oRulesSet->fetch() ) + { + $aRow = array(); + $aRow['description'] = $oAuditRule->Get('name'); + if ($iCount == 0) + { + // nothing to check, really ! + $aRow['nb_errors'] = "GetKey()."&rule=".$oAuditRule->GetKey()."\">0"; + $aRow['percent_ok'] = '100.00'; + $aRow['class'] = GetReportColor($iCount, 0); + } + else + { + $oRuleFilter = DBObjectSearch::FromOQL($oAuditRule->Get('query')); + $oErrorObjectSet = GetRuleResultSet($oAuditRule->GetKey(), $oDefinitionFilter); + $iErrorsCount = $oErrorObjectSet->Count(); + while($oObj = $oErrorObjectSet->Fetch()) + { + $aObjectsWithErrors[$oObj->GetKey()] = true; + } + $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCount"; + $aRow['percent_ok'] = sprintf('%.2f', 100.0 * (($iCount - $iErrorsCount) / $iCount)); + $aRow['class'] = GetReportColor($iCount, $iErrorsCount); + } + $aResults[] = $aRow; + $iTotalErrors = count($aObjectsWithErrors); + $sOverallPercentOk = ($iCount == 0) ? '100.00' : sprintf('%.2f', 100.0 * (($iCount - $iTotalErrors) / $iCount)); + $sClass = GetReportColor($iCount, $iTotalErrors); -$oP->output(); + } + $oP->add("
        ".$oAuditCategory->GetName()."$iCount$iTotalErrors$sOverallPercentOk %
         ".$aRow['description']."".$aRow['nb_errors']."".$aRow['percent_ok']." %
        \n"); + $oP->add("\n"); + $oP->add("\n"); + } + + $oP->output(); +} +catch(CoreException $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:FatalErrorMessage')."

        \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', $e->GetIssue()); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', $e->getContextData()); + $oLog->DBInsertNoReload(); + } + + IssueLog::Error($e->getMessage()); + } + + // For debugging only + //throw $e; +} +catch(Exception $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:FatalErrorMessage')."

        \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', 'PHP Exception'); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', array()); + $oLog->DBInsertNoReload(); + } + + IssueLog::Error($e->getMessage()); + } +} ?> diff --git a/pages/csvimport.php b/pages/csvimport.php index 08ef30528b..548bc7b24a 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -23,631 +23,633 @@ * @author Denis Flaven * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ - -ini_set('memory_limit', '256M'); -require_once('../application/application.inc.php'); -require_once('../application/itopwebpage.class.inc.php'); - -require_once('../application/startup.inc.php'); - -require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - -$oAppContext = new ApplicationContext(); -$currentOrganization = utils::ReadParam('org_id', 1); -$iStep = utils::ReadParam('step', 1); - -$oPage = new iTopWebPage(Dict::S('UI:Title:BulkImport'), $currentOrganization); - -/** - * Helper function to build a select from the list of valid classes for a given action - * @param string $sName The name of the select in the HTML form - * @param string $sDefaulfValue The defaut value (i.e the value selected by default) - * @param integer $iWidthPx The width (in pixels) of the drop-down list - * @param integer $iActionCode The ActionCode (from UserRights) to check for authorization for the classes - * @return string The HTML fragment corresponding to the select tag - */ -function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null) +try { - $sHtml = ""; - return $sHtml; -} - -/** - * Helper to 'check' an input in an HTML form if the current value equals the value given - * @param mixed $sCurrentValue The current value to be chacked against the value of the input - * @param mixed $sProposedValue The value of the input - * @param bool $bInverseCondition Set to true to perform the reversed comparison - * @return string Either ' checked' or an empty string - */ -function IsChecked($sCurrentValue, $sProposedValue, $bInverseCondition = false) -{ - $bCondition = ($sCurrentValue == $sProposedValue); + require_once('../application/startup.inc.php'); - return ($bCondition xor $bInverseCondition) ? ' checked' : ''; -} - -/** - * Get the user friendly name for an 'extended' attribute code i.e 'name', becomes 'Name' and 'org_id->name' becomes 'Organization->Name' - * @param string $sClassName The name of the class - * @param string $sAttCodeEx Either an attribute code of ext_key_name->att_code - * @return string A user friendly format of the string: AttributeName or AttributeName->ExtAttributeName - */ -function GetFriendlyAttCodeName($sClassName, $sAttCodeEx) -{ - $sFriendlyName = ''; - if (preg_match('/(.+)->(.+)/', $sAttCodeEx, $aMatches) > 0) - { - $sAttribute = $aMatches[1]; - $sField = $aMatches[2]; - $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttribute); - if ($oAttDef->IsExternalKey()) - { - $sTargetClass = $oAttDef->GetTargetClass(); - $oTargetAttDef = MetaModel::GetAttributeDef($sTargetClass, $sField); - $sFriendlyName = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); - } - else - { - // hum, hum... should never happen, we'd better raise an exception - throw(new Exception(Dict::Format('UI:CSVImport:ErrorExtendedAttCode', $sAttCodeEx, $sAttribute, $sClassName))); - } - - } - else - { - if ($sAttCodeEx == 'id') - { - $sFriendlyName = Dict::S('UI:CSVImport:idField'); - } - else - { - $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCodeEx); - $sFriendlyName = $oAttDef->GetLabel(); - } - } - return $sFriendlyName; -} - -/** - * Returns the number of occurences of each char from the set in the specified string - * @param string $sString The input data - * @param array $aSet The set of characters to count - * @return hash 'char' => nb of occurences - */ -function CountCharsFromSet($sString, $aSet) -{ - $aResult = array(); - $aCount = count_chars($sString); - foreach($aSet as $sChar) - { - $aResult[$sChar] = isset($aCount[ord($sChar)]) ? $aCount[ord($sChar)] : 0; - } - return $aResult; -} - -/** - * Return the most frequent (and regularly occuring) character among the given set, in the specified lines - * @param array $aCSVData The input data, one entry per line - * @param array $aPossibleSeparators The list of characters to count - * @return string The most frequent character from the set - */ -function GuessFromFrequency($aCSVData, $aPossibleSeparators) -{ - $iLine = 0; - $iMaxLine = 20; // Process max 20 lines to guess the parameters - foreach($aPossibleSeparators as $sSep) - { - $aGuesses[$sSep]['total'] = $aGuesses[$sSep]['max'] = 0; - $aGuesses[$sSep]['min'] = 999; - } - $aStats = array(); - while(($iLine < count($aCSVData)) && ($iLine < $iMaxLine) ) - { - if (strlen($aCSVData[$iLine]) > 0) - { - $aStats[$iLine] = CountCharsFromSet($aCSVData[$iLine], $aPossibleSeparators); - } - $iLine++; - } - $iLine = 1; - foreach($aStats as $aLineStats) - { - foreach($aPossibleSeparators as $sSep) - { - $aGuesses[$sSep]['total'] += $aLineStats[$sSep]; - if ($aLineStats[$sSep] > $aGuesses[$sSep]['max']) $aGuesses[$sSep]['max'] = $aLineStats[$sSep]; - if ($aLineStats[$sSep] < $aGuesses[$sSep]['min']) $aGuesses[$sSep]['min'] = $aLineStats[$sSep]; - } - $iLine++; - } + require_once('../application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(); // Check user rights and prompt if needed - $aScores = array(); - foreach($aGuesses as $sSep => $aData) - { - $aScores[$sSep] = $aData['total'] + $aData['max'] - $aData['min']; - } - arsort($aScores, SORT_NUMERIC); // Sort the array, higher scores first - $aKeys = array_keys($aScores); - $sSeparator = $aKeys[0]; // Take the first key, the one with the best score - return $sSeparator; -} - -/** - * Try to predict the CSV parameters based on the input data - * @param string $sCSVData The input data - * @return hash 'separator' => the_guessed_separator, 'qualifier' => the_guessed_text_qualifier - */ -function GuessParameters($sCSVData) -{ - $aData = explode("\n", $sCSVData); - $sSeparator = GuessFromFrequency($aData, array("\t", ',', ';', '|')); // Guess the most frequent (and regular) character on each line - $sQualifier = GuessFromFrequency($aData, array('"', "'")); // Guess the most frequent (and regular) character on each line + $oAppContext = new ApplicationContext(); + $currentOrganization = utils::ReadParam('org_id', 1); + $iStep = utils::ReadParam('step', 1); - return array('separator' => $sSeparator, 'qualifier' => $sQualifier); -} - -/** - * Process the CSV data, for real or as a simulation - * @param WebPage $oPage The page used to display the wizard - * @param bool $bSimulate Whether or not to simulate the data load - * @return array The CSV lines in error that were rejected from the load (with the header line - if any) or null - */ -function ProcessCSVData(WebPage $oPage, $bSimulate = true) -{ - $aResult = array(); - $sCSVData = utils::ReadParam('csvdata', ''); - $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', ''); - $sSeparator = utils::ReadParam('separator', ','); - $sTextQualifier = utils::ReadParam('text_qualifier', '"'); - $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); - $iRealSkippedLines = $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); - $sClassName = utils::ReadParam('class_name', ''); - $aFieldsMapping = utils::ReadParam('field', array()); - $aSearchFields = utils::ReadParam('search_field', array()); - $iCurrentStep = $bSimulate ? 4 : 5; - $bAdvanced = utils::ReadParam('advanced', 0); - $sEncoding = utils::ReadParam('encoding', 'UTF-8'); + $oPage = new iTopWebPage(Dict::S('UI:Title:BulkImport'), $currentOrganization); - // Parse the data set - $oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier); - $aData = $oCSVParser->ToArray($iSkippedLines); - if ($bHeaderLine) + /** + * Helper function to build a select from the list of valid classes for a given action + * @param string $sName The name of the select in the HTML form + * @param string $sDefaulfValue The defaut value (i.e the value selected by default) + * @param integer $iWidthPx The width (in pixels) of the drop-down list + * @param integer $iActionCode The ActionCode (from UserRights) to check for authorization for the classes + * @return string The HTML fragment corresponding to the select tag + */ + function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null) { - $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, array_shift($aData)).$sTextQualifier; // Remove the first line and store it in case of error - $iRealSkippedLines++; - } - - // Format for the line numbers - $sMaxLen = (strlen(''.count($aData)) < 3) ? 3 : strlen(''.count($aData)); // Pad line numbers to the appropriate number of chars, but at least 3 - - // Compute the list of search/reconciliation criteria - $aSearchKeys = array(); - foreach($aSearchFields as $index => $sDummy) - { - $sSearchField = $aFieldsMapping[$index]; - $aMatches = array(); - if (preg_match('/(.+)->(.+)/', $sSearchField, $aMatches) > 0) + $sHtml = ""; + return $sHtml; + } + + /** + * Helper to 'check' an input in an HTML form if the current value equals the value given + * @param mixed $sCurrentValue The current value to be chacked against the value of the input + * @param mixed $sProposedValue The value of the input + * @param bool $bInverseCondition Set to true to perform the reversed comparison + * @return string Either ' checked' or an empty string + */ + function IsChecked($sCurrentValue, $sProposedValue, $bInverseCondition = false) + { + $bCondition = ($sCurrentValue == $sProposedValue); + + return ($bCondition xor $bInverseCondition) ? ' checked' : ''; + } + + /** + * Get the user friendly name for an 'extended' attribute code i.e 'name', becomes 'Name' and 'org_id->name' becomes 'Organization->Name' + * @param string $sClassName The name of the class + * @param string $sAttCodeEx Either an attribute code of ext_key_name->att_code + * @return string A user friendly format of the string: AttributeName or AttributeName->ExtAttributeName + */ + function GetFriendlyAttCodeName($sClassName, $sAttCodeEx) + { + $sFriendlyName = ''; + if (preg_match('/(.+)->(.+)/', $sAttCodeEx, $aMatches) > 0) + { + $sAttribute = $aMatches[1]; + $sField = $aMatches[2]; + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttribute); + if ($oAttDef->IsExternalKey()) + { + $sTargetClass = $oAttDef->GetTargetClass(); + $oTargetAttDef = MetaModel::GetAttributeDef($sTargetClass, $sField); + $sFriendlyName = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel(); } else { - if ($sAttCode == 'id') + // hum, hum... should never happen, we'd better raise an exception + throw(new Exception(Dict::Format('UI:CSVImport:ErrorExtendedAttCode', $sAttCodeEx, $sAttribute, $sClassName))); + } + + } + else + { + if ($sAttCodeEx == 'id') + { + $sFriendlyName = Dict::S('UI:CSVImport:idField'); + } + else + { + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCodeEx); + $sFriendlyName = $oAttDef->GetLabel(); + } + } + return $sFriendlyName; + } + + /** + * Returns the number of occurences of each char from the set in the specified string + * @param string $sString The input data + * @param array $aSet The set of characters to count + * @return hash 'char' => nb of occurences + */ + function CountCharsFromSet($sString, $aSet) + { + $aResult = array(); + $aCount = count_chars($sString); + foreach($aSet as $sChar) + { + $aResult[$sChar] = isset($aCount[ord($sChar)]) ? $aCount[ord($sChar)] : 0; + } + return $aResult; + } + + /** + * Return the most frequent (and regularly occuring) character among the given set, in the specified lines + * @param array $aCSVData The input data, one entry per line + * @param array $aPossibleSeparators The list of characters to count + * @return string The most frequent character from the set + */ + function GuessFromFrequency($aCSVData, $aPossibleSeparators) + { + $iLine = 0; + $iMaxLine = 20; // Process max 20 lines to guess the parameters + foreach($aPossibleSeparators as $sSep) + { + $aGuesses[$sSep]['total'] = $aGuesses[$sSep]['max'] = 0; + $aGuesses[$sSep]['min'] = 999; + } + $aStats = array(); + while(($iLine < count($aCSVData)) && ($iLine < $iMaxLine) ) + { + if (strlen($aCSVData[$iLine]) > 0) + { + $aStats[$iLine] = CountCharsFromSet($aCSVData[$iLine], $aPossibleSeparators); + } + $iLine++; + } + $iLine = 1; + foreach($aStats as $aLineStats) + { + foreach($aPossibleSeparators as $sSep) + { + $aGuesses[$sSep]['total'] += $aLineStats[$sSep]; + if ($aLineStats[$sSep] > $aGuesses[$sSep]['max']) $aGuesses[$sSep]['max'] = $aLineStats[$sSep]; + if ($aLineStats[$sSep] < $aGuesses[$sSep]['min']) $aGuesses[$sSep]['min'] = $aLineStats[$sSep]; + } + $iLine++; + } + + $aScores = array(); + foreach($aGuesses as $sSep => $aData) + { + $aScores[$sSep] = $aData['total'] + $aData['max'] - $aData['min']; + } + arsort($aScores, SORT_NUMERIC); // Sort the array, higher scores first + $aKeys = array_keys($aScores); + $sSeparator = $aKeys[0]; // Take the first key, the one with the best score + return $sSeparator; + } + + /** + * Try to predict the CSV parameters based on the input data + * @param string $sCSVData The input data + * @return hash 'separator' => the_guessed_separator, 'qualifier' => the_guessed_text_qualifier + */ + function GuessParameters($sCSVData) + { + $aData = explode("\n", $sCSVData); + $sSeparator = GuessFromFrequency($aData, array("\t", ',', ';', '|')); // Guess the most frequent (and regular) character on each line + $sQualifier = GuessFromFrequency($aData, array('"', "'")); // Guess the most frequent (and regular) character on each line + + return array('separator' => $sSeparator, 'qualifier' => $sQualifier); + } + + /** + * Process the CSV data, for real or as a simulation + * @param WebPage $oPage The page used to display the wizard + * @param bool $bSimulate Whether or not to simulate the data load + * @return array The CSV lines in error that were rejected from the load (with the header line - if any) or null + */ + function ProcessCSVData(WebPage $oPage, $bSimulate = true) + { + $aResult = array(); + $sCSVData = utils::ReadParam('csvdata', ''); + $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', ''); + $sSeparator = utils::ReadParam('separator', ','); + $sTextQualifier = utils::ReadParam('text_qualifier', '"'); + $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); + $iRealSkippedLines = $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); + $sClassName = utils::ReadParam('class_name', ''); + $aFieldsMapping = utils::ReadParam('field', array()); + $aSearchFields = utils::ReadParam('search_field', array()); + $iCurrentStep = $bSimulate ? 4 : 5; + $bAdvanced = utils::ReadParam('advanced', 0); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); + + // Parse the data set + $oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier); + $aData = $oCSVParser->ToArray($iSkippedLines); + if ($bHeaderLine) + { + $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, array_shift($aData)).$sTextQualifier; // Remove the first line and store it in case of error + $iRealSkippedLines++; + } + + // Format for the line numbers + $sMaxLen = (strlen(''.count($aData)) < 3) ? 3 : strlen(''.count($aData)); // Pad line numbers to the appropriate number of chars, but at least 3 + + // Compute the list of search/reconciliation criteria + $aSearchKeys = array(); + foreach($aSearchFields as $index => $sDummy) + { + $sSearchField = $aFieldsMapping[$index]; + $aMatches = array(); + if (preg_match('/(.+)->(.+)/', $sSearchField, $aMatches) > 0) + { + $sSearchField = $aMatches[1]; + $aSearchKeys[$aMatches[1]] = ''; + } + else + { + $aSearchKeys[$sSearchField] = ''; + } + if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField)) + { + // Remove invalid or unmapped search fields + $aSearchFields[$index] = null; + unset($aSearchKeys[$sSearchField]); + } + } + + // Compute the list of fields and external keys to process + $aExtKeys = array(); + $aAttributes = array(); + $aExternalKeysByColumn = array(); + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + $iIndex = $iNumber-1; + if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) + { + if (preg_match('/(.+)->(.+)/', $sAttCode, $aMatches) > 0) { - $aAttributes['id'] = $iIndex; + $sAttribute = $aMatches[1]; + $sField = $aMatches[2]; + $aExtKeys[$sAttribute][$sField] = $iIndex; + $aExternalKeysByColumn[$iIndex] = $sAttribute; } else { - $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); - if ($oAttDef->IsExternalKey()) + if ($sAttCode == 'id') { - $aExtKeys[$sAttCode]['id'] = $iIndex; - $aExternalKeysByColumn[$iIndex] = $sAttCode; + $aAttributes['id'] = $iIndex; } else { - $aAttributes[$sAttCode] = $iIndex; + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $aExtKeys[$sAttCode]['id'] = $iIndex; + $aExternalKeysByColumn[$iIndex] = $sAttCode; + } + else + { + $aAttributes[$sAttCode] = $iIndex; + } } } - } - } - } - - $oMyChange = null; - if (!$bSimulate) - { - // We're doing it for real, let's create a change - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + } } - else + + $oMyChange = null; + if (!$bSimulate) { - $sUserString = UserRights::GetUser(); - } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - } - - $oBulk = new BulkChange( - $sClassName, - $aData, - $aAttributes, - $aExtKeys, - array_keys($aSearchKeys) - ); - - $oPage->add(''); - $aRes = $oBulk->Process($oMyChange); - - $sHtml = ''; - $sHtml .= ''; - $sHtml .= ''; - $sHtml .= ''; - foreach($aFieldsMapping as $iNumber => $sAttCode) - { - if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) - { - $sHtml .= ""; - } - } - $sHtml .= ''; - $sHtml .= ''; - $iLine = 0; - - $iErrors = 0; - $iCreated = 0; - $iModified = 0; - $iUnchanged = 0; - - foreach($aData as $aRow) - { - $oStatus = $aRes[$iLine]['__STATUS__']; - $sUrl = ''; - $sMessage = ''; - $sCSSRowClass = ''; - $sCSSMessageClass = 'cell_ok'; - switch(get_class($oStatus)) - { - case 'RowStatus_NoChange': - $iUnchanged++; - $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); - $sUrl = $oObj->GetHyperlink(); - $sStatus = ''; - $sCSSRowClass = 'row_unchanged'; - break; - - case 'RowStatus_Modify': - $iModified++; - $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); - $sUrl = $oObj->GetHyperlink(); - $sStatus = ''; - $sCSSRowClass = 'row_modified'; - break; - - case 'RowStatus_NewObj': - $iCreated++; - $sFinalClass = $aRes[$iLine]['finalclass']; - $sStatus = ''; - $sCSSRowClass = 'row_added'; - if ($bSimulate) + // We're doing it for real, let's create a change + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::IsImpersonated()) { - $sMessage = 'Object will be created'; + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); } else { - $sFinalClass = $aRes[$iLine]['finalclass']; - $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); - $sUrl = $oObj->GetHyperlink(); - $sMessage = 'Object created'; + $sUserString = UserRights::GetUser(); } - break; - - case 'RowStatus_Issue': - $iErrors++; - $sMessage .= $oPage->GetP($oStatus->GetDescription()); - $sStatus = ''; - $sCSSMessageClass = 'cell_error'; - $sCSSRowClass = 'row_error'; - $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error - break; + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); } - $sHtml .= ''; - $sHtml .= ""; - $sHtml .= ""; - $sHtml .= ""; + + $oBulk = new BulkChange( + $sClassName, + $aData, + $aAttributes, + $aExtKeys, + array_keys($aSearchKeys) + ); + + $oPage->add(''); + $aRes = $oBulk->Process($oMyChange); + + $sHtml = '
        LineStatusObject".GetFriendlyAttCodeName($sClassName, $sAttCode)."Message
        ".sprintf("%0{$sMaxLen}d", 1+$iLine+$iRealSkippedLines)."$sStatus$sUrl
        '; + $sHtml .= ''; + $sHtml .= ''; + $sHtml .= ''; foreach($aFieldsMapping as $iNumber => $sAttCode) { if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) { - $oCellStatus = $aRes[$iLine][$iNumber -1]; - $sCellMessage = ''; - if (isset($aExternalKeysByColumn[$iNumber -1])) + $sHtml .= ""; + } + } + $sHtml .= ''; + $sHtml .= ''; + $iLine = 0; + + $iErrors = 0; + $iCreated = 0; + $iModified = 0; + $iUnchanged = 0; + + foreach($aData as $aRow) + { + $oStatus = $aRes[$iLine]['__STATUS__']; + $sUrl = ''; + $sMessage = ''; + $sCSSRowClass = ''; + $sCSSMessageClass = 'cell_ok'; + switch(get_class($oStatus)) + { + case 'RowStatus_NoChange': + $iUnchanged++; + $sFinalClass = $aRes[$iLine]['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sStatus = ''; + $sCSSRowClass = 'row_unchanged'; + break; + + case 'RowStatus_Modify': + $iModified++; + $sFinalClass = $aRes[$iLine]['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sStatus = ''; + $sCSSRowClass = 'row_modified'; + break; + + case 'RowStatus_NewObj': + $iCreated++; + $sFinalClass = $aRes[$iLine]['finalclass']; + $sStatus = ''; + $sCSSRowClass = 'row_added'; + if ($bSimulate) { - $sExtKeyName = $aExternalKeysByColumn[$iNumber -1]; - $oExtKeyCellStatus = $aRes[$iLine][$sExtKeyName]; - switch(get_class($oExtKeyCellStatus)) + $sMessage = 'Object will be created'; + } + else + { + $sFinalClass = $aRes[$iLine]['finalclass']; + $oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); + $sUrl = $oObj->GetHyperlink(); + $sMessage = 'Object created'; + } + break; + + case 'RowStatus_Issue': + $iErrors++; + $sMessage .= $oPage->GetP($oStatus->GetDescription()); + $sStatus = ''; + $sCSSMessageClass = 'cell_error'; + $sCSSRowClass = 'row_error'; + $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error + break; + } + $sHtml .= ''; + $sHtml .= ""; + $sHtml .= ""; + $sHtml .= ""; + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) + { + $oCellStatus = $aRes[$iLine][$iNumber -1]; + $sCellMessage = ''; + if (isset($aExternalKeysByColumn[$iNumber -1])) + { + $sExtKeyName = $aExternalKeysByColumn[$iNumber -1]; + $oExtKeyCellStatus = $aRes[$iLine][$sExtKeyName]; + switch(get_class($oExtKeyCellStatus)) + { + case 'CellStatus_Issue': + $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); + break; + + case 'CellStatus_Ambiguous': + $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); + break; + + default: + // Do nothing + } + } + switch(get_class($oCellStatus)) { case 'CellStatus_Issue': - $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); + $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); + $sHtml .= ''; break; case 'CellStatus_Ambiguous': - $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); + $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); + $sHtml .= ''; + break; + + case 'CellStatus_Modify': + $sHtml .= ''; break; default: - // Do nothing + $sHtml .= ''; } } - switch(get_class($oCellStatus)) - { - case 'CellStatus_Issue': - $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); - $sHtml .= ''; - break; - - case 'CellStatus_Ambiguous': - $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); - $sHtml .= ''; - break; - - case 'CellStatus_Modify': - $sHtml .= ''; - break; - - default: - $sHtml .= ''; - } } + $sHtml .= ""; + $iLine++; + $sHtml .= ''; } - $sHtml .= ""; - $iLine++; - $sHtml .= ''; - } - $sHtml .= '
        LineStatusObject".GetFriendlyAttCodeName($sClassName, $sAttCode)."Message
        ".sprintf("%0{$sMaxLen}d", 1+$iLine+$iRealSkippedLines)."$sStatus$sUrlERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'AMBIGUOUS: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.''.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').''.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'AMBIGUOUS: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.''.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').''.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'$sMessage
        $sMessage
        '; - $oPage->add('
        '); - $oPage->add('
        '); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - foreach($aFieldsMapping as $iNumber => $sAttCode) - { - $oPage->add(''); - } - foreach($aSearchFields as $index => $sDummy) - { - $oPage->add(''); - } - $aFieldsMapping = utils::ReadParam('field', array()); - $aSearchFields = utils::ReadParam('search_field', array()); - $aDisplayFilters = array(); - if ($bSimulate) - { - $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsWillStayUnchanged'); - $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWillBeModified'); - $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWillBeAdded'); - $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsWillHaveErrors'); - } - else - { - $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsRemainedUnchanged'); - $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWereModified'); - $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWereAdded'); - $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsHadErrors'); - } - $oPage->add('

          '.sprintf($aDisplayFilters['unchanged'], $iUnchanged).'  '); - $oPage->add('  '.sprintf($aDisplayFilters['modified'], $iModified).'  '); - $oPage->add('  '.sprintf($aDisplayFilters['added'], $iCreated).'  '); - $oPage->add('  '.sprintf($aDisplayFilters['errors'], $iErrors).'

        '); - $oPage->add('
        '); - $oPage->add($sHtml); - $oPage->add('
        '); - $oPage->add('

          '); - if ($bSimulate) - { - $oPage->add('

        '); - } - else - { - $oPage->add('

        '); - } - $oPage->add('
        '); - $oPage->add('
        '); - $oPage->add_script( + $sHtml .= ''; + $oPage->add('
        '); + $oPage->add('
        '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + foreach($aFieldsMapping as $iNumber => $sAttCode) + { + $oPage->add(''); + } + foreach($aSearchFields as $index => $sDummy) + { + $oPage->add(''); + } + $aFieldsMapping = utils::ReadParam('field', array()); + $aSearchFields = utils::ReadParam('search_field', array()); + $aDisplayFilters = array(); + if ($bSimulate) + { + $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsWillStayUnchanged'); + $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWillBeModified'); + $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWillBeAdded'); + $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsWillHaveErrors'); + } + else + { + $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsRemainedUnchanged'); + $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWereModified'); + $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWereAdded'); + $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsHadErrors'); + } + $oPage->add('

          '.sprintf($aDisplayFilters['unchanged'], $iUnchanged).'  '); + $oPage->add('  '.sprintf($aDisplayFilters['modified'], $iModified).'  '); + $oPage->add('  '.sprintf($aDisplayFilters['added'], $iCreated).'  '); + $oPage->add('  '.sprintf($aDisplayFilters['errors'], $iErrors).'

        '); + $oPage->add('
        '); + $oPage->add($sHtml); + $oPage->add('
        '); + $oPage->add('

          '); + if ($bSimulate) + { + $oPage->add('

        '); + } + else + { + $oPage->add('

        '); + } + $oPage->add('
        '); + $oPage->add('
        '); + $oPage->add_script( <<< EOF - function CSVGoBack() - { - $('input[name=step]').val($iCurrentStep-1); - $('#wizForm').submit(); - - } +function CSVGoBack() +{ + $('input[name=step]').val($iCurrentStep-1); + $('#wizForm').submit(); + +} - function ToggleRows(sCSSClass) - { - $('.'+sCSSClass).toggle(); - } +function ToggleRows(sCSSClass) +{ + $('.'+sCSSClass).toggle(); +} EOF -); - if ($iErrors > 0) - { - return $aResult; - } - else - { - return null; - } - -} -/** - * Perform the actual load of the CSV data and display the results - * @param WebPage $oPage The web page to display the wizard - * @return void - */ -function LoadData(WebPage $oPage) -{ - $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep5').'

        '); - $aResult = ProcessCSVData($oPage, false /* simulate = false */); - if (is_array($aResult)) - { - $oPage->StartCollapsibleSection(Dict::S('UI:CSVImport:LinesNotImported'), false); - $oPage->p(Dict::S('UI:CSVImport:LinesNotImported+')); - $oPage->add(''); - $oPage->EndCollapsibleSection(); - } -} - -/** - * Simulate the load of the CSV data and display the results - * @param WebPage $oPage The web page to display the wizard - * @return void - */ -function Preview(WebPage $oPage) -{ - $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep4').'

        '); - ProcessCSVData($oPage, true /* simulate */); -} - -/** - * Select the mapping between the CSV column and the fields of the objects - * @param WebPage $oPage The web page to display the wizard - * @return void - */ -function SelectMapping(WebPage $oPage) -{ - $sCSVData = utils::ReadParam('csvdata', ''); - $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '');; - $sSeparator = utils::ReadParam('separator', ','); - if ($sSeparator == 'tab') $sSeparator = "\t"; - if ($sSeparator == 'other') - { - $sSeparator = utils::ReadParam('other_separator', ','); - } - $sTextQualifier = utils::ReadParam('text_qualifier', '"'); - if ($sTextQualifier == 'other') - { - $sTextQualifier = utils::ReadParam('other_qualifier', '"'); - } - $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); - $iSkippedLines = 0; - if (utils::ReadParam('box_skiplines', '0') == 1) - { - $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); - } - $sClassName = utils::ReadParam('class_name', ''); - $bAdvanced = utils::ReadParam('advanced', 0); - $sEncoding = utils::ReadParam('encoding', 'UTF-8'); - - $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep3').'

        '); - $oPage->add('
        '); - $oPage->add('
        '.Dict::S('UI:CSVImport:SelectClass').' '); - $oPage->add(GetClassesSelect('class_name', $sClassName, 300, UR_ACTION_BULK_MODIFY)); - $oPage->add(' '.Dict::S('UI:CSVImport:AdvancedMode').'
        '); - $oPage->add(''); - $oPage->add('

        '.Dict::S('UI:CSVImport:SelectAClassFirst').'

        '); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add('

          '); - $oPage->add('

        '); - $oPage->add('
        '); - $oPage->add('
        '); + ); + if ($iErrors > 0) + { + return $aResult; + } + else + { + return null; + } - $sAlertIncompleteMapping = Dict::S('UI:CSVImport:AlertIncompleteMapping'); - $sAlertNoSearchCriteria = Dict::S('UI:CSVImport:AlertNoSearchCriteria'); + } + /** + * Perform the actual load of the CSV data and display the results + * @param WebPage $oPage The web page to display the wizard + * @return void + */ + function LoadData(WebPage $oPage) + { + $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep5').'

        '); + $aResult = ProcessCSVData($oPage, false /* simulate = false */); + if (is_array($aResult)) + { + $oPage->StartCollapsibleSection(Dict::S('UI:CSVImport:LinesNotImported'), false); + $oPage->p(Dict::S('UI:CSVImport:LinesNotImported+')); + $oPage->add(''); + $oPage->EndCollapsibleSection(); + } + } - $oPage->add_ready_script( + /** + * Simulate the load of the CSV data and display the results + * @param WebPage $oPage The web page to display the wizard + * @return void + */ + function Preview(WebPage $oPage) + { + $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep4').'

        '); + ProcessCSVData($oPage, true /* simulate */); + } + + /** + * Select the mapping between the CSV column and the fields of the objects + * @param WebPage $oPage The web page to display the wizard + * @return void + */ + function SelectMapping(WebPage $oPage) + { + $sCSVData = utils::ReadParam('csvdata', ''); + $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '');; + $sSeparator = utils::ReadParam('separator', ','); + if ($sSeparator == 'tab') $sSeparator = "\t"; + if ($sSeparator == 'other') + { + $sSeparator = utils::ReadParam('other_separator', ','); + } + $sTextQualifier = utils::ReadParam('text_qualifier', '"'); + if ($sTextQualifier == 'other') + { + $sTextQualifier = utils::ReadParam('other_qualifier', '"'); + } + $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); + $iSkippedLines = 0; + if (utils::ReadParam('box_skiplines', '0') == 1) + { + $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); + } + $sClassName = utils::ReadParam('class_name', ''); + $bAdvanced = utils::ReadParam('advanced', 0); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); + + $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep3').'

        '); + $oPage->add('
        '); + $oPage->add('
        '.Dict::S('UI:CSVImport:SelectClass').' '); + $oPage->add(GetClassesSelect('class_name', $sClassName, 300, UR_ACTION_BULK_MODIFY)); + $oPage->add(' '.Dict::S('UI:CSVImport:AdvancedMode').'
        '); + $oPage->add(''); + $oPage->add('

        '.Dict::S('UI:CSVImport:SelectAClassFirst').'

        '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add('

          '); + $oPage->add('

        '); + $oPage->add('
        '); + $oPage->add('
        '); + + $sAlertIncompleteMapping = Dict::S('UI:CSVImport:AlertIncompleteMapping'); + $sAlertNoSearchCriteria = Dict::S('UI:CSVImport:AlertNoSearchCriteria'); + + $oPage->add_ready_script( <<add_ready_script("DoMapping();"); // There is already a class selected, run the mapping - } - - $oPage->add_script( + ); + if ($sClassName != '') + { + $oPage->add_ready_script("DoMapping();"); // There is already a class selected, run the mapping + } + + $oPage->add_script( <<IsEmpty()) + $sOperation = utils::ReadParam('operation', 'csv_data', 'post'); + $sCSVData = ''; + switch($sOperation) { - $sCSVData = $oDocument->GetData(); + case 'file_upload': + $oDocument = utils::ReadPostedDocument('csvdata'); + if (!$oDocument->IsEmpty()) + { + $sCSVData = $oDocument->GetData(); + } + break; + + default: + $sCSVData = utils::ReadParam('csvdata', '', 'post'); } - break; - - default: - $sCSVData = utils::ReadParam('csvdata', '', 'post'); - } - $sEncoding = utils::ReadParam('encoding', 'UTF-8'); - - // Compute a subset of the data set, now that we know the charset - if ($sEncoding == 'UTF-8') - { - $sUTF8Data = $sCSVData; - } - else - { - $sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); - } - - $aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); - $sSeparator = utils::ReadParam('separator', ''); - if ($sSeparator == '') // May be set to an empty value by the previous page - { - $sSeparator = $aGuesses['separator']; - } - $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); - $bBoxSkipLines = utils::ReadParam('box_skiplines', 0); - if ($sSeparator == 'tab') $sSeparator = "\t"; - $sOtherSeparator = in_array($sSeparator, array(',', ';', "\t")) ? '' : $sSeparator; - $sTextQualifier = utils::ReadParam('text_qualifier', ''); - if ($sTextQualifier == '') // May be set to an empty value by the previous page - { - $sTextQualifier = $aGuesses['qualifier']; - } - $sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier; - $bHeaderLine = utils::ReadParam('header_line', 0); - $sClassName = utils::ReadParam('class_name', ''); - $bAdvanced = utils::ReadParam('advanced', 0); - - // Create a truncated version of the data used for the fast preview - // Take about 20 lines of data... knowing that some lines may contain carriage returns - $iMaxLines = 20; - $iMaxLen = strlen($sUTF8Data); - $iCurPos = true; - while ( ($iCurPos > 0) && ($iMaxLines > 0)) - { - $pos = strpos($sUTF8Data, "\n", $iCurPos); - if ($pos !== false) + // Compute a subset of the data set, now that we know the charset + if ($sEncoding == 'UTF-8') { - $iCurPos = 1+$pos; + $sUTF8Data = $sCSVData; } else { - $iCurPos = strlen($sUTF8Data); - $iMaxLines = 1; + $sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); } - $iMaxLines--; - } - $sCSVDataTruncated = substr($sUTF8Data, 0, $iCurPos); - $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep2').'

        '); - $oPage->add('
        '); - $oPage->add('
        '); - $oPage->add('
        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:SeparatorCharacter').'

        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:SeparatorComma+').'
        '); - $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorSemicolon+').'
        '); - $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorTab+').'
        '); - $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorOther').' '); - $oPage->add('

        '); - $oPage->add('
        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:TextQualifierCharacter').'

        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:QualifierDoubleQuote+').'
        '); - $oPage->add(' '.Dict::S('UI:CSVImport:QualifierSimpleQuote+').'
        '); - $oPage->add(' '.Dict::S('UI:CSVImport:QualifierOther').' '); - $oPage->add('

        '); - $oPage->add('
        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:CommentsAndHeader').'

        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:TreatFirstLineAsHeader').'

        '); - $oPage->add('

        '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '').'

        '); - $oPage->add('

        '); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add('
        '); - $oPage->add('

        '.Dict::S('UI:CSVImport:CSVDataPreview').'

        '); - $oPage->add('
        '); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add('
        '); - - $oPage->add_script( + $aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data + + $sSeparator = utils::ReadParam('separator', ''); + if ($sSeparator == '') // May be set to an empty value by the previous page + { + $sSeparator = $aGuesses['separator']; + } + $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); + $bBoxSkipLines = utils::ReadParam('box_skiplines', 0); + if ($sSeparator == 'tab') $sSeparator = "\t"; + $sOtherSeparator = in_array($sSeparator, array(',', ';', "\t")) ? '' : $sSeparator; + $sTextQualifier = utils::ReadParam('text_qualifier', ''); + if ($sTextQualifier == '') // May be set to an empty value by the previous page + { + $sTextQualifier = $aGuesses['qualifier']; + } + $sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier; + $bHeaderLine = utils::ReadParam('header_line', 0); + $sClassName = utils::ReadParam('class_name', ''); + $bAdvanced = utils::ReadParam('advanced', 0); + + // Create a truncated version of the data used for the fast preview + // Take about 20 lines of data... knowing that some lines may contain carriage returns + $iMaxLines = 20; + $iMaxLen = strlen($sUTF8Data); + $iCurPos = true; + while ( ($iCurPos > 0) && ($iMaxLines > 0)) + { + $pos = strpos($sUTF8Data, "\n", $iCurPos); + if ($pos !== false) + { + $iCurPos = 1+$pos; + } + else + { + $iCurPos = strlen($sUTF8Data); + $iMaxLines = 1; + } + $iMaxLines--; + } + $sCSVDataTruncated = substr($sUTF8Data, 0, $iCurPos); + + $oPage->add('

        '.Dict::S('UI:Title:CSVImportStep2').'

        '); + $oPage->add('
        '); + $oPage->add('
        '); + $oPage->add('
        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:SeparatorCharacter').'

        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:SeparatorComma+').'
        '); + $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorSemicolon+').'
        '); + $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorTab+').'
        '); + $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorOther').' '); + $oPage->add('

        '); + $oPage->add('
        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:TextQualifierCharacter').'

        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:QualifierDoubleQuote+').'
        '); + $oPage->add(' '.Dict::S('UI:CSVImport:QualifierSimpleQuote+').'
        '); + $oPage->add(' '.Dict::S('UI:CSVImport:QualifierOther').' '); + $oPage->add('

        '); + $oPage->add('
        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:CommentsAndHeader').'

        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:TreatFirstLineAsHeader').'

        '); + $oPage->add('

        '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '').'

        '); + $oPage->add('

        '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add('
        '); + $oPage->add('

        '.Dict::S('UI:CSVImport:CSVDataPreview').'

        '); + $oPage->add('
        '); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add('
        '); + + $oPage->add_script( <<add_ready_script('DoPreview();'); -} - -/** - * Prompt for the data to be loaded (either via a file or a copy/paste) - * @param WebPage $oPage The current web page - * @return void - */ -function Welcome(iTopWebPage $oPage) -{ - // Encodings supported: - // ICONV_CODE => Display Name - // Each iconv installation supports different encodings - // Some reasonably common and useful encodnings are listed here - $aPossibleEncodings = array( - 'UTF-8' => 'Unicode (UTF-8)', - 'ISO-8859-1' => 'Western (ISO-8859-1)', - 'WINDOWS-1251' => 'Cyrilic (Windows 1251)', - 'WINDOWS-1252' => 'Western (Windows 1252)', - 'ISO-8859-15' => 'Western (ISO-8859-15)', ); - // Some more encodings can be specified in the config file - $aExtraCharsets = utils::GetConfig()->GetCSVImportCharsets(); + $oPage->add_ready_script('DoPreview();'); + } - $aPossibleEncodings = array_merge($aPossibleEncodings, $aExtraCharsets); - asort($aPossibleEncodings); - - $oPage->add("

        ".Dict::S('UI:Title:BulkImport+')."

        \n"); - $oPage->AddTabContainer('tabs1'); - - $sSeparator = utils::ReadParam('separator', ''); - $sTextQualifier = utils::ReadParam('text_qualifier', ''); - $bHeaderLine = utils::ReadParam('header_line', true); - $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); - $sClassName = utils::ReadParam('class_name', ''); - $bAdvanced = utils::ReadParam('advanced', 0); - $sEncoding = utils::ReadParam('encoding', 'UTF-8'); - - $sFileLoadHtml = '

        '.Dict::S('UI:CSVImport:SelectFile').'

        '. - '

        '; - - $sFileLoadHtml .= '

        '.Dict::S('UI:CSVImport:Encoding').': '; - $sFileLoadHtml .= '

        '; + + $sFileLoadHtml .= '

        '.Dict::S('UI:CSVImport:Encoding').': '; + $sFileLoadHtml .= '

        '; - $sFileLoadHtml .= '

        '. - ''. - ''. - ''. - ''. - ''. - ''. - '
        '; - - $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:LoadFromFile'), $sFileLoadHtml); - $sCSVData = utils::ReadParam('csvdata', ''); - $sPasteDataHtml = '

        '.Dict::S('UI:CSVImport:PasteData').'

        '. - '

        '; - $sPasteDataHtml .= '

        '.Dict::S('UI:CSVImport:Encoding').': '; - $sPasteDataHtml .= '

        '. - '

        '. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - '
        '; - - $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste'), $sPasteDataHtml); - if (!empty($sCSVData)) - { - // When there are some data, activate the 'copy & paste' tab by default - $oPage->SelectTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste')); - } - $sTemplateHtml = '

        '.Dict::S('UI:CSVImport:PickClassForTemplate').' '; - $sTemplateHtml .= GetClassesSelect('template_class', '', 300, UR_ACTION_BULK_MODIFY); - $sTemplateHtml .= '

        '; - $sTemplateHtml .= '
        '; - $sTemplateHtml .= '
        '; - - $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:Templates'), $sTemplateHtml); - $oPage->add_script( -<<add_ready_script( + $sFileLoadHtml .= ''; + } + $sFileLoadHtml .= '

        '; + $sFileLoadHtml .= '

        '. + ''. + ''. + ''. + ''. + ''. + ''. + '
        '; + + $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:LoadFromFile'), $sFileLoadHtml); + $sCSVData = utils::ReadParam('csvdata', ''); + $sPasteDataHtml = '

        '.Dict::S('UI:CSVImport:PasteData').'

        '. + '

        '; + $sPasteDataHtml .= '

        '.Dict::S('UI:CSVImport:Encoding').': '; + $sPasteDataHtml .= '

        '. + '

        '. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
        '; + + $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste'), $sPasteDataHtml); + if (!empty($sCSVData)) + { + // When there are some data, activate the 'copy & paste' tab by default + $oPage->SelectTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste')); + } + $sTemplateHtml = '

        '.Dict::S('UI:CSVImport:PickClassForTemplate').' '; + $sTemplateHtml .= GetClassesSelect('template_class', '', 300, UR_ACTION_BULK_MODIFY); + $sTemplateHtml .= '

        '; + $sTemplateHtml .= '
        '; + $sTemplateHtml .= '
        '; + + $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:Templates'), $sTemplateHtml); + $oPage->add_script( <<output(); +ajax_request = $.post('ajax.csvimport.php', + { operation: 'get_csv_template', class_name: sClassName }, + function(data) { + $('#template').empty(); + $('#template').append(data); + $('#template').unblock(); + } + ); +} +EOF + ); + $oPage->add_ready_script( +<<output(); +} +catch(CoreException $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:FatalErrorMessage')."

        \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', $e->GetIssue()); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', $e->getContextData()); + $oLog->DBInsertNoReload(); + } + + IssueLog::Error($e->getMessage()); + } + + // For debugging only + //throw $e; +} +catch(Exception $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:FatalErrorMessage')."

        \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', 'PHP Exception'); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', array()); + $oLog->DBInsertNoReload(); + } + + IssueLog::Error($e->getMessage()); + } +} ?> From cb98432f6d94057ba2f93079a6c4a919b3e88965 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 12:46:47 +0000 Subject: [PATCH 681/970] - Fixed bug #243. OQLMenuNodes can now auto-refresh. I've enabled this by default for the "shortcuts" of Helpdesk, Incident Management and Change Management SVN:trunk[764] --- application/menunode.class.inc.php | 30 +++++++++++++++++-- .../model.itop-change-mgmt.php | 13 ++++---- .../model.itop-incident-mgmt.php | 9 ++++-- .../model.itop-request-mgmt.php | 9 ++++-- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index ed643a333b..012013c342 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -448,6 +448,12 @@ class OQLMenuNode extends MenuNode protected $sOQL; protected $bSearch; + /** + * Extra parameters to be passed to the display block to fine tune its appearence + */ + protected $m_aParams; + + /** * Create a menu item based on an OQL query and inserts it into the application's main menu * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) @@ -466,12 +472,23 @@ class OQLMenuNode extends MenuNode $this->sPageTitle = "Menu:$sMenuId+"; $this->sOQL = $sOQL; $this->bSearch = $bSearch; + $this->m_aParams = array(); // Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects // of the class specified by the OQL... } + /** + * Set some extra parameters to be passed to the display block to fine tune its appearence + * @param Hash $aParams paramCode => value. See DisplayBlock::GetDisplay for the meaning of the parameters + */ + public function SetParameters($aParams) + { + $this->m_aParams = $aParams; + } + public function RenderContent(WebPage $oPage, $aExtraParams = array()) { + $aExtraParams = array_merge($aExtraParams, $this->m_aParams); try { $oSearch = DBObjectSearch::FromOQL($this->sOQL); @@ -490,10 +507,19 @@ class OQLMenuNode extends MenuNode $this->sOQL EOF; } - + $sParams = ''; + if (!empty($this->m_aParams)) + { + $sParams = 'parameters="'; + foreach($this->m_aParams as $sName => $sValue) + { + $sParams .= $sName.':'.$sValue.';'; + } + $sParams .= '"'; + } $sTemplate .= <<$sIcon$this->sPageTitle

        -$this->sOQL +$this->sOQL EOF; $oTemplate = new DisplayTemplate($sTemplate); $oTemplate->Render($oPage, $aExtraParams); diff --git a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php index 73c7a0dd10..67c660449a 100644 --- a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php @@ -538,9 +538,12 @@ new TemplateMenuNode('Change:Overview', '../modules/itop-change-mgmt-1.0.0/overv new NewObjectMenuNode('NewChange', 'Change', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchChanges', 'Change', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('Change:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oShortcutNode->GetIndex(), 2 /* fRank */); -new OQLMenuNode('WaitingApproval', 'SELECT ApprovedChange WHERE status IN ("plannedscheduled")', $oShortcutNode->GetIndex(), 3 /* fRank */); -new OQLMenuNode('WaitingAcceptance', 'SELECT NormalChange WHERE status IN ("new")', $oShortcutNode->GetIndex(), 4 /* fRank */); - +$oNode = new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oShortcutNode->GetIndex(), 2 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('WaitingApproval', 'SELECT ApprovedChange WHERE status IN ("plannedscheduled")', $oShortcutNode->GetIndex(), 3 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('WaitingAcceptance', 'SELECT NormalChange WHERE status IN ("new")', $oShortcutNode->GetIndex(), 4 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); ?> diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index 54e66c8292..a8feb4582b 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -221,8 +221,11 @@ new TemplateMenuNode('Incident:Overview', '../modules/itop-incident-mgmt-1.0.0/o new NewObjectMenuNode('NewIncident', 'Incident', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchIncidents', 'Incident', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('Incident:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); +$oNode = new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); ?> diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index 6bd86d55f1..ae774e762e 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -179,8 +179,11 @@ new TemplateMenuNode('UserRequest:Overview', '../modules/itop-request-mgmt-1.0.0 new NewObjectMenuNode('NewUserRequest', 'UserRequest', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchUserRequests', 'UserRequest', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('UserRequest:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); -new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); +$oNode = new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode = new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); +$oNode->SetParameters(array('auto_reload' => 'fast')); ?> From 4ed6b7618829905a80d32198eb7616e1f224ad9f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 12:51:58 +0000 Subject: [PATCH 682/970] - Simplified Notifications page (Trac #163) SVN:trunk[765] --- core/trigger.class.inc.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index dc23d1b4cd..b6ced9b520 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -31,7 +31,7 @@ * * @package iTopORM */ -class Trigger extends cmdbAbstractObject +abstract class Trigger extends cmdbAbstractObject { public static function Init() { @@ -77,7 +77,7 @@ class Trigger extends cmdbAbstractObject } } -class TriggerOnObject extends Trigger +abstract class TriggerOnObject extends Trigger { public static function Init() { @@ -106,7 +106,7 @@ class TriggerOnObject extends Trigger } } -class TriggerOnStateChange extends TriggerOnObject +abstract class TriggerOnStateChange extends TriggerOnObject { public static function Init() { From f522e06bce89ab8f644bf284d9f1a5f7291b802c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 12:52:50 +0000 Subject: [PATCH 683/970] Typo in the localization... fixed. SVN:trunk[766] --- core/cmdbchangeop.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index f32dc2bf32..5d2be978da 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -253,7 +253,7 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute } else { - $sResult = Dict::Format('Change:Att_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue); + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue); } } return $sResult; From 798558f33141807472b8d80f9dfa966ee7f8a852 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 12:55:21 +0000 Subject: [PATCH 684/970] - Fixed Trac #177: Icons for everything ! For Incidents, User Requests and Changes the icon depends on the state of the object. SVN:trunk[767] --- .../itop-problem-mgmt-1.0.0/images/problem.png | Bin 0 -> 3207 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 modules/itop-problem-mgmt-1.0.0/images/problem.png diff --git a/modules/itop-problem-mgmt-1.0.0/images/problem.png b/modules/itop-problem-mgmt-1.0.0/images/problem.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf47a8501cb5bb86bd3eafdf6f3339d70d694d3 GIT binary patch literal 3207 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 literal 0 HcmV?d00001 From d892005e7fcf957da83dd4cced22d92c39b5b3f9 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 13:20:09 +0000 Subject: [PATCH 685/970] - Fixed Trac #224: properly validate non-mandatory integer fields. SVN:trunk[768] --- application/cmdbabstract.class.inc.php | 7 ++++++- js/forms-json-utils.js | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 183ab29e74..3fe26a1627 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1078,7 +1078,12 @@ abstract class cmdbAbstractObject extends CMDBObject $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; if (!empty($aEventsList)) { - $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate + $sNullValue = $oAttDef->GetNullValue(); + if (!is_numeric($sNullValue)) + { + $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number + } + $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId, $sNullValue) } );"); // Bind to a custom event: validate } $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one if (count($aDependencies) > 0) diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index cad353a868..6a82e0a843 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -150,15 +150,15 @@ function CheckFields(sFormId, bDisplayAlert) return (oFormErrors['err_'+sFormId] == 0); // If no error, submit the form } -function ValidateField(sFieldId, sPattern, bMandatory, sFormId) +function ValidateField(sFieldId, sPattern, bMandatory, sFormId, nullValue) { var bValid = true; var currentVal = $('#'+sFieldId).val(); - if (bMandatory && ((currentVal == '') || (currentVal == 0) || (currentVal == '[]'))) + if (bMandatory && (currentVal == nullValue)) { bValid = false; } - else if ((currentVal == '') || (currentVal == 0) || (currentVal == '[]')) + else if (currentVal == nullValue) { // An empty field is Ok... bValid = true; From 633497d7ff1f00478b33811cbaf09f72d7576586 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 15:49:25 +0000 Subject: [PATCH 686/970] - Fixing more accurately #222 and #172 SVN:trunk[769] --- application/applicationcontext.class.inc.php | 9 ++ application/cmdbabstract.class.inc.php | 88 ++++++++++++++++++-- 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index 85f9daab95..fd29c7ab5f 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -106,6 +106,15 @@ class ApplicationContext { return $this->aValues; } + + /** + * Returns an array of the context parameters NAMEs + * @return array The list of context parameters + */ + public function GetNames() + { + return $this->aNames; + } /** * Removes the specified parameter from the context, for example when the same parameter * is already a search parameter diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 3fe26a1627..47cd5a8e9f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -146,6 +146,7 @@ abstract class cmdbAbstractObject extends CMDBObject // Empty ZList defined, display all the linkedset attributes defined $aList = array_keys(MetaModel::ListAttributeDefs(get_class($this))); } + $sClass = get_class($this); foreach($aList as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); @@ -156,7 +157,6 @@ abstract class cmdbAbstractObject extends CMDBObject if ($bEditMode) { $iFlags = $this->GetAttributeFlags($sAttCode); - $sClass = get_class($this); $sInputId = $this->m_iFormId.'_'.$sAttCode; if (get_class($oAttDef) == 'AttributeLinkedSet') { @@ -167,11 +167,21 @@ abstract class cmdbAbstractObject extends CMDBObject $oFilter = new DBObjectSearch($sTargetClass); $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey(),'='); + $aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey()); + $oAppContext = new ApplicationContext(); + foreach($oAppContext->GetNames() as $sKey) + { + // The linked object inherits the parent's value for the context + if (MetaModel::IsValidAttCode($sClass, $sKey)) + { + $aDefaults[$sKey] = $this->Get($sKey); + } + } $aParams = array( 'target_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), 'menu' => true, - 'default' => array($oAttDef->GetExtKeyToMe() => $this->GetKey()), + 'default' => $aDefaults, ); $oBlock = new DisplayBlock($oFilter, 'list', false); @@ -201,11 +211,21 @@ abstract class cmdbAbstractObject extends CMDBObject // 1:n links $sTargetClass = $oAttDef->GetLinkedClass(); + $aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey()); + $oAppContext = new ApplicationContext(); + foreach($oAppContext->GetNames() as $sKey) + { + // The linked object inherits the parent's value for the context + if (MetaModel::IsValidAttCode($sClass, $sKey)) + { + $aDefaults[$sKey] = $this->Get($sKey); + } + } $aParams = array( 'target_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), 'menu' => true, - 'default' => array($oAttDef->GetExtKeyToMe() => $this->GetKey()), + 'default' => $aDefaults, ); } else @@ -1250,15 +1270,24 @@ EOF // AND the field is mandatory (otherwise there is always the possiblity to let it empty) $aArgs['this'] = $oObj; $aDetailsList = self::FLattenZList(MetaModel::GetZListItems($sClass, 'details')); - // TO DO: the list should be ordered based on the dependencies between fields - // i.e. if some fields depend on another field, the latter should be computed first... - // Right now we depend on the display order... anyhow if the display order does not - // reflect the dependency order, the UI will not be so intuitive to use... beware + // Order the fields based on their dependencies + $aDeps = array(); foreach($aDetailsList as $sAttCode) + { + $aDeps[$sAttCode] = MetaModel::GetPrequisiteAttributes($sClass, $sAttCode); + } + $aList = self::OrderDependentFields($aDeps); + + // Now fill-in the fields with default/supplied values + foreach($aList as $sAttCode) { $bMandatory = false; $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); - if (count($aAllowedValues) == 1) + if ($aArgs['default'][$sAttCode]) + { + $oObj->Set($sAttCode, $aArgs['default'][$sAttCode]); + } + elseif (count($aAllowedValues) == 1) { // If the field is mandatory, set it to the only possible value $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); @@ -1434,5 +1463,48 @@ EOF // HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE return HILIGHT_CLASS_NONE; // Not hilighted by default } + + /** + * Re-order the fields based on their inter-dependencies + * @params hash @aFields field_code => array_of_depencies + * @return array Ordered array of fields or throws an exception + */ + public static function OrderDependentFields($aFields) + { + $bCircular = false; + $aResult = array(); + $iCount = 0; + do + { + $bSet = false; + $iCount++; + foreach($aFields as $sFieldCode => $aDeps) + { + foreach($aDeps as $key => $sDependency) + { + if (in_array($sDependency, $aResult)) + { + // Dependency is resolved, remove it + unset($aFields[$sFieldCode][$key]); + } + } + if (count($aFields[$sFieldCode]) == 0) + { + // No more pending depencies for this field, add it to the list + $aResult[] = $sFieldCode; + unset($aFields[$sFieldCode]); + $bSet = true; + } + } + } + while($bSet && (count($aFields) > 0)); + + if (count($aFields) > 0) + { + $sMessage = "Error: Circular dependencies between the fields !
        ".print_r($aFields, true)."
        "; + throw(new Exception($sMessage)); + } + return $aResult; + } } ?> From 7d327b1395e3bda3cdd0bb3321dbda465a98727e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 16:23:44 +0000 Subject: [PATCH 687/970] - Fixed Trac #158: removed (Debug) message about "applying a stimulus" to an object. The page just says "Object updated" as for any other kind of update. SVN:trunk[770] --- pages/UI.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pages/UI.php b/pages/UI.php index 00d23d9849..f3e59de451 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1211,11 +1211,11 @@ EOF $aTransition = $aTransitions[$sStimulus]; $sTargetState = $aTransition['target_state']; $aTargetStates = MetaModel::EnumStates($sClass); - $oP->add("
        \n"); - $oP->add("

        $sActionLabel - {$oObj->GetName()}

        \n"); - $oP->add("

        $sActionDetails

        \n"); - $oP->p(Dict::Format('UI:Apply_Stimulus_On_Object_In_State_ToTarget_State', $sActionLabel, $oObj->GetName(), $oObj->GetStateLabel(), $sTargetState)); - $oP->add("
        \n"); + //$oP->add("
        \n"); + //$oP->add("

        $sActionLabel - {$oObj->GetName()}

        \n"); + //$oP->add("

        $sActionDetails

        \n"); + //$oP->p(Dict::Format('UI:Apply_Stimulus_On_Object_In_State_ToTarget_State', $sActionLabel, $oObj->GetName(), $oObj->GetStateLabel(), $sTargetState)); + //$oP->add("
        \n"); $aTargetState = $aTargetStates[$sTargetState]; $aExpectedAttributes = $aTargetState['attribute_list']; $aDetails = array(); @@ -1371,7 +1371,7 @@ EOF case 'swf_navigator': $sClass = utils::ReadParam('class', ''); $id = utils::ReadParam('id', 0); - $sRelation = utils::ReadParam('relation', 'neighbours'); + $sRelation = utils::ReadParam('relation', 'impact'); $width = 1000; $height = 700; $sParams = "pWidth=$width&pHeight=$height&drillUrl=".urlencode('../pages/UI.php?operation=details')."&displayController=false&xmlUrl=".urlencode("./xml.navigator.php")."&obj_class=$sClass&obj_id=$id&relation=$sRelation"; From 47e18d9927dc2bbc6c75d29c06ba3158b8a8dee1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sun, 5 Sep 2010 20:54:53 +0000 Subject: [PATCH 688/970] - Handle expansion/truncation of objects lists as part of the browser navigation history... implemented only for "simple lists" (Trac #73) SVN:trunk[771] --- application/cmdbabstract.class.inc.php | 74 ++++++++++++---- application/itopwebpage.class.inc.php | 117 ++++++++++++++++++++----- dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + js/jquery.ba-bbq.min.js | 18 ++++ js/utils.js | 18 ++++ 6 files changed, 190 insertions(+), 39 deletions(-) create mode 100644 js/jquery.ba-bbq.min.js diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 47cd5a8e9f..dc4e66cd32 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -240,7 +240,7 @@ abstract class cmdbAbstractObject extends CMDBObject 'object_id' => $this->GetKey(), 'target_attr' => $oAttDef->GetExtKeyToRemote(), 'view_link' => false, - 'menu' => false, + 'menu' => true, 'display_limit' => true, // By default limit the list to speed up the initial load & display ); } @@ -393,6 +393,7 @@ abstract class cmdbAbstractObject extends CMDBObject } } $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; + $bTruncated = isset($aExtraParams['truncated']) ? $aExtraParams['truncated'] == true : true; $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; $aExtraFields = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array(); @@ -460,7 +461,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aValues = array(); $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; $iMaxObjects = -1; - if ($bDisplayLimit) + if ($bDisplayLimit && $bTruncated) { if ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit()) { @@ -501,26 +502,67 @@ abstract class cmdbAbstractObject extends CMDBObject } $sHtml .= ''; $sColspan = ''; - if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) + $divId = $aExtraParams['block_id']; + $sFilter = $oSet->GetFilter()->serialize(); + $iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit(); + $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count()); + $sLinkLabel = Dict::S('UI:DisplayAll'); + foreach($oSet->GetFilter()->GetInternalParams() as $sName => $sValue) + { + $aExtraParams['query_params'][$sName] = $sValue; + } + if ($bDisplayLimit && $bTruncated && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())) { // list truncated - $divId = $aExtraParams['block_id']; - $sFilter = $oSet->GetFilter()->serialize(); - $aExtraParams['display_limit'] = false; // To expand the full list - foreach($oSet->GetFilter()->GetInternalParams() as $sName => $sValue) - { - $aExtraParams['query_params'][$sName] = $sValue; - } - $sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them - $sHtml .= ' From 066b738f6acda89b630b54cc8784286b33203f22 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Mon, 6 Sep 2010 14:55:35 +0000 Subject: [PATCH 698/970] update SVN:trunk[781] --- .../itop-change-mgmt-1.0.0/model.itop-change-mgmt.php | 2 +- .../en.dict.itop-config-mgmt.php | 10 +++++----- .../es_cr.dict.itop-config-mgmt.php | 10 +++++----- .../fr.dict.itop-config-mgmt.php | 10 +++++----- .../itop-config-mgmt-1.0.0/model.itop-config-mgmt.php | 8 ++++---- .../model.itop-incident-mgmt.php | 4 ++-- modules/itop-incident-mgmt-1.0.0/overview.html | 2 +- .../module.itop-knownerror-mgmt.php | 2 ++ .../en.dict.itop-problem-mgmt.php | 9 +++++++++ .../model.itop-problem-mgmt.php | 2 +- .../module.itop-problem-mgmt.php | 1 + modules/itop-request-mgmt-1.0.0/overview.html | 2 +- .../model.itop-service-mgmt.php | 2 +- 13 files changed, 38 insertions(+), 26 deletions(-) diff --git a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php index 67c660449a..244c00b2f6 100644 --- a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php @@ -538,7 +538,7 @@ new TemplateMenuNode('Change:Overview', '../modules/itop-change-mgmt-1.0.0/overv new NewObjectMenuNode('NewChange', 'Change', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchChanges', 'Change', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('Change:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -$oNode = new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +$oNode = new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); $oNode->SetParameters(array('auto_reload' => 'fast')); $oNode = new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oShortcutNode->GetIndex(), 2 /* fRank */); $oNode->SetParameters(array('auto_reload' => 'fast')); diff --git a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php index 3413f4669a..a98b0013df 100644 --- a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php @@ -268,14 +268,14 @@ Dict::Add('EN US', 'English', 'English', array( )); // -// Class: ExternalDoc +// Class: WebDoc // Dict::Add('EN US', 'English', 'English', array( - 'Class:ExternalDoc' => 'External Document', - 'Class:ExternalDoc+' => 'Document available on another web server', - 'Class:ExternalDoc/Attribute:url' => 'Url', - 'Class:ExternalDoc/Attribute:url+' => '', + 'Class:WebDoc' => 'Web Document', + 'Class:WebDoc+' => 'Document available on another web server', + 'Class:WebDoc/Attribute:url' => 'Url', + 'Class:WebDoc/Attribute:url+' => '', )); // diff --git a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php index a9ba3a1d00..1a5b253422 100644 --- a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php @@ -268,14 +268,14 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( )); // -// Class: ExternalDoc +// Class: WebDoc // Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Class:ExternalDoc' => 'Documento externo', - 'Class:ExternalDoc+' => 'Documento disponible en otro servidor Web', - 'Class:ExternalDoc/Attribute:url' => 'Url', - 'Class:ExternalDoc/Attribute:url+' => '', + 'Class:WebDoc' => 'Documento Web', + 'Class:WebDoc+' => 'Documento disponible en otro servidor Web', + 'Class:WebDoc/Attribute:url' => 'Url', + 'Class:WebDoc/Attribute:url+' => '', )); // diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php index a28ee79a8d..9a9af320f6 100644 --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php @@ -266,14 +266,14 @@ Dict::Add('FR FR', 'French', 'Français', array( )); // -// Class: ExternalDoc +// Class: WebDoc // Dict::Add('FR FR', 'French', 'Français', array( - 'Class:ExternalDoc' => 'Document externe', - 'Class:ExternalDoc+' => 'Document mis à disposition depuis un serveur web externe', - 'Class:ExternalDoc/Attribute:url' => 'Url', - 'Class:ExternalDoc/Attribute:url+' => '', + 'Class:WebDoc' => 'Document Web', + 'Class:WebDoc+' => 'Document mis à disposition depuis un serveur web externe', + 'Class:WebDoc/Attribute:url' => 'Url', + 'Class:WebDoc/Attribute:url+' => '', )); // diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 16f6452554..82d6d0f6ed 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -152,7 +152,7 @@ class Person extends Contact "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name"), + "reconc_keys" => array("name","first_name","org_id","email"), "db_table" => "person", "db_key_field" => "id", "db_finalclass_field" => "", @@ -186,7 +186,7 @@ class Team extends Contact "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name", "org_id", "org_name"), + "reconc_keys" => array("name", "org_id"), "db_table" => "team", "db_key_field" => "id", "db_finalclass_field" => "", @@ -276,7 +276,7 @@ abstract class Document extends cmdbAbstractObject MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status')); } } -class ExternalDoc extends Document +class WebDoc extends Document { public static function Init() @@ -1016,7 +1016,7 @@ class NetworkInterface extends ConnectableCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name","device_id","org_id", "device_name", "owner_name"), + "reconc_keys" => array("name","device_id","org_id"), "db_table" => "networkinterface", "db_key_field" => "id", "db_finalclass_field" => "", diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index 4b703deacf..e3f1d2fe9f 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -221,11 +221,11 @@ new TemplateMenuNode('Incident:Overview', '../modules/itop-incident-mgmt-1.0.0/o new NewObjectMenuNode('NewIncident', 'Incident', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchIncidents', 'Incident', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('Incident:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -$oNode = new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +$oNode = new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); $oNode->SetParameters(array('auto_reload' => 'fast')); $oNode = new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); $oNode->SetParameters(array('auto_reload' => 'fast')); $oNode = new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); -$oNode->SetParameters(array('auto_reload' => 'fast')); +$oNode->SetParameters(array('auto_reload' => 'standard')); ?> diff --git a/modules/itop-incident-mgmt-1.0.0/overview.html b/modules/itop-incident-mgmt-1.0.0/overview.html index 9d7fcbde76..e5c5c8074a 100644 --- a/modules/itop-incident-mgmt-1.0.0/overview.html +++ b/modules/itop-incident-mgmt-1.0.0/overview.html @@ -11,7 +11,7 @@ diff --git a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php index 3414805fa4..17e23fcb3e 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php @@ -27,6 +27,8 @@ SetupWebPage::AddModule( ), 'dictionary' => array( 'en.dict.itop-knownerror-mgmt.php', + 'es_cr.dict.itop-knownerror-mgmt.php', + 'fr.dict.itop-knownerror-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-knownerror-mgmt.xml', diff --git a/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php index 0c5e109669..713cbb33c3 100644 --- a/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/en.dict.itop-problem-mgmt.php @@ -67,6 +67,15 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:Problem:MyProblems+' => 'My Problems', 'Menu:Problem:OpenProblems' => 'All Open problems', 'Menu:Problem:OpenProblems+' => 'All Open problems', + 'UI-ProblemManagementOverview-ProblemByService' => 'Problems by Service', + 'UI-ProblemManagementOverview-ProblemByService+' => 'Problems by Service', + 'UI-ProblemManagementOverview-ProblemByPriority' => 'Problems by Priority', + 'UI-ProblemManagementOverview-ProblemByPriority+' => 'Problems by Priority', + 'UI-ProblemManagementOverview-ProblemUnassigned' => 'Unassigned Problems', + 'UI-ProblemManagementOverview-ProblemUnassigned+' => 'Unassigned Problems', + 'UI:ProblemMgmtMenuOverview:Title' => 'Dashboard for Problem Management', + 'UI:ProblemMgmtMenuOverview:Title+' => 'Dashboard for Problem Management', + )); // // Class: Problem diff --git a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php index 359ef26191..08d65e5bfe 100644 --- a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php @@ -231,7 +231,7 @@ new TemplateMenuNode('Problem:Overview', '../modules/itop-problem-mgmt-1.0.0/ove new NewObjectMenuNode('NewProblem', 'Problem', $iIndex, 1 /* fRank */); new SearchMenuNode('SearchProblems', 'Problem', $iIndex, 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('Problem:Shortcuts', '', $iIndex, 4 /* fRank */); -new OQLMenuNode('Problem:MyProblems', 'SELECT Problem WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('Problem:MyProblems', 'SELECT Problem WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); new OQLMenuNode('Problem:OpenProblems', 'SELECT Problem WHERE status IN ("new", "assigned", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); ?> diff --git a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php index 4ccd59f3ce..d084e0338b 100644 --- a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php @@ -28,6 +28,7 @@ SetupWebPage::AddModule( 'dictionary' => array( 'en.dict.itop-problem-mgmt.php', 'es_cr.dict.itop-problem-mgmt.php', + 'fr.dict.itop-problem-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-problem-mgmt.xml', diff --git a/modules/itop-request-mgmt-1.0.0/overview.html b/modules/itop-request-mgmt-1.0.0/overview.html index 32601fdb00..eaf0b921c5 100644 --- a/modules/itop-request-mgmt-1.0.0/overview.html +++ b/modules/itop-request-mgmt-1.0.0/overview.html @@ -11,7 +11,7 @@
        '.Dict::Format('UI:TruncatedResults', utils::GetConfig()->GetMinDisplayLimit(), $oSet->Count()).'  '.Dict::S('UI:DisplayAll').''; - $oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');"); - $oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');"); + $aExtraParams['display_limit'] = true; + $sHtml .= '
        '.$sCollapsedLabel.'  '.$sLinkLabel.''; + $oPage->add_ready_script( +<<Count() > utils::GetConfig()->GetMaxDisplayLimit())) + { + // Collapsible list + $aExtraParams['display_limit'] = true; + $sHtml .= '
        '.Dict::Format('UI:CountOfResults', $oSet->Count()).''.Dict::S('UI:CollapseList').''; + } + $aExtraParams['truncated'] = false; // To expand the full list when clicked + $sExtraParamsExpand = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them + $oPage->add_ready_script( +<<Count()).''; + state[ this.id ] = 'close'; } + $.bbq.pushState( state ); + $(this).trigger(state[this.id]); + }); + + $('#trc_$divId').bind('open', function() + { + ReloadTruncatedList('$divId', '$sFilter', '$sExtraParamsExpand'); + }); + + $('#trc_$divId').bind('close', function() + { + TruncateList('$divId', $iMinDisplayLimit, '$sCollapsedLabel', '$sLinkLabel'); + }); +EOF +); if ($bDisplayMenu) { $oMenuBlock = new MenuBlock($oSet->GetFilter()); diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 18d656f07b..396443134a 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -53,7 +53,7 @@ class iTopWebPage extends NiceWebPage $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); // $this->add_linked_stylesheet("../css/date.picker.css"); $this->add_linked_script('../js/jquery.layout.min.js'); - $this->add_linked_script('../js/jquery.history.js'); + $this->add_linked_script('../js/jquery.ba-bbq.min.js'); // $this->add_linked_script("../js/jquery.dimensions.js"); $this->add_linked_script("../js/jquery.tablehover.js"); $this->add_linked_script("../js/jquery.treeview.js"); @@ -148,12 +148,73 @@ class iTopWebPage extends NiceWebPage $("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even'); $("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even'); } - }); + }); - // tabs - $("div[id^=tabbedContent]").tabs( { show: function(event, ui) { - window.location.href = ui.tab.href; // So that history can keep track of the tabs - } }); + // Tabs, using JQuery BBQ to store the history + // The "tab widgets" to handle. + var tabs = $('div[id^=tabbedContent]'); + + // This selector will be reused when selecting actual tab widget A elements. + var tab_a_selector = 'ul.ui-tabs-nav a'; + + // Enable tabs on all tab widgets. The `event` property must be overridden so + // that the tabs aren't changed on click, and any custom event name can be + // specified. Note that if you define a callback for the 'select' event, it + // will be executed for the selected tab whenever the hash changes. + tabs.tabs({ event: 'change' }); + + // Define our own click handler for the tabs, overriding the default. + tabs.find( tab_a_selector ).click(function() + { + var state = {}; + + // Get the id of this tab widget. + var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' ); + + // Get the index of this tab. + var idx = $(this).parent().prevAll().length; + + // Set the state! + state[ id ] = idx; + $.bbq.pushState( state ); + }); + + // Bind an event to window.onhashchange that, when the history state changes, + // iterates over all tab widgets, changing the current tab as necessary. + $(window).bind( 'hashchange', function(e) + { + // Iterate over all tab widgets. + tabs.each(function() + { + // Get the index for this tab widget from the hash, based on the + // appropriate id property. In jQuery 1.4, you should use e.getState() + // instead of $.bbq.getState(). The second, 'true' argument coerces the + // string value to a number. + var idx = $.bbq.getState( this.id, true ) || 0; + + // Select the appropriate tab for this tab widget by triggering the custom + // event specified in the .tabs() init above (you could keep track of what + // tab each widget is on using .data, and only select a tab if it has + // changed). + $(this).find( tab_a_selector ).eq( idx ).triggerHandler( 'change' ); + }); + + // Iterate over all truncated lists to find whether they are expanded or not + $('a.truncated').each(function() + { + var state = $.bbq.getState( this.id, true ) || 'close'; + if (state == 'open') + { + $(this).trigger('open'); + } + else + { + $(this).trigger('close'); + } + }); + }); + + // End of Tabs handling $("table.listResults").tableHover(); // hover tables $(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables $(".date-pick").datepicker({ @@ -170,12 +231,12 @@ class iTopWebPage extends NiceWebPage $('#ModalDlg').dialog({ autoOpen: false, modal: true, width: 0.8*docWidth }); // JQuery UI dialogs ShowDebug(); $('#logOffBtn>ul').popupmenu(); - $.history.init(history_callback); - $("a[rel='history']").click(function() - { - $.history.load(this.href.replace(/^.*#/, '')); - return false; - }); +// $.history.init(history_callback); +// $("a[rel='history']").click(function() +// { +// $.history.load(this.href.replace(/^.*#/, '')); +// return false; +// }); } catch(err) { @@ -189,17 +250,17 @@ EOF $sUserPrefs = appUserPreferences::GetAsJSON(); $this->add_script( <<DisplayMenu(); // Compute the menu + + // Put here the 'ready scripts' that must be executed after all others + $this->add_ready_script( +<<a_headers as $s_header) { header($s_header); diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index d7fb9d5ea0..e33fdcd509 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -412,6 +412,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:SelectAllToggle+' => 'Select / Deselect All', 'UI:TruncatedResults' => '%1$d objects displayed out of %2$d', 'UI:DisplayAll' => 'Display All', + 'UI:CollapseList' => 'Collapse', 'UI:CountOfResults' => '%1$d object(s)', 'UI:ChangesLogTitle' => 'Changes log (%1$d):', 'UI:EmptyChangesLogTitle' => 'Changes log is empty', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 1827373181..5440a196d9 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -412,6 +412,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:SelectAllToggle+' => 'Tout Sélectionner / Tout Désélectionner', 'UI:TruncatedResults' => '%1$d objets affichés sur %2$d', 'UI:DisplayAll' => 'Tout afficher', + 'UI:CollapseList' => 'Refermer', 'UI:CountOfResults' => '%1$d objet(s)', 'UI:ChangesLogTitle' => 'Liste de modifications (%1$d):', 'UI:EmptyChangesLogTitle' => 'Aucune modification', diff --git a/js/jquery.ba-bbq.min.js b/js/jquery.ba-bbq.min.js new file mode 100644 index 0000000000..bcbf24834a --- /dev/null +++ b/js/jquery.ba-bbq.min.js @@ -0,0 +1,18 @@ +/* + * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 + * http://benalman.com/projects/jquery-bbq-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index ccb8f07842..d7dd9344da 100644 --- a/js/utils.js +++ b/js/utils.js @@ -18,6 +18,24 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) } ); } +/** + * Truncate a previously expanded list ! + */ +function TruncateList(divId, iLimit, sNewLabel, sLinkLabel) +{ + var iCount = 0; + $('#'+divId+' table.listResults tr').each( function(){ + if (iCount > iLimit) + { + $(this).remove(); + } + iCount++; + }); + $('#lbl_'+divId).html(sNewLabel); + $('#'+divId+' table.listResults tr:last td').addClass('truncated'); + $('#'+divId+' table.listResults').addClass('truncated'); + $('#trc_'+divId).html(sLinkLabel); +} /** * Reload any block -- used for periodic auto-reload */ From 40eb8367a40e686e4401057a789d635adaae9241 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Sun, 5 Sep 2010 22:20:28 +0000 Subject: [PATCH 689/970] #233 Speed up data load, found a bottleneck, but still some room for improvement, added internal settings to boost up huge load or workaround a bug in CheckToWrite (if any) SVN:trunk[772] --- core/config.class.inc.php | 91 ++++++++++++++++++++++++++++++++++++++- core/dbobject.class.php | 65 ++++++++++++++++++---------- core/metamodel.class.php | 16 +++++++ setup/benchmark.php | 3 +- 4 files changed, 151 insertions(+), 24 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 46733fcd22..44de81061c 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -75,6 +75,64 @@ class Config protected $m_aModuleSettings; + // New way to store the settings ! + // + protected $m_aSettings = array( + 'skip_check_to_write' => array( + 'type' => 'bool', + 'description' => 'Disable data format and integrity checks to boost up data load (insert or update)', + 'default' => false, + 'value' => false, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), + 'skip_check_ext_keys' => array( + 'type' => 'bool', + 'description' => 'Disable external key check when checking the value of attribtutes', + 'default' => false, + 'value' => false, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), + ); + + public function IsProperty($sPropCode) + { + return (array_key_exists($sPropCode, $this->m_aSettings)); + } + + public function Set($sPropCode, $value, $sSourceDesc = 'unknown') + { + $sType = $this->m_aSettings[$sPropCode]['type']; + switch($sType) + { + case 'bool': + $value = (bool) $value; + break; + case 'string': + $value = (string) $value; + break; + case 'integer': + $value = (integer) $value; + break; + case 'float': + $value = (float) $value; + break; + default: + throw new CoreException('Unknown type for setting', array('property' => $sPropCode, 'type' => $sType)); + } + + $this->m_aSettings[$sPropCode]['value'] = $value; + $this->m_aSettings[$sPropCode]['source_of_value'] = $sSourceDesc; + + } + + public function Get($sPropCode) + { + return $this->m_aSettings[$sPropCode]['value']; + } + + // Those variables will be deprecated later, when the transition to ...Get('my_setting') will be done protected $m_sDBHost; protected $m_sDBUser; protected $m_sDBPwd; @@ -84,8 +142,9 @@ class Config protected $m_sDBCollation; /** - * @var integer Event log options (see LOG_... definition) + * Event log options (see LOG_... definition) */ + // Those variables will be deprecated later, when the transition to ...Get('my_setting') will be done protected $m_bLogGlobal; protected $m_bLogNotification; protected $m_bLogIssue; @@ -188,6 +247,11 @@ class Config '../dictionaries/es_cr.dictionary.itop.core.php', // Support for Spanish (from Costa Rica) ); + foreach($this->m_aSettings as $sPropCode => $aSettingInfo) + { + $this->m_aSettings[$sPropCode]['value'] = $aSettingInfo['default']; + } + $this->m_sDBHost = ''; $this->m_sDBUser = ''; $this->m_sDBPwd = ''; @@ -291,6 +355,15 @@ class Config $this->m_aAddons = $MyModules['addons']; $this->m_aDictionaries = $MyModules['dictionaries']; + foreach($MySettings as $sPropCode => $rawvalue) + { + if ($this->IsProperty($sPropCode)) + { + $value = trim($rawvalue); + $this->Set($sPropCode, $value, $sConfigFile); + } + } + $this->m_sDBHost = trim($MySettings['db_host']); $this->m_sDBUser = trim($MySettings['db_user']); $this->m_sDBPwd = trim($MySettings['db_pwd']); @@ -670,6 +743,22 @@ class Config fwrite($hFile, "\n"); fwrite($hFile, "\$MySettings = array(\n"); + foreach($this->m_aSettings as $sPropCode => $aSettingInfo) + { + if ($aSettingInfo['show_in_conf_sample']) + { + $sType = $this->m_aSettings[$sPropCode]['type']; + switch($sType) + { + case 'bool': + $sSeenAs = $aSettingInfo['value'] ? '1' : '0'; + break; + default: + $sSeenAs = "'".$aSettingInfo['value']."'"; + } + fwrite($hFile, "\t'$sPropCode' => $sSeenAs,\n"); + } + } fwrite($hFile, "\t'db_host' => '{$this->m_sDBHost}',\n"); fwrite($hFile, "\t'db_user' => '{$this->m_sDBUser}',\n"); fwrite($hFile, "\t'db_pwd' => '".addslashes($this->m_sDBPwd)."',\n"); diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 6c910ae7a7..24b701d1cc 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -43,7 +43,9 @@ abstract class DBObject // The object may have incorrect external keys, then any attempt of reload must be avoided private $m_bCheckStatus = null; // Means: the object has been verified and is consistent with integrity rules // if null, then the check has to be performed again to know the status - // otherwise, + protected $m_aCheckIssues = null; + protected $m_aAsArgs = null; // The current object as a standard argument (cache) + private $m_bFullyLoaded = false; // Compound objects can be partially loaded private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode @@ -277,7 +279,10 @@ abstract class DBObject } else { + // The object has changed, reset caches $this->m_bCheckStatus = null; + $this->m_aAsArgs = null; + $this->m_aCurrValues[$sAttCode] = $value->GetKey(); foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef) { @@ -313,8 +318,12 @@ abstract class DBObject $realvalue = $oAttDef->MakeRealValue($value); $this->m_aCurrValues[$sAttCode] = $realvalue; + // The object has changed, reset caches $this->m_bCheckStatus = null; - $this->RegisterAsDirty(); // Make sure we do not reload it anymore... before saving it + $this->m_aAsArgs = null; + + // Make sure we do not reload it anymore... before saving it + $this->RegisterAsDirty(); } public function Get($sAttCode) @@ -619,11 +628,14 @@ abstract class DBObject } elseif ($oAtt->IsExternalKey()) { - $sTargetClass = $oAtt->GetTargetClass(); - $oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/); - if (is_null($oTargetObj)) + if (!MetaModel::SkipCheckExtKeys()) { - return "Target object not found ($sTargetClass::$toCheck)"; + $sTargetClass = $oAtt->GetTargetClass(); + $oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/); + if (is_null($oTargetObj)) + { + return "Target object not found ($sTargetClass::$toCheck)"; + } } } elseif ($oAtt->IsScalar()) @@ -689,14 +701,15 @@ abstract class DBObject final public function CheckToWrite() { - if (false) + if (MetaModel::SkipCheckToWrite()) { return array(true, array()); } - if (is_null($this->m_bCheckStatus)) { + $oKPI = new ExecutionKPI(); $this->DoCheckToWrite(); + $oKPI->ComputeStats('CheckToWrite', get_class($this)); if (count($this->m_aCheckIssues) == 0) { $this->m_bCheckStatus = true; @@ -1106,24 +1119,32 @@ abstract class DBObject // 2) set only the object ref and resolve the values iif needed from contextual templates and queries (easy for the queries, not for the templates) public function ToArgs($sArgName = 'this') { - $aScalarArgs = array(); - $aScalarArgs[$sArgName] = $this->GetKey(); - $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); - $aScalarArgs[$sArgName.'->object()'] = $this; - $aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink(); - $aScalarArgs[$sArgName.'->name()'] = $this->GetName(); - - $sClass = get_class($this); - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) + if (is_null($this->m_aAsArgs)) { - $aScalarArgs[$sArgName.'->'.$sAttCode] = $this->Get($sAttCode); - if ($oAttDef->IsScalar()) + $oKPI = new ExecutionKPI(); + $aScalarArgs = array(); + $aScalarArgs[$sArgName] = $this->GetKey(); + $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); + $aScalarArgs[$sArgName.'->object()'] = $this; + $aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink(); + $aScalarArgs[$sArgName.'->name()'] = $this->GetName(); + + $sClass = get_class($this); + foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { - $aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $this->GetAsHtml($sAttCode); - $aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = strip_tags($this->GetAsHtml($sAttCode)); + $aScalarArgs[$sArgName.'->'.$sAttCode] = $this->Get($sAttCode); + if ($oAttDef->IsScalar()) + { + // #@# Note: This has been proven to be quite slow, this can slow down bulk load + $sAsHtml = $this->GetAsHtml($sAttCode); + $aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $sAsHtml; + $aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = strip_tags($sAsHtml); + } } + $this->m_aAsArgs = $aScalarArgs; + $oKPI->ComputeStats('ToArgs', get_class($this)); } - return $aScalarArgs; + return $this->m_aAsArgs; } // To be optionaly overloaded diff --git a/core/metamodel.class.php b/core/metamodel.class.php index c6f5247f7c..d10bd0eb35 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -202,6 +202,9 @@ abstract class MetaModel private static $m_oConfig = null; + private static $m_bSkipCheckToWrite = false; + private static $m_bSkipCheckExtKeys = false; + private static $m_bQueryCacheEnabled = false; private static $m_bTraceQueries = false; private static $m_aQueriesLog = array(); @@ -210,6 +213,16 @@ abstract class MetaModel private static $m_bLogNotification = false; private static $m_bLogWebService = false; + public static function SkipCheckToWrite() + { + return self::$m_bSkipCheckToWrite; + } + + public static function SkipCheckExtKeys() + { + return self::$m_bSkipCheckExtKeys; + } + public static function IsLogEnabledIssue() { return self::$m_bLogIssue; @@ -3254,6 +3267,9 @@ abstract class MetaModel self::$m_bTraceQueries = self::$m_oConfig->GetDebugQueries(); self::$m_bQueryCacheEnabled = self::$m_oConfig->GetQueryCacheEnabled(); + self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write'); + self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys'); + // Note: load the dictionary as soon as possible, because it might be // needed when some error occur foreach (self::$m_oConfig->GetDictionaries() as $sModule => $sToInclude) diff --git a/setup/benchmark.php b/setup/benchmark.php index bef9b9d8ba..3ebd62be02 100644 --- a/setup/benchmark.php +++ b/setup/benchmark.php @@ -859,7 +859,6 @@ try default: $oP->error("Error: unsupported operation '$sOperation'"); - } } catch(ZZException $e) @@ -871,5 +870,7 @@ catch(ZZCoreException $e) $oP->error("Error: '".$e->getHtmlDesc()."'"); } $oKPI->ComputeAndReport('Total execution'); +// too big (showing all queries) ExecutionKPI::ReportStats(); +//MetaModel::ShowQueryTrace(); $oP->output(); ?> From da42afed88e9ee154907f5d01b856543eff03340 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 6 Sep 2010 08:45:21 +0000 Subject: [PATCH 690/970] Bug fix: escape dialog's title since it may contain quotes (especially in localized versions). SVN:trunk[773] --- application/ui.linkswidget.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index f0a453511b..2555e76638 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -358,7 +358,7 @@ EOF $sHtml .= "\n"; $sHtml .= "\n"; $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget$iWidgetIndex.UpdateSizes });"); - $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass))."'});"); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."'});"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget$iWidgetIndex.SearchObjectsToAdd);"); $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget$iWidgetIndex.UpdateSizes);"); return $sHtml; From 8f982bc5bd6900b11871bcc00fbbf005075dd619 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 6 Sep 2010 09:06:57 +0000 Subject: [PATCH 691/970] Bug fix: make sure that the log is properly filled when creating a notification. SVN:trunk[774] --- core/action.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index f47c0043b5..1dabdd59a4 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -324,7 +324,7 @@ class ActionEmail extends ActionNotification $oLog->Set('userinfo', UserRights::GetUser()); $oLog->Set('trigger_id', $oTrigger->GetKey()); $oLog->Set('action_id', $this->GetKey()); - $oLog->Set('object_id', $aContextArgs['this->id']); + $oLog->Set('object_id', $aContextArgs['this->object()']->GetKey()); // Note: we have to secure this because those values are calculated // inside the try statement, and we would like to keep track of as From e965ba71b9edc2d6a6c9ce311544d30be81f0942 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 6 Sep 2010 09:09:31 +0000 Subject: [PATCH 692/970] - Fixed the display of "Notifications" - Rolled back, not to display menus on linkedset when not in edition mode SVN:trunk[775] --- application/cmdbabstract.class.inc.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index dc4e66cd32..c0c660730c 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -224,7 +224,7 @@ abstract class cmdbAbstractObject extends CMDBObject $aParams = array( 'target_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), - 'menu' => true, + 'menu' => false, 'default' => $aDefaults, ); } @@ -240,7 +240,7 @@ abstract class cmdbAbstractObject extends CMDBObject 'object_id' => $this->GetKey(), 'target_attr' => $oAttDef->GetExtKeyToRemote(), 'view_link' => false, - 'menu' => true, + 'menu' => false, 'display_limit' => true, // By default limit the list to speed up the initial load & display ); } @@ -266,8 +266,8 @@ abstract class cmdbAbstractObject extends CMDBObject // Display notifications regarding the object $iId = $this->GetKey(); - $oNotifSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT EventNotificationEmail AS Ev JOIN TriggerOnObject AS T ON Ev.trigger_id = T.id WHERE T.target_class IN ('$sClassList') AND Ev.object_id = $iId")); - self::DisplaySet($oPage, $oNotifSet); + $oBlock = new DisplayBlock(DBObjectSearch::FromOQL("SELECT EventNotificationEmail AS Ev JOIN TriggerOnObject AS T ON Ev.trigger_id = T.id WHERE T.target_class IN ('$sClassList') AND Ev.object_id = $iId"), 'list', false); + $oBlock->Display($oPage, 'notifications', array()); } } } From 5c4614a3af4ad087baa9b43306909b0fb98079f4 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 09:15:26 +0000 Subject: [PATCH 693/970] Dehardcoded against the DB engine (now defaults to myisam) SVN:trunk[776] --- core/metamodel.class.php | 9 ++++++++- pages/testlist.inc.php | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index d10bd0eb35..144b6ada32 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -97,6 +97,13 @@ define('OPT_ATT_MUSTCHANGE', 8); */ define('OPT_ATT_MUSTPROMPT', 16); +/** + * DB Engine -should be moved into CMDBSource + * + * @package iTopORM + */ +//define('MYSQL_ENGINE', 'innodb'); +define('MYSQL_ENGINE', 'myisam'); @@ -2676,7 +2683,7 @@ abstract class MetaModel if (!CMDBSource::IsTable($sTable)) { $aErrors[$sClass]['*'][] = "table '$sTable' could not be found into the DB"; - $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; + $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = ".MYSQL_ENGINE." CHARACTER SET utf8 COLLATE utf8_unicode_ci"; } // Check that the key field exists // diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index d263dd0646..ac87523367 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -39,9 +39,9 @@ class TestSQLQuery extends TestScenarioOnDB protected function DoPrepare() { parent::DoPrepare(); - cmdbSource::CreateTable('CREATE TABLE `myTable` (myKey INT(11) NOT NULL auto_increment, column1 VARCHAR(255), column2 VARCHAR(255), PRIMARY KEY (`myKey`)) ENGINE = innodb'); - cmdbSource::CreateTable('CREATE TABLE `myTable1` (myKey1 INT(11) NOT NULL auto_increment, column1_1 VARCHAR(255), column1_2 VARCHAR(255), PRIMARY KEY (`myKey1`)) ENGINE = innodb'); - cmdbSource::CreateTable('CREATE TABLE `myTable2` (myKey2 INT(11) NOT NULL auto_increment, column2_1 VARCHAR(255), column2_2 VARCHAR(255), PRIMARY KEY (`myKey2`)) ENGINE = innodb'); + cmdbSource::CreateTable('CREATE TABLE `myTable` (myKey INT(11) NOT NULL auto_increment, column1 VARCHAR(255), column2 VARCHAR(255), PRIMARY KEY (`myKey`)) ENGINE = '.MYSQL_ENGINE); + cmdbSource::CreateTable('CREATE TABLE `myTable1` (myKey1 INT(11) NOT NULL auto_increment, column1_1 VARCHAR(255), column1_2 VARCHAR(255), PRIMARY KEY (`myKey1`)) ENGINE = '.MYSQL_ENGINE); + cmdbSource::CreateTable('CREATE TABLE `myTable2` (myKey2 INT(11) NOT NULL auto_increment, column2_1 VARCHAR(255), column2_2 VARCHAR(255), PRIMARY KEY (`myKey2`)) ENGINE = '.MYSQL_ENGINE); } protected function DoExecute() From 6ab797b03a27db0bb7738aa2499225a8b06d8517 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 09:21:57 +0000 Subject: [PATCH 694/970] Removed related_change_id/related_problem_id from standard searches because it might slow down the load of search forms when many tickets are in the DB SVN:trunk[777] --- .../model.itop-incident-mgmt.php | 2 +- .../model.itop-problem-mgmt.php | 326 +++++++++--------- .../model.itop-request-mgmt.php | 4 +- .../itop-tickets-1.0.0/model.itop-tickets.php | 6 +- 4 files changed, 169 insertions(+), 169 deletions(-) diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index a8feb4582b..4b703deacf 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -46,7 +46,7 @@ class Incident extends ResponseTicket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); } diff --git a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php index 3c1261fc25..359ef26191 100644 --- a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php @@ -23,7 +23,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -class Problem extends Ticket +class Problem extends Ticket { public static function Init() { @@ -44,174 +44,174 @@ class Problem extends Ticket MetaModel::Init_InheritAttributes(); // MetaModel::Init_InheritLifecycle(); - MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,assigned,resolved,closed'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"Service", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Service AS s JOIN SLA AS sla ON sla.service_id=s.id JOIN lnkContractToSLA AS ln ON ln.sla_id=sla.id JOIN CustomerContract AS cc ON ln.contract_id=cc.id WHERE cc.org_id =:this->org_id'), "sql"=>"service_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("allowed_values"=>null, "extkey_attcode"=>"service_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("servicesubcategory_id", array("targetclass"=>"ServiceSubcategory", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT ServiceSubcategory WHERE service_id = :this->service_id'), "sql"=>"servicesubcategory_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("service_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("servicesubcategory_name", array("allowed_values"=>null, "extkey_attcode"=>"servicesubcategory_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("product", array("allowed_values"=>null, "sql"=>"product", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("impact", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"impact", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("urgency", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"urgency", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEnum("priority", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"priority", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"Team", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Team AS t JOIN CustomerContract AS cc ON cc.support_team_id=t.id JOIN lnkContractToSLA AS ln ON ln.contract_id=cc.id JOIN SLA AS sla ON ln.sla_id=sla.id WHERE sla.service_id = :this->service_id AND cc.org_id = :this->org_id'), "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id","service_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=>"workgroup_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"Person", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Person AS p JOIN lnkTeamToContact AS l ON l.contact_id=p.id JOIN Team AS t ON l.team_id=t.id WHERE t.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_name", array("allowed_values"=>null, "extkey_attcode"=>"agent_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("agent_email", array("allowed_values"=>null, "extkey_attcode"=>"agent_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalKey("related_change_id", array("targetclass"=>"Change", "jointype"=>null, "allowed_values"=>null, "sql"=>"related_change_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeExternalField("related_change_ref", array("allowed_values"=>null, "extkey_attcode"=>"related_change_id", "target_attcode"=>"ref", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDateTime("close_date", array("allowed_values"=>null, "sql"=>"close_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDateTime("assignment_date", array("allowed_values"=>null, "sql"=>"assignment_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeDateTime("resolution_date", array("allowed_values"=>null, "sql"=>"resolution_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSet("knownerrors_list", array("linked_class"=>"KnownError", "ext_key_to_me"=>"problem_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); - - + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('new,assigned,resolved,closed'), "sql"=>"status", "default_value"=>"new", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"Service", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Service AS s JOIN SLA AS sla ON sla.service_id=s.id JOIN lnkContractToSLA AS ln ON ln.sla_id=sla.id JOIN CustomerContract AS cc ON ln.contract_id=cc.id WHERE cc.org_id =:this->org_id'), "sql"=>"service_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("allowed_values"=>null, "extkey_attcode"=>"service_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("servicesubcategory_id", array("targetclass"=>"ServiceSubcategory", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT ServiceSubcategory WHERE service_id = :this->service_id'), "sql"=>"servicesubcategory_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("service_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("servicesubcategory_name", array("allowed_values"=>null, "extkey_attcode"=>"servicesubcategory_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("product", array("allowed_values"=>null, "sql"=>"product", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("impact", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"impact", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("urgency", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"urgency", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("priority", array("allowed_values"=>new ValueSetEnum('1,2,3'), "sql"=>"priority", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"Team", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Team AS t JOIN CustomerContract AS cc ON cc.support_team_id=t.id JOIN lnkContractToSLA AS ln ON ln.contract_id=cc.id JOIN SLA AS sla ON ln.sla_id=sla.id WHERE sla.service_id = :this->service_id AND cc.org_id = :this->org_id'), "sql"=>"workgroup_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id","service_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("allowed_values"=>null, "extkey_attcode"=>"workgroup_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"Person", "jointype"=>null, "allowed_values"=>new ValueSetObjects('SELECT Person AS p JOIN lnkTeamToContact AS l ON l.contact_id=p.id JOIN Team AS t ON l.team_id=t.id WHERE t.id = :this->workgroup_id'), "sql"=>"agent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("workgroup_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_name", array("allowed_values"=>null, "extkey_attcode"=>"agent_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("agent_email", array("allowed_values"=>null, "extkey_attcode"=>"agent_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("related_change_id", array("targetclass"=>"Change", "jointype"=>null, "allowed_values"=>null, "sql"=>"related_change_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("related_change_ref", array("allowed_values"=>null, "extkey_attcode"=>"related_change_id", "target_attcode"=>"ref", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("close_date", array("allowed_values"=>null, "sql"=>"close_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("last_update", array("allowed_values"=>null, "sql"=>"last_update", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("assignment_date", array("allowed_values"=>null, "sql"=>"assignment_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeDateTime("resolution_date", array("allowed_values"=>null, "sql"=>"resolution_date", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSet("knownerrors_list", array("linked_class"=>"KnownError", "ext_key_to_me"=>"problem_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + + MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date','knownerrors_list', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'service_id', 'servicesubcategory_id','product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date', 'last_update', 'assignment_date')); MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date')); MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority')); - // Lifecycle - MetaModel::Init_DefineState( - "new", - array( - "attribute_inherit" => null, - "attribute_list" => array( - 'ref' => OPT_ATT_READONLY, - 'ticket_log' => OPT_ATT_HIDDEN, - 'related_change_id' => OPT_ATT_HIDDEN, - 'description' => OPT_ATT_MUSTCHANGE, - 'contact_list' => OPT_ATT_READONLY, - 'start_date' => OPT_ATT_READONLY, - 'last_update' => OPT_ATT_READONLY, - 'assignment_date' => OPT_ATT_HIDDEN, - 'resolution_date' => OPT_ATT_HIDDEN, - 'close_date' => OPT_ATT_HIDDEN, - 'org_id' => OPT_ATT_MUSTCHANGE, - 'service_id' => OPT_ATT_MUSTCHANGE, - 'servicesubcategory_id' => OPT_ATT_MUSTCHANGE, - 'product' => OPT_ATT_MUSTPROMPT, - 'impact' => OPT_ATT_MUSTCHANGE, - 'urgency' => OPT_ATT_MUSTCHANGE, - 'priority' => OPT_ATT_READONLY, - 'workgroup_id' => OPT_ATT_MUSTCHANGE, - 'agent_id' => OPT_ATT_HIDDEN, - 'agent_email' => OPT_ATT_HIDDEN, - ), - ) - ); - MetaModel::Init_DefineState( - "assigned", - array( - "attribute_inherit" => 'new', - "attribute_list" => array( - 'title' => OPT_ATT_READONLY, - 'org_id' => OPT_ATT_READONLY, - 'ticket_log' => OPT_ATT_NORMAL, - 'description' => OPT_ATT_READONLY, - 'agent_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, - 'agent_email' => OPT_ATT_READONLY, - 'workgroup_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, -// 'related_change_id' => OPT_ATT_NORMAL, - ), - ) - ); - MetaModel::Init_DefineState( - "resolved", - array( - "attribute_inherit" => 'assigned', - "attribute_list" => array( - 'service_id' => OPT_ATT_READONLY, - 'servicesubcategory_id' => OPT_ATT_READONLY, - 'product' => OPT_ATT_READONLY, - 'impact' => OPT_ATT_READONLY, - 'workgroup_id' => OPT_ATT_READONLY, - 'agent_id' => OPT_ATT_READONLY, - 'urgency' => OPT_ATT_READONLY, - ), - ) - ); - MetaModel::Init_DefineState( - "closed", - array( - "attribute_inherit" => 'resolved', - "attribute_list" => array( - 'ticket_log' => OPT_ATT_READONLY, - 'close_date' => OPT_ATT_READONLY, - ), - ) - ); - - MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_assign", array())); - MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reassign", array())); - MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_resolve", array())); - MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_close", array())); - - MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array('SetAssignedDate'), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("assigned", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("assigned", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetResolveDate'), "user_restriction"=>null)); - - MetaModel::Init_DefineTransition("resolved", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); - MetaModel::Init_DefineTransition("resolved", "ev_close", array("target_state"=>"closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); - } - - // Lifecycle actions - // - public function SetAssignedDate($sStimulusCode) - { - $this->Set('assignment_date', time()); - return true; - } - public function SetResolveDate($sStimulusCode) - { - $this->Set('resolution_date', time()); - return true; - } - public function SetClosureDate($sStimulusCode) - { - $this->Set('close_date', time()); - return true; - } - - /** Compute the priority of the ticket based on its impact and urgency - * @return integer The priority of the ticket 1(high) .. 3(low) - */ - public function ComputePriority() - { - // priority[impact][urgency] - $aPriorities = array( - // single person - 1 => array( - 1 => 1, - 2 => 1, - 3 => 2, - ), - // a group - 2 => array( - 1 => 1, - 2 => 2, - 3 => 3, - ), - // a departement! - 3 => array( - 1 => 2, - 2 => 3, - 3 => 3, - ), - ); - $iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')]; - return $iPriority; - } - - + // Lifecycle + MetaModel::Init_DefineState( + "new", + array( + "attribute_inherit" => null, + "attribute_list" => array( + 'ref' => OPT_ATT_READONLY, + 'ticket_log' => OPT_ATT_HIDDEN, + 'related_change_id' => OPT_ATT_HIDDEN, + 'description' => OPT_ATT_MUSTCHANGE, + 'contact_list' => OPT_ATT_READONLY, + 'start_date' => OPT_ATT_READONLY, + 'last_update' => OPT_ATT_READONLY, + 'assignment_date' => OPT_ATT_HIDDEN, + 'resolution_date' => OPT_ATT_HIDDEN, + 'close_date' => OPT_ATT_HIDDEN, + 'org_id' => OPT_ATT_MUSTCHANGE, + 'service_id' => OPT_ATT_MUSTCHANGE, + 'servicesubcategory_id' => OPT_ATT_MUSTCHANGE, + 'product' => OPT_ATT_MUSTPROMPT, + 'impact' => OPT_ATT_MUSTCHANGE, + 'urgency' => OPT_ATT_MUSTCHANGE, + 'priority' => OPT_ATT_READONLY, + 'workgroup_id' => OPT_ATT_MUSTCHANGE, + 'agent_id' => OPT_ATT_HIDDEN, + 'agent_email' => OPT_ATT_HIDDEN, + ), + ) + ); + MetaModel::Init_DefineState( + "assigned", + array( + "attribute_inherit" => 'new', + "attribute_list" => array( + 'title' => OPT_ATT_READONLY, + 'org_id' => OPT_ATT_READONLY, + 'ticket_log' => OPT_ATT_NORMAL, + 'description' => OPT_ATT_READONLY, + 'agent_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, + 'agent_email' => OPT_ATT_READONLY, + 'workgroup_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, +// 'related_change_id' => OPT_ATT_NORMAL, + ), + ) + ); + MetaModel::Init_DefineState( + "resolved", + array( + "attribute_inherit" => 'assigned', + "attribute_list" => array( + 'service_id' => OPT_ATT_READONLY, + 'servicesubcategory_id' => OPT_ATT_READONLY, + 'product' => OPT_ATT_READONLY, + 'impact' => OPT_ATT_READONLY, + 'workgroup_id' => OPT_ATT_READONLY, + 'agent_id' => OPT_ATT_READONLY, + 'urgency' => OPT_ATT_READONLY, + ), + ) + ); + MetaModel::Init_DefineState( + "closed", + array( + "attribute_inherit" => 'resolved', + "attribute_list" => array( + 'ticket_log' => OPT_ATT_READONLY, + 'close_date' => OPT_ATT_READONLY, + ), + ) + ); + + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_assign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_reassign", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_resolve", array())); + MetaModel::Init_DefineStimulus(new StimulusUserAction("ev_close", array())); + + MetaModel::Init_DefineTransition("new", "ev_assign", array("target_state"=>"assigned", "actions"=>array('SetAssignedDate'), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("assigned", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("assigned", "ev_resolve", array("target_state"=>"resolved", "actions"=>array('SetResolveDate'), "user_restriction"=>null)); + + MetaModel::Init_DefineTransition("resolved", "ev_reassign", array("target_state"=>"assigned", "actions"=>array(), "user_restriction"=>null)); + MetaModel::Init_DefineTransition("resolved", "ev_close", array("target_state"=>"closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null)); + } + + // Lifecycle actions + // + public function SetAssignedDate($sStimulusCode) + { + $this->Set('assignment_date', time()); + return true; + } + public function SetResolveDate($sStimulusCode) + { + $this->Set('resolution_date', time()); + return true; + } + public function SetClosureDate($sStimulusCode) + { + $this->Set('close_date', time()); + return true; + } + + /** Compute the priority of the ticket based on its impact and urgency + * @return integer The priority of the ticket 1(high) .. 3(low) + */ + public function ComputePriority() + { + // priority[impact][urgency] + $aPriorities = array( + // single person + 1 => array( + 1 => 1, + 2 => 1, + 3 => 2, + ), + // a group + 2 => array( + 1 => 1, + 2 => 2, + 3 => 3, + ), + // a departement! + 3 => array( + 1 => 2, + 2 => 3, + 3 => 3, + ), + ); + $iPriority = $aPriorities[(int)$this->Get('impact')][(int)$this->Get('urgency')]; + return $iPriority; + } + + public function ComputeValues() { - // Compute the priority of the ticket - $this->Set('priority', $this->ComputePriority()); - + // Compute the priority of the ticket + $this->Set('priority', $this->ComputePriority()); + $iKey = $this->GetKey(); if ($iKey < 0) { diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index ae774e762e..82812b78d6 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -44,12 +44,12 @@ class UserRequest extends ResponseTicket MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritLifecycle(); - MetaModel::Init_AddAttribute(new AttributeEnum("request_type", array("allowed_values"=>new ValueSetEnum('service request,issue,information'), "sql"=>"request_type", "default_value"=>"service request", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("request_type", array("allowed_values"=>new ValueSetEnum('service request,issue,information'), "sql"=>"request_type", "default_value"=>"service request", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("freeze_reason", array("allowed_values"=>null, "sql"=>"freeze_reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'request_type','ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment', 'freeze_reason')); MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'request_type','start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'request_type','start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); MetaModel::Init_OverloadStateAttribute('frozen', 'freeze_reason', OPT_ATT_MANDATORY); diff --git a/modules/itop-tickets-1.0.0/model.itop-tickets.php b/modules/itop-tickets-1.0.0/model.itop-tickets.php index d85e38da73..e725c21d48 100644 --- a/modules/itop-tickets-1.0.0/model.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/model.itop-tickets.php @@ -214,7 +214,7 @@ abstract class ResponseTicket extends Ticket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'document_list', 'ci_list', 'contact_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('advanced_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); + MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('list', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'priority', 'workgroup_id', 'agent_id', 'last_update')); // Lifecycle @@ -277,8 +277,8 @@ abstract class ResponseTicket extends Ticket 'workgroup_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, 'tto_escalation_deadline' => OPT_ATT_HIDDEN, 'ttr_escalation_deadline' => OPT_ATT_READONLY, - 'related_problem_id' => OPT_ATT_MUSTPROMPT, -// 'related_change_id' => OPT_ATT_MUSTPROMPT, + 'related_problem_id' => OPT_ATT_MUSTPROMPT, +// 'related_change_id' => OPT_ATT_MUSTPROMPT, ), ) ); From 4c789e8fbbec8da6b927ec6d3c67f79a3d9ed531 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 6 Sep 2010 11:36:11 +0000 Subject: [PATCH 695/970] - Fixed bug #102: regression introduced with the encoding of passwords: users were no longer able to change their own password... SVN:trunk[778] --- application/loginwebpage.class.inc.php | 6 ------ modules/authent-local/fr.dict.authent-local.php | 2 +- modules/authent-local/model.authent-local.php | 10 +++++----- modules/authent-local/module.authent-local.php | 1 + 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index eb485d8265..5e2caf914b 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -380,12 +380,6 @@ EOF $oPage->output(); exit; } - else - { - // Remember the changed password - $_SESSION['auth_pwd'] = $sNewPwd; - return; - } } self::Login(); diff --git a/modules/authent-local/fr.dict.authent-local.php b/modules/authent-local/fr.dict.authent-local.php index 6e98c42c84..5822ffee65 100644 --- a/modules/authent-local/fr.dict.authent-local.php +++ b/modules/authent-local/fr.dict.authent-local.php @@ -37,7 +37,7 @@ // Class: UserLocal // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:UserLocal' => 'Utilisateur iTop', 'Class:UserLocal+' => 'Utilisateur authentifié par iTop', 'Class:UserLocal/Attribute:password' => 'Mot de passe', diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index 3da9c9ca1c..1631395d72 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -56,10 +56,6 @@ class UserLocal extends UserInternal public function CheckCredentials($sPassword) { -// if ($this->Get('password') == $sPassword) -// { -// return true; -// } $oPassword = $this->Get('password'); // ormPassword object // Cannot compare directly the values since they are hashed, so // Let's ask the password to compare the hashed values @@ -89,7 +85,10 @@ class UserLocal extends UserInternal public function ChangePassword($sOldPassword, $sNewPassword) { - if ($this->Get('password') == $sOldPassword) + $oPassword = $this->Get('password'); // ormPassword object + // Cannot compare directly the values since they are hashed, so + // Let's ask the password to compare the hashed values + if ($oPassword->CheckPassword($sOldPassword)) { $this->Set('password', $sNewPassword); $oChange = MetaModel::NewObject("CMDBChange"); @@ -103,6 +102,7 @@ class UserLocal extends UserInternal $sUserString = UserRights::GetUser(); } $oChange->Set("userinfo", $sUserString); + $oChange->DBInsert(); $this->DBUpdateTracked($oChange); return true; } diff --git a/modules/authent-local/module.authent-local.php b/modules/authent-local/module.authent-local.php index 082eb8bb54..dcaf8c3fe3 100644 --- a/modules/authent-local/module.authent-local.php +++ b/modules/authent-local/module.authent-local.php @@ -24,6 +24,7 @@ SetupWebPage::AddModule( ), 'dictionary' => array( 'en.dict.authent-local.php', + 'fr.dict.authent-local.php', ), 'data.struct' => array( //'data.struct.authent-local.xml', From 73975a953104b145f14ac2d06054d56c06f640e6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 14:42:57 +0000 Subject: [PATCH 696/970] Simplified user management by profile (optimization for viewing a huge list of objects) SVN:trunk[779] --- .../userrightsprofile.class.inc.php | 155 +++--------------- 1 file changed, 23 insertions(+), 132 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 3913f173e5..f85bf16ff1 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -621,7 +621,7 @@ exit; $sAction = self::$m_aActionCodes[$iActionCode]; - $iInstancePermission = UR_ALLOWED_NO; + $iPermission = UR_ALLOWED_NO; $aAttributes = array(); if (isset($this->m_aUserProfiles[$iUser])) { @@ -634,7 +634,7 @@ exit; } else { - $iInstancePermission = UR_ALLOWED_YES; + $iPermission = UR_ALLOWED_YES; // update the list of attributes with those allowed for this profile // @@ -655,7 +655,7 @@ exit; } $aRes = array( - 'permission' => $iInstancePermission, + 'permission' => $iPermission, 'attributes' => $aAttributes, ); $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes; @@ -666,92 +666,23 @@ exit; { $this->LoadCache(); - if (is_null($oInstanceSet)) - { - $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); - return $aObjectPermissions['permission']; - } - - $oInstanceSet->Rewind(); - while($oObject = $oInstanceSet->Fetch()) - { - $aObjectPermissions = $this->GetUserActionGrant($oUser, get_class($oObject), $iActionCode); - - $iInstancePermission = $aObjectPermissions['permission']; - if (isset($iGlobalPermission)) - { - if ($iInstancePermission != $iGlobalPermission) - { - $iGlobalPermission = UR_ALLOWED_DEPENDS; - break; - } - } - else - { - $iGlobalPermission = $iInstancePermission; - } - } - $oInstanceSet->Rewind(); - - if (isset($iGlobalPermission)) - { - return $iGlobalPermission; - } - else - { - return UR_ALLOWED_NO; - } + // Note: The object set is ignored because it was interesting to optimize for huge data sets + // and acceptable to consider only the root class of the object set + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); + return $aObjectPermissions['permission']; } public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) { $this->LoadCache(); - if (is_null($oInstanceSet)) + // Note: The object set is ignored because it was interesting to optimize for huge data sets + // and acceptable to consider only the root class of the object set + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); + $aAttributes = $aObjectPermissions['attributes']; + if (in_array($sAttCode, $aAttributes)) { - $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); - $aAttributes = $aObjectPermissions['attributes']; - if (in_array($sAttCode, $aAttributes)) - { - return $aObjectPermissions['permission']; - } - else - { - return UR_ALLOWED_NO; - } - } - - $oInstanceSet->Rewind(); - while($oObject = $oInstanceSet->Fetch()) - { - $aObjectPermissions = $this->GetUserActionGrant($oUser, get_class($oObject), $iActionCode); - $aAttributes = $aObjectPermissions['attributes']; - if (in_array($sAttCode, $aAttributes)) - { - $iInstancePermission = $aObjectPermissions['permission']; - } - else - { - $iInstancePermission = UR_ALLOWED_NO; - } - - if (isset($iGlobalPermission)) - { - if ($iInstancePermission != $iGlobalPermission) - { - $iGlobalPermission = UR_ALLOWED_DEPENDS; - } - } - else - { - $iGlobalPermission = $iInstancePermission; - } - } - $oInstanceSet->Rewind(); - - if (isset($iGlobalPermission)) - { - return $iGlobalPermission; + return $aObjectPermissions['permission']; } else { @@ -780,62 +711,22 @@ exit; // Note: this code is VERY close to the code of IsActionAllowed() $iUser = $oUser->GetKey(); - if (is_null($oInstanceSet)) + // Note: The object set is ignored because it was interesting to optimize for huge data sets + // and acceptable to consider only the root class of the object set + $iPermission = UR_ALLOWED_NO; + if (isset($this->m_aUserProfiles[$iUser])) { - $iInstancePermission = UR_ALLOWED_NO; - if (isset($this->m_aUserProfiles[$iUser])) + foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) { - foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (!is_null($oGrantRecord)) { - $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); - if (!is_null($oGrantRecord)) - { - // no need to fetch the record, we've requested the records having permission = 'yes' - $iInstancePermission = UR_ALLOWED_YES; - } + // no need to fetch the record, we've requested the records having permission = 'yes' + $iPermission = UR_ALLOWED_YES; } } - return $iInstancePermission; - } - - $oInstanceSet->Rewind(); - while($oObject = $oInstanceSet->Fetch()) - { - $iInstancePermission = UR_ALLOWED_NO; - if (isset($this->m_aUserProfiles[$iUser])) - { - foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile) - { - $oGrantRecord = $this->GetClassStimulusGrant($iProfile, get_class($oObject), $sStimulusCode); - if (!is_null($oGrantRecord)) - { - // no need to fetch the record, we've requested the records having permission = 'yes' - $iInstancePermission = UR_ALLOWED_YES; - } - } - } - if (isset($iGlobalPermission)) - { - if ($iInstancePermission != $iGlobalPermission) - { - $iGlobalPermission = UR_ALLOWED_DEPENDS; - } - } - else - { - $iGlobalPermission = $iInstancePermission; - } - } - $oInstanceSet->Rewind(); - - if (isset($iGlobalPermission)) - { - return $iGlobalPermission; - } - else - { - return UR_ALLOWED_NO; } + return $iPermission; } public function FlushPrivileges() From 89154d14ddde218ff3796cdf5d9abcfaa8ff2f22 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Mon, 6 Sep 2010 14:54:31 +0000 Subject: [PATCH 697/970] update with good status SVN:trunk[780] --- application/templates/welcome_menu.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/templates/welcome_menu.html b/application/templates/welcome_menu.html index 3f914b3682..d0b5fd9dec 100644 --- a/application/templates/welcome_menu.html +++ b/application/templates/welcome_menu.html @@ -38,7 +38,7 @@ padding:0;

        Request Management

        SELECT UserRequest

        UI:WelcomeMenu:MyCalls

        -SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id +SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id AND status NOT IN ("closed", "resolved")
        @@ -47,7 +47,7 @@ padding:0;

        Incident Management

        SELECT Incident

        UI:WelcomeMenu:MyIncidents

        -SELECT Incident AS i WHERE i.agent_id = :current_contact_id +SELECT Incident AS i WHERE i.agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")

        UI-IncidentManagementOverview-IncidentUnassigned

        -SELECT Incident WHERE status = 'new' +SELECT Incident WHERE status IN ("new", "escalated_tto")

        UI-RequestManagementOverview-RequestUnassigned

        -SELECT UserRequest WHERE status = 'new' +SELECT UserRequest WHERE status IN ("new", "escalated_tto")
          diff --git a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php index f0b7542ac6..14bd129c22 100644 --- a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php @@ -315,7 +315,7 @@ class Service extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("type", array("allowed_values"=>new ValueSetEnum('RequestManagement,IncidentManagement'), "sql"=>"type", "default_value"=>"IncidentManagement", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('design,production,obsolete'), "sql"=>"status", "default_value"=>"design", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeLinkedSet("subcategory_list", array("linked_class"=>"ServiceSubcategory", "ext_key_to_me"=>"service_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); From 666eb3245a5ec9b6497088c8c1d254a1a9c98e88 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Mon, 6 Sep 2010 14:57:54 +0000 Subject: [PATCH 699/970] SVN:trunk[782] --- .../fr.dict.itop-knownerror-mgmt.php | 142 +++++++++++++++ .../fr.dict.itop-problem-mgmt.php | 172 ++++++++++++++++++ modules/itop-problem-mgmt-1.0.0/overview.html | 20 ++ 3 files changed, 334 insertions(+) create mode 100644 modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php create mode 100644 modules/itop-problem-mgmt-1.0.0/fr.dict.itop-problem-mgmt.php create mode 100644 modules/itop-problem-mgmt-1.0.0/overview.html diff --git a/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php new file mode 100644 index 0000000000..d4ecef42bc --- /dev/null +++ b/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php @@ -0,0 +1,142 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: KnownError +// + +Dict::Add('FR FR', 'French', 'Français', array( + 'Class:KnownError' => 'Erreur Connue', + 'Class:KnownError+' => 'Erreur documenté pour un problème connu', + 'Class:KnownError/Attribute:name' => 'Nom', + 'Class:KnownError/Attribute:name+' => '', + 'Class:KnownError/Attribute:org_id' => 'Client', + 'Class:KnownError/Attribute:org_id+' => '', + 'Class:KnownError/Attribute:cust_name' => 'Nom du client', + 'Class:KnownError/Attribute:cust_name+' => '', + 'Class:KnownError/Attribute:problem_id' => 'Problème lié', + 'Class:KnownError/Attribute:problem_id+' => '', + 'Class:KnownError/Attribute:problem_ref' => 'Ref', + 'Class:KnownError/Attribute:problem_ref+' => '', + 'Class:KnownError/Attribute:symptom' => 'Symptome', + 'Class:KnownError/Attribute:symptom+' => '', + 'Class:KnownError/Attribute:root_cause' => 'Cause première', + 'Class:KnownError/Attribute:root_cause+' => '', + 'Class:KnownError/Attribute:workaround' => 'Contournement', + 'Class:KnownError/Attribute:workaround+' => '', + 'Class:KnownError/Attribute:solution' => 'Solution', + 'Class:KnownError/Attribute:solution+' => '', + 'Class:KnownError/Attribute:error_code' => 'Code d\'erreur', + 'Class:KnownError/Attribute:error_code+' => '', + 'Class:KnownError/Attribute:domain' => 'Domaine', + 'Class:KnownError/Attribute:domain+' => '', + 'Class:KnownError/Attribute:domain/Value:Application' => 'Application', + 'Class:KnownError/Attribute:domain/Value:Application+' => 'Application', + 'Class:KnownError/Attribute:domain/Value:Desktop' => 'Desktop', + 'Class:KnownError/Attribute:domain/Value:Desktop+' => 'Desktop', + 'Class:KnownError/Attribute:domain/Value:Network' => 'Réseau', + 'Class:KnownError/Attribute:domain/Value:Network+' => 'Réseau', + 'Class:KnownError/Attribute:domain/Value:Server' => 'Serveur', + 'Class:KnownError/Attribute:domain/Value:Server+' => 'Serveur', + 'Class:KnownError/Attribute:vendor' => 'Vendeur', + 'Class:KnownError/Attribute:vendor+' => '', + 'Class:KnownError/Attribute:model' => 'Modèle', + 'Class:KnownError/Attribute:model+' => '', + 'Class:KnownError/Attribute:version' => 'Version', + 'Class:KnownError/Attribute:version+' => '', + 'Class:KnownError/Attribute:ci_list' => 'CIs', + 'Class:KnownError/Attribute:ci_list+' => '', + 'Class:KnownError/Attribute:document_list' => 'Documents', + 'Class:KnownError/Attribute:document_list+' => '', +)); + + +// +// Class: lnkInfraError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkInfraError' => 'Lien erreur CI', + 'Class:lnkInfraError+' => 'CIs liés à une erreur connue', + 'Class:lnkInfraError/Attribute:infra_id' => 'CI', + 'Class:lnkInfraError/Attribute:infra_id+' => '', + 'Class:lnkInfraError/Attribute:infra_name' => 'Nom du CI', + 'Class:lnkInfraError/Attribute:infra_name+' => '', + 'Class:lnkInfraError/Attribute:infra_status' => 'Status du CI', + 'Class:lnkInfraError/Attribute:infra_status+' => '', + 'Class:lnkInfraError/Attribute:error_id' => 'Erreur', + 'Class:lnkInfraError/Attribute:error_id+' => '', + 'Class:lnkInfraError/Attribute:error_name' => 'Nom de l\'erreur', + 'Class:lnkInfraError/Attribute:error_name+' => '', + 'Class:lnkInfraError/Attribute:reason' => 'Raison', + 'Class:lnkInfraError/Attribute:reason+' => '', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkDocumentError' => 'Lien erreur document', + 'Class:lnkDocumentError+' => 'Lien entre une erreur et un document', + 'Class:lnkDocumentError/Attribute:doc_id' => 'Document', + 'Class:lnkDocumentError/Attribute:doc_id+' => '', + 'Class:lnkDocumentError/Attribute:doc_name' => 'Nom du document', + 'Class:lnkDocumentError/Attribute:doc_name+' => '', + 'Class:lnkDocumentError/Attribute:error_id' => 'Erreur', + 'Class:lnkDocumentError/Attribute:error_id+' => '', + 'Class:lnkDocumentError/Attribute:error_name' => 'Nom de l\'erreur', + 'Class:lnkDocumentError/Attribute:error_name+' => '', + 'Class:lnkDocumentError/Attribute:link_type' => 'Information', + 'Class:lnkDocumentError/Attribute:link_type+' => '', +)); + + +?> diff --git a/modules/itop-problem-mgmt-1.0.0/fr.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/fr.dict.itop-problem-mgmt.php new file mode 100644 index 0000000000..2e30ffa09a --- /dev/null +++ b/modules/itop-problem-mgmt-1.0.0/fr.dict.itop-problem-mgmt.php @@ -0,0 +1,172 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + + + +Dict::Add('FR FR', 'French', 'Français', array( + 'Menu:ProblemManagement' => 'Gestion des Problèmes', + 'Menu:ProblemManagement+' => 'Gestion des Problèmes', + 'Menu:Problem:Overview' => 'Vue d\'ensemble', + 'Menu:Problem:Overview+' => 'Vue d\'ensemble', + 'Menu:NewProblem' => 'Nouveau Problème', + 'Menu:NewProblem+' => 'Nouveau Problème', + 'Menu:SearchProblems' => 'Rechercer des Problèmes', + 'Menu:SearchProblems+' => 'Rechercher des Problèmes', + 'Menu:Problem:KnownErrors' => 'Erreurs connues', + 'Menu:Problem:KnownErrors+' => 'Erreurs connues', + 'Menu:Problem:Shortcuts' => 'Raccourcis', + 'Menu:Problem:MyProblems' => 'Mes Problèmes', + 'Menu:Problem:MyProblems+' => 'Mes Problèmes', + 'Menu:Problem:OpenProblems' => 'Problèmes ouverts', + 'Menu:Problem:OpenProblems+' => 'Problèmes ouverts', + 'UI-ProblemManagementOverview-ProblemByService' => 'Problèmes par service', + 'UI-ProblemManagementOverview-ProblemByService+' => 'Problèmes par service', + 'UI-ProblemManagementOverview-ProblemByPriority' => 'Problèmes par priorité', + 'UI-ProblemManagementOverview-ProblemByPriority+' => 'Problèmes par priorité', + 'UI-ProblemManagementOverview-ProblemUnassigned' => 'Problèmes non affectés à un agent', + 'UI-ProblemManagementOverview-ProblemUnassigned+' => 'Problèmes non affectés à un agent', + 'UI:ProblemMgmtMenuOverview:Title' => 'Tableau de bord de la Gestion des Problèmes', + 'UI:ProblemMgmtMenuOverview:Title+' => 'Tableau de bord de la Gestion des Problèmes', + +)); + + + +// +// Class: Problem +// + +Dict::Add('FR FR', 'French', 'Français', array( + 'Class:Problem' => 'Problème', + 'Class:Problem+' => '', + 'Class:Problem/Attribute:status' => 'Status', + 'Class:Problem/Attribute:status+' => '', + 'Class:Problem/Attribute:status/Value:new' => 'Nouveau', + 'Class:Problem/Attribute:status/Value:new+' => '', + 'Class:Problem/Attribute:status/Value:assigned' => 'Assigné', + 'Class:Problem/Attribute:status/Value:assigned+' => '', + 'Class:Problem/Attribute:status/Value:resolved' => 'Résolu', + 'Class:Problem/Attribute:status/Value:resolved+' => '', + 'Class:Problem/Attribute:status/Value:closed' => 'Fermé', + 'Class:Problem/Attribute:status/Value:closed+' => '', + 'Class:Problem/Attribute:org_id' => 'Client', + 'Class:Problem/Attribute:org_id+' => '', + 'Class:Problem/Attribute:org_name' => 'Nom', + 'Class:Problem/Attribute:org_name+' => 'Nom commun', + 'Class:Problem/Attribute:service_id' => 'Service', + 'Class:Problem/Attribute:service_id+' => '', + 'Class:Problem/Attribute:service_name' => 'Nom du service', + 'Class:Problem/Attribute:service_name+' => '', + 'Class:Problem/Attribute:servicesubcategory_id' => 'Catégorie de service', + 'Class:Problem/Attribute:servicesubcategory_id+' => '', + 'Class:Problem/Attribute:servicesubcategory_name' => 'Nom', + 'Class:Problem/Attribute:servicesubcategory_name+' => '', + 'Class:Problem/Attribute:product' => 'Produit', + 'Class:Problem/Attribute:product+' => '', + 'Class:Problem/Attribute:impact' => 'Impacte', + 'Class:Problem/Attribute:impact+' => '', + 'Class:Problem/Attribute:impact/Value:1' => 'Une personne', + 'Class:Problem/Attribute:impact/Value:1+' => '', + 'Class:Problem/Attribute:impact/Value:2' => 'Un Service', + 'Class:Problem/Attribute:impact/Value:2+' => '', + 'Class:Problem/Attribute:impact/Value:3' => 'Un Département', + 'Class:Problem/Attribute:impact/Value:3+' => '', + 'Class:Problem/Attribute:urgency' => 'Urgence', + 'Class:Problem/Attribute:urgency+' => '', + 'Class:Problem/Attribute:urgency/Value:1' => 'Basse', + 'Class:Problem/Attribute:urgency/Value:1+' => 'Basse', + 'Class:Problem/Attribute:urgency/Value:2' => 'Moyenne', + 'Class:Problem/Attribute:urgency/Value:2+' => 'Moyenne', + 'Class:Problem/Attribute:urgency/Value:3' => 'Haute', + 'Class:Problem/Attribute:urgency/Value:3+' => 'Haute', + 'Class:Problem/Attribute:priority' => 'Priorité', + 'Class:Problem/Attribute:priority+' => '', + 'Class:Problem/Attribute:priority/Value:1' => 'Basse', + 'Class:Problem/Attribute:priority/Value:1+' => '', + 'Class:Problem/Attribute:priority/Value:2' => 'Moyenne', + 'Class:Problem/Attribute:priority/Value:2+' => '', + 'Class:Problem/Attribute:priority/Value:3' => 'Haute', + 'Class:Problem/Attribute:priority/Value:3+' => '', + 'Class:Problem/Attribute:workgroup_id' => 'Groupe de travail', + 'Class:Problem/Attribute:workgroup_id+' => '', + 'Class:Problem/Attribute:workgroup_name' => 'Nom', + 'Class:Problem/Attribute:workgroup_name+' => '', + 'Class:Problem/Attribute:agent_id' => 'Agent', + 'Class:Problem/Attribute:agent_id+' => '', + 'Class:Problem/Attribute:agent_name' => 'Nom', + 'Class:Problem/Attribute:agent_name+' => '', + 'Class:Problem/Attribute:agent_email' => 'Email de l\'agent', + 'Class:Problem/Attribute:agent_email+' => '', + 'Class:Problem/Attribute:related_change_id' => 'Changement relatif', + 'Class:Problem/Attribute:related_change_id+' => '', + 'Class:Problem/Attribute:related_change_ref' => 'Ref', + 'Class:Problem/Attribute:related_change_ref+' => '', + 'Class:Problem/Attribute:close_date' => 'Date de cloture', + 'Class:Problem/Attribute:close_date+' => '', + 'Class:Problem/Attribute:last_update' => 'Dernière mise à jour', + 'Class:Problem/Attribute:last_update+' => '', + 'Class:Problem/Attribute:assignment_date' => 'Date d\'assignation', + 'Class:Problem/Attribute:assignment_date+' => '', + 'Class:Problem/Attribute:resolution_date' => 'Date de résolution', + 'Class:Problem/Attribute:resolution_date+' => '', + 'Class:Problem/Attribute:knownerrors_list' => 'Erreurs connues', + 'Class:Problem/Attribute:knownerrors_list+' => '', + 'Class:Problem/Stimulus:ev_assign' => 'Assigner', + 'Class:Problem/Stimulus:ev_assign+' => '', + 'Class:Problem/Stimulus:ev_reassign' => 'Réaassigner', + 'Class:Problem/Stimulus:ev_reassign+' => '', + 'Class:Problem/Stimulus:ev_resolve' => 'Résoudre', + 'Class:Problem/Stimulus:ev_resolve+' => '', + 'Class:Problem/Stimulus:ev_close' => 'Fermer', + 'Class:Problem/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-problem-mgmt-1.0.0/overview.html b/modules/itop-problem-mgmt-1.0.0/overview.html new file mode 100644 index 0000000000..e95d1e2339 --- /dev/null +++ b/modules/itop-problem-mgmt-1.0.0/overview.html @@ -0,0 +1,20 @@ +

        UI:ProblemMgmtMenuOverview:Title

        + + + + + + + + + +
        +SELECT Problem + +SELECT Problem +
        +

        UI-ProblemManagementOverview-ProblemUnassigned

        +SELECT Problem WHERE status IN ("new") +
        +
        + From 49d96e503fb96681f6f8dfdbe391cb76d71f2861 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 16:40:16 +0000 Subject: [PATCH 700/970] #205 Missing object hyperlink when using trigger on object creation SVN:trunk[783] --- core/dbobject.class.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 24b701d1cc..ef84955d55 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -334,7 +334,7 @@ abstract class DBObject } if ($this->m_bIsInDB && !$this->m_aLoadedAtt[$sAttCode] && !$this->m_bDirty) { - // #@# non-scalar attributes.... handle that differentely + // #@# non-scalar attributes.... handle that differently $this->Reload(); } return $this->m_aCurrValues[$sAttCode]; @@ -918,6 +918,9 @@ abstract class DBObject $this->DBWriteLinks(); $this->m_bIsInDB = true; $this->m_bDirty = false; + + // Arg cache invalidated (in particular, it needs the object key -could be improved later) + $this->m_aAsArgs = null; $this->AfterInsert(); From a9bfaee2efa2dacc428544911f2bd9149cf52aff Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 16:47:14 +0000 Subject: [PATCH 701/970] Fixed typo causing a warning on some PHP compilations SVN:trunk[784] --- application/cmdbabstract.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index c0c660730c..754ab8f7aa 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1325,7 +1325,7 @@ EOF { $bMandatory = false; $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); - if ($aArgs['default'][$sAttCode]) + if (isset($aArgs['default'][$sAttCode])) { $oObj->Set($sAttCode, $aArgs['default'][$sAttCode]); } From 5b728dc6b8648b3aa1df876e0486a55c453b2117 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 16:56:54 +0000 Subject: [PATCH 702/970] #251 Allow configuration managers to modify/delete Contacts SVN:trunk[785] --- addons/userrights/userrightsprofile.class.inc.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index f85bf16ff1..244c8f92f9 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -831,9 +831,6 @@ class SetupProfiles } foreach ($aWriteableClasses as $sClass => $foo) { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - if (!MetaModel::IsValidClass($sClass)) { throw new CoreException("Invalid class name '$sClass'"); From de5d345e4ebd3f5400cdc392445c18edaae40cac Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 6 Sep 2010 20:01:58 +0000 Subject: [PATCH 703/970] Grant Summary on profiles was saying no for everything SVN:trunk[786] --- addons/userrights/userrightsprofile.class.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 244c8f92f9..62a70baeb7 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -82,8 +82,8 @@ class URP_Profiles extends UserRightsBaseClass function GetGrantAsHtml($oUserRights, $sClass, $sAction) { - $oGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); - if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) + $iGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); + if (!is_null($iGrant)) { return ''.Dict::S('UI:UserManagement:ActionAllowed:Yes').''; } From 82bcf663c4bb280fe938e8e82c1f29b41eb0d757 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 08:10:49 +0000 Subject: [PATCH 704/970] Fixed Trac #252: pb when doing a CSV load with no header line. SVN:trunk[787] --- pages/ajax.csvimport.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pages/ajax.csvimport.php b/pages/ajax.csvimport.php index 6b402014d8..7bbc0ad4df 100644 --- a/pages/ajax.csvimport.php +++ b/pages/ajax.csvimport.php @@ -266,8 +266,8 @@ switch($sOperation) $sSeparator = utils::ReadParam('separator', ','); $sTextQualifier = utils::ReadParam('qualifier', '"'); $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0); - $bFirstLineAsHeader = utils::ReadParam('header_line', true); - $sData = stripslashes(utils::ReadParam('csvdata', true)); + $bFirstLineAsHeader = utils::ReadParam('header_line', false); + $sData = stripslashes(utils::ReadParam('csvdata', '')); $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', false); $sEncoding = utils::ReadParam('encoding', 'UTF-8'); @@ -281,12 +281,12 @@ switch($sOperation) else { $oPage->add(""); - $index = 1; $aFirstLine = $aData[0]; // Use the first row to determine the number of columns $iStartLine = 0; $iNbColumns = count($aFirstLine); if ($bFirstLineAsHeader) - { $iStartLine = 1; + { + $iStartLine = 1; foreach($aFirstLine as $sField) { $aHeader[] = $sField; @@ -306,6 +306,7 @@ switch($sOperation) $oPage->add(''); $oPage->add(''); $oPage->add(''); + $index = 1; foreach($aHeader as $sField) { $oPage->add(''); From 2aed08ccd333a8677ed76a11c39f4c26649234b8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 7 Sep 2010 10:14:35 +0000 Subject: [PATCH 705/970] #255 Added the profile "Problem Manager" SVN:trunk[788] --- .../userrights/userrightsprofile.class.inc.php | 14 +++++++++++--- .../model.itop-knownerror-mgmt.php | 18 +++++++++--------- .../itop-tickets-1.0.0/model.itop-tickets.php | 10 +++++----- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 62a70baeb7..05fb60f27d 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -187,7 +187,7 @@ class URP_UserProfile extends UserRightsBaseClass // Display lists MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details - MetaModel::Init_SetZListItems('list', array('profileid', 'reason')); // Attributes to be displayed for a list + MetaModel::Init_SetZListItems('list', array('userid', 'profileid', 'reason')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form @@ -948,13 +948,21 @@ class SetupProfiles ), ), 'Support Agent' => array( - 'description' => 'Person analyzing and solving the current incidents or problems', - 'write_modules' => 'Incident,Problem,KnownError', + 'description' => 'Person analyzing and solving the current incidents', + 'write_modules' => 'Incident', 'stimuli' => array( 'Incident' => 'ev_assign,ev_reassign,ev_resolve,ev_close', 'UserRequest' => 'ev_assign,ev_reassign,ev_resolve,ev_close,ev_freeze', ), ), + 'Problem Manager' => array( + 'description' => 'Person analyzing and solving the current problems', + 'write_modules' => 'Problem,KnownError', + 'stimuli' => array( + 'Problem' => 'ev_assign,ev_reassign,ev_resolve,ev_close', + ), + ), + 'Change Implementor' => array( 'description' => 'Person executing the changes', 'write_modules' => 'Change', diff --git a/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php index 991768512b..4e591323ff 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/model.itop-knownerror-mgmt.php @@ -34,7 +34,7 @@ class KnownError extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable", + "category" => "bizmodel,searchable,knownerrormgmt", "name" => "Known Error", "description" => "Error documented for a known issue", "key_type" => "autoincrement", @@ -67,9 +67,9 @@ class KnownError extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("vendor", array("allowed_values"=>null, "sql"=>"vendor", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("model", array("allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkInfraError", "ext_key_to_me"=>"error_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("document_list", array("linked_class"=>"lnkDocumentError", "ext_key_to_me"=>"error_id", "ext_key_to_remote"=>"doc_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); - + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkInfraError", "ext_key_to_me"=>"error_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("document_list", array("linked_class"=>"lnkDocumentError", "ext_key_to_me"=>"error_id", "ext_key_to_remote"=>"doc_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + @@ -93,7 +93,7 @@ class lnkInfraError extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable", + "category" => "bizmodel,searchable,knownerrormgmt", "name" => "InfraErrorLinks", "description" => "Infra related to a known error", "key_type" => "autoincrement", @@ -137,7 +137,7 @@ class lnkDocumentError extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable", + "category" => "bizmodel,searchable,knownerrormgmt", "name" => "DocumentsErrorLinks", "description" => "A link between a document and a known error", "key_type" => "autoincrement", @@ -165,7 +165,7 @@ class lnkDocumentError extends cmdbAbstractObject } -$oMyMenuGroup = new MenuGroup('ProblemManagement', 42 /* fRank */); -new OQLMenuNode('Problem:KnownErrors', 'SELECT KnownError', $oMyMenuGroup->GetIndex(), 3 /* fRank */); - +$oMyMenuGroup = new MenuGroup('ProblemManagement', 42 /* fRank */); +new OQLMenuNode('Problem:KnownErrors', 'SELECT KnownError', $oMyMenuGroup->GetIndex(), 3 /* fRank */); + ?> diff --git a/modules/itop-tickets-1.0.0/model.itop-tickets.php b/modules/itop-tickets-1.0.0/model.itop-tickets.php index e725c21d48..5b4d65ec3c 100644 --- a/modules/itop-tickets-1.0.0/model.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/model.itop-tickets.php @@ -30,7 +30,7 @@ abstract class Ticket extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt", + "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt,problemmgmt", "key_type" => "autoincrement", "name_attcode" => "ref", "state_attcode" => "", @@ -66,7 +66,7 @@ class lnkTicketToDoc extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt", + "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt,problemmgmt", "key_type" => "autoincrement", "name_attcode" => "ticket_id", "state_attcode" => "", @@ -97,7 +97,7 @@ class lnkTicketToContact extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt", + "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt,problemmgmt", "key_type" => "autoincrement", "name_attcode" => "ticket_id", "state_attcode" => "", @@ -130,7 +130,7 @@ class lnkTicketToCI extends cmdbAbstractObject { $aParams = array ( - "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt", + "category" => "bizmodel,searchable,incidentmgmt,requestmgmt,changemgmt,problemmgmt", "key_type" => "autoincrement", "name_attcode" => "ticket_id", "state_attcode" => "", @@ -165,7 +165,7 @@ abstract class ResponseTicket extends Ticket { $aParams = array ( - "category" => "bizmodel", + "category" => "bizmodel,incidentmgmt,requestmgmt", "key_type" => "autoincrement", "name_attcode" => "ref", "state_attcode" => "status", From f68a00c5f6c20240c4365ddff47cc6d363cb3f19 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 10:33:33 +0000 Subject: [PATCH 706/970] - Fixed Trac #249: loosing links when modifying a SLA ! SVN:trunk[789] --- application/ui.linkswidget.class.inc.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 2555e76638..74484719fe 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -108,6 +108,7 @@ class UILinksWidget $sNameSuffix = "]"; // To make a tabular form $aArgs['prefix'] = $sPrefix; $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] .= ""; foreach($this->m_aEditableFields as $sFieldCode) { $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); From 88643dab961da50139042f72a1e4675b3883e598 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Tue, 7 Sep 2010 15:39:58 +0000 Subject: [PATCH 707/970] add reconciliation key for NEtwork Interface SVN:trunk[790] --- modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 82d6d0f6ed..07c4eeded0 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -1016,7 +1016,7 @@ class NetworkInterface extends ConnectableCI "key_type" => "autoincrement", "name_attcode" => "name", "state_attcode" => "", - "reconc_keys" => array("name","device_id","org_id"), + "reconc_keys" => array("name","device_id","device_name","org_id"), "db_table" => "networkinterface", "db_key_field" => "id", "db_finalclass_field" => "", From 142a64dfc962818b6207d0d233f5355d21531791 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 15:43:26 +0000 Subject: [PATCH 708/970] - Fixed bug #250: sorting truncated tables. - Tables can now be sorted on the first column too (object name). SVN:trunk[791] --- application/itopwebpage.class.inc.php | 2 +- application/uilinkswizard.class.inc.php | 4 ++-- js/linkswidget.js | 4 ++-- js/utils.js | 11 ++++------- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 396443134a..53fbdf241a 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -216,7 +216,7 @@ class iTopWebPage extends NiceWebPage // End of Tabs handling $("table.listResults").tableHover(); // hover tables - $(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables + $(".listResults").tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables $(".date-pick").datepicker({ showOn: 'button', buttonImage: '../images/calendar.png', diff --git a/application/uilinkswizard.class.inc.php b/application/uilinkswizard.class.inc.php index 6eac530202..4c48a1e0dc 100644 --- a/application/uilinkswizard.class.inc.php +++ b/application/uilinkswizard.class.inc.php @@ -179,7 +179,7 @@ class UILinksWizard function(data) { $('#SearchResultsToAdd').html(data); - $('#SearchResultsToAdd .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('#SearchResultsToAdd .listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables }, 'html' @@ -233,7 +233,7 @@ class UILinksWizard } $('.listResults tbody').append(data); $('.listResults').trigger('update'); - $('.listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('.listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables }, 'html' ); diff --git a/js/linkswidget.js b/js/linkswidget.js index d57352a3da..4a5b8b1fc4 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -97,7 +97,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix) function(data) { $(sSearchAreaId).html(data); - $(sSearchAreaId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $(sSearchAreaId+' .listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables }, 'html' @@ -151,7 +151,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix) $('#'+me.id+'_empty_row').remove(); $('#linkedset_'+me.id+' .listResults tbody').append(data); $('#linkedset_'+me.id+' .listResults').trigger('update'); - $('#linkedset_'+me.id+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('#linkedset_'+me.id+' .listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables $('#linkedset_'+me.id+' :input').each( function() { $(this).trigger('validate', ''); }); // Validate newly added form fields... } }, diff --git a/js/utils.js b/js/utils.js index d7dd9344da..b1763c38f4 100644 --- a/js/utils.js +++ b/js/utils.js @@ -13,7 +13,7 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) $('#'+divId).append(data); $('#'+divId).removeClass('loading'); $('#'+divId+' .listResults').tableHover(); // hover tables - $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('#'+divId+' .listResults').tablesorter( { widgets: ['zebra', 'truncatedList']} ); // sortable and zebra tables //$('#'+divId).unblockUI(); } ); @@ -24,17 +24,14 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams) function TruncateList(divId, iLimit, sNewLabel, sLinkLabel) { var iCount = 0; - $('#'+divId+' table.listResults tr').each( function(){ - if (iCount > iLimit) - { + $('#'+divId+' table.listResults tr:gt('+iLimit+')').each( function(){ $(this).remove(); - } - iCount++; }); $('#lbl_'+divId).html(sNewLabel); $('#'+divId+' table.listResults tr:last td').addClass('truncated'); $('#'+divId+' table.listResults').addClass('truncated'); $('#trc_'+divId).html(sLinkLabel); + $('#'+divId+' .listResults').trigger("update"); // Reset the cache } /** * Reload any block -- used for periodic auto-reload @@ -50,7 +47,7 @@ function ReloadBlock(divId, sStyle, sSerializedFilter, sExtraParams) $('#'+divId).append(data); $('#'+divId).removeClass('loading'); $('#'+divId+' .listResults').tableHover(); // hover tables - $('#'+divId+' .listResults').tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables + $('#'+divId+' .listResults').tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables //$('#'+divId).unblockUI(); } ); From d0e07c634a794041ef38224c25f8f891d036bf0a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 16:17:09 +0000 Subject: [PATCH 709/970] - Fixed bug #226: Back button no longer uses the history "back" which is too unpredictable. SVN:trunk[792] --- application/cmdbabstract.class.inc.php | 2 +- application/itopwebpage.class.inc.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 754ab8f7aa..65ba879922 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1267,7 +1267,7 @@ EOF { // The object does not exist in the database it's a creation $oPage->add("\n"); - $oPage->add("    \n"); + $oPage->add("    \n"); $oPage->add("\n"); } $oPage->add("\n"); diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 53fbdf241a..4aa2c6ba80 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -294,6 +294,12 @@ EOF window.location.href = './UI.php?operation=details&class='+sClass+'&id='+id; } + + function BackToList(sClass) + { + window.location.href = './UI.php?operation=search_oql&oql_class='+sClass+'&oql_clause=WHERE id=0'; + } + function ShowDebug() { if ($('#rawOutput > div').html() != '') From c39ec1eec4867b31015ab2379f5bdd4baf5cbec1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 19:02:59 +0000 Subject: [PATCH 710/970] - Properly deactivate the creation of 1:n links while creating an object, since it is not possible to create such links at this time. SVN:trunk[793] --- application/cmdbabstract.class.inc.php | 51 +++++++++++++++----------- dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 65ba879922..dbfde42e82 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -162,30 +162,37 @@ abstract class cmdbAbstractObject extends CMDBObject { // 1:n links $sTargetClass = $oAttDef->GetLinkedClass(); - $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); - - $oFilter = new DBObjectSearch($sTargetClass); - $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey(),'='); - - $aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey()); - $oAppContext = new ApplicationContext(); - foreach($oAppContext->GetNames() as $sKey) + if ($this->IsNew()) { - // The linked object inherits the parent's value for the context - if (MetaModel::IsValidAttCode($sClass, $sKey)) - { - $aDefaults[$sKey] = $this->Get($sKey); - } + $oPage->p(Dict::Format('UI:BeforeAdding_Class_ObjectsSaveThisObject', MetaModel::GetName($sTargetClass))); + } + else + { + $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); + + $oFilter = new DBObjectSearch($sTargetClass); + $oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey(),'='); + + $aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey()); + $oAppContext = new ApplicationContext(); + foreach($oAppContext->GetNames() as $sKey) + { + // The linked object inherits the parent's value for the context + if (MetaModel::IsValidAttCode($sClass, $sKey)) + { + $aDefaults[$sKey] = $this->Get($sKey); + } + } + $aParams = array( + 'target_attr' => $oAttDef->GetExtKeyToMe(), + 'object_id' => $this->GetKey(), + 'menu' => true, + 'default' => $aDefaults, + ); + + $oBlock = new DisplayBlock($oFilter, 'list', false); + $oBlock->Display($oPage, $sInputId, $aParams); } - $aParams = array( - 'target_attr' => $oAttDef->GetExtKeyToMe(), - 'object_id' => $this->GetKey(), - 'menu' => true, - 'default' => $aDefaults, - ); - - $oBlock = new DisplayBlock($oFilter, 'list', false); - $oBlock->Display($oPage, $sInputId, $aParams); } else // get_class($oAttDef) == 'AttributeLinkedSetIndirect' { diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index e33fdcd509..141667afb4 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -823,6 +823,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Help', 'UI:PasswordConfirm' => '(Confirm)', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Before adding more %1$s objects, save this object.', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 5440a196d9..ffb12c4190 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -834,6 +834,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Deadline_Days_Hours_Minutes' => '%1$dj %2$dh %3$dmin', 'UI:Help' => 'Aide', 'UI:PasswordConfirm' => '(Confirmer)', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Enregistrez l\'objet courant avant de créer de nouveaux éléments de type %1$s.', )); ?> From 306168c76163b5758ca2cbe841921d0c8b79060e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 19:22:33 +0000 Subject: [PATCH 711/970] - FIxed Trac#21: comsetic enhancement to the final step of the CSV import wizard. SVN:trunk[794] --- pages/csvimport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 548bc7b24a..4eb12a8967 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -514,7 +514,7 @@ try $oPage->add('

          '); if ($bSimulate) { - $oPage->add('

        '); + $oPage->add('

        '); } else { From 39e943c20a2f2123b88e43be0793361d87e8fbe9 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Sep 2010 20:04:21 +0000 Subject: [PATCH 712/970] - Fixing Trac #173. The unused states are now excluded from the graph. - The path to graphviz installation (dot executable) is now configurable via the 'graphviz_path' config variable (defaults to /usr/bin/dot). - If graphviz is present, the graph is generated on the fly, and using the current user's localization/translation for the state & transition names. SVN:trunk[795] --- core/config.class.inc.php | 8 ++++++++ pages/graphviz.php | 40 ++++++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 44de81061c..2fbca23514 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -94,6 +94,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'graphviz_path' => array( + 'type' => 'string', + 'description' => 'Path to the Graphviz "dot" executable for graphing objects lifecycle', + 'default' => '/usr/bin/dot', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), ); public function IsProperty($sPropCode) diff --git a/pages/graphviz.php b/pages/graphviz.php index 89e067b58f..61116f98cb 100644 --- a/pages/graphviz.php +++ b/pages/graphviz.php @@ -15,7 +15,7 @@ // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA /** - * Renders a graph as a png (directly in the HTTP response) + * Renders a graph of the class' lifecycle as a png (directly in the HTTP response) * * @author Erwan Taloc * @author Romain Quetiez @@ -27,6 +27,10 @@ require_once('../application/application.inc.php'); require_once('../application/itopwebpage.class.inc.php'); require_once('../application/startup.inc.php'); +require_once('../application/utils.inc.php'); + +require_once('../application/loginwebpage.class.inc.php'); +LoginWebPage::DoLogin(); // Check user rights and prompt if needed /** * Helper to generate a Graphviz code for displaying the life cycle of a class @@ -72,14 +76,19 @@ function GraphvizLifecycle($sClass) } foreach($aStates as $sStateCode => $aStateDef) { - $sStateLabel = str_replace(' ', '\n', MetaModel::GetStateLabel($sClass, $sStateCode)); - if ( ($aStatesLinks[$sStateCode]['in'] == 0) || ($aStatesLinks[$sStateCode]['out'] == 0)) + if (($aStatesLinks[$sStateCode]['out'] > 0) || ($aStatesLinks[$sStateCode]['in'] > 0)) { - $sDotFileContent .= "\t$sStateCode [ shape=doublecircle,label=\"$sStateLabel\"];\n"; - } - else - { - $sDotFileContent .= "\t$sStateCode [ shape=circle,label=\"$sStateLabel\"];\n"; + // Show only reachable states + $sStateLabel = str_replace(' ', '\n', MetaModel::GetStateLabel($sClass, $sStateCode)); + if ( ($aStatesLinks[$sStateCode]['in'] == 0) || ($aStatesLinks[$sStateCode]['out'] == 0)) + { + // End or Start state, make it look different + $sDotFileContent .= "\t$sStateCode [ shape=doublecircle,label=\"$sStateLabel\"];\n"; + } + else + { + $sDotFileContent .= "\t$sStateCode [ shape=circle,label=\"$sStateLabel\"];\n"; + } } } $sDotFileContent .= "}\n"; @@ -90,15 +99,20 @@ function GraphvizLifecycle($sClass) $sClass = utils::ReadParam('class', 'bizIncidentTicket'); $sDir = dirname(__FILE__); $sImageFilePath = $sDir."/../images/lifecycle/".$sClass.".png"; -if (file_exists("/iTop/Graphviz/bin/dot.exe")) +$sDotExecutable = utils::GetConfig()->Get('graphviz_path'); +if (file_exists($sDotExecutable)) { // create the file with Graphviz $sDotDescription = GraphvizLifecycle($sClass); $sDotFilePath = $sDir."/tmp-lifecycle.dot"; - $rFile = fopen($sDotFilePath, "w"); - fwrite($rFile, $sDotDescription); - fclose($rFile); - exec("/iTop/Graphviz/bin/dot.exe -Tpng < $sDotFilePath > $sImageFilePath"); + // From now on, fail silently, since the image file may + // already exist and we should not "pollute" the page's output + // with warnings in case we are unable to refresh the image + $rFile = @fopen($sDotFilePath, "w"); + @fwrite($rFile, $sDotDescription); + @fclose($rFile); + //echo "

        Executing command: $sDotExecutable -Tpng < $sDotFilePath > $sImageFilePath

        \n"; + @exec("$sDotExecutable -Tpng < $sDotFilePath > $sImageFilePath"); } header('Content-type: image/png'); From 32b337b9a828a84f8026be0c26bbb959d556960a Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 8 Sep 2010 08:11:46 +0000 Subject: [PATCH 713/970] #140 Check that user logins are unique SVN:trunk[796] --- core/userrights.class.inc.php | 21 ++ dictionaries/dictionary.itop.ui.php | 3 + dictionaries/es_cr.dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 3 + .../fr.dict.itop-knownerror-mgmt.php | 182 +++++++++--------- pages/UI.php | 123 ++++++++---- 6 files changed, 209 insertions(+), 124 deletions(-) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 75322d5250..7d432f0778 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -110,6 +110,27 @@ abstract class User extends cmdbAbstractObject abstract public function CanChangePassword(); abstract public function ChangePassword($sOldPassword, $sNewPassword); + /* + * Overload the standard behavior + */ + public function DoCheckToWrite() + { + parent::DoCheckToWrite(); + + // Note: This MUST be factorized later: declare unique keys (set of columns) in the data model + $aChanges = $this->ListChanges(); + if (array_key_exists('login', $aChanges)) + { + $sNewLogin = $aChanges['login']; + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin"); + $oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin)); + if ($oSet->Count() > 0) + { + $this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin); + } + } + } + function GetGrantAsHtml($sClass, $iAction) { if (UserRights::IsActionAllowed($sClass, $iAction, null, $this)) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 141667afb4..ce1492d8c5 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -108,6 +108,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:User/Attribute:profile_list+' => 'Roles, granting rights for that person', 'Class:User/Attribute:allowed_org_list' => 'Allowed Organizations', 'Class:User/Attribute:allowed_org_list+' => 'The end user is allowed to see data belonging to the following organizations. If no organization is specified, there is no restriction.', + + 'Class:User/Error:LoginMustBeUnique' => 'Login must be unique - "%1s" is already being used.', )); // @@ -648,6 +650,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:PageTitle:ObjectCreated' => 'iTop Object Created.', 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s created.', 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Applying %1$s on object: %2$s in state %3$s to target state: %4$s.', + 'UI:ObjectCouldNotBeWritten' => 'The object could not be written: %1$s', 'UI:PageTitle:FatalError' => 'iTop - Fatal Error', 'UI:FatalErrorMessage' => 'Fatal error, iTop cannot continue.', 'UI:Error_Details' => 'Error: %1$s.', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index a6fa853ba4..d6f3574f16 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -658,6 +658,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:PageTitle:ObjectCreated' => 'iTop Object Created.', 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s created.', 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Applying %1$s on object: %2$s in state %3$s to target state: %4$s.', + 'UI:ObjectCouldNotBeWritten' => 'The object could not be written: %1$s', 'UI:PageTitle:FatalError' => 'iTop - Fatal Error', 'UI:FatalErrorMessage' => 'Fatal error, iTop cannot continue.', 'UI:Error_Details' => 'Error: %1$s.', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index ffb12c4190..ca34702d79 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -108,6 +108,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:User/Attribute:profile_list+' => 'Rôles, ouvrants les droits d\'accès', 'Class:User/Attribute:allowed_org_list' => 'Organisations permises', 'Class:User/Attribute:allowed_org_list+' => 'L\'utilisateur a le droit de voir les données des organisations listées ici. Si aucune organisation n\'est spécifiée, alors aucune restriction ne s\'applique.', + + 'Class:User/Error:LoginMustBeUnique' => 'Le login doit être unique - "%1s" est déjà utilisé.', )); // @@ -651,6 +653,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:PageTitle:ObjectCreated' => 'iTop objet créé.', 'UI:Title:Object_Of_Class_Created' => '%2$s - %1$s créé(e).', 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '%1$s pour %2$s de l\'état %3$s vers l\'état %4$s.', + 'UI:ObjectCouldNotBeWritten' => 'L\'objet ne peut pas être enregistré: %1$s', 'UI:PageTitle:FatalError' => 'iTop - Erreur Fatale', 'UI:FatalErrorMessage' => 'Erreur fatale, iTop ne peut pas continuer.', 'UI:Error_Details' => 'Erreur: %1$s.', diff --git a/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php index d4ecef42bc..c8c0bcb21c 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/fr.dict.itop-knownerror-mgmt.php @@ -48,95 +48,95 @@ // Class:/Stimulus: // Class:/Stimulus:+ -// -// Class: KnownError -// - -Dict::Add('FR FR', 'French', 'Français', array( - 'Class:KnownError' => 'Erreur Connue', - 'Class:KnownError+' => 'Erreur documenté pour un problème connu', - 'Class:KnownError/Attribute:name' => 'Nom', - 'Class:KnownError/Attribute:name+' => '', - 'Class:KnownError/Attribute:org_id' => 'Client', - 'Class:KnownError/Attribute:org_id+' => '', - 'Class:KnownError/Attribute:cust_name' => 'Nom du client', - 'Class:KnownError/Attribute:cust_name+' => '', - 'Class:KnownError/Attribute:problem_id' => 'Problème lié', - 'Class:KnownError/Attribute:problem_id+' => '', - 'Class:KnownError/Attribute:problem_ref' => 'Ref', - 'Class:KnownError/Attribute:problem_ref+' => '', - 'Class:KnownError/Attribute:symptom' => 'Symptome', - 'Class:KnownError/Attribute:symptom+' => '', - 'Class:KnownError/Attribute:root_cause' => 'Cause première', - 'Class:KnownError/Attribute:root_cause+' => '', - 'Class:KnownError/Attribute:workaround' => 'Contournement', - 'Class:KnownError/Attribute:workaround+' => '', - 'Class:KnownError/Attribute:solution' => 'Solution', - 'Class:KnownError/Attribute:solution+' => '', - 'Class:KnownError/Attribute:error_code' => 'Code d\'erreur', - 'Class:KnownError/Attribute:error_code+' => '', - 'Class:KnownError/Attribute:domain' => 'Domaine', - 'Class:KnownError/Attribute:domain+' => '', - 'Class:KnownError/Attribute:domain/Value:Application' => 'Application', - 'Class:KnownError/Attribute:domain/Value:Application+' => 'Application', - 'Class:KnownError/Attribute:domain/Value:Desktop' => 'Desktop', - 'Class:KnownError/Attribute:domain/Value:Desktop+' => 'Desktop', - 'Class:KnownError/Attribute:domain/Value:Network' => 'Réseau', - 'Class:KnownError/Attribute:domain/Value:Network+' => 'Réseau', - 'Class:KnownError/Attribute:domain/Value:Server' => 'Serveur', - 'Class:KnownError/Attribute:domain/Value:Server+' => 'Serveur', - 'Class:KnownError/Attribute:vendor' => 'Vendeur', - 'Class:KnownError/Attribute:vendor+' => '', - 'Class:KnownError/Attribute:model' => 'Modèle', - 'Class:KnownError/Attribute:model+' => '', - 'Class:KnownError/Attribute:version' => 'Version', - 'Class:KnownError/Attribute:version+' => '', - 'Class:KnownError/Attribute:ci_list' => 'CIs', - 'Class:KnownError/Attribute:ci_list+' => '', - 'Class:KnownError/Attribute:document_list' => 'Documents', - 'Class:KnownError/Attribute:document_list+' => '', -)); - - -// -// Class: lnkInfraError -// - -Dict::Add('EN US', 'English', 'English', array( - 'Class:lnkInfraError' => 'Lien erreur CI', - 'Class:lnkInfraError+' => 'CIs liés à une erreur connue', - 'Class:lnkInfraError/Attribute:infra_id' => 'CI', - 'Class:lnkInfraError/Attribute:infra_id+' => '', - 'Class:lnkInfraError/Attribute:infra_name' => 'Nom du CI', - 'Class:lnkInfraError/Attribute:infra_name+' => '', - 'Class:lnkInfraError/Attribute:infra_status' => 'Status du CI', - 'Class:lnkInfraError/Attribute:infra_status+' => '', - 'Class:lnkInfraError/Attribute:error_id' => 'Erreur', - 'Class:lnkInfraError/Attribute:error_id+' => '', - 'Class:lnkInfraError/Attribute:error_name' => 'Nom de l\'erreur', - 'Class:lnkInfraError/Attribute:error_name+' => '', - 'Class:lnkInfraError/Attribute:reason' => 'Raison', - 'Class:lnkInfraError/Attribute:reason+' => '', -)); - -// -// Class: lnkDocumentError -// - -Dict::Add('EN US', 'English', 'English', array( - 'Class:lnkDocumentError' => 'Lien erreur document', - 'Class:lnkDocumentError+' => 'Lien entre une erreur et un document', - 'Class:lnkDocumentError/Attribute:doc_id' => 'Document', - 'Class:lnkDocumentError/Attribute:doc_id+' => '', - 'Class:lnkDocumentError/Attribute:doc_name' => 'Nom du document', - 'Class:lnkDocumentError/Attribute:doc_name+' => '', - 'Class:lnkDocumentError/Attribute:error_id' => 'Erreur', - 'Class:lnkDocumentError/Attribute:error_id+' => '', - 'Class:lnkDocumentError/Attribute:error_name' => 'Nom de l\'erreur', - 'Class:lnkDocumentError/Attribute:error_name+' => '', - 'Class:lnkDocumentError/Attribute:link_type' => 'Information', - 'Class:lnkDocumentError/Attribute:link_type+' => '', -)); - - +// +// Class: KnownError +// + +Dict::Add('FR FR', 'French', 'Français', array( + 'Class:KnownError' => 'Erreur Connue', + 'Class:KnownError+' => 'Erreur documenté pour un problème connu', + 'Class:KnownError/Attribute:name' => 'Nom', + 'Class:KnownError/Attribute:name+' => '', + 'Class:KnownError/Attribute:org_id' => 'Client', + 'Class:KnownError/Attribute:org_id+' => '', + 'Class:KnownError/Attribute:cust_name' => 'Nom du client', + 'Class:KnownError/Attribute:cust_name+' => '', + 'Class:KnownError/Attribute:problem_id' => 'Problème lié', + 'Class:KnownError/Attribute:problem_id+' => '', + 'Class:KnownError/Attribute:problem_ref' => 'Ref', + 'Class:KnownError/Attribute:problem_ref+' => '', + 'Class:KnownError/Attribute:symptom' => 'Symptome', + 'Class:KnownError/Attribute:symptom+' => '', + 'Class:KnownError/Attribute:root_cause' => 'Cause première', + 'Class:KnownError/Attribute:root_cause+' => '', + 'Class:KnownError/Attribute:workaround' => 'Contournement', + 'Class:KnownError/Attribute:workaround+' => '', + 'Class:KnownError/Attribute:solution' => 'Solution', + 'Class:KnownError/Attribute:solution+' => '', + 'Class:KnownError/Attribute:error_code' => 'Code d\'erreur', + 'Class:KnownError/Attribute:error_code+' => '', + 'Class:KnownError/Attribute:domain' => 'Domaine', + 'Class:KnownError/Attribute:domain+' => '', + 'Class:KnownError/Attribute:domain/Value:Application' => 'Application', + 'Class:KnownError/Attribute:domain/Value:Application+' => 'Application', + 'Class:KnownError/Attribute:domain/Value:Desktop' => 'Desktop', + 'Class:KnownError/Attribute:domain/Value:Desktop+' => 'Desktop', + 'Class:KnownError/Attribute:domain/Value:Network' => 'Réseau', + 'Class:KnownError/Attribute:domain/Value:Network+' => 'Réseau', + 'Class:KnownError/Attribute:domain/Value:Server' => 'Serveur', + 'Class:KnownError/Attribute:domain/Value:Server+' => 'Serveur', + 'Class:KnownError/Attribute:vendor' => 'Vendeur', + 'Class:KnownError/Attribute:vendor+' => '', + 'Class:KnownError/Attribute:model' => 'Modèle', + 'Class:KnownError/Attribute:model+' => '', + 'Class:KnownError/Attribute:version' => 'Version', + 'Class:KnownError/Attribute:version+' => '', + 'Class:KnownError/Attribute:ci_list' => 'CIs', + 'Class:KnownError/Attribute:ci_list+' => '', + 'Class:KnownError/Attribute:document_list' => 'Documents', + 'Class:KnownError/Attribute:document_list+' => '', +)); + + +// +// Class: lnkInfraError +// + +Dict::Add('FR FR', 'English', 'English', array( + 'Class:lnkInfraError' => 'Lien erreur CI', + 'Class:lnkInfraError+' => 'CIs liés à une erreur connue', + 'Class:lnkInfraError/Attribute:infra_id' => 'CI', + 'Class:lnkInfraError/Attribute:infra_id+' => '', + 'Class:lnkInfraError/Attribute:infra_name' => 'Nom du CI', + 'Class:lnkInfraError/Attribute:infra_name+' => '', + 'Class:lnkInfraError/Attribute:infra_status' => 'Status du CI', + 'Class:lnkInfraError/Attribute:infra_status+' => '', + 'Class:lnkInfraError/Attribute:error_id' => 'Erreur', + 'Class:lnkInfraError/Attribute:error_id+' => '', + 'Class:lnkInfraError/Attribute:error_name' => 'Nom de l\'erreur', + 'Class:lnkInfraError/Attribute:error_name+' => '', + 'Class:lnkInfraError/Attribute:reason' => 'Raison', + 'Class:lnkInfraError/Attribute:reason+' => '', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('FR FR', 'English', 'English', array( + 'Class:lnkDocumentError' => 'Lien erreur document', + 'Class:lnkDocumentError+' => 'Lien entre une erreur et un document', + 'Class:lnkDocumentError/Attribute:doc_id' => 'Document', + 'Class:lnkDocumentError/Attribute:doc_id+' => '', + 'Class:lnkDocumentError/Attribute:doc_name' => 'Nom du document', + 'Class:lnkDocumentError/Attribute:doc_name+' => '', + 'Class:lnkDocumentError/Attribute:error_id' => 'Erreur', + 'Class:lnkDocumentError/Attribute:error_id+' => '', + 'Class:lnkDocumentError/Attribute:error_name' => 'Nom de l\'erreur', + 'Class:lnkDocumentError/Attribute:error_name+' => '', + 'Class:lnkDocumentError/Attribute:link_type' => 'Information', + 'Class:lnkDocumentError/Attribute:link_type+' => '', +)); + + ?> diff --git a/pages/UI.php b/pages/UI.php index f3e59de451..be603e0ef8 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -484,7 +484,7 @@ try { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } - $oObj = MetaModel::GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id, false); if ($oObj != null) { $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetName(), $sClassLabel)); @@ -694,6 +694,7 @@ try $bIsModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && !MetaModel::IsReadOnlyClass($sClass); if( ($oObj != null) && $bIsModifiedAllowed ) { + // Note: code duplicated to the case 'apply_modify' when a data integrity issue has been found $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->add("
        \n"); $oP->add("

        ".$oObj->GetIcon()." ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

        \n"); @@ -768,6 +769,7 @@ try { throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } + // Note: code duplicated to the case 'apply_modify' when a data integrity issue has been found $oP->add_linked_script("../js/json.js"); $oP->add_linked_script("../js/forms-json-utils.js"); $oP->add_linked_script("../js/wizardhelper.js"); @@ -806,6 +808,7 @@ try { // Display the creation form $sClassLabel = MetaModel::GetName($sRealClass); + // Note: some code has been duplicated to the case 'apply_new' when a data integrity issue has been found $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); $oP->add("

        ".MetaModel::GetClassIcon($sRealClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

        \n"); $oP->add("
        \n"); @@ -872,26 +875,36 @@ try { throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); } - $oObj = MetaModel::GetObject($sClass, $id); - if (!utils::IsTransactionValid($sTransactionId)) + $bDisplayDetails = true; + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj == null) { + $bDisplayDetails = false; + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + elseif (!utils::IsTransactionValid($sTransactionId)) + { + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); } else { - if ($oObj != null) + UpdateObject($oObj); + + if (!$oObj->IsModified()) { $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); - $oP->add("

        ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

        \n"); - - UpdateObject($oObj); - - if (!$oObj->IsModified()) - { - $oP->p(Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); - } - else + $oP->p(Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); + } + else + { + list($bRes, $aIssues) = $oObj->CheckToWrite(); + if ($bRes) { + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); + $oP->add("

        ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

        \n"); + $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); if (UserRights::IsImpersonated()) @@ -908,15 +921,35 @@ try $oP->p(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())); } - } - else - { - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); + else + { + $bDisplayDetails = false; + // Found issues, explain and give the user a second chance + // + // Note: code duplicated from the case 'modify' + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetName(), $sClassLabel)); + $oP->add("
        \n"); + $oP->add("

        ".$oObj->GetIcon()." ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $oObj->GetName())."

        \n"); + $oP->add("
        \n"); + $oP->add("
        \n"); + $oObj->DisplayModifyForm($oP); + $oP->add("
        \n"); + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); + } } } - $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object some that the linkedset are displayed properly - $oObj->DisplayDetails($oP); + if ($bDisplayDetails) + { + $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly + $oObj->DisplayDetails($oP); + } break; case 'select_for_deletion': @@ -1037,22 +1070,46 @@ try { $sClass = get_class($oObj); $sClassLabel = MetaModel::GetName($sClass); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) + + list($bRes, $aIssues) = $oObj->CheckToWrite(); + if ($bRes) { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::IsImpersonated()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBInsertTracked($oMyChange); + $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); + $oP->add("

        ".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

        \n"); + $oObj->DisplayDetails($oP); } else { - $sUserString = UserRights::GetUser(); + // Found issues, explain and give the user a second chance + // + // Note: code similar to the case 'modify' + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); + $oP->add("

        ".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:CreationTitle_Class', $sClassLabel)."

        \n"); + $oP->add("
        \n"); + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj); + $oP->add("
        \n"); + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); } - $oMyChange->Set("userinfo", $sUserString); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBInsertTracked($oMyChange); - $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); - $oP->add("

        ".Dict::Format('UI:Title:Object_Of_Class_Created', $oObj->GetName(), $sClassLabel)."

        \n"); - $oObj->DisplayDetails($oP); } break; @@ -1099,7 +1156,7 @@ try { throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); } - $oObj = MetaModel::GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id, false); if ($oObj != null) { $aTransitions = $oObj->EnumTransitions(); @@ -1191,7 +1248,7 @@ EOF { throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); } - $oObj = MetaModel::GetObject($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id, false); if ($oObj != null) { $aTransitions = $oObj->EnumTransitions(); From c91b99c6a355c880ec50641b9b5aa674ce5998a7 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 8 Sep 2010 08:23:52 +0000 Subject: [PATCH 714/970] - New Welcome popup message, displayed once per session at maximum. SVN:trunk[797] --- application/templates/welcome_popup.html | 46 ++++++++++++++++++++++++ dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + pages/UI.php | 44 ++++++++++++++++++++++- 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 application/templates/welcome_popup.html diff --git a/application/templates/welcome_popup.html b/application/templates/welcome_popup.html new file mode 100644 index 0000000000..0b3634702e --- /dev/null +++ b/application/templates/welcome_popup.html @@ -0,0 +1,46 @@ +
        + +

        +

        +

        UI:WelcomeMenu:Title

        +

        +
        '.Dict::S('UI:CSVImport:HeaderFields').''.Dict::S('UI:CSVImport:HeaderMappings').' '.Dict::S('UI:CSVImport:HeaderSearch').''.Dict::S('UI:CSVImport:DataLine1').''.Dict::S('UI:CSVImport:DataLine2').'
        + + + + +
        +UI:WelcomeMenu:LeftBlock + +UI:WelcomeMenu:RightBlock +
        + diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index ce1492d8c5..3245baced5 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -827,6 +827,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Help' => 'Help', 'UI:PasswordConfirm' => '(Confirm)', 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Before adding more %1$s objects, save this object.', + 'UI:DisplayThisMessageAtStartup' => 'Display this message at startup', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index ca34702d79..b8092fa530 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -838,6 +838,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Help' => 'Aide', 'UI:PasswordConfirm' => '(Confirmer)', 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Enregistrez l\'objet courant avant de créer de nouveaux éléments de type %1$s.', + 'UI:DisplayThisMessageAtStartup' => 'Afficher ce message au démarrage', )); ?> diff --git a/pages/UI.php b/pages/UI.php index be603e0ef8..26d02c9f5a 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -445,6 +445,47 @@ function UpdateObject(&$oObj) } } } + +/** + * Displays a popup welcome message, once per session at maximum + * until the user unchecks the "Display welcome at startup" + * @param WebPage $oP The current web page for the display + * @return void + */ +function DisplayWelcomePopup(WebPage $oP) +{ + if (!isset($_SESSION['welcome'])) + { + // Check, only once per session, if the popup should be displayed... + // If the user did not already ask for hiding it forever + $bPopup = appUserPreferences::GetPref('welcome_popup', true); + if ($bPopup) + { + $sTemplate = @file_get_contents('../application/templates/welcome_popup.html'); + if ($sTemplate !== false) + { + $oTemplate = new DisplayTemplate($sTemplate); + $oP->add("
        "); + $oTemplate->Render($oP, array()); + $oP->add("

        \n"); + $oP->add("

        \n"); + $oP->add("

        \n"); + $sTitle = addslashes(Dict::S('UI:WelcomeMenu:Title')); + $oP->add_ready_script( +<<output(); + DisplayWelcomePopup($oP); + $oP->output(); } catch(CoreException $e) { From 3f0b5ca63ac02813277c0a74ed2c0684e6d7fa07 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 8 Sep 2010 13:47:45 +0000 Subject: [PATCH 715/970] #123 Switch back to the InnoDB engine, which shows better performances (moreover we did far more testing with this engine) SVN:trunk[798] --- core/metamodel.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 144b6ada32..9c3e428fd7 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -102,8 +102,8 @@ define('OPT_ATT_MUSTPROMPT', 16); * * @package iTopORM */ -//define('MYSQL_ENGINE', 'innodb'); -define('MYSQL_ENGINE', 'myisam'); +define('MYSQL_ENGINE', 'innodb'); +//define('MYSQL_ENGINE', 'myisam'); From a3f4dad24e6fcea2791260ee2d6bad082368eaee Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 8 Sep 2010 20:01:06 +0000 Subject: [PATCH 716/970] Added memory peak to the internal profiling instrumentation SVN:trunk[799] --- core/kpi.class.inc.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/kpi.class.inc.php b/core/kpi.class.inc.php index 6822c94e99..64aa10b289 100644 --- a/core/kpi.class.inc.php +++ b/core/kpi.class.inc.php @@ -116,6 +116,11 @@ class ExecutionKPI $iMemory = self::memory_get_usage(); $iMemoryUsed = $iMemory - $this->m_iInitialMemory; $this->Report($sOperationDesc.' / memory: '.self::MemStr($iMemoryUsed).' (Total: '.self::MemStr($iMemory).')'); + if (function_exists('memory_get_peak_usage')) + { + $iMemoryPeak = memory_get_peak_usage(); + $this->Report($sOperationDesc.' / memory peak: '.self::MemStr($iMemoryPeak)); + } } $this->ResetCounters(); From 5070dd2abebacce10baf79edc9567c68f3080049 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Sep 2010 05:44:18 +0000 Subject: [PATCH 717/970] #260 Restrict some pages to administrators SVN:trunk[800] --- application/loginwebpage.class.inc.php | 15 ++++++++++++--- dictionaries/dictionary.itop.ui.php | 1 + dictionaries/es_cr.dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 3 ++- pages/UniversalSearch.php | 2 +- pages/run_query.php | 3 +-- pages/schema.php | 3 +++ 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 5e2caf914b..23c48eb540 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -217,7 +217,7 @@ EOF header("Location: $sUrl"); exit; } - + $aAllowedLoginTypes = utils::GetConfig()->GetAllowedLoginTypes(); if (isset($_SESSION['auth_user'])) @@ -329,7 +329,7 @@ EOF } } - static function DoLogin() + static function DoLogin($bMustBeAdmin = false) { $operation = utils::ReadParam('loginop', ''); session_start(); @@ -378,11 +378,20 @@ EOF $oPage = new LoginWebPage(); $oPage->DisplayChangePwdForm(true); // old pwd was wrong $oPage->output(); - exit; } } self::Login(); + + if ($bMustBeAdmin && !UserRights::IsAdministrator()) + { + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:Login:Error:AccessAdmin')."

        \n"); + $oP->p("".Dict::S('UI:LogOffMenu').""); + $oP->output(); + exit; + } } } // End of class diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 3245baced5..a0b45eab52 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -443,6 +443,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', + 'UI:Login:Error:AccessAdmin' => 'Access restricted to people having administrator privileges. Please, contact an iTop administrator.', 'UI:CSVImport:MappingSelectOne' => '-- select one --', 'UI:CSVImport:MappingNotApplicable' => '-- ignore this field --', 'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index d6f3574f16..a3bc810b10 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -452,6 +452,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', + 'UI:Login:Error:AccessAdmin' => 'Access restricted to people having administrator privileges. Please, contact an iTop administrator.', 'UI:CSVImport:MappingSelectOne' => '-- select one --', 'UI:CSVImport:MappingNotApplicable' => '-- ignore this field --', 'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index b8092fa530..ebaae475e5 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -443,6 +443,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Login:RetypePwdDoesNotMatch' => 'Les deux saisies du nouveau mot de passe ne sont pas identiques !', 'UI:Button:Login' => 'Entrer dans iTop', 'UI:Login:Error:AccessRestricted' => 'L\'accès à iTop est soumis à autorisation. Merci de contacter votre administrateur iTop.', + 'UI:Login:Error:AccessAdmin' => 'Accès resreint aux utilisateurs possédant le profil Administrateur.', 'UI:CSVImport:MappingSelectOne' => '-- choisir une valeur --', 'UI:CSVImport:MappingNotApplicable' => '-- ignorer ce champ --', 'UI:CSVImport:NoData' => 'Aucune donnée... merci de fournir des données !', @@ -700,7 +701,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:AdminTools' => 'Outils d\'admin', 'Menu:AdminTools+' => 'Outils d\'administration', - 'Menu:AdminTools?' => 'Ces outils sont accessibles uniquement aux utilisateur possédant le profil Administrateur.', + 'Menu:AdminTools?' => 'Ces outils sont accessibles uniquement aux utilisateurs possédant le profil Administrateur.', 'UI:AuditMenu' => 'Audit', 'UI:AuditMenu+' => 'Audit', diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index f4ecf5f1d2..3c09a2bd01 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -30,7 +30,7 @@ require_once('../application/applicationcontext.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin) $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); diff --git a/pages/run_query.php b/pages/run_query.php index c1ed44f703..4bea654a3a 100644 --- a/pages/run_query.php +++ b/pages/run_query.php @@ -29,8 +29,7 @@ require_once('../application/itopwebpage.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - +LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin) function ShowExamples($oP, $sExpression) { diff --git a/pages/schema.php b/pages/schema.php index c1471a9284..c04bc0472b 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -28,6 +28,9 @@ require_once('../application/itopwebpage.class.inc.php'); require_once('../application/startup.inc.php'); +require_once('../application/loginwebpage.class.inc.php'); +LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin) + /** * Helper for this page -> link to a class From 5f497a55dea00763996d7b53b5d75990c1e10ca3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Sep 2010 13:47:22 +0000 Subject: [PATCH 718/970] New profile: Portal User (and link from a ticket to the user portal) SVN:trunk[801] --- .../userrights/userrightsmatrix.class.inc.php | 5 + .../userrights/userrightsnull.class.inc.php | 5 + .../userrightsprofile.class.inc.php | 121 ++++++++++++++---- .../userrightsprojection.class.inc.php | 6 + core/dbobject.class.php | 2 + core/metamodel.class.php | 12 ++ core/userrights.class.inc.php | 18 +++ 7 files changed, 147 insertions(+), 22 deletions(-) diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 9919204d12..f9b8d5c196 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -144,6 +144,11 @@ class UserRightsMatrix extends UserRightsAddOnAPI return ($oUser->GetKey() == 1); } + public function IsPortalUser($oUser) + { + return ($oUser->GetKey() == 1); + } + public function Setup() { // Users must be added manually diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index ad1b807b83..f31ce3b253 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -37,6 +37,11 @@ class UserRightsNull extends UserRightsAddOnAPI return true; } + public function IsPortalUser($oUser) + { + return true; + } + public function Setup() { return true; diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 05fb60f27d..05e729e045 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -24,7 +24,8 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -define('ADMIN_PROFILE_ID', 1); +define('ADMIN_PROFILE_NAME', 'Administrator'); +define('PORTAL_PROFILE_NAME', 'Portal user'); class UserRightsBaseClass extends cmdbAbstractObject { @@ -80,6 +81,68 @@ class URP_Profiles extends UserRightsBaseClass MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form } + protected $m_bCheckReservedNames = true; + protected function DisableCheckOnReservedNames() + { + $this->m_bCheckReservedNames = false; + } + + /* + * Create the built-in Administrator profile with its reserved name + */ + public static function DoCreateAdminProfile() + { + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', ADMIN_PROFILE_NAME); + $oNewObj->Set('description', 'Has the rights on everything (bypassing any control)'); + $oNewObj->DisableCheckOnReservedNames(); + $iNewId = $oNewObj->DBInsertNoReload(); + } + + /* + * Create the built-in User Portal profile with its reserved name + */ + public static function DoCreateUserPortalProfile() + { + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', PORTAL_PROFILE_NAME); + $oNewObj->Set('description', 'Has the rights to access to the user portal. People having this profile will not be allowed to access the standard application, they will be automatically redirected to the user portal.'); + $oNewObj->DisableCheckOnReservedNames(); + $iNewId = $oNewObj->DBInsertNoReload(); + } + + /* + * Overload the standard behavior to preserve reserved names + */ + public function DoCheckToWrite() + { + parent::DoCheckToWrite(); + + if ($this->m_bCheckReservedNames) + { + $aChanges = $this->ListChanges(); + if (array_key_exists('name', $aChanges)) + { + if ($this->GetOriginal('name') == ADMIN_PROFILE_NAME) + { + $this->m_aCheckIssues[] = "The name of the Administrator profile must not be changed"; + } + elseif ($this->Get('name') == ADMIN_PROFILE_NAME) + { + $this->m_aCheckIssues[] = ADMIN_PROFILE_NAME." is a reserved to the built-in Administrator profile"; + } + elseif ($this->GetOriginal('name') == PORTAL_PROFILE_NAME) + { + $this->m_aCheckIssues[] = "The name of the User Portal profile must not be changed"; + } + elseif ($this->Get('name') == PORTAL_PROFILE_NAME) + { + $this->m_aCheckIssues[] = PORTAL_PROFILE_NAME." is a reserved to the built-in User Portal profile"; + } + } + } + } + function GetGrantAsHtml($oUserRights, $sClass, $sAction) { $iGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); @@ -385,20 +448,24 @@ class UserRightsProfile extends UserRightsAddOnAPI //$oContact->Set('location_id', $iLocationId); //$oContact->Set('employee_number', ''); $iContactId = $oContact->DBInsertTrackedNoReload($oChange); - + $oUser = new UserLocal(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); $oUser->Set('contactid', $iContactId); $oUser->Set('language', $sLanguage); // Language was chosen during the installation $iUserId = $oUser->DBInsertTrackedNoReload($oChange); - + // Add this user to the very specific 'admin' profile - $oUserProfile = new URP_UserProfile(); - $oUserProfile->Set('userid', $iUserId); - $oUserProfile->Set('profileid', ADMIN_PROFILE_ID); - $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); - $oUserProfile->DBInsertTrackedNoReload($oChange); + $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/); + if (is_object($oAdminProfile)) + { + $oUserProfile = new URP_UserProfile(); + $oUserProfile->Set('userid', $iUserId); + $oUserProfile->Set('profileid', $oAdminProfile->GetKey()); + $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); + $oUserProfile->DBInsertTrackedNoReload($oChange); + } return true; } @@ -417,6 +484,7 @@ class UserRightsProfile extends UserRightsAddOnAPI protected $m_aAdmins; // id of users being linked to the well-known admin profile + protected $m_aPortalUsers; // id of users being linked to the well-known admin profile protected $m_aProfiles; // id -> object protected $m_aUserProfiles; // userid,profileid -> object @@ -437,6 +505,7 @@ class UserRightsProfile extends UserRightsAddOnAPI $this->m_aUserOrgs = null; $this->m_aAdmins = null; + $this->m_aPortalUsers = null; // Loaded on demand (time consuming as compared to the others) $this->m_aClassActionGrants = null; @@ -481,13 +550,18 @@ class UserRightsProfile extends UserRightsAddOnAPI $oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserProfile")); $this->m_aUserProfiles = array(); $this->m_aAdmins = array(); + $this->m_aPortalUsers = array(); while ($oUserProfile = $oUserProfileSet->Fetch()) { $this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; - if ($oUserProfile->Get('profileid') == ADMIN_PROFILE_ID) + if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME) { $this->m_aAdmins[] = $oUserProfile->Get('userid'); } + elseif ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME) + { + $this->m_aPortalUsers[] = $oUserProfile->Get('userid'); + } } $oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserOrg")); @@ -535,6 +609,20 @@ exit; } } + public function IsPortalUser($oUser) + { + $this->LoadCache(); + + if (in_array($oUser->GetKey(), $this->m_aPortalUsers)) + { + return true; + } + else + { + return false; + } + } + public function GetSelectFilter($oUser, $sClass) { $this->LoadCache(); @@ -780,18 +868,6 @@ class SetupProfiles return $iId; } - protected static function DoCreateAdminProfile() - { - $oNewObj = MetaModel::NewObject("URP_Profiles"); - $oNewObj->Set('name', 'Administrator'); - $oNewObj->Set('description', 'Has the rights on everything (bypassing any control)'); - $iNewId = $oNewObj->DBInsertNoReload(); - if ($iNewId != ADMIN_PROFILE_ID) - { - throw new CoreException('Admin profile could not be created with its standard id', array('requested'=>ADMIN_PROFILE_ID, 'obtained'=>$iNewId)); - } - } - protected static function DoCreateOneProfile($sName, $aProfileData) { $sDescription = $aProfileData['description']; @@ -873,7 +949,8 @@ class SetupProfiles public static function DoCreateProfiles() { - self::DoCreateAdminProfile(); + URP_Profiles::DoCreateAdminProfile(); + URP_Profiles::DoCreateUserPortalProfile(); foreach(self::$m_aProfiles as $sName => $aProfileData) { diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php index dc4aab1f2e..b690022657 100644 --- a/addons/userrights/userrightsprojection.class.inc.php +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -653,6 +653,12 @@ class UserRightsProjection extends UserRightsAddOnAPI } } + public function IsPortalUser($oUser) + { + return true; + // See implementation of userrightsprofile + } + public function Setup() { SetupProfiles::ComputeITILProfiles(); diff --git a/core/dbobject.class.php b/core/dbobject.class.php index ef84955d55..555a10a147 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1130,6 +1130,8 @@ abstract class DBObject $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); $aScalarArgs[$sArgName.'->object()'] = $this; $aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink(); + // #@# Prototype for a user portal - to be dehardcoded later + $aScalarArgs[$sArgName.'->hyperlink(portal)'] = '../portal/index.php?operation=details&id='.$this->GetKey(); $aScalarArgs[$sArgName.'->name()'] = $this->GetName(); $sClass = get_class($this); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 9c3e428fd7..ce66476d4c 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3468,6 +3468,18 @@ abstract class MetaModel return self::GetObjectByRow($sClass, $aRow); } + public static function GetObjectFromOQL($sQuery, $aParams = null, $bAllowAllData = false) + { + $oFilter = DBObjectSearch::FromOQL($sQuery, $aParams); + if ($bAllowAllData) + { + $oFilter->AllowAllData(); + } + $oSet = new DBObjectSet($oFilter); + $oObject = $oSet->Fetch(); + return $oObject; + } + public static function GetHyperLink($sTargetClass, $iKey) { if ($iKey < 0) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 7d432f0778..94e3ff2140 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -62,6 +62,7 @@ abstract class UserRightsAddOnAPI abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsAdministrator($oUser); + abstract public function IsPortalUser($oUser); abstract public function FlushPrivileges(); } @@ -620,6 +621,23 @@ class UserRights return self::$m_aAdmins[$iUser]; } + static $m_aPortalUsers = array(); + public static function IsPortalUser($oUser = null) + { + if (!self::CheckLogin()) return false; + + if (is_null($oUser)) + { + $oUser = self::$m_oUser; + } + $iUser = $oUser->GetKey(); + if (!isset(self::$m_aPortalUsers[$iUser])) + { + self::$m_aPortalUsers[$iUser] = self::$m_oAddOn->IsPortalUser($oUser); + } + return self::$m_aPortalUsers[$iUser]; + } + /** * Reset cached data * @param Bool Reset admin cache as well From 1d1c7b44c590832d8a56788c0943080233ba3e18 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Sep 2010 14:15:32 +0000 Subject: [PATCH 719/970] Fixed bug: losing links profiles/user when editing profiles SVN:trunk[802] --- addons/userrights/userrightsprofile.class.inc.php | 2 +- addons/userrights/userrightsprojection.class.inc.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 05e729e045..c469e22183 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -211,7 +211,7 @@ class URP_Profiles extends UserRightsBaseClass function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { - parent::DisplayBareRelations($oPage); + parent::DisplayBareRelations($oPage, $bEditMode); if (!$bEditMode) { $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php index b690022657..3705a201c5 100644 --- a/addons/userrights/userrightsprojection.class.inc.php +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -151,7 +151,7 @@ class URP_Profiles extends UserRightsBaseClass function DisplayBareRelations(WebPage $oPage, $bEditMode = false) { - parent::DisplayBareRelations($oPage); + parent::DisplayBareRelations($oPage, $bEditMode); if (!$bEditMode) { $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); From ad6669135c0151fd9ddd19bc58703c7800d3114b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Sep 2010 14:57:38 +0000 Subject: [PATCH 720/970] Fixed cosmetic issues is data model walker utility SVN:trunk[803] --- pages/schema.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pages/schema.php b/pages/schema.php index c04bc0472b..f454b91b61 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -260,7 +260,7 @@ function DisplayTriggers($oPage, $sClass) { $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObject WHERE target_class IN ('$sClassList')")); - cmdbAbstractObject::DisplaySet($oPage, $oSet); + cmdbAbstractObject::DisplaySet($oPage, $oSet, array('block_id' => 'triggers')); } @@ -479,9 +479,6 @@ function DisplayRelationDetails($oPage, $sRelCode) } -require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - // Display the menu on the left $oAppContext = new ApplicationContext(); $iActiveNodeId = utils::ReadParam('menu', -1); From e9a84e1e846f30e7aa8f83ee469346e66ebb39bc Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Sep 2010 15:14:05 +0000 Subject: [PATCH 721/970] Fixed cosmetic issues (block_id) and made sure the warning would never appear again SVN:trunk[804] --- application/cmdbabstract.class.inc.php | 31 ++++++------------- .../model.itop-config-mgmt.php | 8 ++--- .../model.itop-service-mgmt.php | 30 +++++++++--------- webservices/export.php | 2 +- 4 files changed, 28 insertions(+), 43 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index dbfde42e82..030756e63d 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -69,8 +69,7 @@ abstract class cmdbAbstractObject extends CMDBObject $oAppContext = new ApplicationContext(); $sExtClassNameAtt = MetaModel::GetNameAttributeCode($sObjClass); $sPage = self::ComputeUIPage($sObjClass); - $sAbsoluteUrl = utils::GetAbsoluteUrl(false); // False => Don't get the query string - $sAbsoluteUrl = substr($sAbsoluteUrl, 0, 1+strrpos($sAbsoluteUrl, '/')); // remove the current page, keep just the path, up to the last / + $sAbsoluteUrl = utils::GetAbsoluteUrlPath(); // Use the "name" of the target class as the label of the hyperlink // unless it's not available in the external attributes... @@ -509,7 +508,14 @@ abstract class cmdbAbstractObject extends CMDBObject } $sHtml .= ''; $sColspan = ''; - $divId = $aExtraParams['block_id']; + if (isset($aExtraParams['block_id'])) + { + $divId = $aExtraParams['block_id']; + } + else + { + $divId = 'missingblockid'; + } $sFilter = $oSet->GetFilter()->serialize(); $iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit(); $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count()); @@ -812,25 +818,6 @@ EOF $oPage->add("\n"); } - // By rom - function DisplayChangesLog(WebPage $oPage) - { - $oFltChangeOps = new CMDBSearchFilter('CMDBChangeOpSetAttribute'); - $oFltChangeOps->AddCondition('objkey', $this->GetKey(), '='); - $oFltChangeOps->AddCondition('objclass', get_class($this), '='); - $oSet = new CMDBObjectSet($oFltChangeOps, array('date' => false)); // order by date descending (i.e. false) - $count = $oSet->Count(); - if ($count > 0) - { - $oPage->p(Dict::Format('UI:ChangesLogTitle', $count)); - self::DisplaySet($oPage, $oSet); - } - else - { - $oPage->p(Dict::S('UI:EmptyChangesLogTitle')); - } - } - public static function DisplaySearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 07c4eeded0..e24206de24 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -472,7 +472,7 @@ class Subnet extends cmdbAbstractObject $oPage->p(Dict::Format('Class:Subnet/Tab:IPUsage-explain', $sIPMin, $sIPMax)); $oIfSet = new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT NetworkInterface AS if WHERE INET_ATON(if.ip_address) >= INET_ATON('$sIPMin') AND INET_ATON(if.ip_address) <= INET_ATON('$sIPMax')")); - self::DisplaySet($oPage, $oIfSet); + self::DisplaySet($oPage, $oIfSet, array('block_id' => 'nwif')); $iCountUsed = $oIfSet->Count(); $iCountRange = $iIPMax - $iIPMin; @@ -1050,9 +1050,9 @@ class NetworkInterface extends ConnectableCI { return $this->Get('device_name').' - '.$this->Get('name'); } - - - + + + public static function GetRelationQueries($sRelCode) { switch ($sRelCode) diff --git a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php index 14bd129c22..73d15b0947 100644 --- a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php @@ -327,22 +327,20 @@ class Service extends cmdbAbstractObject MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'org_id', 'type', 'status')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'org_id', 'type', 'status')); MetaModel::Init_SetZListItems('list', array('name', 'description', 'org_id', 'type', 'status')); - - } - function DisplayBareRelations(WebPage $oPage, $bEditMode = false) - { - parent::DisplayBareRelations($oPage, $bEditMode); - $aExtraParam = array ('menu' => false); - $ServiceID=$this->GetKey(); - if (!$bEditMode) - { - $oPage->SetCurrentTab(Dict::S('Class:Service/Tab:Related_Contracts')); - $oCustomerContracts=new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT CustomerContract AS cc JOIN lnkContractToSLA AS ln ON ln.contract_id=cc.id JOIN SLA AS sla ON ln.sla_id=sla.id WHERE sla.service_id=$ServiceID")); - self::DisplaySet($oPage,$oCustomerContracts,$aExtraParam); - - - } - + + } + + function DisplayBareRelations(WebPage $oPage, $bEditMode = false) + { + parent::DisplayBareRelations($oPage, $bEditMode); + $aExtraParam = array ('menu' => false, 'block_id' => 'service'); + $ServiceID=$this->GetKey(); + if (!$bEditMode) + { + $oPage->SetCurrentTab(Dict::S('Class:Service/Tab:Related_Contracts')); + $oCustomerContracts=new CMDBObjectSet(DBObjectSearch::FromOQL("SELECT CustomerContract AS cc JOIN lnkContractToSLA AS ln ON ln.contract_id=cc.id JOIN SLA AS sla ON ln.sla_id=sla.id WHERE sla.service_id=$ServiceID")); + self::DisplaySet($oPage,$oCustomerContracts,$aExtraParam); + } } } class ServiceSubcategory extends cmdbAbstractObject diff --git a/webservices/export.php b/webservices/export.php index e6743ba61c..3a63fa9b32 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -70,7 +70,7 @@ if (!empty($sExpression)) } $sUrl = "$sProtocol://{$sServerName}{$sPort}/pages/"; $oP->set_base($sUrl); - cmdbAbstractObject::DisplaySet($oP, $oSet, array('menu' => false, 'display_limit' => false, 'zlist' => 'details')); // no menu, no truncated list, "details" zlist + cmdbAbstractObject::DisplaySet($oP, $oSet, array('block_id' => 'expresult', 'menu' => false, 'display_limit' => false, 'zlist' => 'details')); // no menu, no truncated list, "details" zlist break; case 'csv': From 7312d977f3b812cf4ea94e2ad441f5ce35d1a5f3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Sep 2010 15:26:07 +0000 Subject: [PATCH 722/970] Notifications template may specify this->hyperlink(portal) (and fixed issues in sample data) SVN:trunk[805] --- application/utils.inc.php | 13 +++++++++++++ core/dbobject.class.php | 5 +++-- .../itop-tickets-1.0.0/data.struct.ta-actions.xml | 7 +++---- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 68c7d40b6a..26922f4a44 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -189,6 +189,7 @@ class utils /** * Returns an absolute URL to the current page * @param $bQueryString bool True to also get the query string, false otherwise + * @param $bForceHTTPS bool True to force HTTPS, false otherwise * @return string The absolute URL to the current page */ static public function GetAbsoluteUrl($bQueryString = true, $bForceHTTPS = false) @@ -248,6 +249,18 @@ class utils return $sUrl; } + /** + * Returns the absolute URL PATH of the current page + * @param $bForceHTTPS bool True to force HTTPS, false otherwise + * @return string The absolute URL to the current page + */ + static public function GetAbsoluteUrlPath($bForceHTTPS = false) + { + $sAbsoluteUrl = self::GetAbsoluteUrl(false, $bForceHTTPS); // False => Don't get the query string + $sAbsoluteUrl = substr($sAbsoluteUrl, 0, 1+strrpos($sAbsoluteUrl, '/')); // remove the current page, keep just the path, up to the last / + return $sAbsoluteUrl; + } + /** * Tells whether or not log off operation is supported. * Actually in only one case: diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 555a10a147..c999ceb62e 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -1130,8 +1130,9 @@ abstract class DBObject $aScalarArgs[$sArgName.'->id'] = $this->GetKey(); $aScalarArgs[$sArgName.'->object()'] = $this; $aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink(); - // #@# Prototype for a user portal - to be dehardcoded later - $aScalarArgs[$sArgName.'->hyperlink(portal)'] = '../portal/index.php?operation=details&id='.$this->GetKey(); + // #@# Prototype for a user portal - to be dehardcoded later + $sToPortal = utils::GetAbsoluteUrlPath().'portal/index.php?operation=details&id='.$this->GetKey(); + $aScalarArgs[$sArgName.'->hyperlink(portal)'] = ''.$this->GetName().''; $aScalarArgs[$sArgName.'->name()'] = $this->GetName(); $sClass = get_class($this); diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml index 64e3c84ee5..388f80e511 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml @@ -54,14 +54,13 @@ SELECT Person WHERE id=:this->caller_id -Ticket $this->name()$, priority $this->label(priority)$ - $this->ticket_status$ +Ticket $this->name()$, priority $this->label(priority)$ - $this->status$ <html> <body> -<p>The incident ticket $this->name()$ has changed to status $this->ticket_status$</p> -<p>Current situation: $this->current_situation$</p> +<p>The incident ticket $this->name()$ has changed to status $this->status$</p> <p>Last update: $this->last_update$</p> <hr/> -<p>for more information on this ticket, click here: $this->hyperlink()$</p> +<p>for more information on this ticket, click here: $this->hyperlink(portal)$</p> </body> </html> normal From 463e7b54139035eaa614a1bf27c21d8d23c3d3f9 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 10 Sep 2010 10:10:57 +0000 Subject: [PATCH 723/970] - Enhancement (Trac#189) first version of a (simple) End-Users portal. - Fix for the appUserPreferences class SVN:trunk[806] --- application/loginwebpage.class.inc.php | 14 +++++++++- application/user.preferences.class.inc.php | 4 +-- core/userrights.class.inc.php | 2 ++ dictionaries/dictionary.itop.ui.php | 29 +++++++++++++++++++-- dictionaries/fr.dictionary.itop.ui.php | 24 +++++++++++++++++ images/back.png | Bin 0 -> 3704 bytes images/logoff.png | Bin 0 -> 2067 bytes images/password.png | Bin 0 -> 3286 bytes images/refresh.png | Bin 0 -> 3381 bytes pages/ajax.render.php | 2 +- pages/logoff.php | 11 +++++++- 11 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 images/back.png create mode 100644 images/logoff.png create mode 100644 images/password.png create mode 100644 images/refresh.png diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 23c48eb540..e7119189f7 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -329,7 +329,14 @@ EOF } } - static function DoLogin($bMustBeAdmin = false) + /** + * Check if the user is already authentified, if yes, then performs some additional validations: + * - if $bMustBeAdmin is true, then the user must be an administrator, otherwise an error is displayed + * - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected to the portal + * @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page + * @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal + */ + static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false) { $operation = utils::ReadParam('loginop', ''); session_start(); @@ -392,6 +399,11 @@ EOF $oP->output(); exit; } + elseif ( (!$bIsAllowedToPortalUsers) && (UserRights::IsPortalUser())) + { + // No rights to be here, redirect to the portal + header('Location: ../portal/index.php'); + } } } // End of class diff --git a/application/user.preferences.class.inc.php b/application/user.preferences.class.inc.php index 14a7ed1e48..873c1055d2 100644 --- a/application/user.preferences.class.inc.php +++ b/application/user.preferences.class.inc.php @@ -125,14 +125,14 @@ class appUserPreferences extends DBObject { if (self::$oUserPrefs != null) return; $oSearch = new DBObjectSearch('appUserPreferences'); - $oSearch->AddCondition('userid', UserRights::GetUser(), '='); + $oSearch->AddCondition('userid', UserRights::GetUserId(), '='); $oSet = new DBObjectSet($oSearch); $oObj = $oSet->Fetch(); if ($oObj == null) { // No prefs (yet) for this user, create the object $oObj = new appUserPreferences(); - $oObj->Set('userid', UserRights::GetUser()); + $oObj->Set('userid', UserRights::GetUserId()); $oObj->Set('preferences', array()); // Default preferences: an empty array $oObj->DBInsert(); } diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 94e3ff2140..0f63306c46 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -529,6 +529,8 @@ class UserRights if (!self::CheckLogin()) return true; if (self::IsAdministrator()) return true; + // Portal users actions are limited by the portal page... + if (self::IsPortalUser()) return true; // this module is forbidden for non admins.... BUT I NEED IT HERE TO DETERMINE USER RIGHTS if (MetaModel::HasCategory($sClass, 'addon/userrights')) return true; diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index a0b45eab52..6e536929cc 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -39,7 +39,7 @@ // Dict::Add('EN US', 'English', 'English', array( - 'Class:AuditCategory' => 'AuditCategory', + 'Class:AuditCategory' => 'Audit Category', 'Class:AuditCategory+' => 'A section inside the overall audit', 'Class:AuditCategory/Attribute:name' => 'Category Name', 'Class:AuditCategory/Attribute:name+' => 'Short name for this category', @@ -47,6 +47,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:AuditCategory/Attribute:description+' => 'Long description for this audit category', 'Class:AuditCategory/Attribute:definition_set' => 'Definition Set', 'Class:AuditCategory/Attribute:definition_set+' => 'OQL expression defining the set of objects to audit', + 'Class:AuditCategory/Attribute:rules_list' => 'Audit Rules', + 'Class:AuditCategory/Attribute:rules_list+' => 'Audit rules for this category', )); // @@ -54,7 +56,7 @@ Dict::Add('EN US', 'English', 'English', array( // Dict::Add('EN US', 'English', 'English', array( - 'Class:AuditRule' => 'AuditRule', + 'Class:AuditRule' => 'Audit Rule', 'Class:AuditRule+' => 'A rule to check for a given Audit category', 'Class:AuditRule/Attribute:name' => 'Rule Name', 'Class:AuditRule/Attribute:name+' => 'Short name for this rule', @@ -788,6 +790,10 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:NotificationsMenu:Actions' => 'Actions', 'UI:NotificationsMenu:AvailableActions' => 'Available actions', + 'Menu:AuditCategories' => 'Audit Categories', + 'Menu:AuditCategories+' => 'Audit Categories', + 'Menu:Notifications:Title' => 'Audit Categories', + 'Menu:RunQueriesMenu' => 'Run Queries', 'Menu:RunQueriesMenu+' => 'Run any query', @@ -829,6 +835,25 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:PasswordConfirm' => '(Confirm)', 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Before adding more %1$s objects, save this object.', 'UI:DisplayThisMessageAtStartup' => 'Display this message at startup', + + 'Portal:Title' => 'iTop user portal', + 'Portal:Refresh' => 'Refresh', + 'Portal:Back' => 'Back', + 'Portal:CreateNewRequest' => 'Create a new request', + 'Portal:ChangeMyPassword' => 'Change my password', + 'Portal:Disconnect' => 'Disconnect', + 'Portal:OpenRequests' => 'My open requests', + 'Portal:ResolvedRequests' => 'My resolved requests', + 'Portal:SelectService' => 'Select a service from the catalog:', + 'Portal:PleaseSelectOneService' => 'Please select one service', + 'Portal:SelectSubcategoryFrom_Service' => 'Select a sub-category for the service %1$s:', + 'Portal:PleaseSelectAServiceSubCategory' => 'Please select one sub-category', + 'Portal:DescriptionOfTheRequest' => 'Enter the description of your request:', + 'Portal:TitleRequestDetailsFor_Request' => 'Details for request %1$s:', + 'Portal:NoOpenRequest' => 'No request in this category.', + 'Portal:Button:CloseTicket' => 'Close this ticket', + 'Portal:EnterYourCommentsOnTicket' => 'Enter your comments about the resolution of this ticket:', + 'Portal:ErrorNoContactForThisUser' => 'Error: the current user is not associated with a Contact/Person. Please contact your administrator.', )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index ebaae475e5..896fbf2ee6 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -47,6 +47,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:AuditCategory/Attribute:description+' => 'Description', 'Class:AuditCategory/Attribute:definition_set' => 'Ensemble de définition', 'Class:AuditCategory/Attribute:definition_set+' => 'Expression OQL qui défini le périmètre d\'application de l\'audit', + 'Class:AuditCategory/Attribute:rules_list' => 'Règles d\'audit', + 'Class:AuditCategory/Attribute:rules_list+' => 'Règles d\'audit pour cette catégorie', )); // @@ -802,6 +804,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:NotificationsMenu:Actions' => 'Actions', 'UI:NotificationsMenu:AvailableActions' => 'Actions existantes', + 'Menu:AuditCategories' => 'Catégories d\'audit', + 'Menu:AuditCategories+' => 'Catégories d\'audit', + 'Menu:Notifications:Title' => 'Catégories d\'audit', 'Menu:RunQueriesMenu' => 'Requêtes OQL', 'Menu:RunQueriesMenu+' => 'Executer une requête OQL', @@ -840,6 +845,25 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:PasswordConfirm' => '(Confirmer)', 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Enregistrez l\'objet courant avant de créer de nouveaux éléments de type %1$s.', 'UI:DisplayThisMessageAtStartup' => 'Afficher ce message au démarrage', + + 'Portal:Title' => 'Portail utilisateur iTop', + 'Portal:Refresh' => 'Rafraîchir', + 'Portal:Back' => 'Retour', + 'Portal:CreateNewRequest' => 'Créer une nouvelle requête', + 'Portal:ChangeMyPassword' => 'Changer mon mot de passe', + 'Portal:Disconnect' => 'Déconnexion', + 'Portal:OpenRequests' => 'Mes requêtes en cours', + 'Portal:ResolvedRequests' => 'Mes requêtes résolues', + 'Portal:SelectService' => 'Choisissez un service dans le catalogue:', + 'Portal:PleaseSelectOneService' => 'Veuillez choisir un service', + 'Portal:SelectSubcategoryFrom_Service' => 'Choisissez une sous-catégorie du service %1$s:', + 'Portal:PleaseSelectAServiceSubCategory' => 'Veuillez choisir une sous-catégorie', + 'Portal:DescriptionOfTheRequest' => 'Entrez la description de votre requête:', + 'Portal:TitleRequestDetailsFor_Request' => 'Détails de votre requête %1$s:', + 'Portal:NoOpenRequest' => 'Aucune requête.', + 'Portal:Button:CloseTicket' => 'Clôre cette requête', + 'Portal:EnterYourCommentsOnTicket' => 'Vos commentaires à propos du traitement de cette requête:', + 'Portal:ErrorNoContactForThisUser' => 'Erreur: l\'utilisateur courant n\'est pas associé à une Personne/Contact. Contactez votre administrateur.', )); ?> diff --git a/images/back.png b/images/back.png new file mode 100644 index 0000000000000000000000000000000000000000..f0e128b03797be64bebc1698b2c749431c192782 GIT binary patch literal 3704 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 W1xmqkBdS3F0000>6 literal 0 HcmV?d00001 diff --git a/images/logoff.png b/images/logoff.png new file mode 100644 index 0000000000000000000000000000000000000000..7e1bfb68c7cb194e0fbd88d31454cf471e0f7835 GIT binary patch literal 2067 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 LNkvXXu0mjfjdEpB literal 0 HcmV?d00001 diff --git a/pages/ajax.render.php b/pages/ajax.render.php index cbd1796daf..eb1b4aaefe 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -33,7 +33,7 @@ require_once('../application/startup.inc.php'); require_once('../application/user.preferences.class.inc.php'); require_once('../application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(); // Check user rights and prompt if needed +LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed $oPage = new ajax_page(""); $oPage->no_cache(); diff --git a/pages/logoff.php b/pages/logoff.php index 5f8c2ccd7b..0ceef4da66 100644 --- a/pages/logoff.php +++ b/pages/logoff.php @@ -31,7 +31,16 @@ $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); $oPage->add("
        \n"); $oPage->add("
        \n"); $oPage->add("

        ".Dict::S('UI:LogOff:ThankYou')."

        \n"); -$oPage->add("

        ".Dict::S('UI:LogOff:ClickHereToLoginAgain')."

        "); +$bPortal = utils::ReadParam('portal', false); +if ($bPortal) +{ + $sUrl = '../portal/'; +} +else +{ + $sUrl = '../pages/UI.php'; +} +$oPage->add("

        ".Dict::S('UI:LogOff:ClickHereToLoginAgain')."

        "); $oPage->add("
        \n"); $oPage->output(); ?> From 815de3bc2a3347b0393ba72dd45c91063e65038c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 10 Sep 2010 11:20:23 +0000 Subject: [PATCH 724/970] - Enhancement (Trac#189) first version of a (simple) End-Users portal. - Fix for the appUserPreferences class SVN:trunk[807] --- portal/index.php | 837 ++++++++++++++++++++++++++++++++++++++++++++++ portal/portal.css | 114 +++++++ 2 files changed, 951 insertions(+) create mode 100644 portal/index.php create mode 100644 portal/portal.css diff --git a/portal/index.php b/portal/index.php new file mode 100644 index 0000000000..1070534b56 --- /dev/null +++ b/portal/index.php @@ -0,0 +1,837 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ +require_once('../application/application.inc.php'); +require_once('../application/nicewebpage.class.inc.php'); +require_once('../application/wizardhelper.class.inc.php'); + +/** + * Get the list of parameters (i.e. attribute codes) to be handled while creating a new UserRequest object + * @return Array The list of attribute codes + */ +function GetParamsList() +{ + return array('org_id', 'caller_id', 'service_id', 'servicesubcategory_id', 'request_type', 'title', 'description', 'impact', 'urgency', 'workgroup_id'); +} + +/** + * Outputs a list of parameters as hidden field into the current page + * (must be called when inside a form) + * @param WebPage $oP The current web page + * @param Array $aInteractive The list of parameters that are handled intractively and thus should not be output as hidden fields + * @param Hash $aParameters Array name => value for the parameters + * @return void + */ +function DumpHiddenParams($oP, $aInteractive, $aParameters) +{ + foreach($aParameters as $sAttCode => $value) + { + if (!in_array($sAttCode, $aInteractive)) + { + $oP->Add(""); + } + } +} + +/** + * Read all the parameters of the page for building a UserRequest + * Parameters that were absent from the page's parameters are not set in the resulting hash array + * @input string $sMethod Either get or post + * @return Hash Array of name => value corresponding to the parameters that were passed to the page + */ +function ReadAllParams($sMethod = 'get') +{ + $aParams = GetParamsList(); + $aValues = array(); + foreach($aParams as $sName) + { + $value = utils::ReadParam('attr_'.$sName, null, $sMethod); + if (!is_null($value)) + { + $aValues[$sName] = $value; + } + } + return $aValues; +} + +/** + * Displays the portal main menu + * @param WebPage $oP The current web page + * @return void + */ +function DisplayMainMenu(WebPage $oP) +{ + $oP->AddMenuButton('refresh', 'Portal:Refresh', './index.php?operation=welcome'); + $oP->AddMenuButton('create', 'Portal:CreateNewRequest', './index.php?operation=create_request'); + $oP->AddMenuButton('change_pwd', 'Portal:ChangeMyPassword', './index.php?loginop=change_pwd'); + + $oP->add("
        \n"); + $oP->add("

        ".Dict::S('Portal:OpenRequests')."

        \n"); + ListOpenRequests($oP); + $oP->add("
        \n"); + $oP->add("
        \n"); + $oP->add("

        ".Dict::S('Portal:ResolvedRequests')."

        \n"); + ListResolvedRequests($oP); + $oP->add("
        \n"); +} + +/** + * Displays the form to select a Service Id (among the valid ones for the specified user Organization) + * @param WebPage $oP Web page for the form output + * @param Organization $oUserOrg The organization of the current user + * @return void + */ +function SelectService($oP, $oUserOrg) +{ + // Init forms parameters + $aParameters = ReadAllParams(); + + $oSearch = DBObjectSearch::FromOQL('SELECT Service AS s JOIN SLA AS sla ON sla.service_id=s.id JOIN lnkContractToSLA AS ln ON ln.sla_id=sla.id JOIN CustomerContract AS cc ON ln.contract_id=cc.id WHERE cc.org_id = :org_id'); + $oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey())); + $oP->add("
        \n"); + $oP->add("

        ".Dict::S('Portal:SelectService')."

        \n"); + $oP->add("
        \n"); + $oP->add("
        \n"); + while($oService = $oSet->Fetch()) + { + $id = $oService->GetKey(); + $sChecked = ""; + if ($id == $aParameters['service_id']) + { + $sChecked = "checked"; + } + $oP->p(""); DumpHiddenParams($oP, array('service_id'), $aParameters); + + } + $oP->add("

        "); + $oP->p("

        ".htmlentities($oService->Get('description'))."

        \n"); + DumpHiddenParams($oP, array('service_id'), $aParameters); + $oP->add(""); + $oP->add(""); + $oP->p(""); + $oP->add(""); + $oP->add("\n"); + $sMessage = Dict::S('Portal:PleaseSelectOneService'); + $oP->add_ready_script( +<< $iSvcId)); + $oService = MetaModel::GetObject('Service', $iSvcId, false); + if (is_object($oService)) + { + $oP->add("
        \n"); + $oP->add("

        ".Dict::Format('Portal:SelectSubcategoryFrom_Service', htmlentities($oService->GetName()))."

        \n"); + $oP->add("
        \n"); + $oP->add("\n"); + while($oSubService = $oSet->Fetch()) + { + $id = $oSubService->GetKey(); + $sChecked = ""; + if ($id == $iDefaultSubSvcId) + { + $sChecked = "checked"; + } + $oP->p(""); + } + $sMessage = Dict::S('Portal:PleaseSelectAServiceSubCategory'); + $oP->add_ready_script( +<<add("

        "); + $oP->p("

        ".htmlentities($oSubService->Get('description'))."

        \n"); + DumpHiddenParams($oP, array('servicesubcategory_id'), $aParameters); + $oP->add(""); + $oP->add(""); + $oP->p(" "); + $oP->add("
        "); + $oP->add("
        \n"); + } + else + { + $oP->p("Error: Invalid Service: id = $iSvcId"); + } +} +/** + * Displays the form for the final step of the UserRequest creation + * @param WebPage $oP The current web page for the form output + * @param Organization $oUserOrg The organization of the current user + * @return void + */ +function RequestCreationForm($oP, $oUserOrg) +{ + $aList = array('request_type', 'title', 'description', 'impact', 'urgency', 'workgroup_id'); + $aParameters = ReadAllParams(); + + $oService = MetaModel::GetObject('Service', $aParameters['service_id'], false); + $oSubService = MetaModel::GetObject('ServiceSubcategory', $aParameters['servicesubcategory_id'], false); + if (is_object($oService) && is_object($oSubService)) + { + $oRequest = new UserRequest(); + $oRequest->Set('org_id', $oUserOrg->GetKey()); + $oRequest->Set('caller_id', UserRights::GetContactId()); + $oRequest->Set('service_id', $aParameters['service_id']); + $oRequest->Set('servicesubcategory_id', $aParameters['servicesubcategory_id']); + + $oAttDef = MetaModel::GetAttributeDef('UserRequest', 'service_id'); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => htmlentities($oService->GetName())); + $oAttDef = MetaModel::GetAttributeDef('UserRequest', 'servicesubcategory_id'); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => htmlentities($oSubService->GetName())); + $iFlags = 0; + foreach($aList as $sAttCode) + { + $value = ''; + if (isset($aParameters[$sAttCode])) + { + $value = $aParameters[$sAttCode]; + $oRequest->Set($sAttCode, $value); + } + } + foreach($aList as $sAttCode) + { + $value = ''; + $oAttDef = MetaModel::GetAttributeDef(get_class($oRequest), $sAttCode); + $iFlags = $oRequest->GetAttributeFlags($sAttCode); + if (isset($aParameters[$sAttCode])) + { + $value = $aParameters[$sAttCode]; + } + $aArgs = array('this' => $oRequest); + + $sValue = $oRequest->GetFormElementForField($oP, get_class($oRequest), $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue); + } + $oP->add("
        \n"); + $oP->add("

        ".Dict::S('Portal:DescriptionOfTheRequest')."

        \n"); + $oP->add("
        \n"); + $oP->add("\n"); + $oP->details($aDetails); + DumpHiddenParams($oP, $aList, $aParameters); + $oP->add(""); + $oP->add(""); + $oP->add("\n"); + $oP->p(" "); + $oP->add(""); + $oP->add("\n"); + $oP->add_ready_script( +<<\n"; + print_r($aParameters); + echo "\n"; + } +} + +/** + * Validate the parameters and create the UserRequest object (based on the page's POSTed parameters) + * @param WebPage $oP The current web page for the output + * @param Organization $oUserOrg The organization of the current user + * @return void + */ +function DoCreateRequest($oP, $oUserOrg) +{ + $aParameters = ReadAllParams(); + $sTransactionId = utils::ReadPostedParam('transaction_id', ''); + if (!utils::IsTransactionValid($sTransactionId)) + { + $oP->add("

        ".Dict::S('UI:Error:ObjectAlreadyCreated')."

        \n"); + DisplayMainMenu($oP); + return; + } + + // Validate the parameters + // 1) Service + $oSearch = DBObjectSearch::FromOQL('SELECT Service AS s JOIN SLA AS sla ON sla.service_id=s.id JOIN lnkContractToSLA AS ln ON ln.sla_id=sla.id JOIN CustomerContract AS cc ON ln.contract_id=cc.id WHERE cc.org_id = :org_id AND s.id = :svc_id'); + $oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey(), 'svc_id' => $aParameters['service_id'])); + if ($oSet->Count() != 1) + { + // Invalid service for the current user ! + throw new Exception("Invalid Service: id={$aParameters['servicesubcategory_id']} for the current user (org_id=".$oUserOrg->GetKey().")."); + } + $oService = $oSet->Fetch(); + // 2) Service Subcategory + $oSearch = DBObjectSearch::FromOQL('SELECT ServiceSubcategory AS sc WHERE sc.id = :subcategory_id AND sc.service_id = :svc_id'); + $oSet = new CMDBObjectSet($oSearch, array(), array('svc_id' => $aParameters['service_id'], 'subcategory_id' =>$aParameters['servicesubcategory_id'] )); + if ($oSet->Count() != 1) + { + // Invalid subcategory + throw new Exception("Invalid ServiceSubcategory: id={$aParameters['servicesubcategory_id']} for service ".$oService->GetName()."({$aParameters['service_id']})"); + } + + $oRequest = new UserRequest(); + $oRequest->Set('org_id', $oUserOrg->GetKey()); + $oRequest->Set('caller_id', UserRights::GetContactId()); + $aList = array('service_id', 'servicesubcategory_id', 'request_type', 'title', 'description', 'impact', 'urgency', 'workgroup_id'); + foreach($aList as $sAttCode) + { + $oRequest->Set($sAttCode, $aParameters[$sAttCode]); + } + + list($bRes, $aIssues) = $oRequest->CheckToWrite(); + if ($bRes) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::IsImpersonated()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oRequest->DBInsertTracked($oMyChange); + $oP->add("

        ".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName(get_class($oRequest)))."

        \n"); + DisplayMainMenu($oP); + } + else + { + RequestCreationForm($oP, $oUserOrg); + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); + } +} + +/** + * Prompts the user for creating a new request + * @param WebPage $oP The current web page + * @return void + */ +function CreateRequest(WebPage $oP, Organization $oUserOrg) +{ + $iStep = utils::ReadParam('step', 0); + + switch($iStep) + { + case 0: + default: + $oP->AddMenuButton('cancel', 'UI:Button:Cancel', './index.php?operation=welcome'); + SelectService($oP, $oUserOrg); + break; + + case 1: + $oP->AddMenuButton('cancel', 'UI:Button:Cancel', './index.php?operation=welcome'); + SelectSubService($oP, $oUserOrg); + break; + + case 2: + $oP->AddMenuButton('cancel', 'UI:Button:Cancel', './index.php?operation=welcome'); + RequestCreationForm($oP, $oUserOrg); + break; + + case 3: + DoCreateRequest($oP, $oUserOrg); + break; + } +} + +/** + * Displays the value of the given field, in HTML, without any hyperlink to other objects + * @param DBObject $oObj The object to use + * @param string $sAttCode Code of the attribute to display + * @return string HTML text representing the value of this field + */ +function GetFieldAsHtml($oObj, $sAttCode) +{ + $sValue = ''; + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + if ($oAttDef->IsExternalKey()) + { + // Special processing for external keys: don't display any hyperlink + $oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $oObj->Get($sAttCode), false); + if (is_object($oTargetObj)) + { + $sValue = $oTargetObj->GetName(); + } + else + { + $sValue = Dict::S('UI:UndefinedObject'); + } + } + else + { + $sValue = $oObj->GetAsHTML($sAttCode); + } + return $sValue; +} + +/** + * Displays a list of objects, without any hyperlink (except for the object's details) + * @param WebPage $oP The web page for the output + * @param DBObjectSet $oSet The set of objects to display + * @param Array $aZList The ZList (list of field codes) to use for the tabular display + * @return string The HTML text representing the list + */ + function DisplaySet($oP, $oSet, $aZList) + { + if ($oSet->Count() > 0) + { + $aAttribs = array(); + $aValues = array(); + $oAttDef = MetaModel::GetAttributeDef('UserRequest', 'ref'); + $aAttribs['key'] = array('label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); + foreach($aZList as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef('UserRequest', $sAttCode); + $aAttribs[$sAttCode] = array('label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); + } + while($oRequest = $oSet->Fetch()) + { + $aRow = array(); + + $aRow['key'] = ''.$oRequest->Get('ref').''; + $sHilightClass = $oRequest->GetHilightClass(); + if ($sHilightClass != '') + { + $aRow['@class'] = $sHilightClass; + } + foreach($aZList as $sAttCode) + { + $aRow[$sAttCode] = GetFieldAsHtml($oRequest, $sAttCode); + } + $aValues[$oRequest->GetKey()] = $aRow; + } + $oP->Table($aAttribs, $aValues); + } + else + { + $oP->add(Dict::S('Portal:NoOpenRequest')); + } +} + +/** + * Lists all the currently opened User Requests for the current user + * @param WebPage $oP The current web page + * @return void + */ +function ListOpenRequests(WebPage $oP) +{ + $iContactId = UserRights::GetContactId(); + $oContact = MetaModel::GetObject('Contact', $iContactId, false); // false => Can fail + if (is_object($oContact)) + { + $sOQL = 'SELECT UserRequest WHERE caller_id = :contact_id AND status NOT IN ("resolved", "closed")'; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new CMDBObjectSet($oSearch, array(), array('contact_id' => $iContactId)); + $aZList = array('title', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id'); + DisplaySet($oP, $oSet, $aZList); + } +} + +/** + * Lists all the currently Resolved (not "Closed")User Requests for the current user + * @param WebPage $oP The current web page + * @return void + */ +function ListResolvedRequests(WebPage $oP) +{ + $iContactId = UserRights::GetContactId(); + $oContact = MetaModel::GetObject('Contact', $iContactId, false); // false => Can fail + if (is_object($oContact)) + { + $sOQL = 'SELECT UserRequest WHERE caller_id = :contact_id AND status ="resolved"'; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new CMDBObjectSet($oSearch, array(), array('contact_id' => $iContactId)); + $aZList = array('title', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id'); + DisplaySet($oP, $oSet, $aZList); + } +} +/** + * Displays the details of the specified UserRequest object + * @param WebPage $oP The current web page for the output + * @param UserRequest $oRequest The object to display + * @return void + */ +function DisplayRequestDetails($oP, UserRequest $oRequest) +{ + $aList = array('ref', 'status', 'title', 'description', 'request_type','ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment', 'freeze_reason'); + $aDetails = array(); + foreach($aList as $sAttCode) + { + $iFlags = $oRequest->GetAttributeFlags($sAttCode); + $oAttDef = MetaModel::GetAttributeDef(get_class($oRequest), $sAttCode); + if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) + { + // Don't display linked set and non-visible attributes (in this state) + $sDisplayValue = GetFieldAsHtml($oRequest, $sAttCode); + $aDetails[] = array('label' => ''.MetaModel::GetLabel('UserRequest', $sAttCode).'', 'value' => $sDisplayValue); + } + } + $oP->add('
        '); + $oP->details($aDetails); + $oP->add('
        '); +} + +/** + * Displays a form for the user to provide feedback about a 'resolved' UserRequest and then close the request + * @param WebPage $oP The current web page + * @param UserRequest $oRequest The object to display + * @return void + */ +function DisplayResolvedRequestForm($oP, UserRequest $oRequest) +{ + $oP->add("
        \n"); + $oP->add("
        \n"); + $oP->add('
        '); + $oP->add("

        ".Dict::Format('Portal:TitleRequestDetailsFor_Request', $oRequest->GetName())."

        \n"); + DisplayRequestDetails($oP, $oRequest); + $oP->add('
        '); + $aArgs = array('this' => $oRequest); + $sClass = get_class($oRequest); + + $aDetails = array(); + $aTargetStates = MetaModel::EnumStates($sClass); + $aTargetState = $aTargetStates['closed']; + $aExpectedAttributes = $aTargetState['attribute_list']; + $iFieldIndex = 0; + + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oRequest->Get($sAttCode) == '')) ) + { + $aAttributesDef = MetaModel::ListAttributeDefs($sClass); + $oAttDef = $aAttributesDef[$sAttCode]; + $aArgs = array('this' => $oRequest); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oRequest->Get($sAttCode), $oRequest->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode, $aArgs); + $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => "$sHTMLValue"); + $aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex; + $iFieldIndex++; + } + } + $aStimuli = MetaModel::EnumStimuli($sClass); + $oP->add("

        ".Dict::S('Portal:EnterYourCommentsOnTicket')."

        "); + $oP->details($aDetails); + $oP->add(""); + $oP->add("\n"); + $oP->add(""); + $oP->p(""); + $oP->add('
        '); + $oP->add(""); + $oP->add("
        \n"); + $oP->add_ready_script( +<<add("

        ".Dict::S('UI:Error:ObjectAlreadyCreated')."

        \n"); + DisplayMainMenu($oP); + return; + } + + $sClass = get_class($oRequest); + $aDetails = array(); + $aTargetStates = MetaModel::EnumStates($sClass); + $aTargetState = $aTargetStates['closed']; + $aExpectedAttributes = $aTargetState['attribute_list']; + $iFieldIndex = 0; + + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oRequest->Get($sAttCode) == '')) ) + { + $value = utils::ReadParam('attr_'.$sAttCode, null, 'post'); + if (!is_null($value)) + { + $oRequest->Set($sAttCode, $value); + } + } + } + if ($oRequest->ApplyStimulus('ev_close')) + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + if (UserRights::IsImpersonated()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); + } + else + { + $sUserString = UserRights::GetUser(); + } + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + $oRequest->DBUpdateTracked($oMyChange); + $oP->p("

        ".Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oRequest)), $oRequest->GetName())."

        \n"); + DisplayMainMenu($oP); + } + else + { + $oP->AddMenuButton('back', 'Portal:Back', './index.php?operation=welcome'); + $oP->add('Error: cannot close the request - '.$oRequest->GetName()); + } +} + +/** + * Find the UserRequest object of the specified ID. Make sure that it the caller is the current user + * @param integer $id The ID of the request to find + * @return UserRequert The found object, or null in case of failure (object does not exist, user has no rights to see it...) + */ +function FindRequest($id) +{ + $oRequest = null; + $iContactId = UserRights::GetContactId(); + $oContact = MetaModel::GetObject('Contact', $iContactId, false); // false => Can fail + if (is_object($oContact)) + { + $sOQL = "SELECT UserRequest WHERE caller_id = :contact_id AND id = :request_id"; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new CMDBObjectSet($oSearch, array(), array('contact_id' => $iContactId, 'request_id' => $id)); + if ($oSet->Count() > 0) + { + $oRequest = $oSet->Fetch(); + } + } + else + { + $oP->AddMenuButton('back', 'Portal:Back', './index.php?operation=welcome'); + $oP->add("

        ".Dict::S('Portal:ErrorNoContactForThisUser')."

        "); + } + return $oRequest; +} +/** + * Displays the details of a request + * @param WebPage $oP The current web page + * @return void + */ +function RequestDetails(WebPage $oP, $id) +{ + $oRequest = FindRequest($id); + if (!is_object($oRequest)) + { + echo "Request not found ! count=".$oSet->Count(); + return; + } + $iDefaultStep = 0; + if ($oRequest->GetState() == 'resolved') + { + // The current ticket is in 'resolved' state, prompt to close it + $iDefaultStep = 1; + } + + $iStep = utils::ReadParam('step', $iDefaultStep); + + switch($iStep) + { + case 0: + $oP->AddMenuButton('back', 'Portal:Back', './index.php?operation=welcome'); + $oP->add("

        ".$oRequest->GetIcon()." ".Dict::Format('Portal:TitleRequestDetailsFor_Request', $oRequest->GetName())."

        \n"); + DisplayRequestDetails($oP, $oRequest); + break; + + case 1: + $oP->AddMenuButton('cancel', 'UI:Button:Cancel', './index.php?operation=welcome'); + DisplayResolvedRequestForm($oP, $oRequest); + break; + + case 2: + DoCloseRequest($oP, $oRequest); + break; + } +} + +/** + * Get The organization of the current user (i.e. the organization of its contact) + * @param WebPage $oP The current page, for errors output + * @return Organization The user's org or null in case of problem... + */ +function GetUserOrg($oP) +{ + $oOrg = null; + $iContactId = UserRights::GetContactId(); + $oContact = MetaModel::GetObject('Contact', $iContactId, false); // false => Can fail + if (is_object($oContact)) + { + $oOrg = MetaModel::GetObject('Organization', $oContact->Get('org_id'), false); // false => can fail + } + else + { + $oP->add("

        ".Dict::S('Portal:ErrorNoContactForThisUser')."

        "); + } + return $oOrg; +} + +try +{ + require_once('../application/startup.inc.php'); + require_once('../application/portalwebpage.class.inc.php'); + $oAppContext = new ApplicationContext(); + $sOperation = utils::ReadParam('operation', ''); + + require_once('../application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed + + $oP = new PortalWebPage(Dict::S('Portal:Title')); + $oUserOrg = GetUserOrg($oP); + + if (is_object($oUserOrg)) + { + switch($sOperation) + { + case 'create_request': + CreateRequest($oP, $oUserOrg); + break; + + case 'details': + $iRequestId = utils::ReadParam('id', 0); + RequestDetails($oP, $iRequestId); + break; + + case 'welcome': + default: + DisplayMainMenu($oP); + } + } + $oP->output(); +} +catch(CoreException $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:FatalErrorMessage')."

        \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + try + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', $e->GetIssue()); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', $e->getContextData()); + $oLog->DBInsertNoReload(); + } + catch(Exception $e) + { + IssueLog::Error("Failed to log issue into the DB"); + } + } + + IssueLog::Error($e->getMessage()); + } + + // For debugging only + //throw $e; +} +catch(Exception $e) +{ + require_once('../setup/setuppage.class.inc.php'); + $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

        ".Dict::S('UI:FatalErrorMessage')."

        \n"); + $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); + $oP->output(); + + if (MetaModel::IsLogEnabledIssue()) + { + if (MetaModel::IsValidClass('EventIssue')) + { + try + { + $oLog = new EventIssue(); + + $oLog->Set('message', $e->getMessage()); + $oLog->Set('userinfo', ''); + $oLog->Set('issue', 'PHP Exception'); + $oLog->Set('impact', 'Page could not be displayed'); + $oLog->Set('callstack', $e->getTrace()); + $oLog->Set('data', array()); + $oLog->DBInsertNoReload(); + } + catch(Exception $e) + { + IssueLog::Error("Failed to log issue into the DB"); + } + } + + IssueLog::Error($e->getMessage()); + } +} +?> \ No newline at end of file diff --git a/portal/portal.css b/portal/portal.css new file mode 100644 index 0000000000..9bde162750 --- /dev/null +++ b/portal/portal.css @@ -0,0 +1,114 @@ +div#portal #banner { + width: 100%; + height: 60px; + display: inline-block; + vertical-align:middle; + background-color: #f6f6f1; +} + +div#portal #logo { + width: 126px; + background: url(../images/itop-logo.png) 0 0 no-repeat; + margin-left:20px; + margin-right:20px; + height: 60px; + border: 0; + vertical-align: middle; + text-align: center; + display: inline-block; + line-height: 48px; + padding-right:50px; +} + +#portal_menu { + height: 60px; +} + +div.button { + margin-left:20px; + margin-right:20px; + height: 60px; + border: 0; + vertical-align: middle; + text-align: center; + display: inline-block; + line-height: 48px; +} + +a.button , a.button:visited { + color: #1C94C4; + text-decoration: none; + vertical-align: middle; + height: 48px; + line-height: 48px; + display: inline-block; +} + +a.button span { + vertical-align:middle; + margin-right: 20px; + margin-left: 50px; +} + +#close_form_table { + width: 100%; + padding: 20px; +} + +#logoff { + float: right; + background: url(../images/logoff.png) right center no-repeat; +} + +#logoff span { + margin-right: 50px; + margin-left: 20px; +} + +#cancel { + background: url(../images/stop-mid.png) 0 0 no-repeat; +} + +#create { + background: url(../modules/itop-request-mgmt-1.0.0/images/user-request.png) 0 0 no-repeat; +} +#user_info { + background: url(../images/clean-mid.png) 0 0 no-repeat; +} + +#change_pwd { + background: url(../images/password.png) 0 0 no-repeat; +} +#back { + background: url(../images/back.png) 0 0 no-repeat; +} +#back span { + margin-left: 54px; +} +#refresh { + background: url(../images/refresh.png) 0 0 no-repeat; + margin-right: 40px; +} +#refresh span { + margin-left: 54px; + margin-right: 20px; +} + +#content { + margin: 10px; + padding: 10px; + text-align: center; +} +#request_details { + display: inline-block; +} +#request_details table { + border: #f1f1f6 2px solid; +} +#form_details { + display: inline-block; +} +.wizContainer table { + display: inline-block; + text-align: left; +} From 50b6cd001240518a6f37a092e6dd27e447fde296 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 10 Sep 2010 11:23:09 +0000 Subject: [PATCH 725/970] - Enhancement (Trac#189) first version of a (simple) End-Users portal. - Fix for the appUserPreferences class SVN:trunk[808] --- application/portalwebpage.class.inc.php | 170 ++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 application/portalwebpage.class.inc.php diff --git a/application/portalwebpage.class.inc.php b/application/portalwebpage.class.inc.php new file mode 100644 index 0000000000..b6a443b758 --- /dev/null +++ b/application/portalwebpage.class.inc.php @@ -0,0 +1,170 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once("../application/nicewebpage.class.inc.php"); +require_once("../application/applicationcontext.class.inc.php"); +require_once("../application/user.preferences.class.inc.php"); +/** + * Web page with some associated CSS and scripts (jquery) for a fancier display + * of the Portal web page + */ +class PortalWebPage extends NiceWebPage +{ + /** + * Portal menu + */ + protected $m_aMenuButtons; + + public function __construct($sTitle) + { + $this->m_aMenuButtons = array(); + parent::__construct($sTitle); + $this->add_header("Content-type: text/html; charset=utf-8"); + $this->add_header("Cache-control: no-cache"); + $this->add_linked_stylesheet("../css/jquery.treeview.css"); + $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); + $this->add_linked_stylesheet("../portal/portal.css"); + $this->add_linked_script('../js/jquery.layout.min.js'); + $this->add_linked_script('../js/jquery.ba-bbq.min.js'); + $this->add_linked_script("../js/jquery.tablehover.js"); + $this->add_linked_script("../js/jquery.treeview.js"); + $this->add_linked_script("../js/jquery.autocomplete.js"); + $this->add_linked_script("../js/jquery.bgiframe.js"); + $this->add_linked_script("../js/jquery.positionBy.js"); + $this->add_linked_script("../js/jquery.popupmenu.js"); + $this->add_linked_script("../js/date.js"); + $this->add_linked_script("../js/jquery.tablesorter.min.js"); + $this->add_linked_script("../js/jquery.blockUI.js"); + $this->add_linked_script("../js/utils.js"); + $this->add_linked_script("../js/forms-json-utils.js"); + $this->add_linked_script("../js/swfobject.js"); + $this->add_ready_script( +<< 0) + { + this.truncatedList = true; + } + if (this.truncatedList) + { + $("tr td",table).removeClass('truncated'); + $("tr:last td",table).addClass('truncated'); + } + } + }); + + + $.tablesorter.addWidget({ + // give the widget a id + id: "myZebra", + // format is called when the on init and when a sorting has finished + format: function(table) + { + // Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc.. + $("tbody tr:even",table).addClass('even'); + $("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even'); + $("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even'); + $("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even'); + } + }); + + $("table.listResults").tableHover(); // hover tables + $(".listResults").tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables + $(".date-pick").datepicker({ + showOn: 'button', + buttonImage: '../images/calendar.png', + buttonImageOnly: true, + dateFormat: 'yy-mm-dd', + constrainInput: false, + changeMonth: true, + changeYear: true + }); + $('.resizable').resizable(); // Make resizable everything that claims to be resizable ! +} +catch(err) +{ + // Do something with the error ! + alert(err); +} +EOF +); + + $this->add_script( +<< 0); + if (!bResult) + { + alert(sMessage); + } + return bResult; + } + + function GoBack() + { + var form = $('#request_form'); + var step = $('input[name=step]'); + + form.unbind('submit'); // De-activate validation + step.val(step.val() -2); // To go Back one step: next step is x, current step is x-1, previous step is x-2 + form.submit(); // Go + } +EOF +); + + } + + /** + * Add a button to the portal's main menu + */ + public function AddMenuButton($sId, $sLabel, $sHyperlink) + { + $this->m_aMenuButtons[] = array('id' => $sId, 'label' => $sLabel, 'hyperlink' => $sHyperlink); + } + + public function output() + { + $this->AddMenuButton('logoff', 'Portal:Disconnect', '../pages/logoff.php?portal=1'); // This menu is always present and is the last one + $sMenu = '
        \n"); $aReconciliationKeys = MetaModel::GetReconcKeys($sClassName); - $aMoreReconciliationKeys = array(); + $aMoreReconciliationKeys = array(); // Store: key => void to automatically remove duplicates foreach($aReconciliationKeys as $sAttCode) { $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); @@ -329,7 +329,7 @@ switch($sOperation) { // An external key is specified as a reconciliation key: this means that all the reconciliation // keys of this class are proposed to identify the target object - $aMoreReconciliationKeys = array_keys(GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced)); + $aMoreReconciliationKeys = array_merge($aMoreReconciliationKeys, GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced)); } elseif($oAttDef->IsExternalField()) { @@ -337,11 +337,10 @@ switch($sOperation) // since external fields are not writable, and thus never appears in the mapping form $sKeyAttCode = $oAttDef->GetKeyAttCode(); $sTargetAttCode = $oAttDef->GetExtAttCode(); - - $aMoreReconciliationKeys = array($sKeyAttCode.'->'.$sTargetAttCode); + $aMoreReconciliationKeys[$sKeyAttCode.'->'.$sTargetAttCode] = ''; } } - $sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys,$aMoreReconciliationKeys)).'"'; + $sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys, array_keys($aMoreReconciliationKeys))).'"'; $oPage->add_ready_script( << Date: Mon, 13 Sep 2010 10:16:09 +0000 Subject: [PATCH 753/970] Dispatched sample data for notifications (user requests vs incident tickets) SVN:trunk[836] --- dictionaries/fr.dictionary.itop.ui.php | 2 -- .../data.struct.ta-links.xml | 0 .../data.struct.ta-triggers.xml | 0 .../module.itop-incident-mgmt.php | 3 ++- .../data.struct.ta-links.xml | 23 +++++++++++++++++++ .../data.struct.ta-triggers.xml | 19 +++++++++++++++ .../module.itop-request-mgmt.php | 3 ++- .../data.struct.ta-actions.xml | 12 +++++----- .../module.itop-tickets.php | 2 -- 9 files changed, 52 insertions(+), 12 deletions(-) rename modules/{itop-tickets-1.0.0 => itop-incident-mgmt-1.0.0}/data.struct.ta-links.xml (100%) rename modules/{itop-tickets-1.0.0 => itop-incident-mgmt-1.0.0}/data.struct.ta-triggers.xml (100%) create mode 100644 modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml create mode 100644 modules/itop-request-mgmt-1.0.0/data.struct.ta-triggers.xml diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 363f3cedb6..7734f65c26 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -775,8 +775,6 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:ExportMenu' => 'Exportation', 'Menu:ExportMenu+' => 'Exportation des résultats d\'une requête en HTML, CSV ou XML', - 'Menu:IncidentManagementMenu' => 'Gestion des Incidents', - 'Menu:IncidentManagementMenu+' => 'Gestion des Incidents', 'UI:IncidentManagementMenu:Title' => 'Résumé des incidents', 'UI-IncidentManagementMenu-IncidentsByType' => 'Incidents par type', 'UI-IncidentManagementMenu-IncidentsByStatus' => 'Incidents par état', diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-links.xml b/modules/itop-incident-mgmt-1.0.0/data.struct.ta-links.xml similarity index 100% rename from modules/itop-tickets-1.0.0/data.struct.ta-links.xml rename to modules/itop-incident-mgmt-1.0.0/data.struct.ta-links.xml diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-triggers.xml b/modules/itop-incident-mgmt-1.0.0/data.struct.ta-triggers.xml similarity index 100% rename from modules/itop-tickets-1.0.0/data.struct.ta-triggers.xml rename to modules/itop-incident-mgmt-1.0.0/data.struct.ta-triggers.xml diff --git a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php index 37f7ce6623..8ca773ab25 100644 --- a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php @@ -31,7 +31,8 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-incident-mgmt.php', ), 'data.struct' => array( - //'data.struct.itop-incident-mgmt.xml', + 'data.struct.ta-triggers.xml', + 'data.struct.ta-links.xml', ), 'data.sample' => array( //'data.sample.itop-incident-mgmt.xml', diff --git a/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml b/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml new file mode 100644 index 0000000000..6e980a646b --- /dev/null +++ b/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml @@ -0,0 +1,23 @@ + + + +2 +50 +1 + + +3 +50 +2 + + +3 +60 +1 + + +1 +20 +1 + + diff --git a/modules/itop-request-mgmt-1.0.0/data.struct.ta-triggers.xml b/modules/itop-request-mgmt-1.0.0/data.struct.ta-triggers.xml new file mode 100644 index 0000000000..19377ebe19 --- /dev/null +++ b/modules/itop-request-mgmt-1.0.0/data.struct.ta-triggers.xml @@ -0,0 +1,19 @@ + + + +User Request ticket creation +UserRequest + + + +User Request ticket assigned to agent +UserRequest +assigned + + + +User Request ticket resolved +UserRequest +resolved + + diff --git a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php index 203e14563d..59b8a4e6c4 100644 --- a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php @@ -30,7 +30,8 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-request-mgmt.php', ), 'data.struct' => array( - //'data.struct.itop-request-mgmt.xml', + 'data.struct.ta-triggers.xml', + 'data.struct.ta-links.xml', ), 'data.sample' => array( //'data.sample.itop-request-mgmt.xml', diff --git a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml index 388f80e511..dfab3914fb 100644 --- a/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml +++ b/modules/itop-tickets-1.0.0/data.struct.ta-actions.xml @@ -1,7 +1,7 @@ -Incident Notification to a Workgroup +Notification to a Workgroup This action informs a team that a ticket has been assigned their workgroup disabled @@ -13,7 +13,7 @@ The ticket $this->name()$, priority $this->label(priority)$ has been assigned to the workgroup $this->workgroup_name$ <html> <body> -<p>The incident ticket $this->name()$ has been assigned to the workgroup $this->workgroup_name$.</p> +<p>The ticket $this->name()$ has been assigned to the workgroup $this->workgroup_name$.</p> <p>Description: $this->title$</p> <p>Title: $this->title$</p> <hr/> @@ -23,7 +23,7 @@ normal -Incident notification to Agent +Notification to Agent This action informs an agent that a ticket has been assigned to her/him disabled @@ -35,7 +35,7 @@ The ticket $this->name()$, priority $this->label(priority)$ has been assigned to you <html> <body> -<p>The incident ticket $this->name()$ has been assigned to you.</p> +<p>The ticket $this->name()$ has been assigned to you.</p> <p>Description: $this->title$</p> <p>Title: $this->title$</p> <hr/> @@ -45,7 +45,7 @@ normal -Incident Notification to caller +Notification to caller This action is used to inform the caller disabled @@ -57,7 +57,7 @@ Ticket $this->name()$, priority $this->label(priority)$ - $this->status$ <html> <body> -<p>The incident ticket $this->name()$ has changed to status $this->status$</p> +<p>The ticket $this->name()$ has changed to status $this->status$</p> <p>Last update: $this->last_update$</p> <hr/> <p>for more information on this ticket, click here: $this->hyperlink(portal)$</p> diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index 310cb5adcf..3ff3b46221 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -29,9 +29,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-tickets.php', ), 'data.struct' => array( - 'data.struct.ta-triggers.xml', 'data.struct.ta-actions.xml', - 'data.struct.ta-links.xml', ), 'data.sample' => array( ), From 4f45baae9d1084ef070409ce8acf290434ecb9a6 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Mon, 13 Sep 2010 10:29:00 +0000 Subject: [PATCH 754/970] add object group of CI SVN:trunk[837] --- .../en.dict.itop-config-mgmt.php | 56 +++++++++++++ .../es_cr.dict.itop-config-mgmt.php | 55 +++++++++++++ .../fr.dict.itop-config-mgmt.php | 55 +++++++++++++ .../model.itop-config-mgmt.php | 82 +++++++++++++++++-- toolkit.php | 31 ++++++- 5 files changed, 269 insertions(+), 10 deletions(-) diff --git a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php index 150ae41f7b..006e249305 100644 --- a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php @@ -131,6 +131,60 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Location/Attribute:infra_list' => 'Infrastructure', 'Class:Location/Attribute:infra_list+' => 'CIs located on this site', )); +// +// Class: Group +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:Group' => 'Group', + 'Class:Group+' => '', + 'Class:Group/Attribute:name' => 'Name', + 'Class:Group/Attribute:name+' => '', + 'Class:Group/Attribute:status' => 'Status', + 'Class:Group/Attribute:status+' => '', + 'Class:Group/Attribute:status/Value:implementation' => 'Implementation', + 'Class:Group/Attribute:status/Value:implementation+' => 'Implementation', + 'Class:Group/Attribute:status/Value:obsolete' => 'Obsolete', + 'Class:Group/Attribute:status/Value:obsolete+' => 'Obsolete', + 'Class:Group/Attribute:status/Value:production' => 'Production', + 'Class:Group/Attribute:status/Value:production+' => 'Production', + 'Class:Group/Attribute:org_id' => 'Organization', + 'Class:Group/Attribute:org_id+' => '', + 'Class:Group/Attribute:owner_name' => 'Name', + 'Class:Group/Attribute:owner_name+' => 'Common name', + 'Class:Group/Attribute:description' => 'Description', + 'Class:Group/Attribute:description+' => '', + 'Class:Group/Attribute:type' => 'Type', + 'Class:Group/Attribute:type+' => '', + 'Class:Group/Attribute:parent_id' => 'Parent Group', + 'Class:Group/Attribute:parent_id+' => '', + 'Class:Group/Attribute:parent_name' => 'Name', + 'Class:Group/Attribute:parent_name+' => '', + 'Class:Group/Attribute:ci_list' => 'Linked CIs', + 'Class:Group/Attribute:ci_list+' => '', +)); + +// +// Class: lnkGroupToCI +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:lnkGroupToCI' => 'Group / CI', + 'Class:lnkGroupToCI+' => '', + 'Class:lnkGroupToCI/Attribute:group_id' => 'Group', + 'Class:lnkGroupToCI/Attribute:group_id+' => '', + 'Class:lnkGroupToCI/Attribute:group_name' => 'Name', + 'Class:lnkGroupToCI/Attribute:group_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_id' => 'CI', + 'Class:lnkGroupToCI/Attribute:ci_id+' => '', + 'Class:lnkGroupToCI/Attribute:ci_name' => 'Name', + 'Class:lnkGroupToCI/Attribute:ci_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_status' => 'CI Status', + 'Class:lnkGroupToCI/Attribute:ci_status+' => '', + 'Class:lnkGroupToCI/Attribute:reason' => 'Reason', + 'Class:lnkGroupToCI/Attribute:reason+' => '', +)); + // // Class: Contact @@ -991,6 +1045,8 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:ConfigManagement:AllDevices' => 'Number of devices: %1$d', 'Menu:ConfigManagement:SWAndApps' => 'Software and Applications', 'Menu:ConfigManagement:Misc' => 'Miscellaneous', +'Menu:Group' => 'Groups of CIs', +'Menu:Group+' => 'Groups of CIs', )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php index 1a5b253422..d082b789e5 100644 --- a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php @@ -131,6 +131,59 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:Location/Attribute:infra_list' => 'Infraestructura', 'Class:Location/Attribute:infra_list+' => 'Ítem Configurados (CI) ubicados en este sitio', )); +// +// Class: Group +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:Group' => 'Grupo', + 'Class:Group+' => '', + 'Class:Group/Attribute:name' => 'Nombre', + 'Class:Group/Attribute:name+' => '', + 'Class:Group/Attribute:status' => 'Estado', + 'Class:Group/Attribute:status+' => '', + 'Class:Group/Attribute:status/Value:implementation' => 'Implementación', + 'Class:Group/Attribute:status/Value:implementation+' => 'Implementación', + 'Class:Group/Attribute:status/Value:obsolete' => 'Obsoleto', + 'Class:Group/Attribute:status/Value:obsolete+' => 'Obsoleto', + 'Class:Group/Attribute:status/Value:production' => 'Producción', + 'Class:Group/Attribute:status/Value:production+' => 'Producción', + 'Class:Group/Attribute:org_id' => 'Organización propietaria', + 'Class:Group/Attribute:org_id+' => '', + 'Class:Group/Attribute:owner_name' => 'Nombre de la Organización propietaria', + 'Class:Group/Attribute:owner_name+' => 'Organización propietaria', + 'Class:Group/Attribute:description' => 'Descripción', + 'Class:Group/Attribute:description+' => '', + 'Class:Group/Attribute:type' => 'Tipo', + 'Class:Group/Attribute:type+' => '', + 'Class:Group/Attribute:parent_id' => 'Padre', + 'Class:Group/Attribute:parent_id+' => '', + 'Class:Group/Attribute:parent_name' => 'Grupo padre', + 'Class:Group/Attribute:parent_name+' => '', + 'Class:Group/Attribute:ci_list' => 'I.C.s', + 'Class:Group/Attribute:ci_list+' => 'Ítems Configurados relacionados con el grupo', +)); + +// +// Class: lnkGroupToCI +// + +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Class:lnkGroupToCI' => 'Grupo I.C', + 'Class:lnkGroupToCI+' => '', + 'Class:lnkGroupToCI/Attribute:group_id' => 'Grupo', + 'Class:lnkGroupToCI/Attribute:group_id+' => '', + 'Class:lnkGroupToCI/Attribute:group_name' => 'Nombre', + 'Class:lnkGroupToCI/Attribute:group_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_id' => 'I.C', + 'Class:lnkGroupToCI/Attribute:ci_id+' => '', + 'Class:lnkGroupToCI/Attribute:ci_name' => 'Nombre', + 'Class:lnkGroupToCI/Attribute:ci_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_status' => 'Estato', + 'Class:lnkGroupToCI/Attribute:ci_status+' => '', + 'Class:lnkGroupToCI/Attribute:reason' => 'Razón', + 'Class:lnkGroupToCI/Attribute:reason+' => '', +)); // // Class: Contact @@ -960,5 +1013,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:MobilePhone+' => 'Todos los Teléfonos Celulares', 'Menu:PC' => 'PCs (Computadores de Personales', 'Menu:PC+' => 'Todos los PCs (Computadores de Personales', +'Menu:Group' => 'Grupos de ICs', +'Menu:Group+' => 'Grupos de ICs', )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php index a110848946..ee953afc20 100644 --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php @@ -130,6 +130,59 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:Location/Attribute:infra_list' => 'Infrastructure', 'Class:Location/Attribute:infra_list+' => 'Eléments d\'infrastructure situés sur ce lieu', )); +// +// Class: Group +// + +Dict::Add('FR FR', 'French', 'Français', array( + 'Class:Group' => 'Groupe', + 'Class:Group+' => '', + 'Class:Group/Attribute:name' => 'Nom', + 'Class:Group/Attribute:name+' => '', + 'Class:Group/Attribute:status' => 'Etat', + 'Class:Group/Attribute:status+' => '', + 'Class:Group/Attribute:status/Value:implementation' => 'Implémentation', + 'Class:Group/Attribute:status/Value:implementation+' => 'Implémentation', + 'Class:Group/Attribute:status/Value:obsolete' => 'Obsolète', + 'Class:Group/Attribute:status/Value:obsolete+' => 'Obsolète', + 'Class:Group/Attribute:status/Value:production' => 'Production', + 'Class:Group/Attribute:status/Value:production+' => 'Production', + 'Class:Group/Attribute:org_id' => 'Organization', + 'Class:Group/Attribute:org_id+' => '', + 'Class:Group/Attribute:owner_name' => 'Nom', + 'Class:Group/Attribute:owner_name+' => 'Nom commun', + 'Class:Group/Attribute:description' => 'Description', + 'Class:Group/Attribute:description+' => '', + 'Class:Group/Attribute:type' => 'Type', + 'Class:Group/Attribute:type+' => '', + 'Class:Group/Attribute:parent_id' => 'Group parent', + 'Class:Group/Attribute:parent_id+' => '', + 'Class:Group/Attribute:parent_name' => 'Nom', + 'Class:Group/Attribute:parent_name+' => '', + 'Class:Group/Attribute:ci_list' => 'CIs lié', + 'Class:Group/Attribute:ci_list+' => '', +)); +// +// Class: lnkGroupToCI +// + +Dict::Add('FR FR', 'French', 'Français', array( + 'Class:lnkGroupToCI' => 'Groupe / CI', + 'Class:lnkGroupToCI+' => '', + 'Class:lnkGroupToCI/Attribute:group_id' => 'Groupe', + 'Class:lnkGroupToCI/Attribute:group_id+' => '', + 'Class:lnkGroupToCI/Attribute:group_name' => 'Nom', + 'Class:lnkGroupToCI/Attribute:group_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_id' => 'CI', + 'Class:lnkGroupToCI/Attribute:ci_id+' => '', + 'Class:lnkGroupToCI/Attribute:ci_name' => 'Nom', + 'Class:lnkGroupToCI/Attribute:ci_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_status' => 'Etat du CI', + 'Class:lnkGroupToCI/Attribute:ci_status+' => '', + 'Class:lnkGroupToCI/Attribute:reason' => 'Raison', + 'Class:lnkGroupToCI/Attribute:reason+' => '', +)); + // // Class: Contact @@ -964,5 +1017,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:ConfigManagement:AllDevices' => 'Nombre d\'équipements: %1$d', 'Menu:ConfigManagement:SWAndApps' => 'Logiciels et Applications', 'Menu:ConfigManagement:Misc' => 'Divers', +'Menu:Group' => 'Groupes de CIs', +'Menu:Group+' => 'Groupes de CIs', )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 2a34375517..b1180a705b 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -273,7 +273,7 @@ abstract class Document extends cmdbAbstractObject MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status')); MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status')); - MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status')); + MetaModel::Init_SetZListItems('list', array('org_id', 'type', 'status')); } } class WebDoc extends Document @@ -301,7 +301,7 @@ class WebDoc extends Document MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'url')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status', 'url')); MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status', 'url')); - MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status', 'url')); + MetaModel::Init_SetZListItems('list', array('org_id', 'type', 'status', 'url')); } } class Note extends Document @@ -329,7 +329,7 @@ class Note extends Document MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'note')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status', 'note')); MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status', 'note')); - MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status', 'note')); + MetaModel::Init_SetZListItems('list', array('org_id', 'type', 'status', 'note')); } } class FileDoc extends Document @@ -357,7 +357,7 @@ class FileDoc extends Document MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'type', 'status', 'contract_list', 'service_list', 'ticket_list', 'ci_list', 'contents')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'org_id', 'description', 'type', 'status')); MetaModel::Init_SetZListItems('standard_search', array('name', 'org_id', 'description', 'type', 'status')); - MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'type', 'status', 'contents')); + MetaModel::Init_SetZListItems('list', array('org_id', 'type', 'status', 'contents')); } /** @@ -876,6 +876,75 @@ class DatabaseInstance extends FunctionalCI } } } +class Group extends cmdbAbstractObject +{ + + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,searchable,configmgmt", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array("name","org_id","owner_name"), + "db_table" => "group", + "db_key_field" => "id", + "db_finalclass_field" => "", + "icon" => "../modules/itop-config-mgmt-1.0.0/images/solution.png", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('production,implementation,obsolete'), "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"Organization", "jointype"=>null, "allowed_values"=>null, "sql"=>"org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("owner_name", array("allowed_values"=>null, "extkey_attcode"=>"org_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeWikiText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("type", array("allowed_values"=>null, "sql"=>"type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"Group", "jointype"=>null, "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array("org_id")))); + MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("ci_list", array("linked_class"=>"lnkGroupToCI", "ext_key_to_me"=>"group_id", "ext_key_to_remote"=>"ci_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array()))); + + MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'type','description', 'parent_id', 'ci_list')); + MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'type')); + MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'type')); + MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'type','parent_id')); + } +} +class lnkGroupToCI extends cmdbAbstractObject +{ + + public static function Init() + { + $aParams = array + ( + "category" => "bizmodel,configmgmt", + "key_type" => "autoincrement", + "name_attcode" => "group_id", + "state_attcode" => "", + "reconc_keys" => array("group_id","ci_id"), + "db_table" => "lnkgrouptoci", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("group_id", array("targetclass"=>"Group", "jointype"=>null, "allowed_values"=>null, "sql"=>"group_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("group_name", array("allowed_values"=>null, "extkey_attcode"=>"group_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalKey("ci_id", array("targetclass"=>"FunctionalCI", "jointype"=>null, "allowed_values"=>null, "sql"=>"ci_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ci_name", array("allowed_values"=>null, "extkey_attcode"=>"ci_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("ci_status", array("allowed_values"=>null, "extkey_attcode"=>"ci_id", "target_attcode"=>"status", "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_SetZListItems('details', array('group_id', 'ci_id', 'ci_status', 'reason')); + MetaModel::Init_SetZListItems('advanced_search', array('group_id', 'ci_id', 'reason')); + MetaModel::Init_SetZListItems('standard_search', array('group_id', 'ci_id', 'reason')); + MetaModel::Init_SetZListItems('list', array('group_id', 'ci_id', 'ci_status', 'reason')); + } +} class ApplicationSolution extends FunctionalCI { @@ -1291,7 +1360,7 @@ abstract class InfrastructureCI extends Device MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway', 'contact_list', 'document_list', 'solution_list', 'contract_list', 'ticket_list', 'nwinterface_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref', 'description', 'location_id', 'location_details', 'management_ip', 'default_gateway')); MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'serial_number', 'asset_ref','location_id','management_ip', 'default_gateway')); - MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'importance', 'brand', 'model', 'location_id')); + MetaModel::Init_SetZListItems('list', array('status', 'org_id', 'importance', 'brand', 'model', 'location_id')); } } class NetworkDevice extends InfrastructureCI @@ -1563,9 +1632,10 @@ new OQLMenuNode('Team', 'SELECT Team', $oContactNode->GetIndex(), 4 /* fRank */) new OQLMenuNode('Document', 'SELECT Document', $oConfigManagementGroup->GetIndex(), 2 /* fRank */, true /* bSearch */); new OQLMenuNode('Location', 'SELECT Location', $oConfigManagementGroup->GetIndex(), 3 /* fRank */, true /* bSearch */); +new OQLMenuNode('Group', 'SELECT Group', $oConfigManagementGroup->GetIndex(), 4 /* fRank */, true /* bSearch */); -$oCINode = new TemplateMenuNode('ConfigManagementCI', '../modules/itop-config-mgmt-1.0.0/cis_menu.html', $oConfigManagementGroup->GetIndex(), 4 /* fRank */); +$oCINode = new TemplateMenuNode('ConfigManagementCI', '../modules/itop-config-mgmt-1.0.0/cis_menu.html', $oConfigManagementGroup->GetIndex(), 5 /* fRank */); new NewObjectMenuNode('NewCI', 'FunctionalCI', $oCINode->GetIndex(), 0 /* fRank */); new SearchMenuNode('SearchCIs', 'FunctionalCI', $oCINode->GetIndex(), 1 /* fRank */); diff --git a/toolkit.php b/toolkit.php index 2a99d122e4..d0b92b45a9 100644 --- a/toolkit.php +++ b/toolkit.php @@ -8,18 +8,41 @@ $sStyle = " padding:1.5em; "; echo "
        \n"; -echo "

        Shortcuts

        \n"; echo "
        Main page
        \n"; echo "CSV import (shortcut)
        \n"; echo "Universal search (shortcut)
        \n"; -echo "

        Itop customization

        \n"; -echo "Please contact the iTop support team
        \n"; +echo "

        Itop consultant

        \n"; +echo "Check model, Create DB, Update DB (new class, new attribute)
        \n"; +echo "Backup and restore (shortcut)
        \n"; +echo "Objects schema (shortcut)
        \n"; +echo "Setup the email
        \n"; +echo "Generate data for benchmarking purposes
        \n"; echo "

        Web services

        \n"; echo "Available functions
        \n"; echo "WSDL (dynamically generated)
        \n"; -echo "Check SLA for tickets
        \n"; +echo "

        Not working or deprecated

        \n"; +echo "Data generator
        \n"; +echo "ITop finder
        \n"; +echo "navITop
        \n"; +echo "@ITop
        \n"; echo "
        \n"; +echo "

        phpMyORM

        "; + + +$sStyle = " + border: 1px dashed #CCC; + background: #CFC; + padding:1.5em; +"; +echo "
        \n"; + + +echo "Manage configurations
        \n"; +echo "Core unit tests
        \n"; + + +echo "
        \n"; echo "

        phpinfo()

        "; From b537a3e8107b65c719a406959e281fd5f8f15432 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 11:45:57 +0000 Subject: [PATCH 755/970] Removed automatic test tools (separate module) SVN:trunk[838] --- core/test.class.inc.php | 557 ----------- pages/test.php | 157 --- pages/testlist.inc.php | 2104 --------------------------------------- 3 files changed, 2818 deletions(-) delete mode 100644 core/test.class.inc.php delete mode 100644 pages/test.php delete mode 100644 pages/testlist.inc.php diff --git a/core/test.class.inc.php b/core/test.class.inc.php deleted file mode 100644 index 437dcafc01..0000000000 --- a/core/test.class.inc.php +++ /dev/null @@ -1,557 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - - -require_once('coreexception.class.inc.php'); -require_once('attributedef.class.inc.php'); -require_once('filterdef.class.inc.php'); -require_once('stimulus.class.inc.php'); -require_once('MyHelpers.class.inc.php'); - -require_once('expression.class.inc.php'); -require_once('cmdbsource.class.inc.php'); -require_once('sqlquery.class.inc.php'); - -require_once('log.class.inc.php'); -require_once('kpi.class.inc.php'); - -require_once('dbobject.class.php'); -require_once('dbobjectsearch.class.php'); -require_once('dbobjectset.class.php'); - -require_once('../application/cmdbabstract.class.inc.php'); - -require_once('userrights.class.inc.php'); - -require_once('../webservices/webservices.class.inc.php'); - - -// Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?) -class UnitTestException extends Exception -{} - - -/** - * Improved display of the backtrace - * - * @package iTopORM - */ -class ExceptionFromError extends Exception -{ - public function getTraceAsHtml() - { - $aBackTrace = $this->getTrace(); - return MyHelpers::get_callstack_html(0, $this->getTrace()); - // return "
        \n".$this->getTraceAsString()."
        \n"; - } -} - - -/** - * Test handler API and basic helpers - * - * @package iTopORM - */ -abstract class TestHandler -{ - protected $m_aSuccesses; - protected $m_aWarnings; - protected $m_aErrors; - protected $m_sOutput; - - public function __construct() - { - $this->m_aSuccesses = array(); - $this->m_aWarnings = array(); - $this->m_aErrors = array(); - } - - static public function GetName() {return "fooname";} - static public function GetDescription(){return "foodesc";} - - protected function DoPrepare() {return true;} - abstract protected function DoExecute(); - protected function DoCleanup() {return true;} - - protected static function DumpVariable($var) - { - echo "
        \n"; 
        -		print_r($var);
        -		echo "
        \n"; - } - - protected function ReportSuccess($sMessage, $sSubtestId = '') - { - $this->m_aSuccesses[] = $sMessage; - } - - protected function ReportWarning($sMessage, $sSubtestId = '') - { - $this->m_aWarnings[] = $sMessage; - } - - protected function ReportError($sMessage, $sSubtestId = '') - { - $this->m_aErrors[] = $sMessage; - } - - public function GetResults() - { - return $this->m_aSuccesses; - } - - public function GetWarnings() - { - return $this->m_aWarnings; - } - - public function GetErrors() - { - return $this->m_aErrors; - } - - public function GetOutput() - { - return $this->m_sOutput; - } - - public function error_handler($errno, $errstr, $errfile, $errline) - { - // Note: return false to call the default handler (stop the program if an error) - - switch ($errno) - { - case E_USER_ERROR: - $this->ReportError($errstr); - //throw new ExceptionFromError("Fatal error in line $errline of file $errfile: $errstr"); - break; - case E_USER_WARNING: - $this->ReportWarning($errstr); - break; - case E_USER_NOTICE: - $this->ReportWarning($errstr); - break; - default: - $this->ReportWarning("Unknown error type: [$errno] $errstr in $errfile at $errline"); - echo "Unknown error type: [$errno] $errstr in $errfile at $errline
        \n"; - break; - } - return true; // do not call the default handler - } - - public function Execute() - { - ob_start(); - set_error_handler(array($this, 'error_handler')); - try - { - $this->DoPrepare(); - $this->DoExecute(); - } - catch (ExceptionFromError $e) - { - $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); - } - catch (CoreException $e) - { - //$this->ReportError($e->getMessage()); - //$this->ReportError($e->__tostring()); - $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); - } - catch (Exception $e) - { - //$this->ReportError($e->getMessage()); - //$this->ReportError($e->__tostring()); - $this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString()); - } - restore_error_handler(); - $this->m_sOutput = ob_get_clean(); - return (count($this->GetErrors()) == 0); - } -} - - - - -/** - * Test to execute a piece of code (checks if an error occurs) - * - * @package iTopORM - */ -abstract class TestFunction extends TestHandler -{ - // simply overload DoExecute (temporary) -} - - -/** - * Test to execute a piece of code (checks if an error occurs) - * - * @package iTopORM - */ -abstract class TestWebServices extends TestHandler -{ - // simply overload DoExecute (temporary) - - static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null) - { - $aDataAndAuth = $aData; -// To be changed to use basic authentication - $aDataAndAuth['operation'] = 'login'; - $aDataAndAuth['auth_user'] = $sLogin; - $aDataAndAuth['auth_pwd'] = $sPassword; - $sHost = $_SERVER['HTTP_HOST']; - $sRawPath = $_SERVER['SCRIPT_NAME']; - $sPath = dirname($sRawPath); - $sUrl = "http://$sHost/$sPath/$sRelativeUrl"; - - return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders); - } - - // Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl - // originaly named after do_post_request - // Partially adapted to our coding conventions - static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null) - { - // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. - - $sData = http_build_query($aData); - - $aParams = array('http' => array( - 'method' => 'POST', - 'content' => $sData, - 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", - )); - if ($sOptionnalHeaders !== null) - { - $aParams['http']['header'] .= $sOptionnalHeaders; - } - $ctx = stream_context_create($aParams); - - $fp = @fopen($sUrl, 'rb', false, $ctx); - if (!$fp) - { - global $php_errormsg; - if (isset($php_errormsg)) - { - throw new Exception("Problem with $sUrl, $php_errormsg"); - } - else - { - throw new Exception("Problem with $sUrl"); - } - } - $response = @stream_get_contents($fp); - if ($response === false) - { - throw new Exception("Problem reading data from $sUrl, $php_errormsg"); - } - return $response; - } -} - -/** - * Test to execute a piece of code (checks if an error occurs) - * - * @package iTopORM - */ -abstract class TestSoapWebService extends TestHandler -{ - // simply overload DoExecute (temporary) - - function __construct() - { - parent::__construct(); - } -} - -/** - * Test to check that a function outputs some values depending on its input - * - * @package iTopORM - */ -abstract class TestFunctionInOut extends TestFunction -{ -// abstract static public function GetCallSpec(); // parameters to call_user_func -// abstract static public function GetInOut(); // array of input => output - - protected function DoExecute() - { - $aTests = $this->GetInOut(); - if (is_array($aTests)) - { - foreach ($aTests as $iTestId => $aTest) - { - $ret = call_user_func_array($this->GetCallSpec(), $aTest['args']); - if ($ret != $aTest['output']) - { - // Note: to be improved to cope with non string parameters - $this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId); - } - else - { - $this->ReportSuccess("Found the expected output '$ret'", $iTestId); - } - } - } - else - { - $ret = call_user_func($this->GetCallSpec()); - $this->ReportSuccess('Finished successfully'); - } - } -} - - -/** - * Test to check an URL (Searches for Error/Warning/Etc keywords) - * - * @package iTopORM - */ -abstract class TestUrl extends TestHandler -{ -// abstract static public function GetUrl(); -// abstract static public function GetErrorKeywords(); -// abstract static public function GetWarningKeywords(); - - protected function DoExecute() - { - return true; - } -} - - -/** - * Test to check a user management module - * - * @package iTopORM - */ -abstract class TestUserRights extends TestHandler -{ - protected function DoExecute() - { - return true; - } -} - - -/** - * Test to execute a scenario on a given DB - * - * @package iTopORM - */ -abstract class TestScenarioOnDB extends TestHandler -{ -// abstract static public function GetDBHost(); -// abstract static public function GetDBUser(); -// abstract static public function GetDBPwd(); -// abstract static public function GetDBName(); - - protected function DoPrepare() - { - $sDBHost = $this->GetDBHost(); - $sDBUser = $this->GetDBUser(); - $sDBPwd = $this->GetDBPwd(); - $sDBName = $this->GetDBName(); - - CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd); - CMDBSource::SetCharacterSet(); - if (CMDBSource::IsDB($sDBName)) - { - CMDBSource::DropDB($sDBName); - } - CMDBSource::CreateDB($sDBName); - } - - protected function DoCleanup() - { - // CMDBSource::DropDB($this->GetDBName()); - } -} - - -/** - * Test to use a business model on a given DB - * - * @package iTopORM - */ -abstract class TestBizModel extends TestHandler -{ -// abstract static public function GetDBSubName(); -// abstract static public function GetBusinessModelFile(); -// abstract static public function GetConfigFile(); - - protected function DoPrepare() - { - MetaModel::Startup($this->GetConfigFile()); -// #@# Temporary disabled by Romain -// MetaModel::CheckDefinitions(); - - // something here to create records... but that's another story - } - - protected $m_oChange; - protected function ObjectToDB($oNew, $bReload = false) - { - list($bRes, $aIssues) = $oNew->CheckToWrite(); - if (!$bRes) - { - throw new CoreException('Could not create object, unexpected values', array('issues' => $aIssues)); - } - if ($oNew instanceof CMDBObject) - { - if (!isset($this->m_oChange)) - { - new CMDBChange(); - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Someone doing some tests"); - $iChangeId = $oMyChange->DBInsertNoReload(); - $this->m_oChange = $oMyChange; - } - if ($bReload) - { - $iId = $oNew->DBInsertTracked($this->m_oChange); - } - else - { - $iId = $oNew->DBInsertTrackedNoReload($this->m_oChange); - } - } - else - { - if ($bReload) - { - $iId = $oNew->DBInsert(); - } - else - { - $iId = $oNew->DBInsertNoReload(); - } - } - return $iId; - } - - protected function ResetDB() - { - if (MetaModel::DBExists(false)) - { - MetaModel::DBDrop(); - } - MetaModel::DBCreate(); - } - - static protected function show_list($oObjectSet) - { - $oObjectSet->Rewind(); - $aData = array(); - while ($oItem = $oObjectSet->Fetch()) - { - $aValues = array(); - foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode) - { - $aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode); - } - //echo $oItem->GetKey()." => ".implode(", ", $aValues)."
        \n"; - $aData[] = $aValues; - } - echo MyHelpers::make_table_from_assoc_array($aData); - } - - static protected function search_and_show_list(DBObjectSearch $oMyFilter) - { - $oObjSet = new CMDBObjectSet($oMyFilter); - echo $oMyFilter->__DescribeHTML()."' - Found ".$oObjSet->Count()." items.
        \n"; - self::show_list($oObjSet); - } - - static protected function search_and_show_list_from_oql($sOQL) - { - echo $sOQL."...
        \n"; - $oNewFilter = DBObjectSearch::FromOQL($sOQL); - self::search_and_show_list($oNewFilter); - } -} - - -/** - * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.) - * - * @package iTopORM - */ -abstract class TestBizModelGeneric extends TestBizModel -{ - static public function GetName() - { - return 'Full test on a given business model'; - } - - static public function GetDescription() - { - return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.'; - } - - protected function DoPrepare() - { - parent::DoPrepare(); - - if (!MetaModel::DBExists(false)) - { - MetaModel::DBCreate(); - } - // something here to create records... but that's another story - } - - protected function DoExecute() - { - foreach(MetaModel::GetClasses() as $sClassName) - { - if (MetaModel::HasTable($sClassName)) continue; - - $oNobody = MetaModel::GetObject($sClassName, 123); - $oBaby = new $sClassName; - $oFilter = new DBObjectSearch($sClassName); - - // Challenge reversibility of OQL / filter object - // - $sExpr1 = $oFilter->ToOQL(); - $oNewFilter = DBObjectSearch::FromOQL($sExpr1); - $sExpr2 = $oNewFilter->ToOQL(); - if ($sExpr1 != $sExpr2) - { - $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); - } - - // Use the filter (perform the query) - // - $oSet = new CMDBObjectSet($oFilter); - $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - } - return true; - } -} - - -?> diff --git a/pages/test.php b/pages/test.php deleted file mode 100644 index 076045b168..0000000000 --- a/pages/test.php +++ /dev/null @@ -1,157 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -?> - -Missing mandatory argument $sName

        "; - exit; - } - return $value; -} - -function IsAValidTestClass($sClassName) -{ - // Must be a child of TestHandler - // - if (!is_subclass_of($sClassName, 'TestHandler')) return false; - - // Must not be abstract - // - $oReflectionClass = new ReflectionClass($sClassName); - if (!$oReflectionClass->isInstantiable()) return false; - - return true; -} - -function DisplayEvents($aEvents, $sTitle) -{ - echo "

        $sTitle

        \n"; - if (count($aEvents) > 0) - { - echo "
          \n"; - foreach ($aEvents as $sEvent) - { - echo "
        • $sEvent
        • \n"; - } - echo "
        \n"; - } - else - { - echo "

        none

        \n"; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Main -/////////////////////////////////////////////////////////////////////////////// - - -require_once('../application/utils.inc.php'); -require_once('../core/test.class.inc.php'); -require_once('testlist.inc.php'); - -require_once('../core/cmdbobject.class.inc.php'); - -$sTodo = utils::ReadParam("todo", ""); -if ($sTodo == '') -{ - // Show the list of tests - // - echo "

        Existing tests

        \n"; - echo "
          \n"; - foreach (get_declared_classes() as $sClassName) - { - if (!IsAValidTestClass($sClassName)) continue; - - $sName = call_user_func(array($sClassName, 'GetName')); - $sDescription = call_user_func(array($sClassName, 'GetDescription')); - echo "
        • $sName ($sDescription)\n"; -} -else if ($sTodo == 'exec') -{ - // Execute a test - // - $sTestClass = ReadMandatoryParam("testid"); - - if (!IsAValidTestClass($sTestClass)) - { - echo "

          Wrong value for testid, expecting a valid class name

          \n"; - } - else - { - $oTest = new $sTestClass(); - echo "

          Testing: ".$oTest->GetName()."

          \n"; - $bRes = $oTest->Execute(); - } - -/* -MyHelpers::var_dump_html($oTest->GetResults()); -MyHelpers::var_dump_html($oTest->GetWarnings()); -MyHelpers::var_dump_html($oTest->GetErrors()); -*/ - - if ($bRes) - { - echo "

          Success :-)

          \n"; - DisplayEvents($oTest->GetResults(), 'Results'); - } - else - { - echo "

          Failure :-(

          \n"; - } - DisplayEvents($oTest->GetErrors(), 'Errors'); - DisplayEvents($oTest->GetWarnings(), 'Warnings'); - - // Render the output - // - echo "

          Actual output

          \n"; - echo "
          \n"; - echo $oTest->GetOutput(); - echo "
          \n"; -} -else -{ -} - - -?> diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php deleted file mode 100644 index 55c8872d58..0000000000 --- a/pages/testlist.inc.php +++ /dev/null @@ -1,2104 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - - -class TestSQLQuery extends TestScenarioOnDB -{ - static public function GetName() {return 'SQLQuery';} - static public function GetDescription() {return 'SQLQuery does not depend on the rest of the framework, therefore it makes sense to have a separate test framework for it';} - - static public function GetDBHost() {return 'localhost';} - static public function GetDBUser() {return 'root';} - static public function GetDBPwd() {return '';} - static public function GetDBName() {return 'TestSQLQuery';} - static public function GetDBSubName() {return 'taratata';} - - - protected function DoPrepare() - { - parent::DoPrepare(); - cmdbSource::CreateTable('CREATE TABLE `myTable` (myKey INT(11) NOT NULL auto_increment, column1 VARCHAR(255), column2 VARCHAR(255), PRIMARY KEY (`myKey`)) ENGINE = '.MYSQL_ENGINE); - cmdbSource::CreateTable('CREATE TABLE `myTable1` (myKey1 INT(11) NOT NULL auto_increment, column1_1 VARCHAR(255), column1_2 VARCHAR(255), PRIMARY KEY (`myKey1`)) ENGINE = '.MYSQL_ENGINE); - cmdbSource::CreateTable('CREATE TABLE `myTable2` (myKey2 INT(11) NOT NULL auto_increment, column2_1 VARCHAR(255), column2_2 VARCHAR(255), PRIMARY KEY (`myKey2`)) ENGINE = '.MYSQL_ENGINE); - } - - protected function DoExecute() - { - $oQuery = new SQLQuery( - $sTable = 'myTable', - $sTableAlias = 'myTableAlias', - $aFields = array('column1'=>new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), - $oCondition = new BinaryExpression(new FieldExpression('column1', 'myTableAlias'), 'LIKE', new ScalarExpression('trash')), - $aFullTextNeedles = array('column1'), - $bToDelete = false, - $aValues = array() - ); - $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); - - $oSubQuery1 = new SQLQuery( - $sTable = 'myTable1', - $sTableAlias = 'myTable1Alias', - $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), - $oCondition = new TrueSQLExpression, - $aFullTextNeedles = array(), - $bToDelete = false, - $aValues = array() - ); - - $oSubQuery2 = new SQLQuery( - $sTable = 'myTable2', - $sTableAlias = 'myTable2Alias', - $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), - $oCondition = new TrueSQLExpression, - $aFullTextNeedles = array(), - $bToDelete = false, - $aValues = array() - ); - - $oQuery->AddInnerJoin($oSubQuery1, 'column1', 'column1_1'); - $oQuery->AddLeftJoin($oSubQuery2, 'column2', 'column2_2'); - - $oQuery->DisplayHtml(); - $oQuery->RenderDelete(); - $oQuery->RenderUpdate(); - echo '

          '.$oQuery->RenderSelect().'

          '; - $oQuery->RenderSelect(array('column1')); - $oQuery->RenderSelect(array('column1', 'column2')); - } -} - -class TestOQLParser extends TestFunction -{ - static public function GetName() {return 'Check OQL parsing';} - static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - $oOql = new OqlInterpreter($sQuery); - try - { - $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery - self::DumpVariable($oTrash); - } - catch (OQLException $OqlException) - { - if ($bIsCorrectQuery) - { - echo "

          More info on this unexpected failure:
          ".$OqlException->getHtmlDesc()."

          \n"; - throw $OqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

          More info on this expected failure:
          ".$OqlException->getHtmlDesc()."

          \n"; - return true; - } - } - // The query was correctly parsed, was it expected to be correct ? - if ($bIsCorrectQuery) - { - return true; - } - else - { - throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); - return false; - } - } - - protected function TestQuery($sQuery, $bIsCorrectQuery) - { - if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) - { - return false; - } - return true; - } - - public function DoExecute() - { - $aQueries = array( - 'SELECT toto' => true, - 'SELECT toto WHERE toto.a = 1' => true, - 'SELECT toto WHERE toto.a=1' => true, - 'SELECT toto WHERE toto.a = "1"' => true, - 'SELECT toto WHHHERE toto.a = "1"' => false, - 'SELECT toto WHERE toto.a == "1"' => false, - 'SELECT toto WHERE toto.a % 1' => false, - //'SELECT toto WHERE toto.a LIKE 1' => false, - 'SELECT toto WHERE toto.a like \'arg\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, - 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, - 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, - - 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE ""' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, - "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, - "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, - - 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, - - //'SELECT toto WHERE toto.a > \'asd\'' => false, - 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, - 'SELECT Device JOIN Site ON Device.site = Site.id' => true, - 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, - - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, - - 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, - - // Several objects in a row... - // - 'SELECT A FROM A' => true, - 'SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A, B,C FROM A JOIN B ON A.myB = B.id' => false, - 'SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => false, - ); - - $iErrors = 0; - - foreach($aQueries as $sQuery => $bIsCorrectQuery) - { - $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; - echo "

          Testing query: $sQuery ($sIsOk)

          \n"; - $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); - if (!$bRet) $iErrors++; - } - - return ($iErrors == 0); - } -} - - -class TestCSVParser extends TestFunction -{ - static public function GetName() {return 'Check CSV parsing';} - static public function GetDescription() {return 'Loads a set of CSV data';} - - public function DoExecute() - { - $sDataFile = '?field1?;?field2?;?field3? -?a?;?b?;?c? -a;b;c - ? a ? ; ? b ? ; ? c ? - a ; b ; c -??;??;?? -;; -?a"?;?b?;?c? -?a1 -a2?;?b?;?c? -?a1,a2?;?b?;?c? -?a?;?b?;?c1,",c2 -,c3? -?a?;?b?;?ouf !? - Espace sur la fin ; 1234; e@taloc.com '; - - self::DumpVariable($sDataFile); - - $aExpectedResult = array( - //array('field1', 'field2', 'field3'), - array('a', 'b', 'c'), - array('a', 'b', 'c'), - array(' a ', ' b ', ' c '), - array('a', 'b', 'c'), - array('', '', ''), - array('', '', ''), - array('a"', 'b', 'c'), - array("a1\na2", 'b', 'c'), - array('a1,a2', 'b', 'c'), - array('a', 'b', "c1,\",c2\n,c3"), - array('a', 'b', 'ouf !'), - array('Espace sur la fin', '1234', 'e@taloc.com'), - ); - - $oCSVParser = new CSVParser($sDataFile, ';', '?'); - $aData = $oCSVParser->ToArray(1, null, 0); - - $iIssues = 0; - - echo "\n"; - foreach ($aData as $iRow => $aRow) - { - echo "\n"; - foreach ($aRow as $iCol => $sCell) - { - if (empty($sCell)) - { - $sCellValue = ' '; - } - else - { - $sCellValue = htmlentities($sCell); - } - - if (!isset($aExpectedResult[$iRow][$iCol])) - { - $iIssues++; - $sCellValue = "$sCellValue"; - } - elseif ($aExpectedResult[$iRow][$iCol] != $sCell) - { - $iIssues++; - $sCellValue = "$sCellValue, expecting '".$aExpectedResult[$iRow][$iCol]."'"; - } - - echo ""; - } - echo "\n"; - } - echo "
          $sCellValue
          \n"; - return ($iIssues > 0); - } -} - -class TestGenericItoMyModel extends TestBizModelGeneric -{ - static public function GetName() - { - return 'Generic RO test on '.self::GetConfigFile(); - } - - static public function GetConfigFile() {return '../config-test-mymodel.php';} -} - -class TestGenericItopBigModel extends TestBizModelGeneric -{ - static public function GetName() - { - return 'Generic RO test on '.self::GetConfigFile(); - } - - static public function GetConfigFile() {return '../config-test-itopv06.php';} -} - -class TestUserRightsMatrixItop extends TestUserRights -{ - static public function GetName() - { - return 'User rights test on user rights matrix'; - } - - static public function GetDescription() - { - return 'blah blah blah'; - } - - public function DoPrepare() - { - parent::DoPrepare(); - MetaModel::Startup('../config-test-itopv06.php'); - } - - protected function DoExecute() - { - $sUser = 'Romain'; - echo "

          Totor: ".(UserRights::CheckCredentials('Totor', 'toto') ? 'ok' : 'NO')."

          \n"; - echo "

          Romain: ".(UserRights::CheckCredentials('Romain', 'toto') ? 'ok' : 'NO')."

          \n"; - echo "

          User: ".UserRights::GetUser()."

          \n"; - echo "

          On behalf of...".UserRights::GetRealUser()."

          \n"; - - echo "

          Denis (impersonate) : ".(UserRights::Impersonate('Denis', 'tutu') ? 'ok' : 'NO')."

          \n"; - echo "

          User: ".UserRights::GetUser()."

          \n"; - echo "

          On behalf of...".UserRights::GetRealUser()."

          \n"; - - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); - echo "

          IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

          \n"; - echo "

          IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

          \n"; - echo "

          IsActionAllowedOnAttribute...".(UserRights::IsActionAllowedOnAttribute('bizOrganization', 'myattribute', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

          \n"; - return true; - } -} - -/////////////////////////////////////////////////////////////////////////// -// Test a complex biz model on the fly -/////////////////////////////////////////////////////////////////////////// - -class TestMyBizModel extends TestBizModel -{ - static public function GetName() - { - return 'A series of tests on a weird business model'; - } - - static public function GetDescription() - { - return 'Attempts various operations and build complex queries'; - } - - static public function GetConfigFile() {return '../config-test-mymodel.php';} - - function test_linksinfo() - { - echo "

          Enum links

          "; - self::DumpVariable(MetaModel::EnumReferencedClasses("cmdbTeam")); - self::DumpVariable(MetaModel::EnumReferencingClasses("Organization")); - - self::DumpVariable(MetaModel::EnumLinkingClasses()); - self::DumpVariable(MetaModel::EnumLinkingClasses("cmdbContact")); - self::DumpVariable(MetaModel::EnumLinkingClasses("cmdWorkshop")); - self::DumpVariable(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); - } - - function test_list_attributes() - { - echo "

          List attributes

          "; - foreach(MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode=>$oAttDef) - { - echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
          \n"; - } - } - - function test_search() - { - echo "

          Two searches

          "; - $oFilterAllDevs = new DBObjectSearch("cmdbTeam"); - $oAllDevs = new DBObjectSet($oFilterAllDevs); - - echo "Found ".$oAllDevs->Count()." items.
          \n"; - while ($oDev = $oAllDevs->Fetch()) - { - $aValues = array(); - foreach(MetaModel::GetAttributesList($oAllDevs->GetClass()) as $sAttCode) - { - $aValues[] = MetaModel::GetLabel(get_class($oDev), $sAttCode)." (".MetaModel::GetDescription(get_class($oDev), $sAttCode).") = ".$oDev->GetAsHTML($sAttCode); - } - echo $oDev->GetKey()." => ".implode(", ", $aValues)."
          \n"; - } - - // a second one - $oMyFilter = new DBObjectSearch("cmdbContact"); - //$oMyFilter->AddCondition("name", "aii", "Finishes with"); - $oMyFilter->AddCondition("name", "aii"); - $this->search_and_show_list($oMyFilter); - - } - - function test_reload() - { - echo "

          Reload

          "; - $team = MetaModel::GetObject("cmdbContact", "2"); - echo "Chargement de l'attribut headcount: {$team->Get("headcount")}
          \n"; - self::DumpVariable($team); - } - - function test_setattribute() - { - echo "

          Set attribute and update

          "; - $team = MetaModel::GetObject("cmdbTeam", "2"); - $team->Set("headcount", rand(1,1000)); - $team->Set("email", "Luis ".rand(9,250)); - self::DumpVariable($team->ListChanges()); - echo "New headcount = {$team->Get("headcount")}
          \n"; - echo "Computed name = {$team->Get("name")}
          \n"; - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - - //MetaModel::StartDebugQuery(); - $team->DBUpdateTracked($oMyChange); - //MetaModel::StopDebugQuery(); - - echo "

          Check the modified team

          "; - $oTeam = MetaModel::GetObject("cmdbTeam", "2"); - self::DumpVariable($oTeam); - } - function test_newobject() - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_newobject / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - - echo "

          Create a new object (team)

          "; - $oNewTeam = MetaModel::NewObject("cmdbTeam"); - $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); - $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); - $oNewTeam->Set("email", null); - $oNewTeam->Set("owner", "ITOP"); - $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value - $iId = $oNewTeam->DBInsertTracked($oMyChange); - echo "Created new team: $iId
          "; - echo "

          Delete team #$iId

          "; - $oTeam = MetaModel::GetObject("cmdbTeam", $iId); - $oTeam->DBDeleteTracked($oMyChange); - echo "Deleted team: $iId
          "; - self::DumpVariable($oTeam); - } - - - function test_updatecolumn() - { - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_updatecolumn / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - - $sNewEmail = "updatecol".rand(9,250)."@quedlaballe.com"; - echo "

          Update a the email: set to '$sNewEmail'

          "; - $oMyFilter = new DBObjectSearch("cmdbContact"); - $oMyFilter->AddCondition("name", "o", "Contains"); - - echo "Candidates before:
          "; - $this->search_and_show_list($oMyFilter); - - MetaModel::BulkUpdateTracked($oMyChange, $oMyFilter, array("email" => $sNewEmail)); - - echo "Candidates after:
          "; - $this->search_and_show_list($oMyFilter); - } - - function test_error() - { - trigger_error("Stop requested", E_USER_ERROR); - } - - function test_changetracking() - { - echo "

          Create a change

          "; - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - echo "Created new change: $iChangeId
          "; - self::DumpVariable($oMyChange); - - echo "

          Create a new object (team)

          "; - $oNewTeam = MetaModel::NewObject("cmdbTeam"); - $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); - $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); - $oNewTeam->Set("email", null); - $oNewTeam->Set("owner", "ITOP"); - $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value - $iId = $oNewTeam->DBInsertTracked($oMyChange); - echo "Created new team: $iId
          "; - echo "

          Delete team #$iId

          "; - $oTeam = MetaModel::GetObject("cmdbTeam", $iId); - $oTeam->DBDeleteTracked($oMyChange); - echo "Deleted team: $iId
          "; - self::DumpVariable($oTeam); - } - - function test_zlist() - { - echo "

          Test ZLists

          "; - $aZLists = MetaModel::EnumZLists(); - foreach ($aZLists as $sListCode) - { - $aListInfos = MetaModel::GetZListInfo($sListCode); - echo "

          List '".$sListCode."' (".$aListInfos["description"].") of type '".$aListInfos["type"]."'

          \n"; - - foreach (MetaModel::GetSubclasses("cmdbObjectHomeMade") as $sKlass) - { - $aItems = MetaModel::GetZListItems($sKlass, $sListCode); - if (count($aItems) == 0) continue; - - echo "$sKlass - $sListCode : {".implode(", ", $aItems)."}
          \n"; - } - } - - echo "

          IsAttributeInZList()...

          "; - echo "Liens_entre_contacts_et_workshop::ws_info in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "ws_info") ? "yes" : "no")."
          \n"; - echo "Liens_entre_contacts_et_workshop::toworkshop in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "toworkshop") ? "yes" : "no")."
          \n"; - - } - - function test_pkey() - { - echo "

          Test search on pkey

          "; - $sExpr1 = "SELECT cmdbContact WHERE id IN (40, 42)"; - $sExpr2 = "SELECT cmdbContact WHERE IN NOT IN (40, 42)"; - $this->search_and_show_list_from_oql($sExpr1); - $this->search_and_show_list_from_oql($sExpr2); - - echo "Et maintenant, on fusionne....
          \n"; - $oSet1 = new CMDBObjectSet(DBObjectSearch::FromOQL($sExpr1)); - $oSet2 = new CMDBObjectSet(DBObjectSearch::FromOQL($sExpr2)); - $oIntersect = $oSet1->CreateIntersect($oSet2); - $oDelta = $oSet1->CreateDelta($oSet2); - - $oMerge = clone $oSet1; - $oMerge->Merge($oSet2); - $oMerge->Merge($oSet2); - - echo "Set1 - Found ".$oSet1->Count()." items.
          \n"; - echo "Set2 - Found ".$oSet2->Count()." items.
          \n"; - echo "Intersect - Found ".$oIntersect->Count()." items.
          \n"; - echo "Delta - Found ".$oDelta->Count()." items.
          \n"; - echo "Merge - Found ".$oMerge->Count()." items.
          \n"; - //$this->show_list($oObjSet); - } - - function test_relations() - { - echo "

          Test relations

          "; - - //self::DumpVariable(MetaModel::EnumRelationQueries("cmdbObjectHomeMade", "Potes")); - self::DumpVariable(MetaModel::EnumRelationQueries("cmdbContact", "Potes")); - - $iMaxDepth = 9; - echo "Max depth = $iMaxDepth
          \n"; - - $oObj = MetaModel::GetObject("cmdbContact", 18); - $aRels = $oObj->GetRelatedObjects("Potes", $iMaxDepth); - echo $oObj->Get('name')." has some 'Potes'...
          \n"; - foreach ($aRels as $sClass => $aObjs) - { - echo "$sClass, count = ".count($aObjs)." => ".implode(', ', array_keys($aObjs))."
          \n"; - $oObjectSet = CMDBObjectSet::FromArray($sClass, $aObjs); - $this->show_list($oObjectSet); - } - - echo "

          Test relations - same results, by the mean of a OQL

          "; - $this->search_and_show_list_from_oql("cmdbContact: RELATED (Potes, $iMaxDepth) TO (cmdbContact: pkey = 18)"); - - } - - function test_linkedset() - { - echo "

          Linked set attributes

          \n"; - $oObj = MetaModel::GetObject("cmdbContact", 18); - - echo "
          Current workshops
          \n"; - $oSetWorkshopsCurr = $oObj->Get("myworkshops"); - $this->show_list($oSetWorkshopsCurr); - - echo "
          Setting workshops
          \n"; - $oNewLink = new cmdbLiens(); - $oNewLink->Set('toworkshop', 2); - $oNewLink->Set('function', 'mafonctioooon'); - $oNewLink->Set('a1', 'tralala1'); - $oNewLink->Set('a2', 'F7M'); - $oSetWorkshops = CMDBObjectSet::FromArray("cmdbLiens", array($oNewLink)); - $oObj->Set("myworkshops", $oSetWorkshops); - $this->show_list($oSetWorkshops); - - echo "
          New workshops
          \n"; - $oSetWorkshopsCurr = $oObj->Get("myworkshops"); - $this->show_list($oSetWorkshopsCurr); - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "test_linkedset / Made by robot #".rand(1,100)); - $iChangeId = $oMyChange->DBInsert(); - $oObj->DBUpdateTracked($oMyChange); - $oObj = MetaModel::GetObject("cmdbContact", 18); - - echo "
          After the write
          \n"; - $oSetWorkshopsCurr = $oObj->Get("myworkshops"); - $this->show_list($oSetWorkshopsCurr); - } - - function test_object_lifecycle() - { - echo "

          Test object lifecycle

          "; - - - self::DumpVariable(MetaModel::GetStateAttributeCode("cmdbContact")); - self::DumpVariable(MetaModel::EnumStates("cmdbContact")); - self::DumpVariable(MetaModel::EnumStimuli("cmdbContact")); - foreach(MetaModel::EnumStates("cmdbContact") as $sStateCode => $aStateDef) - { - echo "

          Transition from $sStateCode

          \n"; - self::DumpVariable(MetaModel::EnumTransitions("cmdbContact", $sStateCode)); - } - - $oObj = MetaModel::GetObject("cmdbContact", 18); - echo "Current state: ".$oObj->GetState()."... let's go to school..."; - self::DumpVariable($oObj->EnumTransitions()); - $oObj->ApplyStimulus("toschool"); - echo "New state: ".$oObj->GetState()."... let's get older..."; - self::DumpVariable($oObj->EnumTransitions()); - $oObj->ApplyStimulus("raise"); - echo "New state: ".$oObj->GetState()."... let's try to go further... (should give an error)"; - self::DumpVariable($oObj->EnumTransitions()); - $oObj->ApplyStimulus("raise"); // should give an error - } - - - protected function DoExecute() - { -// $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); -// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - //$this->test_linksinfo(); - //$this->test_list_attributes(); - //$this->test_search(); - //$this->test_reload(); - //$this->test_newobject(); - $this->test_setattribute(); - //$this->test_updatecolumn(); - //$this->test_error(); - //$this->test_changetracking(); - $this->test_zlist(); - $this->test_OQL(); - //$this->test_pkey(); - $this->test_relations(); - $this->test_linkedset(); - $this->test_object_lifecycle(); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test a complex biz model on the fly -/////////////////////////////////////////////////////////////////////////// - -abstract class MyFarm extends TestBizModel -{ - static public function GetConfigFile() {return '../config-test-farm.php';} - - protected function DoPrepare() - { - parent::DoPrepare(); - $this->ResetDB(); - MetaModel::DBCheckIntegrity(); - } - - protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) - { - $oNew = MetaModel::NewObject('Mammal'); - $oNew->Set('species', $sSpecies); - $oNew->Set('sex', $sSex); - $oNew->Set('speed', $iSpeed); - $oNew->Set('mother', $iMotherid); - $oNew->Set('father', $iFatherId); - $oNew->Set('name', $sName); - $oNew->Set('height', $iHeight); - $oNew->Set('birth', $sBirth); - return $this->ObjectToDB($oNew); - } - - protected function InsertBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId) - { - $oNew = MetaModel::NewObject('Bird'); - $oNew->Set('species', $sSpecies); - $oNew->Set('sex', $sSex); - $oNew->Set('speed', $iSpeed); - $oNew->Set('mother', $iMotherid); - $oNew->Set('father', $iFatherId); - return $this->ObjectToDB($oNew); - } - - protected function InsertFlyingBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $iFlyingSpeed) - { - $oNew = MetaModel::NewObject('FlyingBird'); - $oNew->Set('species', $sSpecies); - $oNew->Set('sex', $sSex); - $oNew->Set('speed', $iSpeed); - $oNew->Set('mother', $iMotherid); - $oNew->Set('father', $iFatherId); - $oNew->Set('flyingspeed', $iFlyingSpeed); - return $this->ObjectToDB($oNew); - } - - private function InsertGroup($sName, $iLeaderId) - { - $oNew = MetaModel::NewObject('Group'); - $oNew->Set('name', $sName); - $oNew->Set('leader', $iLeaderId); - $iId = $oNew->DBInsertNoReload(); - return $iId; - } -} - - -class TestQueriesOnFarm extends MyFarm -{ - static public function GetName() - { - return 'Farm test'; - } - - static public function GetDescription() - { - return 'A series of tests on the farm business model (SQL generation)'; - } - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - if ($bIsCorrectQuery) - { - echo "

          $sQuery

          \n"; - } - else - { - echo "

          $sQuery

          \n"; - } - try - { - //$oOql = new OqlInterpreter($sQuery); - //$oTrash = $oOql->ParseObjectQuery(); - //self::DumpVariable($oTrash, true); - $oMyFilter = DBObjectSearch::FromOQL($sQuery); - } - catch (OQLException $oOqlException) - { - if ($bIsCorrectQuery) - { - echo "

          More info on this unexpected failure:
          ".$oOqlException->getHtmlDesc()."

          \n"; - throw $oOqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

          More info on this expected failure:\n"; - echo "

            \n"; - echo "
          • ".get_class($oOqlException)."
          • \n"; - echo "
          • ".$oOqlException->getMessage()."
          • \n"; - echo "
          • ".$oOqlException->getHtmlDesc()."
          • \n"; - echo "
          \n"; - echo "

          \n"; - return true; - } - } - // The query was correctly parsed, was it expected to be correct ? - if (!$bIsCorrectQuery) - { - throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); - return false; - } - echo "

          To OQL: ".$oMyFilter->ToOQL()."

          "; - - $this->search_and_show_list($oMyFilter); - - //echo "

          first pass

          \n"; - //self::DumpVariable($oMyFilter, true); - $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); - //echo "

          second pass

          \n"; - //self::DumpVariable($oMyFilter, true); - //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); - - $sSerialize = $oMyFilter->serialize(); - echo "

          Serialized:$sSerialize

          \n"; - $oFilter2 = DBObjectSearch::unserialize($sSerialize); - try - { - $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); - } - catch (Exception $e) - { - echo "

          Could not compute the query after unserialize

          \n"; - echo "

          Query 1: $sQuery1

          \n"; - MyHelpers::var_cmp_html($oMyFilter, $oFilter2); - throw $e; - } - //if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same! - if ($sQuery1 != $sQuery2) - { - echo "

          serialize/unserialize mismatch :-(

          \n"; - MyHelpers::var_cmp_html($sQuery1, $sQuery2); - MyHelpers::var_cmp_html($oMyFilter, $oFilter2); - return false; - } - return true; - } - - protected function DoExecute() - { -// $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); -// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - echo "

          Create protagonists...

          "; - - $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); - $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); - $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); - $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); - $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); - - $this->InsertBird('rooster', 'male', 12, 0, 0); - $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); - - // Benchmarking - // - if (false) - { - define ('COUNT_BENCHMARK', 10); - echo "

          Parsing a long query, ".COUNT_BENCHMARK." times

          "; - $sQuery = "SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR) AND Dad.height * 2 <= ROUND(TO_DAYS(Dad.birth) / (3 + 1) * 5 - 3)"; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $oMyFilter = DBObjectSearch::FromOQL($sQuery); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fParsingDuration = $fDuration / COUNT_BENCHMARK; - echo "

          Mean time by op: $fParsingDuration

          "; - } - - echo "

          Test queries...

          "; - - $aQueries = array( - 'SELECT Animal' => true, - 'SELECT Animal WHERE Animal.pkey = 1' => false, - 'SELECT Animal WHERE Animal.id = 1' => true, - 'SELECT Aniiimal' => false, - 'SELECTe Animal' => false, - 'SELECT * FROM Animal' => false, - 'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true, - 'SELECT Animal AS zoo WHERE species = \'human\'' => true, - 'SELECT Animal AS zoo WHERE espece = \'human\'' => false, - 'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true, - 'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false, - 'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true, - 'SELECT Animal AS zoo WHERE zoo.species NOT IN (\'human\', "pig")' => true, - 'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false, - 'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true, - 'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true, - 'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true, - 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, - 'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true, - 'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true, - 'SELECT Mammal AS m WHERE m.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR)' => true, - 'SELECT Mammal AS m WHERE m.birth > DATE_SUB(NOW(), INTERVAL 2000 DAY)' => true, - 'SELECT Mammal AS m WHERE (TO_DAYS(NOW()) - TO_DAYS(m.birth)) > 2000' => true, - 'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true, - 'SELECT Mammal AS m WHERE (1 + 2' => false, - 'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true, - 'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true, - 'SELECT Mammal AS m WHERE 1/0' => true, - 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, - 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, - 'SELECT Group JOIN Animal ON Group.leader = Animal.id' => true, - 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, - 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.masterchief = A.id' => false, - 'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true, - 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false, - 'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false, - 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true, - 'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false, - 'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false, - 'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false, - 'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true, - 'SELECT Group AS G WHERE G.leader_speed < 100000' => true, - 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, - 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true, - 'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true, - 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false, - 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, - 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true, - 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, - 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, - 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, - // Specifying multiple objects - 'SELECT Animal FROM Animal' => true, - 'SELECT yelele FROM Animal' => false, - 'SELECT Animal FROM Animal AS A' => false, - 'SELECT A FROM Animal AS A' => true, - ); - //$aQueries = array( - // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, - //); - foreach($aQueries as $sQuery => $bIsCorrect) - { - $this->CheckQuery($sQuery, $bIsCorrect); - } - return true; - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test data load -/////////////////////////////////////////////////////////////////////////// - -class TestBulkChangeOnFarm extends TestBizModel -{ - static public function GetName() - { - return 'Farm test - data load'; - } - - static public function GetDescription() - { - return 'Bulk load'; - } - - static public function GetConfigFile() {return '../config-test-farm.php';} - - protected function DoPrepare() - { - parent::DoPrepare(); - $this->ResetDB(); - MetaModel::DBCheckIntegrity(); - } - - protected function DoExecute() - { -// $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); -// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); - - $oParser = new CSVParser("denomination,hauteur,age - suzy,123,2009-01-01 - chita,456, - "); - $aData = $oParser->ToArray(array('_name', '_height', '_birth'), ','); - self::DumpVariable($aData); - - $oBulk = new BulkChange( - 'Mammal', - $aData, - // attributes - array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), - // ext keys - array(), - // reconciliation - array('name') - ); - - $oMyChange = MetaModel::NewObject("CMDBChange"); - $oMyChange->Set("date", time()); - $oMyChange->Set("userinfo", "Testor"); - $iChangeId = $oMyChange->DBInsert(); -// echo "Created new change: $iChangeId
          "; - - echo "

          Planned for loading...

          "; - $aRes = $oBulk->Process(); - self::DumpVariable($aRes); - echo "

          Go for loading...

          "; - $aRes = $oBulk->Process($oMyChange); - self::DumpVariable($aRes); - - return; - - $oRawData = array( - 'Mammal', - array('species', 'sex', 'speed', 'mother', 'father', 'name', 'height', 'birth'), - "human,male,23,0,0,romulus,192,1971 - human,male,23,0,0,remus,154,-50 - human,male,23,0,0,julius,160,-49 - human,female,23,0,0,cleopatra,142,-50 - pig,female,23,0,0,confucius,50,2003" - ); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test data load -/////////////////////////////////////////////////////////////////////////// - -class TestFullTextSearchOnFarm extends MyFarm -{ - static public function GetName() - { - return 'Farm test - full text search'; - } - - static public function GetDescription() - { - return 'Focus on the full text search feature'; - } - - protected function DoExecute() - { - echo "

          Create protagonists...

          "; - - $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); - $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); - $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); - $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); - $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); - - $this->InsertBird('rooster', 'male', 12, 0, 0); - $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); - - echo "

          Search...

          "; - $oSearch = new DBObjectSearch('Mammal'); - $oSearch->AddCondition_FullText('manof'); - //$oResultSet = new DBObjectSet($oSearch); - $this->search_and_show_list($oSearch); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Benchmark queries -/////////////////////////////////////////////////////////////////////////// - -class TestItopEfficiency extends TestBizModel -{ - static public function GetName() - { - return 'Itop - benchmark'; - } - - static public function GetDescription() - { - return 'Measure time to perform the queries'; - } - - static public function GetConfigFile() {return '../config-itop.php';} - - protected function DoBenchmark($sOqlQuery) - { - echo "

          Testing query: $sOqlQuery

          "; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $oFilter = DBObjectSearch::FromOQL($sOqlQuery); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fParsingDuration = $fDuration / COUNT_BENCHMARK; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $sSQL = MetaModel::MakeSelectQuery($oFilter); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fBuildDuration = $fDuration / COUNT_BENCHMARK; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $res = CMDBSource::Query($sSQL); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fQueryDuration = $fDuration / COUNT_BENCHMARK; - - // The fetch could not be repeated with the same results - // But we've seen so far that is was very very quick to exec - // So it makes sense to benchmark it a single time - $fStart = MyHelpers::getmicrotime(); - $aRow = CMDBSource::FetchArray($res); - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fFetchDuration = $fDuration; - - $fStart = MyHelpers::getmicrotime(); - for($i=0 ; $i < COUNT_BENCHMARK ; $i++) - { - $sOql = $oFilter->ToOQL(); - } - $fDuration = MyHelpers::getmicrotime() - $fStart; - $fToOqlDuration = $fDuration / COUNT_BENCHMARK; - - echo "
            \n"; - echo "
          • Parsing: $fParsingDuration
          • \n"; - echo "
          • Build: $fBuildDuration
          • \n"; - echo "
          • Query: $fQueryDuration
          • \n"; - echo "
          • Fetch: $fFetchDuration
          • \n"; - echo "
          • ToOql: $fToOqlDuration
          • \n"; - echo "
          \n"; - - // Everything but the ToOQL (wich is interesting, anyhow) - $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; - - return array( - 'rows' => CMDBSource::NbRows($res), - 'duration (s)' => round($fTotal, 4), - 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), - 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), - 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), - 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), - 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), - 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), - ); - } - - protected function DoExecute() - { - define ('COUNT_BENCHMARK', 3); - echo "

          The test will be repeated ".COUNT_BENCHMARK." times

          "; - - $aQueries = array( - 'SELECT CMDBChangeOpSetAttribute', - 'SELECT CMDBChangeOpSetAttribute WHERE id=10', - 'SELECT CMDBChangeOpSetAttribute WHERE id=123456789', - 'SELECT CMDBChangeOpSetAttribute WHERE CMDBChangeOpSetAttribute.id=10', - 'SELECT Ticket', - 'SELECT Ticket WHERE id=1', - 'SELECT Person', - 'SELECT Person WHERE id=1', - 'SELECT Server', - 'SELECT Server WHERE id=1', - 'SELECT Incident JOIN Person ON Incident.agent_id = Person.id WHERE Person.id = 5', - ); - $aStats = array(); - foreach ($aQueries as $sOQL) - { - $aStats[$sOQL] = $this->DoBenchmark($sOQL); - } - - $aData = array(); - foreach ($aStats as $sOQL => $aResults) - { - $aValues = array(); - $aValues['OQL'] = htmlentities($sOQL); - - foreach($aResults as $sDesc => $sInfo) - { - $aValues[$sDesc] = htmlentities($sInfo); - } - $aData[] = $aValues; - } - echo MyHelpers::make_table_from_assoc_array($aData); - } -} - -/////////////////////////////////////////////////////////////////////////// -// Test data load -/////////////////////////////////////////////////////////////////////////// - -class TestImportREST extends TestWebServices -{ - static public function GetName() - { - return 'CSV import (REST)'; - } - - static public function GetDescription() - { - return 'Test various options and fonctionality of import.php'; - } - - protected function DoExecSingleLoad($aLoadSpec) - { - $sCsvData = $aLoadSpec['csvdata']; - - echo "
          \n"; - echo "

          {$aLoadSpec['desc']}

          \n"; - - $aPostData = array('csvdata' => $sCsvData); - - $aGetParams = array(); - $aGetParamReport = array(); - foreach($aLoadSpec['args'] as $sArg => $sValue) - { - $aGetParams[] = $sArg.'='.urlencode($sValue); - $aGetParamReport[] = $sArg.'='.$sValue; - } - $sGetParams = implode('&', $aGetParams); - $sLogin = isset($aLoadSpec['login']) ? $aLoadSpec['login'] : 'admin'; - $sPassword = isset($aLoadSpec['password']) ? $aLoadSpec['password'] : 'admin'; - - $sRes = self::DoPostRequestAuth('../webservices/import.php?'.$sGetParams, $aPostData, $sLogin, $sPassword); - - $sArguments = implode('
          ', $aGetParamReport); - - if (strlen($sCsvData) > 5000) - { - $sCsvDataViewable = 'INPUT TOO LONG TO BE DISPLAYED ('.strlen($sCsvData).")\n".substr($sCsvData, 0, 500)."\n... TO BE CONTINUED"; - } - else - { - $sCsvDataViewable = $sCsvData; - } - - echo "
          \n"; - echo "
          \n"; - echo " $sArguments\n"; - echo "
          \n"; - echo "
          \n"; - echo "
          $sCsvDataViewable
          \n"; - echo "
          \n"; - echo "
          \n"; - - echo "
          $sRes
          \n"; - - echo "
          \n"; - } - - protected function DoExecute() - { - - $aLoads = array( - array( - 'desc' => 'Missing class', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - ), - 'csvdata' => "xxx", - ), - array( - 'desc' => 'Wrong class', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'toto', - ), - 'csvdata' => "xxx", - ), - array( - 'desc' => 'Wrong output type', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'NetworkDevice', - 'output' => 'onthefly', - ), - 'csvdata' => "xxx", - ), - array( - 'desc' => 'Wrong report level', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'NetworkDevice', - 'reportlevel' => 'errors|ouarnings|changed', - ), - 'csvdata' => "xxx", - ), - array( - 'desc' => 'Weird format, working anyhow...', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Server', - 'output' => 'details', - 'separator' => '*', - 'qualifier' => '@', - 'reconciliationkeys' => 'org_id,name', - ), - 'csvdata' => 'name*org_id - server01*2 - @server02@@combodo@* 2 - server45*99', - ), - array( - 'desc' => 'Load an organization', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Organization', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "name;code\nWorldCompany;WCY", - ), - array( - 'desc' => 'Load a location', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca", - ), - array( - 'desc' => 'Load a person', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Person', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789", - ), - array( - 'desc' => 'Load a person - wrong email format', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Person', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "email;name;first_name;org_id\nemailPASbon;Foo;John;1", - ), - array( - 'desc' => 'Load a team', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Team', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris", - ), - array( - 'desc' => 'Load server', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Server', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "name;status;owner_name;location_name;location_id->org_name;os_family;os_version;management_ip;cpu;ram;brand;model;serial_number\nlocalhost.;production;Demo;Grenoble;Demo;Ubuntu 9.10;2.6.31-19-generic-#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010;16.16.230.232;Intel(R) Core(TM)2 Duo CPU T7100 @ 1.80GHz;2005;Hewlett-Packard;HP Compaq 6510b (GM108UC#ABF);CNU7370BNP", - ), - array( - 'desc' => 'Load NW if', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'NetworkInterface', - 'output' => 'details', - 'reconciliationkeys' => '', - ), - 'csvdata' => "name;status;org_id;device_name;physical_type;ip_address;ip_mask;mac_address;speed\neth0;implementation;2;localhost.;ethernet;16.16.230.232;255.255.240.0;00:1a:4b:68:e3:97;\nlo;implementation;2;localhost.;ethernet;127.0.0.1;255.0.0.0;;", - ), - // Data Bruno - array( - 'desc' => 'Load NW devices from real life', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'NetworkDevice', - 'output' => 'details', - 'reconciliationkeys' => 'org_id->name,name', - ), - 'csvdata' => 'name;management_ip;importance;org_id->name;type - truc-machin-bidule;172.15.255.150;high;My Company/Department;switch - 10.15.255.222;10.15.255.222;high;My Company/Department;switch', - ), - array( - 'desc' => 'Load NW ifs', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'NetworkInterface', - 'output' => 'details', - 'reconciliationkeys' => 'device_id->name,name', - ), - 'csvdata' => 'device_id->name;org_id->name;name;ip_address;ip_mask;speed;link_type;mac_address;physical_type - truc-machin-bidule;My Company/Department;"GigabitEthernet44";;;0;downlink;00 12 F2 CB C4 EB ;ethernet - truc-machin-bidule;My Company/Department;"GigabitEthernet38";;;0;downlink;00 12 F2 CB C4 E5 ;ethernet - un-autre;My Company/Department;"GigabitEthernet2/3";;;1000000000;uplink;00 12 F2 20 0F 1A ;ethernet', - ), - array( - 'desc' => 'The simplest data load', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name\nParis", - ), - array( - 'desc' => 'The simplest data load + org', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name;org_id\nParis;2", - ), - array( - 'desc' => 'The simplest data load + org (name)', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name;org_name\nParis;Demo", - ), - array( - 'desc' => 'The simplest data load + org (code)', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name;org_id->code\nParis;DEMO", - ), - array( - 'desc' => 'Ouput: summary', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'summary', - ), - 'csvdata' => "name;org_id->code\nParis;DEMO", - ), - array( - 'desc' => 'Ouput: retcode', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'retcode', - ), - 'csvdata' => "name;org_id->code\nParis;DEMO", - ), - array( - 'desc' => 'Error in reconciliation list', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - 'reconciliationkeys' => 'org_id', - ), - 'csvdata' => "org_name;name\nDemo;Paris", - ), - array( - 'desc' => 'Error in attribute list that does not allow to compute reconciliation scheme', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "org_name;country\nDemo;France", - ), - array( - 'desc' => 'Error in attribute list - case A', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name;org\nParis;2", - ), - array( - 'desc' => 'Error in attribute list - case B1 (key->attcode)', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name;org->code\nParis;DEMO", - ), - array( - 'desc' => 'Error in attribute list - case B2 (key->attcode)', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - ), - 'csvdata' => "name;org_id->duns\nParis;DEMO", - ), - array( - 'desc' => 'Always changing... special comment in change tracking', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - 'comment' => 'automated testing' - ), - 'csvdata' => "org_name;name;address\nDemo;Le pantheon;Addresse bidon:".((string)microtime(true)), - ), - array( - 'desc' => 'Always changing... but "simulate"', - 'login' => 'admin', - 'password' => 'admin', - 'args' => array( - 'class' => 'Location', - 'output' => 'details', - 'simulate' => '1', - 'comment' => 'SHOULD NEVER APPEAR IN THE HISTORY' - ), - 'csvdata' => "org_name;name;address\nDemo;Le pantheon;restore address?", - ), - ); - - foreach ($aLoads as $aLoadSpec) - { - $this->DoExecSingleLoad($aLoadSpec); - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Test massive data load -/////////////////////////////////////////////////////////////////////////// -define('IMPORT_COUNT', 1000); - -class TestImportRESTMassive extends TestImportREST -{ - static public function GetName() - { - return 'CSV import (REST) - HUGE data set ('.IMPORT_COUNT.' PCs)'; - } - - static public function GetDescription() - { - return 'Stress import.php'; - } - - protected function DoExecute() - { - $aLoadSpec = array( - 'desc' => 'Missing class', - 'args' => array( - 'class' => 'PC', - 'output' => 'summary', - ), - 'csvdata' => "name;org_id;brand\n", - ); - for($i = 0 ; $i <= IMPORT_COUNT ; $i++) - { - $aLoadSpec['csvdata'] .= "pc.import.$i;2;Combodo\n"; - } - $this->DoExecSingleLoad($aLoadSpec); - } -} - - -/////////////////////////////////////////////////////////////////////////// -// Test SOAP services -/////////////////////////////////////////////////////////////////////////// - -$aWebServices = array( - array( - 'verb' => 'GetVersion', - 'expected result' => WebServices::GetVersion(), - 'explain result' => 'no comment!', - 'args' => array(), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'link attribute unknown + a CI not found', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'desc of ticket', /* sDescription */ - 'initial situation blah blah blah', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - new SOAPLinkCreationSpec( - 'InfrastructureCI', - array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), - array(new SOAPAttributeValue('impacting', 'very critical')) - ), - new SOAPLinkCreationSpec( - 'NetworkDevice', - array(new SOAPSearchCondition('name', 'switch01')), - array(new SOAPAttributeValue('impact', 'who cares')) - ), - new SOAPLinkCreationSpec( - 'Server', - array(new SOAPSearchCondition('name', 'thisone')), - array(new SOAPAttributeValue('impact', 'our lives')) - ), - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'caller not specified', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'PC burning', /* sDescription */ - 'The power supply suddenly started to warm up', /* sInitialSituation */ - new SOAPExternalKeySearch(), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - new SOAPLinkCreationSpec( - 'InfrastructureCI', - array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), - array() - ), /* aImpact */ - ), - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong class on CI to attach', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'PC burning', /* sDescription */ - 'The power supply suddenly started to warm up', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - new SOAPLinkCreationSpec( - 'logInfra', - array(new SOAPSearchCondition('dummyfiltercode', 2)), - array(new SOAPAttributeValue('impact', 'very critical')) - ), - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong search condition on CI to attach', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'PC burning', /* sDescription */ - 'The power supply suddenly started to warm up', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - new SOAPLinkCreationSpec( - 'InfrastructureCI', - array(new SOAPSearchCondition('dummyfiltercode', 2)), - array(new SOAPAttributeValue('impact', 'very critical')) - ), - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'no CI to attach (empty array)', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'no CI to attach (null)', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - null, /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => true, - 'explain result' => 'caller unknown', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1000))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong values for impact and urgency', - 'args' => array( - 'admin', /* sLogin */ - 'admin', /* sPassword */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - '6', /* sImpact */ - '7', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong password', - 'args' => array( - 'admin', /* sLogin */ - 'xxxxx', /* sPassword */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), - array( - 'verb' => 'CreateIncidentTicket', - 'expected result' => false, - 'explain result' => 'wrong login', - 'args' => array( - 'xxxxx', /* sLogin */ - 'yyyyy', /* sPassword */ - 'Houston not reachable', /* sDescription */ - 'Tried to join the shuttle', /* sInitialSituation */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ - 'sub product of the service', /* sProduct */ - new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ - array( - ), /* aImpact */ - '1', /* sImpact */ - '1', /* sUrgency */ - ), - ), -); - - -class TestSoap extends TestSoapWebService -{ - static public function GetName() {return 'Test SOAP';} - static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';} - - protected function DoExecute() - { - echo "

          Note: You may also want to try the sample SOAP client itopsoap.examples.php

          \n"; - - global $aSOAPMapping; - - // this file is generated dynamically with location = here - $sWsdlUri = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php'; - - ini_set("soap.wsdl_cache_enabled","0"); - $this->m_SoapClient = new SoapClient - ( - $sWsdlUri, - array( - 'classmap' => $aSOAPMapping, - 'trace' => 1, - ) - ); - - if (false) - { - self::DumpVariable($this->m_SoapClient->__getTypes()); - } - - global $aWebServices; - foreach ($aWebServices as $iPos => $aWebService) - { - echo "

          SOAP call #$iPos - {$aWebService['verb']}

          \n"; - echo "

          {$aWebService['explain result']}

          \n"; - - try - { - $oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']); - } - catch(SoapFault $e) - { - print "
          \n"; 
          -				print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
          -				print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
          -				print "
          "; - print "Response in HTML:

          ".$this->m_SoapClient->__getLastResponse()."

          "; - throw $e; - } - - self::DumpVariable($oRes); - - print "
          \n"; 
          -			print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
          -			print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
          -			print "
          "; - - if ($oRes instanceof SOAPResult) - { - $res = $oRes->status; - } - else - { - $res = $oRes; - } - if ($res != $aWebService['expected result']) - { - echo "Expecting:
          \n"; - var_dump($aWebService['expected result']); - echo "Obtained:
          \n"; - var_dump($res); - throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); - } - } - } -} - -class TestWebServicesDirect extends TestBizModel -{ - static public function GetName() {return 'Test web services locally';} - static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';} - - static public function GetConfigFile() {return '../config-itop.php';} - - protected function DoExecute() - { - $oWebServices = new WebServices(); - - global $aWebServices; - foreach ($aWebServices as $iPos => $aWebService) - { - echo "

          SOAP call #$iPos - {$aWebService['verb']}

          \n"; - echo "

          {$aWebService['explain result']}

          \n"; - $oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']); - self::DumpVariable($oRes); - - if ($oRes instanceof SOAPResult) - { - $res = $oRes->status; - } - else - { - $res = $oRes; - } - if ($res != $aWebService['expected result']) - { - echo "Expecting:
          \n"; - var_dump($aWebService['expected result']); - echo "Obtained:
          \n"; - var_dump($res); - throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); - } - } - return true; - } -} - -class TestTriggerAndEmail extends TestBizModel -{ - static public function GetName() {return 'Test trigger and email';} - static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';} - - static public function GetConfigFile() {return '../config-itop.php';} - - protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail) - { - $oAction = MetaModel::NewObject("ActionEmail"); - $oAction->Set("status", $sStatus); - $oAction->Set("name", "New server"); - $oAction->Set("test_recipient", $sTesterEmail); - $oAction->Set("from", $sTesterEmail); - $oAction->Set("reply_to", $sTesterEmail); - $oAction->Set("to", $sTo); - $oAction->Set("cc", $sCC); - $oAction->Set("bcc", ""); - $oAction->Set("subject", "New server: '\$this->name()$'"); - $oAction->Set("body", "

          Dear customer,

          We have created the server \$this->hyperlink()$ in the IT infrastructure database.

          You will be further notified when it is in Production.

          The IT infrastructure management team.

          Here are some accentuated characters for french people: 'ééà'

          "); - $oAction->Set("importance", "low"); - $iActionId = $this->ObjectToDB($oAction, true); - - $oLink = MetaModel::NewObject("lnkTriggerAction"); - $oLink->Set("trigger_id", $oTrigger->GetKey()); - $oLink->Set("action_id", $iActionId); - $oLink->Set("order", "1"); - $iLink = $this->ObjectToDB($oLink, true); - } - - protected function DoExecute() - { - $oMyPerson = MetaModel::NewObject("bizPerson"); - $oMyPerson->Set("name", "testemail1"); - $oMyPerson->Set("org_id", "1"); - $oMyPerson->Set("email", "romain.quetiez@hp.com"); - $iPersonId = $this->ObjectToDB($oMyPerson, true); - - $oMyPerson = MetaModel::NewObject("bizPerson"); - $oMyPerson->Set("name", "testemail2"); - $oMyPerson->Set("org_id", "1"); - $oMyPerson->Set("email", "denis.flaven@hp.com"); - $iPersonId = $this->ObjectToDB($oMyPerson, true); - - $oMyPerson = MetaModel::NewObject("bizPerson"); - $oMyPerson->Set("name", "testemail3"); - $oMyPerson->Set("org_id", "1"); - $oMyPerson->Set("email", "erwan.taloc@hp.com"); - $iPersonId = $this->ObjectToDB($oMyPerson, true); - - $oMyServer = MetaModel::NewObject("bizServer"); - $oMyServer->Set("name", "wfr.terminator.com"); - $oMyServer->Set("severity", "low"); - $oMyServer->Set("status", "production"); - $oMyServer->Set("org_id", 2); - $oMyServer->Set("location_id", 2); - $iServerId = $this->ObjectToDB($oMyServer, true); - - $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); - $oMyTrigger->Set("description", "Testor"); - $oMyTrigger->Set("target_class", "bizServer"); - $oMyTrigger->Set("state", "Shipped"); - $iTriggerId = $this->ObjectToDB($oMyTrigger, true); - - // Error in OQL field(s) - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "SELECT bizPerson WHERE naime = 'Dali'", - "SELECT bizServer", - 'romain.quetiez@hp.com' - ); - - // Error: no recipient - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "", - "", - 'romain.quetiez@hp.com' - ); - - // Test - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "SELECT bizPerson WHERE name LIKE 'testemail%'", - "SELECT bizPerson", - 'romain.quetiez@hp.com' - ); - - // Test failing because of a wrong test recipient address - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'test', - "SELECT bizPerson WHERE name LIKE 'testemail%'", - "", - 'toto@walibi.bg' - ); - - // Normal behavior - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'enabled', - "SELECT bizPerson WHERE name LIKE 'testemail%'", - "", - 'romain.quetiez@hp.com' - ); - - // Does nothing, because it is disabled - // - $this->CreateEmailSpec - ( - $oMyTrigger, - 'disabled', - "SELECT bizPerson WHERE name = 'testemail%'", - "", - 'romain.quetiez@hp.com' - ); - - $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); - - return true; - } -} -?> From 555dd76ade47243982c4e2af6d43057ced64f5c1 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 11:49:38 +0000 Subject: [PATCH 756/970] Test tools - removed references SVN:trunk[839] --- toolkit.php | 1 - 1 file changed, 1 deletion(-) diff --git a/toolkit.php b/toolkit.php index d0b92b45a9..f8b8035de9 100644 --- a/toolkit.php +++ b/toolkit.php @@ -39,7 +39,6 @@ echo "
          \n"; echo "Manage configurations
          \n"; -echo "Core unit tests
          \n"; echo "
          \n"; From 0a5baa85f41b7cd1c527429c63aa6ccb21db7725 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 11:58:09 +0000 Subject: [PATCH 757/970] Test tools reintroduced as a separate folder, most of the files being excluded from the std build SVN:trunk[840] --- config-dist.php | 37 - .../config-test-farm.php | 0 test/exclude.txt | 8 + test/test.class.inc.php | 557 +++++ test/test.php | 157 ++ test/testlist.inc.php | 2104 +++++++++++++++++ 6 files changed, 2826 insertions(+), 37 deletions(-) delete mode 100644 config-dist.php rename config-test-farm.php => test/config-test-farm.php (100%) create mode 100644 test/exclude.txt create mode 100644 test/test.class.inc.php create mode 100644 test/test.php create mode 100644 test/testlist.inc.php diff --git a/config-dist.php b/config-dist.php deleted file mode 100644 index b87bff0c2d..0000000000 --- a/config-dist.php +++ /dev/null @@ -1,37 +0,0 @@ - 'localhost', - 'db_user' => 'itop', - 'db_pwd' => '1T0p', - 'db_name' => 'itopv06', - 'db_subname' => '', // use it to differentiate two applications instances running on the same DB -); - -// Modules: file names should be specified as a absolute paths - -$MyModules = array( - 'application' => array ( - '../application/menunode.class.inc.php', - '../application/audit.rule.class.inc.php', - // to be continued... - ), - 'business' => array ( - // to be continued... - ), - 'addons' => array ( - 'user rights' => '../addons/userrights/userrightsprofile.class.inc.php', - // other modules to come later - ) -); - - -?> diff --git a/config-test-farm.php b/test/config-test-farm.php similarity index 100% rename from config-test-farm.php rename to test/config-test-farm.php diff --git a/test/exclude.txt b/test/exclude.txt new file mode 100644 index 0000000000..1e327f0fd0 --- /dev/null +++ b/test/exclude.txt @@ -0,0 +1,8 @@ +# +# The following source files are not re-distributed with the "build" of the application +# since they are used solely for constructing other files during the build process +# +config-test-farm.php +test.class.inc.php +test.php +testlist.inc.php diff --git a/test/test.class.inc.php b/test/test.class.inc.php new file mode 100644 index 0000000000..5ac7741c74 --- /dev/null +++ b/test/test.class.inc.php @@ -0,0 +1,557 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +require_once('../core/coreexception.class.inc.php'); +require_once('../core/attributedef.class.inc.php'); +require_once('../core/filterdef.class.inc.php'); +require_once('../core/stimulus.class.inc.php'); +require_once('../core/MyHelpers.class.inc.php'); + +require_once('../core/expression.class.inc.php'); +require_once('../core/cmdbsource.class.inc.php'); +require_once('../core/sqlquery.class.inc.php'); + +require_once('../core/log.class.inc.php'); +require_once('../core/kpi.class.inc.php'); + +require_once('../core/dbobject.class.php'); +require_once('../core/dbobjectsearch.class.php'); +require_once('../core/dbobjectset.class.php'); + +require_once('../application/cmdbabstract.class.inc.php'); + +require_once('../core/userrights.class.inc.php'); + +require_once('../webservices/webservices.class.inc.php'); + + +// Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?) +class UnitTestException extends Exception +{} + + +/** + * Improved display of the backtrace + * + * @package iTopORM + */ +class ExceptionFromError extends Exception +{ + public function getTraceAsHtml() + { + $aBackTrace = $this->getTrace(); + return MyHelpers::get_callstack_html(0, $this->getTrace()); + // return "
          \n".$this->getTraceAsString()."
          \n"; + } +} + + +/** + * Test handler API and basic helpers + * + * @package iTopORM + */ +abstract class TestHandler +{ + protected $m_aSuccesses; + protected $m_aWarnings; + protected $m_aErrors; + protected $m_sOutput; + + public function __construct() + { + $this->m_aSuccesses = array(); + $this->m_aWarnings = array(); + $this->m_aErrors = array(); + } + + static public function GetName() {return "fooname";} + static public function GetDescription(){return "foodesc";} + + protected function DoPrepare() {return true;} + abstract protected function DoExecute(); + protected function DoCleanup() {return true;} + + protected static function DumpVariable($var) + { + echo "
          \n"; 
          +		print_r($var);
          +		echo "
          \n"; + } + + protected function ReportSuccess($sMessage, $sSubtestId = '') + { + $this->m_aSuccesses[] = $sMessage; + } + + protected function ReportWarning($sMessage, $sSubtestId = '') + { + $this->m_aWarnings[] = $sMessage; + } + + protected function ReportError($sMessage, $sSubtestId = '') + { + $this->m_aErrors[] = $sMessage; + } + + public function GetResults() + { + return $this->m_aSuccesses; + } + + public function GetWarnings() + { + return $this->m_aWarnings; + } + + public function GetErrors() + { + return $this->m_aErrors; + } + + public function GetOutput() + { + return $this->m_sOutput; + } + + public function error_handler($errno, $errstr, $errfile, $errline) + { + // Note: return false to call the default handler (stop the program if an error) + + switch ($errno) + { + case E_USER_ERROR: + $this->ReportError($errstr); + //throw new ExceptionFromError("Fatal error in line $errline of file $errfile: $errstr"); + break; + case E_USER_WARNING: + $this->ReportWarning($errstr); + break; + case E_USER_NOTICE: + $this->ReportWarning($errstr); + break; + default: + $this->ReportWarning("Unknown error type: [$errno] $errstr in $errfile at $errline"); + echo "Unknown error type: [$errno] $errstr in $errfile at $errline
          \n"; + break; + } + return true; // do not call the default handler + } + + public function Execute() + { + ob_start(); + set_error_handler(array($this, 'error_handler')); + try + { + $this->DoPrepare(); + $this->DoExecute(); + } + catch (ExceptionFromError $e) + { + $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); + } + catch (CoreException $e) + { + //$this->ReportError($e->getMessage()); + //$this->ReportError($e->__tostring()); + $this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml()); + } + catch (Exception $e) + { + //$this->ReportError($e->getMessage()); + //$this->ReportError($e->__tostring()); + $this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString()); + } + restore_error_handler(); + $this->m_sOutput = ob_get_clean(); + return (count($this->GetErrors()) == 0); + } +} + + + + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + */ +abstract class TestFunction extends TestHandler +{ + // simply overload DoExecute (temporary) +} + + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + */ +abstract class TestWebServices extends TestHandler +{ + // simply overload DoExecute (temporary) + + static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null) + { + $aDataAndAuth = $aData; +// To be changed to use basic authentication + $aDataAndAuth['operation'] = 'login'; + $aDataAndAuth['auth_user'] = $sLogin; + $aDataAndAuth['auth_pwd'] = $sPassword; + $sHost = $_SERVER['HTTP_HOST']; + $sRawPath = $_SERVER['SCRIPT_NAME']; + $sPath = dirname($sRawPath); + $sUrl = "http://$sHost/$sPath/$sRelativeUrl"; + + return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders); + } + + // Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl + // originaly named after do_post_request + // Partially adapted to our coding conventions + static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null) + { + // $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request. + + $sData = http_build_query($aData); + + $aParams = array('http' => array( + 'method' => 'POST', + 'content' => $sData, + 'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n", + )); + if ($sOptionnalHeaders !== null) + { + $aParams['http']['header'] .= $sOptionnalHeaders; + } + $ctx = stream_context_create($aParams); + + $fp = @fopen($sUrl, 'rb', false, $ctx); + if (!$fp) + { + global $php_errormsg; + if (isset($php_errormsg)) + { + throw new Exception("Problem with $sUrl, $php_errormsg"); + } + else + { + throw new Exception("Problem with $sUrl"); + } + } + $response = @stream_get_contents($fp); + if ($response === false) + { + throw new Exception("Problem reading data from $sUrl, $php_errormsg"); + } + return $response; + } +} + +/** + * Test to execute a piece of code (checks if an error occurs) + * + * @package iTopORM + */ +abstract class TestSoapWebService extends TestHandler +{ + // simply overload DoExecute (temporary) + + function __construct() + { + parent::__construct(); + } +} + +/** + * Test to check that a function outputs some values depending on its input + * + * @package iTopORM + */ +abstract class TestFunctionInOut extends TestFunction +{ +// abstract static public function GetCallSpec(); // parameters to call_user_func +// abstract static public function GetInOut(); // array of input => output + + protected function DoExecute() + { + $aTests = $this->GetInOut(); + if (is_array($aTests)) + { + foreach ($aTests as $iTestId => $aTest) + { + $ret = call_user_func_array($this->GetCallSpec(), $aTest['args']); + if ($ret != $aTest['output']) + { + // Note: to be improved to cope with non string parameters + $this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId); + } + else + { + $this->ReportSuccess("Found the expected output '$ret'", $iTestId); + } + } + } + else + { + $ret = call_user_func($this->GetCallSpec()); + $this->ReportSuccess('Finished successfully'); + } + } +} + + +/** + * Test to check an URL (Searches for Error/Warning/Etc keywords) + * + * @package iTopORM + */ +abstract class TestUrl extends TestHandler +{ +// abstract static public function GetUrl(); +// abstract static public function GetErrorKeywords(); +// abstract static public function GetWarningKeywords(); + + protected function DoExecute() + { + return true; + } +} + + +/** + * Test to check a user management module + * + * @package iTopORM + */ +abstract class TestUserRights extends TestHandler +{ + protected function DoExecute() + { + return true; + } +} + + +/** + * Test to execute a scenario on a given DB + * + * @package iTopORM + */ +abstract class TestScenarioOnDB extends TestHandler +{ +// abstract static public function GetDBHost(); +// abstract static public function GetDBUser(); +// abstract static public function GetDBPwd(); +// abstract static public function GetDBName(); + + protected function DoPrepare() + { + $sDBHost = $this->GetDBHost(); + $sDBUser = $this->GetDBUser(); + $sDBPwd = $this->GetDBPwd(); + $sDBName = $this->GetDBName(); + + CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd); + CMDBSource::SetCharacterSet(); + if (CMDBSource::IsDB($sDBName)) + { + CMDBSource::DropDB($sDBName); + } + CMDBSource::CreateDB($sDBName); + } + + protected function DoCleanup() + { + // CMDBSource::DropDB($this->GetDBName()); + } +} + + +/** + * Test to use a business model on a given DB + * + * @package iTopORM + */ +abstract class TestBizModel extends TestHandler +{ +// abstract static public function GetDBSubName(); +// abstract static public function GetBusinessModelFile(); +// abstract static public function GetConfigFile(); + + protected function DoPrepare() + { + MetaModel::Startup($this->GetConfigFile()); +// #@# Temporary disabled by Romain +// MetaModel::CheckDefinitions(); + + // something here to create records... but that's another story + } + + protected $m_oChange; + protected function ObjectToDB($oNew, $bReload = false) + { + list($bRes, $aIssues) = $oNew->CheckToWrite(); + if (!$bRes) + { + throw new CoreException('Could not create object, unexpected values', array('issues' => $aIssues)); + } + if ($oNew instanceof CMDBObject) + { + if (!isset($this->m_oChange)) + { + new CMDBChange(); + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Someone doing some tests"); + $iChangeId = $oMyChange->DBInsertNoReload(); + $this->m_oChange = $oMyChange; + } + if ($bReload) + { + $iId = $oNew->DBInsertTracked($this->m_oChange); + } + else + { + $iId = $oNew->DBInsertTrackedNoReload($this->m_oChange); + } + } + else + { + if ($bReload) + { + $iId = $oNew->DBInsert(); + } + else + { + $iId = $oNew->DBInsertNoReload(); + } + } + return $iId; + } + + protected function ResetDB() + { + if (MetaModel::DBExists(false)) + { + MetaModel::DBDrop(); + } + MetaModel::DBCreate(); + } + + static protected function show_list($oObjectSet) + { + $oObjectSet->Rewind(); + $aData = array(); + while ($oItem = $oObjectSet->Fetch()) + { + $aValues = array(); + foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode) + { + $aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode); + } + //echo $oItem->GetKey()." => ".implode(", ", $aValues)."
          \n"; + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + } + + static protected function search_and_show_list(DBObjectSearch $oMyFilter) + { + $oObjSet = new CMDBObjectSet($oMyFilter); + echo $oMyFilter->__DescribeHTML()."' - Found ".$oObjSet->Count()." items.
          \n"; + self::show_list($oObjSet); + } + + static protected function search_and_show_list_from_oql($sOQL) + { + echo $sOQL."...
          \n"; + $oNewFilter = DBObjectSearch::FromOQL($sOQL); + self::search_and_show_list($oNewFilter); + } +} + + +/** + * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.) + * + * @package iTopORM + */ +abstract class TestBizModelGeneric extends TestBizModel +{ + static public function GetName() + { + return 'Full test on a given business model'; + } + + static public function GetDescription() + { + return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.'; + } + + protected function DoPrepare() + { + parent::DoPrepare(); + + if (!MetaModel::DBExists(false)) + { + MetaModel::DBCreate(); + } + // something here to create records... but that's another story + } + + protected function DoExecute() + { + foreach(MetaModel::GetClasses() as $sClassName) + { + if (MetaModel::HasTable($sClassName)) continue; + + $oNobody = MetaModel::GetObject($sClassName, 123); + $oBaby = new $sClassName; + $oFilter = new DBObjectSearch($sClassName); + + // Challenge reversibility of OQL / filter object + // + $sExpr1 = $oFilter->ToOQL(); + $oNewFilter = DBObjectSearch::FromOQL($sExpr1); + $sExpr2 = $oNewFilter->ToOQL(); + if ($sExpr1 != $sExpr2) + { + $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); + } + + // Use the filter (perform the query) + // + $oSet = new CMDBObjectSet($oFilter); + $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + } + return true; + } +} + + +?> diff --git a/test/test.php b/test/test.php new file mode 100644 index 0000000000..0664aac9b9 --- /dev/null +++ b/test/test.php @@ -0,0 +1,157 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +?> + +Missing mandatory argument $sName

          "; + exit; + } + return $value; +} + +function IsAValidTestClass($sClassName) +{ + // Must be a child of TestHandler + // + if (!is_subclass_of($sClassName, 'TestHandler')) return false; + + // Must not be abstract + // + $oReflectionClass = new ReflectionClass($sClassName); + if (!$oReflectionClass->isInstantiable()) return false; + + return true; +} + +function DisplayEvents($aEvents, $sTitle) +{ + echo "

          $sTitle

          \n"; + if (count($aEvents) > 0) + { + echo "
            \n"; + foreach ($aEvents as $sEvent) + { + echo "
          • $sEvent
          • \n"; + } + echo "
          \n"; + } + else + { + echo "

          none

          \n"; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Main +/////////////////////////////////////////////////////////////////////////////// + + +require_once('../application/utils.inc.php'); +require_once('../core/test.class.inc.php'); +require_once('./testlist.inc.php'); + +require_once('../core/cmdbobject.class.inc.php'); + +$sTodo = utils::ReadParam("todo", ""); +if ($sTodo == '') +{ + // Show the list of tests + // + echo "

          Existing tests

          \n"; + echo "
            \n"; + foreach (get_declared_classes() as $sClassName) + { + if (!IsAValidTestClass($sClassName)) continue; + + $sName = call_user_func(array($sClassName, 'GetName')); + $sDescription = call_user_func(array($sClassName, 'GetDescription')); + echo "
          • $sName ($sDescription)\n"; +} +else if ($sTodo == 'exec') +{ + // Execute a test + // + $sTestClass = ReadMandatoryParam("testid"); + + if (!IsAValidTestClass($sTestClass)) + { + echo "

            Wrong value for testid, expecting a valid class name

            \n"; + } + else + { + $oTest = new $sTestClass(); + echo "

            Testing: ".$oTest->GetName()."

            \n"; + $bRes = $oTest->Execute(); + } + +/* +MyHelpers::var_dump_html($oTest->GetResults()); +MyHelpers::var_dump_html($oTest->GetWarnings()); +MyHelpers::var_dump_html($oTest->GetErrors()); +*/ + + if ($bRes) + { + echo "

            Success :-)

            \n"; + DisplayEvents($oTest->GetResults(), 'Results'); + } + else + { + echo "

            Failure :-(

            \n"; + } + DisplayEvents($oTest->GetErrors(), 'Errors'); + DisplayEvents($oTest->GetWarnings(), 'Warnings'); + + // Render the output + // + echo "

            Actual output

            \n"; + echo "
            \n"; + echo $oTest->GetOutput(); + echo "
            \n"; +} +else +{ +} + + +?> diff --git a/test/testlist.inc.php b/test/testlist.inc.php new file mode 100644 index 0000000000..55c8872d58 --- /dev/null +++ b/test/testlist.inc.php @@ -0,0 +1,2104 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +class TestSQLQuery extends TestScenarioOnDB +{ + static public function GetName() {return 'SQLQuery';} + static public function GetDescription() {return 'SQLQuery does not depend on the rest of the framework, therefore it makes sense to have a separate test framework for it';} + + static public function GetDBHost() {return 'localhost';} + static public function GetDBUser() {return 'root';} + static public function GetDBPwd() {return '';} + static public function GetDBName() {return 'TestSQLQuery';} + static public function GetDBSubName() {return 'taratata';} + + + protected function DoPrepare() + { + parent::DoPrepare(); + cmdbSource::CreateTable('CREATE TABLE `myTable` (myKey INT(11) NOT NULL auto_increment, column1 VARCHAR(255), column2 VARCHAR(255), PRIMARY KEY (`myKey`)) ENGINE = '.MYSQL_ENGINE); + cmdbSource::CreateTable('CREATE TABLE `myTable1` (myKey1 INT(11) NOT NULL auto_increment, column1_1 VARCHAR(255), column1_2 VARCHAR(255), PRIMARY KEY (`myKey1`)) ENGINE = '.MYSQL_ENGINE); + cmdbSource::CreateTable('CREATE TABLE `myTable2` (myKey2 INT(11) NOT NULL auto_increment, column2_1 VARCHAR(255), column2_2 VARCHAR(255), PRIMARY KEY (`myKey2`)) ENGINE = '.MYSQL_ENGINE); + } + + protected function DoExecute() + { + $oQuery = new SQLQuery( + $sTable = 'myTable', + $sTableAlias = 'myTableAlias', + $aFields = array('column1'=>new FieldExpression('column1', 'myTableAlias'), 'column2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new BinaryExpression(new FieldExpression('column1', 'myTableAlias'), 'LIKE', new ScalarExpression('trash')), + $aFullTextNeedles = array('column1'), + $bToDelete = false, + $aValues = array() + ); + $oQuery->AddCondition(Expression::FromOQL('DATE(NOW() - 1200 * 2) > \'2008-07-31\'')); + + $oSubQuery1 = new SQLQuery( + $sTable = 'myTable1', + $sTableAlias = 'myTable1Alias', + $aFields = array('column1_1'=>new FieldExpression('column1', 'myTableAlias'), 'column1_2'=>new FieldExpression('column1', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oSubQuery2 = new SQLQuery( + $sTable = 'myTable2', + $sTableAlias = 'myTable2Alias', + $aFields = array('column2_1'=>new FieldExpression('column2', 'myTableAlias'), 'column2_2'=>new FieldExpression('column2', 'myTableAlias')), + $oCondition = new TrueSQLExpression, + $aFullTextNeedles = array(), + $bToDelete = false, + $aValues = array() + ); + + $oQuery->AddInnerJoin($oSubQuery1, 'column1', 'column1_1'); + $oQuery->AddLeftJoin($oSubQuery2, 'column2', 'column2_2'); + + $oQuery->DisplayHtml(); + $oQuery->RenderDelete(); + $oQuery->RenderUpdate(); + echo '

            '.$oQuery->RenderSelect().'

            '; + $oQuery->RenderSelect(array('column1')); + $oQuery->RenderSelect(array('column1', 'column2')); + } +} + +class TestOQLParser extends TestFunction +{ + static public function GetName() {return 'Check OQL parsing';} + static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + $oOql = new OqlInterpreter($sQuery); + try + { + $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery + self::DumpVariable($oTrash); + } + catch (OQLException $OqlException) + { + if ($bIsCorrectQuery) + { + echo "

            More info on this unexpected failure:
            ".$OqlException->getHtmlDesc()."

            \n"; + throw $OqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

            More info on this expected failure:
            ".$OqlException->getHtmlDesc()."

            \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if ($bIsCorrectQuery) + { + return true; + } + else + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + } + + protected function TestQuery($sQuery, $bIsCorrectQuery) + { + if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) + { + return false; + } + return true; + } + + public function DoExecute() + { + $aQueries = array( + 'SELECT toto' => true, + 'SELECT toto WHERE toto.a = 1' => true, + 'SELECT toto WHERE toto.a=1' => true, + 'SELECT toto WHERE toto.a = "1"' => true, + 'SELECT toto WHHHERE toto.a = "1"' => false, + 'SELECT toto WHERE toto.a == "1"' => false, + 'SELECT toto WHERE toto.a % 1' => false, + //'SELECT toto WHERE toto.a LIKE 1' => false, + 'SELECT toto WHERE toto.a like \'arg\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, + 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, + 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, + 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, + + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, + 'SELECT toto WHERE toto.a NOT LIKE ""' => true, + 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, + "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, + "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, + + 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, + + //'SELECT toto WHERE toto.a > \'asd\'' => false, + 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id' => true, + 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, + + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, + "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, + + 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, + + // Several objects in a row... + // + 'SELECT A FROM A' => true, + 'SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, + 'SELECT A, B,C FROM A JOIN B ON A.myB = B.id' => false, + 'SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => false, + ); + + $iErrors = 0; + + foreach($aQueries as $sQuery => $bIsCorrectQuery) + { + $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; + echo "

            Testing query: $sQuery ($sIsOk)

            \n"; + $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); + if (!$bRet) $iErrors++; + } + + return ($iErrors == 0); + } +} + + +class TestCSVParser extends TestFunction +{ + static public function GetName() {return 'Check CSV parsing';} + static public function GetDescription() {return 'Loads a set of CSV data';} + + public function DoExecute() + { + $sDataFile = '?field1?;?field2?;?field3? +?a?;?b?;?c? +a;b;c + ? a ? ; ? b ? ; ? c ? + a ; b ; c +??;??;?? +;; +?a"?;?b?;?c? +?a1 +a2?;?b?;?c? +?a1,a2?;?b?;?c? +?a?;?b?;?c1,",c2 +,c3? +?a?;?b?;?ouf !? + Espace sur la fin ; 1234; e@taloc.com '; + + self::DumpVariable($sDataFile); + + $aExpectedResult = array( + //array('field1', 'field2', 'field3'), + array('a', 'b', 'c'), + array('a', 'b', 'c'), + array(' a ', ' b ', ' c '), + array('a', 'b', 'c'), + array('', '', ''), + array('', '', ''), + array('a"', 'b', 'c'), + array("a1\na2", 'b', 'c'), + array('a1,a2', 'b', 'c'), + array('a', 'b', "c1,\",c2\n,c3"), + array('a', 'b', 'ouf !'), + array('Espace sur la fin', '1234', 'e@taloc.com'), + ); + + $oCSVParser = new CSVParser($sDataFile, ';', '?'); + $aData = $oCSVParser->ToArray(1, null, 0); + + $iIssues = 0; + + echo "\n"; + foreach ($aData as $iRow => $aRow) + { + echo "\n"; + foreach ($aRow as $iCol => $sCell) + { + if (empty($sCell)) + { + $sCellValue = ' '; + } + else + { + $sCellValue = htmlentities($sCell); + } + + if (!isset($aExpectedResult[$iRow][$iCol])) + { + $iIssues++; + $sCellValue = "$sCellValue"; + } + elseif ($aExpectedResult[$iRow][$iCol] != $sCell) + { + $iIssues++; + $sCellValue = "$sCellValue, expecting '".$aExpectedResult[$iRow][$iCol]."'"; + } + + echo ""; + } + echo "\n"; + } + echo "
            $sCellValue
            \n"; + return ($iIssues > 0); + } +} + +class TestGenericItoMyModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} +} + +class TestGenericItopBigModel extends TestBizModelGeneric +{ + static public function GetName() + { + return 'Generic RO test on '.self::GetConfigFile(); + } + + static public function GetConfigFile() {return '../config-test-itopv06.php';} +} + +class TestUserRightsMatrixItop extends TestUserRights +{ + static public function GetName() + { + return 'User rights test on user rights matrix'; + } + + static public function GetDescription() + { + return 'blah blah blah'; + } + + public function DoPrepare() + { + parent::DoPrepare(); + MetaModel::Startup('../config-test-itopv06.php'); + } + + protected function DoExecute() + { + $sUser = 'Romain'; + echo "

            Totor: ".(UserRights::CheckCredentials('Totor', 'toto') ? 'ok' : 'NO')."

            \n"; + echo "

            Romain: ".(UserRights::CheckCredentials('Romain', 'toto') ? 'ok' : 'NO')."

            \n"; + echo "

            User: ".UserRights::GetUser()."

            \n"; + echo "

            On behalf of...".UserRights::GetRealUser()."

            \n"; + + echo "

            Denis (impersonate) : ".(UserRights::Impersonate('Denis', 'tutu') ? 'ok' : 'NO')."

            \n"; + echo "

            User: ".UserRights::GetUser()."

            \n"; + echo "

            On behalf of...".UserRights::GetRealUser()."

            \n"; + + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT bizOrganization")); + echo "

            IsActionAllowed...".(UserRights::IsActionAllowed('bizOrganization', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

            \n"; + echo "

            IsStimulusAllowed...".(UserRights::IsStimulusAllowed('bizOrganization', 'myStimulus', $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

            \n"; + echo "

            IsActionAllowedOnAttribute...".(UserRights::IsActionAllowedOnAttribute('bizOrganization', 'myattribute', UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES ? 'ok' : 'NO')."

            \n"; + return true; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +class TestMyBizModel extends TestBizModel +{ + static public function GetName() + { + return 'A series of tests on a weird business model'; + } + + static public function GetDescription() + { + return 'Attempts various operations and build complex queries'; + } + + static public function GetConfigFile() {return '../config-test-mymodel.php';} + + function test_linksinfo() + { + echo "

            Enum links

            "; + self::DumpVariable(MetaModel::EnumReferencedClasses("cmdbTeam")); + self::DumpVariable(MetaModel::EnumReferencingClasses("Organization")); + + self::DumpVariable(MetaModel::EnumLinkingClasses()); + self::DumpVariable(MetaModel::EnumLinkingClasses("cmdbContact")); + self::DumpVariable(MetaModel::EnumLinkingClasses("cmdWorkshop")); + self::DumpVariable(MetaModel::GetLinkLabel("Liens_entre_contacts_et_workshop", "toworkshop")); + } + + function test_list_attributes() + { + echo "

            List attributes

            "; + foreach(MetaModel::ListAttributeDefs("cmdbTeam") as $sAttCode=>$oAttDef) + { + echo $oAttDef->GetLabel()." / ".$oAttDef->GetDescription()." / ".$oAttDef->GetType()."
            \n"; + } + } + + function test_search() + { + echo "

            Two searches

            "; + $oFilterAllDevs = new DBObjectSearch("cmdbTeam"); + $oAllDevs = new DBObjectSet($oFilterAllDevs); + + echo "Found ".$oAllDevs->Count()." items.
            \n"; + while ($oDev = $oAllDevs->Fetch()) + { + $aValues = array(); + foreach(MetaModel::GetAttributesList($oAllDevs->GetClass()) as $sAttCode) + { + $aValues[] = MetaModel::GetLabel(get_class($oDev), $sAttCode)." (".MetaModel::GetDescription(get_class($oDev), $sAttCode).") = ".$oDev->GetAsHTML($sAttCode); + } + echo $oDev->GetKey()." => ".implode(", ", $aValues)."
            \n"; + } + + // a second one + $oMyFilter = new DBObjectSearch("cmdbContact"); + //$oMyFilter->AddCondition("name", "aii", "Finishes with"); + $oMyFilter->AddCondition("name", "aii"); + $this->search_and_show_list($oMyFilter); + + } + + function test_reload() + { + echo "

            Reload

            "; + $team = MetaModel::GetObject("cmdbContact", "2"); + echo "Chargement de l'attribut headcount: {$team->Get("headcount")}
            \n"; + self::DumpVariable($team); + } + + function test_setattribute() + { + echo "

            Set attribute and update

            "; + $team = MetaModel::GetObject("cmdbTeam", "2"); + $team->Set("headcount", rand(1,1000)); + $team->Set("email", "Luis ".rand(9,250)); + self::DumpVariable($team->ListChanges()); + echo "New headcount = {$team->Get("headcount")}
            \n"; + echo "Computed name = {$team->Get("name")}
            \n"; + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_setattribute / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + //MetaModel::StartDebugQuery(); + $team->DBUpdateTracked($oMyChange); + //MetaModel::StopDebugQuery(); + + echo "

            Check the modified team

            "; + $oTeam = MetaModel::GetObject("cmdbTeam", "2"); + self::DumpVariable($oTeam); + } + function test_newobject() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_newobject / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + echo "

            Create a new object (team)

            "; + $oNewTeam = MetaModel::NewObject("cmdbTeam"); + $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); + $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); + $oNewTeam->Set("email", null); + $oNewTeam->Set("owner", "ITOP"); + $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
            "; + echo "

            Delete team #$iId

            "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
            "; + self::DumpVariable($oTeam); + } + + + function test_updatecolumn() + { + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_updatecolumn / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + + $sNewEmail = "updatecol".rand(9,250)."@quedlaballe.com"; + echo "

            Update a the email: set to '$sNewEmail'

            "; + $oMyFilter = new DBObjectSearch("cmdbContact"); + $oMyFilter->AddCondition("name", "o", "Contains"); + + echo "Candidates before:
            "; + $this->search_and_show_list($oMyFilter); + + MetaModel::BulkUpdateTracked($oMyChange, $oMyFilter, array("email" => $sNewEmail)); + + echo "Candidates after:
            "; + $this->search_and_show_list($oMyFilter); + } + + function test_error() + { + trigger_error("Stop requested", E_USER_ERROR); + } + + function test_changetracking() + { + echo "

            Create a change

            "; + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + echo "Created new change: $iChangeId
            "; + self::DumpVariable($oMyChange); + + echo "

            Create a new object (team)

            "; + $oNewTeam = MetaModel::NewObject("cmdbTeam"); + $oNewTeam->Set("name", "ekip2choc #".rand(1000, 2000)); + $oNewTeam->Set("email", "machin".rand(1,100)."@tnut.com"); + $oNewTeam->Set("email", null); + $oNewTeam->Set("owner", "ITOP"); + $oNewTeam->Set("headcount", "0".rand(38000, 38999)); // should be reset to an int value + $iId = $oNewTeam->DBInsertTracked($oMyChange); + echo "Created new team: $iId
            "; + echo "

            Delete team #$iId

            "; + $oTeam = MetaModel::GetObject("cmdbTeam", $iId); + $oTeam->DBDeleteTracked($oMyChange); + echo "Deleted team: $iId
            "; + self::DumpVariable($oTeam); + } + + function test_zlist() + { + echo "

            Test ZLists

            "; + $aZLists = MetaModel::EnumZLists(); + foreach ($aZLists as $sListCode) + { + $aListInfos = MetaModel::GetZListInfo($sListCode); + echo "

            List '".$sListCode."' (".$aListInfos["description"].") of type '".$aListInfos["type"]."'

            \n"; + + foreach (MetaModel::GetSubclasses("cmdbObjectHomeMade") as $sKlass) + { + $aItems = MetaModel::GetZListItems($sKlass, $sListCode); + if (count($aItems) == 0) continue; + + echo "$sKlass - $sListCode : {".implode(", ", $aItems)."}
            \n"; + } + } + + echo "

            IsAttributeInZList()...

            "; + echo "Liens_entre_contacts_et_workshop::ws_info in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "ws_info") ? "yes" : "no")."
            \n"; + echo "Liens_entre_contacts_et_workshop::toworkshop in list1 ? ".(MetaModel::IsAttributeInZList("Liens_entre_contacts_et_workshop", "list1", "toworkshop") ? "yes" : "no")."
            \n"; + + } + + function test_pkey() + { + echo "

            Test search on pkey

            "; + $sExpr1 = "SELECT cmdbContact WHERE id IN (40, 42)"; + $sExpr2 = "SELECT cmdbContact WHERE IN NOT IN (40, 42)"; + $this->search_and_show_list_from_oql($sExpr1); + $this->search_and_show_list_from_oql($sExpr2); + + echo "Et maintenant, on fusionne....
            \n"; + $oSet1 = new CMDBObjectSet(DBObjectSearch::FromOQL($sExpr1)); + $oSet2 = new CMDBObjectSet(DBObjectSearch::FromOQL($sExpr2)); + $oIntersect = $oSet1->CreateIntersect($oSet2); + $oDelta = $oSet1->CreateDelta($oSet2); + + $oMerge = clone $oSet1; + $oMerge->Merge($oSet2); + $oMerge->Merge($oSet2); + + echo "Set1 - Found ".$oSet1->Count()." items.
            \n"; + echo "Set2 - Found ".$oSet2->Count()." items.
            \n"; + echo "Intersect - Found ".$oIntersect->Count()." items.
            \n"; + echo "Delta - Found ".$oDelta->Count()." items.
            \n"; + echo "Merge - Found ".$oMerge->Count()." items.
            \n"; + //$this->show_list($oObjSet); + } + + function test_relations() + { + echo "

            Test relations

            "; + + //self::DumpVariable(MetaModel::EnumRelationQueries("cmdbObjectHomeMade", "Potes")); + self::DumpVariable(MetaModel::EnumRelationQueries("cmdbContact", "Potes")); + + $iMaxDepth = 9; + echo "Max depth = $iMaxDepth
            \n"; + + $oObj = MetaModel::GetObject("cmdbContact", 18); + $aRels = $oObj->GetRelatedObjects("Potes", $iMaxDepth); + echo $oObj->Get('name')." has some 'Potes'...
            \n"; + foreach ($aRels as $sClass => $aObjs) + { + echo "$sClass, count = ".count($aObjs)." => ".implode(', ', array_keys($aObjs))."
            \n"; + $oObjectSet = CMDBObjectSet::FromArray($sClass, $aObjs); + $this->show_list($oObjectSet); + } + + echo "

            Test relations - same results, by the mean of a OQL

            "; + $this->search_and_show_list_from_oql("cmdbContact: RELATED (Potes, $iMaxDepth) TO (cmdbContact: pkey = 18)"); + + } + + function test_linkedset() + { + echo "

            Linked set attributes

            \n"; + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
            Current workshops
            \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + echo "
            Setting workshops
            \n"; + $oNewLink = new cmdbLiens(); + $oNewLink->Set('toworkshop', 2); + $oNewLink->Set('function', 'mafonctioooon'); + $oNewLink->Set('a1', 'tralala1'); + $oNewLink->Set('a2', 'F7M'); + $oSetWorkshops = CMDBObjectSet::FromArray("cmdbLiens", array($oNewLink)); + $oObj->Set("myworkshops", $oSetWorkshops); + $this->show_list($oSetWorkshops); + + echo "
            New workshops
            \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "test_linkedset / Made by robot #".rand(1,100)); + $iChangeId = $oMyChange->DBInsert(); + $oObj->DBUpdateTracked($oMyChange); + $oObj = MetaModel::GetObject("cmdbContact", 18); + + echo "
            After the write
            \n"; + $oSetWorkshopsCurr = $oObj->Get("myworkshops"); + $this->show_list($oSetWorkshopsCurr); + } + + function test_object_lifecycle() + { + echo "

            Test object lifecycle

            "; + + + self::DumpVariable(MetaModel::GetStateAttributeCode("cmdbContact")); + self::DumpVariable(MetaModel::EnumStates("cmdbContact")); + self::DumpVariable(MetaModel::EnumStimuli("cmdbContact")); + foreach(MetaModel::EnumStates("cmdbContact") as $sStateCode => $aStateDef) + { + echo "

            Transition from $sStateCode

            \n"; + self::DumpVariable(MetaModel::EnumTransitions("cmdbContact", $sStateCode)); + } + + $oObj = MetaModel::GetObject("cmdbContact", 18); + echo "Current state: ".$oObj->GetState()."... let's go to school..."; + self::DumpVariable($oObj->EnumTransitions()); + $oObj->ApplyStimulus("toschool"); + echo "New state: ".$oObj->GetState()."... let's get older..."; + self::DumpVariable($oObj->EnumTransitions()); + $oObj->ApplyStimulus("raise"); + echo "New state: ".$oObj->GetState()."... let's try to go further... (should give an error)"; + self::DumpVariable($oObj->EnumTransitions()); + $oObj->ApplyStimulus("raise"); // should give an error + } + + + protected function DoExecute() + { +// $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + //$this->test_linksinfo(); + //$this->test_list_attributes(); + //$this->test_search(); + //$this->test_reload(); + //$this->test_newobject(); + $this->test_setattribute(); + //$this->test_updatecolumn(); + //$this->test_error(); + //$this->test_changetracking(); + $this->test_zlist(); + $this->test_OQL(); + //$this->test_pkey(); + $this->test_relations(); + $this->test_linkedset(); + $this->test_object_lifecycle(); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test a complex biz model on the fly +/////////////////////////////////////////////////////////////////////////// + +abstract class MyFarm extends TestBizModel +{ + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth) + { + $oNew = MetaModel::NewObject('Mammal'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('name', $sName); + $oNew->Set('height', $iHeight); + $oNew->Set('birth', $sBirth); + return $this->ObjectToDB($oNew); + } + + protected function InsertBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId) + { + $oNew = MetaModel::NewObject('Bird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + return $this->ObjectToDB($oNew); + } + + protected function InsertFlyingBird($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $iFlyingSpeed) + { + $oNew = MetaModel::NewObject('FlyingBird'); + $oNew->Set('species', $sSpecies); + $oNew->Set('sex', $sSex); + $oNew->Set('speed', $iSpeed); + $oNew->Set('mother', $iMotherid); + $oNew->Set('father', $iFatherId); + $oNew->Set('flyingspeed', $iFlyingSpeed); + return $this->ObjectToDB($oNew); + } + + private function InsertGroup($sName, $iLeaderId) + { + $oNew = MetaModel::NewObject('Group'); + $oNew->Set('name', $sName); + $oNew->Set('leader', $iLeaderId); + $iId = $oNew->DBInsertNoReload(); + return $iId; + } +} + + +class TestQueriesOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test'; + } + + static public function GetDescription() + { + return 'A series of tests on the farm business model (SQL generation)'; + } + + protected function CheckQuery($sQuery, $bIsCorrectQuery) + { + if ($bIsCorrectQuery) + { + echo "

            $sQuery

            \n"; + } + else + { + echo "

            $sQuery

            \n"; + } + try + { + //$oOql = new OqlInterpreter($sQuery); + //$oTrash = $oOql->ParseObjectQuery(); + //self::DumpVariable($oTrash, true); + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + catch (OQLException $oOqlException) + { + if ($bIsCorrectQuery) + { + echo "

            More info on this unexpected failure:
            ".$oOqlException->getHtmlDesc()."

            \n"; + throw $oOqlException; + return false; + } + else + { + // Everything is fine :-) + echo "

            More info on this expected failure:\n"; + echo "

              \n"; + echo "
            • ".get_class($oOqlException)."
            • \n"; + echo "
            • ".$oOqlException->getMessage()."
            • \n"; + echo "
            • ".$oOqlException->getHtmlDesc()."
            • \n"; + echo "
            \n"; + echo "

            \n"; + return true; + } + } + // The query was correctly parsed, was it expected to be correct ? + if (!$bIsCorrectQuery) + { + throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); + return false; + } + echo "

            To OQL: ".$oMyFilter->ToOQL()."

            "; + + $this->search_and_show_list($oMyFilter); + + //echo "

            first pass

            \n"; + //self::DumpVariable($oMyFilter, true); + $sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + //echo "

            second pass

            \n"; + //self::DumpVariable($oMyFilter, true); + //$sQuery1 = MetaModel::MakeSelectQuery($oMyFilter); + + $sSerialize = $oMyFilter->serialize(); + echo "

            Serialized:$sSerialize

            \n"; + $oFilter2 = DBObjectSearch::unserialize($sSerialize); + try + { + $sQuery2 = MetaModel::MakeSelectQuery($oFilter2); + } + catch (Exception $e) + { + echo "

            Could not compute the query after unserialize

            \n"; + echo "

            Query 1: $sQuery1

            \n"; + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + throw $e; + } + //if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same! + if ($sQuery1 != $sQuery2) + { + echo "

            serialize/unserialize mismatch :-(

            \n"; + MyHelpers::var_cmp_html($sQuery1, $sQuery2); + MyHelpers::var_cmp_html($oMyFilter, $oFilter2); + return false; + } + return true; + } + + protected function DoExecute() + { +// $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + echo "

            Create protagonists...

            "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + // Benchmarking + // + if (false) + { + define ('COUNT_BENCHMARK', 10); + echo "

            Parsing a long query, ".COUNT_BENCHMARK." times

            "; + $sQuery = "SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR) AND Dad.height * 2 <= ROUND(TO_DAYS(Dad.birth) / (3 + 1) * 5 - 3)"; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oMyFilter = DBObjectSearch::FromOQL($sQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + echo "

            Mean time by op: $fParsingDuration

            "; + } + + echo "

            Test queries...

            "; + + $aQueries = array( + 'SELECT Animal' => true, + 'SELECT Animal WHERE Animal.pkey = 1' => false, + 'SELECT Animal WHERE Animal.id = 1' => true, + 'SELECT Aniiimal' => false, + 'SELECTe Animal' => false, + 'SELECT * FROM Animal' => false, + 'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE species = \'human\'' => true, + 'SELECT Animal AS zoo WHERE espece = \'human\'' => false, + 'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false, + 'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true, + 'SELECT Animal AS zoo WHERE zoo.species NOT IN (\'human\', "pig")' => true, + 'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false, + 'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true, + 'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true, + 'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true, + 'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true, + 'SELECT Mammal AS m WHERE m.birth < DATE_SUB(CURRENT_DATE(), INTERVAL 10 YEAR)' => true, + 'SELECT Mammal AS m WHERE m.birth > DATE_SUB(NOW(), INTERVAL 2000 DAY)' => true, + 'SELECT Mammal AS m WHERE (TO_DAYS(NOW()) - TO_DAYS(m.birth)) > 2000' => true, + 'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true, + 'SELECT Mammal AS m WHERE (1 + 2' => false, + 'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true, + 'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true, + 'SELECT Mammal AS m WHERE 1/0' => true, + 'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true, + 'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true, + 'SELECT Group JOIN Animal ON Group.leader = Animal.id' => true, + 'SELECT Animal AS A JOIN Group AS G1 ON G1.leader = A.id' => true, + 'SELECT Animal AS A JOIN Group AS G ON FooClass.leader = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = FooClass.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.masterchief = A.id' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true, + 'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false, + 'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true, + 'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false, + 'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false, + 'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false, + 'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true, + 'SELECT Group AS G WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true, + 'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true, + 'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true, + 'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true, + 'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true, + 'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true, + // Specifying multiple objects + 'SELECT Animal FROM Animal' => true, + 'SELECT yelele FROM Animal' => false, + 'SELECT Animal FROM Animal AS A' => false, + 'SELECT A FROM Animal AS A' => true, + ); + //$aQueries = array( + // 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true, + //); + foreach($aQueries as $sQuery => $bIsCorrect) + { + $this->CheckQuery($sQuery, $bIsCorrect); + } + return true; + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestBulkChangeOnFarm extends TestBizModel +{ + static public function GetName() + { + return 'Farm test - data load'; + } + + static public function GetDescription() + { + return 'Bulk load'; + } + + static public function GetConfigFile() {return '../config-test-farm.php';} + + protected function DoPrepare() + { + parent::DoPrepare(); + $this->ResetDB(); + MetaModel::DBCheckIntegrity(); + } + + protected function DoExecute() + { +// $this->ReportError("Found two different OQL expression out of the (same?) filter: $sExpr1 != $sExpr2"); +// $this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName"); + + $oParser = new CSVParser("denomination,hauteur,age + suzy,123,2009-01-01 + chita,456, + "); + $aData = $oParser->ToArray(array('_name', '_height', '_birth'), ','); + self::DumpVariable($aData); + + $oBulk = new BulkChange( + 'Mammal', + $aData, + // attributes + array('name' => '_name', 'height' => '_height', 'birth' => '_birth'), + // ext keys + array(), + // reconciliation + array('name') + ); + + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $oMyChange->Set("userinfo", "Testor"); + $iChangeId = $oMyChange->DBInsert(); +// echo "Created new change: $iChangeId
            "; + + echo "

            Planned for loading...

            "; + $aRes = $oBulk->Process(); + self::DumpVariable($aRes); + echo "

            Go for loading...

            "; + $aRes = $oBulk->Process($oMyChange); + self::DumpVariable($aRes); + + return; + + $oRawData = array( + 'Mammal', + array('species', 'sex', 'speed', 'mother', 'father', 'name', 'height', 'birth'), + "human,male,23,0,0,romulus,192,1971 + human,male,23,0,0,remus,154,-50 + human,male,23,0,0,julius,160,-49 + human,female,23,0,0,cleopatra,142,-50 + pig,female,23,0,0,confucius,50,2003" + ); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestFullTextSearchOnFarm extends MyFarm +{ + static public function GetName() + { + return 'Farm test - full text search'; + } + + static public function GetDescription() + { + return 'Focus on the full text search feature'; + } + + protected function DoExecute() + { + echo "

            Create protagonists...

            "; + + $iId1 = $this->InsertMammal('human', 'male', 10, 0, 0, 'romanoff', 192, '1971-07-19'); + $iId2 = $this->InsertMammal('human', 'female', 9, 0, 0, 'rouanita', 165, '1983-01-23'); + $this->InsertMammal('human', 'female', 3, $iId2, $iId1, 'pomme', 169, '2008-02-23'); + $this->InsertMammal('pig', 'female', 3, 0, 0, 'grouinkette', 85, '2006-06-01'); + $this->InsertMammal('donkey', 'female', 3, 0, 0, 'muleta', 124, '2003-11-11'); + + $this->InsertBird('rooster', 'male', 12, 0, 0); + $this->InsertFlyingBird('pie', 'female', 11, 0, 0, 35); + + echo "

            Search...

            "; + $oSearch = new DBObjectSearch('Mammal'); + $oSearch->AddCondition_FullText('manof'); + //$oResultSet = new DBObjectSet($oSearch); + $this->search_and_show_list($oSearch); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Benchmark queries +/////////////////////////////////////////////////////////////////////////// + +class TestItopEfficiency extends TestBizModel +{ + static public function GetName() + { + return 'Itop - benchmark'; + } + + static public function GetDescription() + { + return 'Measure time to perform the queries'; + } + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoBenchmark($sOqlQuery) + { + echo "

            Testing query: $sOqlQuery

            "; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $oFilter = DBObjectSearch::FromOQL($sOqlQuery); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fParsingDuration = $fDuration / COUNT_BENCHMARK; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $sSQL = MetaModel::MakeSelectQuery($oFilter); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fBuildDuration = $fDuration / COUNT_BENCHMARK; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $res = CMDBSource::Query($sSQL); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fQueryDuration = $fDuration / COUNT_BENCHMARK; + + // The fetch could not be repeated with the same results + // But we've seen so far that is was very very quick to exec + // So it makes sense to benchmark it a single time + $fStart = MyHelpers::getmicrotime(); + $aRow = CMDBSource::FetchArray($res); + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fFetchDuration = $fDuration; + + $fStart = MyHelpers::getmicrotime(); + for($i=0 ; $i < COUNT_BENCHMARK ; $i++) + { + $sOql = $oFilter->ToOQL(); + } + $fDuration = MyHelpers::getmicrotime() - $fStart; + $fToOqlDuration = $fDuration / COUNT_BENCHMARK; + + echo "
              \n"; + echo "
            • Parsing: $fParsingDuration
            • \n"; + echo "
            • Build: $fBuildDuration
            • \n"; + echo "
            • Query: $fQueryDuration
            • \n"; + echo "
            • Fetch: $fFetchDuration
            • \n"; + echo "
            • ToOql: $fToOqlDuration
            • \n"; + echo "
            \n"; + + // Everything but the ToOQL (wich is interesting, anyhow) + $fTotal = $fParsingDuration + $fBuildDuration + $fQueryDuration + $fFetchDuration; + + return array( + 'rows' => CMDBSource::NbRows($res), + 'duration (s)' => round($fTotal, 4), + 'parsing (%)' => round(100 * $fParsingDuration / $fTotal, 1), + 'build SQL (%)' => round(100 * $fBuildDuration / $fTotal, 1), + 'query exec (%)' => round(100 * $fQueryDuration / $fTotal, 1), + 'fetch (%)' => round(100 * $fFetchDuration / $fTotal, 1), + 'to OQL (%)' => round(100 * $fToOqlDuration / $fTotal, 1), + 'parsing+build (%)' => round(100 * ($fParsingDuration + $fBuildDuration) / $fTotal, 1), + ); + } + + protected function DoExecute() + { + define ('COUNT_BENCHMARK', 3); + echo "

            The test will be repeated ".COUNT_BENCHMARK." times

            "; + + $aQueries = array( + 'SELECT CMDBChangeOpSetAttribute', + 'SELECT CMDBChangeOpSetAttribute WHERE id=10', + 'SELECT CMDBChangeOpSetAttribute WHERE id=123456789', + 'SELECT CMDBChangeOpSetAttribute WHERE CMDBChangeOpSetAttribute.id=10', + 'SELECT Ticket', + 'SELECT Ticket WHERE id=1', + 'SELECT Person', + 'SELECT Person WHERE id=1', + 'SELECT Server', + 'SELECT Server WHERE id=1', + 'SELECT Incident JOIN Person ON Incident.agent_id = Person.id WHERE Person.id = 5', + ); + $aStats = array(); + foreach ($aQueries as $sOQL) + { + $aStats[$sOQL] = $this->DoBenchmark($sOQL); + } + + $aData = array(); + foreach ($aStats as $sOQL => $aResults) + { + $aValues = array(); + $aValues['OQL'] = htmlentities($sOQL); + + foreach($aResults as $sDesc => $sInfo) + { + $aValues[$sDesc] = htmlentities($sInfo); + } + $aData[] = $aValues; + } + echo MyHelpers::make_table_from_assoc_array($aData); + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test data load +/////////////////////////////////////////////////////////////////////////// + +class TestImportREST extends TestWebServices +{ + static public function GetName() + { + return 'CSV import (REST)'; + } + + static public function GetDescription() + { + return 'Test various options and fonctionality of import.php'; + } + + protected function DoExecSingleLoad($aLoadSpec) + { + $sCsvData = $aLoadSpec['csvdata']; + + echo "
            \n"; + echo "

            {$aLoadSpec['desc']}

            \n"; + + $aPostData = array('csvdata' => $sCsvData); + + $aGetParams = array(); + $aGetParamReport = array(); + foreach($aLoadSpec['args'] as $sArg => $sValue) + { + $aGetParams[] = $sArg.'='.urlencode($sValue); + $aGetParamReport[] = $sArg.'='.$sValue; + } + $sGetParams = implode('&', $aGetParams); + $sLogin = isset($aLoadSpec['login']) ? $aLoadSpec['login'] : 'admin'; + $sPassword = isset($aLoadSpec['password']) ? $aLoadSpec['password'] : 'admin'; + + $sRes = self::DoPostRequestAuth('../webservices/import.php?'.$sGetParams, $aPostData, $sLogin, $sPassword); + + $sArguments = implode('
            ', $aGetParamReport); + + if (strlen($sCsvData) > 5000) + { + $sCsvDataViewable = 'INPUT TOO LONG TO BE DISPLAYED ('.strlen($sCsvData).")\n".substr($sCsvData, 0, 500)."\n... TO BE CONTINUED"; + } + else + { + $sCsvDataViewable = $sCsvData; + } + + echo "
            \n"; + echo "
            \n"; + echo " $sArguments\n"; + echo "
            \n"; + echo "
            \n"; + echo "
            $sCsvDataViewable
            \n"; + echo "
            \n"; + echo "
            \n"; + + echo "
            $sRes
            \n"; + + echo "
            \n"; + } + + protected function DoExecute() + { + + $aLoads = array( + array( + 'desc' => 'Missing class', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Wrong class', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'toto', + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Wrong output type', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkDevice', + 'output' => 'onthefly', + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Wrong report level', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkDevice', + 'reportlevel' => 'errors|ouarnings|changed', + ), + 'csvdata' => "xxx", + ), + array( + 'desc' => 'Weird format, working anyhow...', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Server', + 'output' => 'details', + 'separator' => '*', + 'qualifier' => '@', + 'reconciliationkeys' => 'org_id,name', + ), + 'csvdata' => 'name*org_id + server01*2 + @server02@@combodo@* 2 + server45*99', + ), + array( + 'desc' => 'Load an organization', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Organization', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;code\nWorldCompany;WCY", + ), + array( + 'desc' => 'Load a location', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;org_id;address\nParis;1;Centre de la Franca", + ), + array( + 'desc' => 'Load a person', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Person', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "email;name;first_name;org_id;phone\njohn.foo@starac.com;Foo;John;1;+33(1)23456789", + ), + array( + 'desc' => 'Load a person - wrong email format', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Person', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "email;name;first_name;org_id\nemailPASbon;Foo;John;1", + ), + array( + 'desc' => 'Load a team', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Team', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;org_id;location_name\nSquadra Azzura2;1;Paris", + ), + array( + 'desc' => 'Load server', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Server', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;status;owner_name;location_name;location_id->org_name;os_family;os_version;management_ip;cpu;ram;brand;model;serial_number\nlocalhost.;production;Demo;Grenoble;Demo;Ubuntu 9.10;2.6.31-19-generic-#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010;16.16.230.232;Intel(R) Core(TM)2 Duo CPU T7100 @ 1.80GHz;2005;Hewlett-Packard;HP Compaq 6510b (GM108UC#ABF);CNU7370BNP", + ), + array( + 'desc' => 'Load NW if', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkInterface', + 'output' => 'details', + 'reconciliationkeys' => '', + ), + 'csvdata' => "name;status;org_id;device_name;physical_type;ip_address;ip_mask;mac_address;speed\neth0;implementation;2;localhost.;ethernet;16.16.230.232;255.255.240.0;00:1a:4b:68:e3:97;\nlo;implementation;2;localhost.;ethernet;127.0.0.1;255.0.0.0;;", + ), + // Data Bruno + array( + 'desc' => 'Load NW devices from real life', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkDevice', + 'output' => 'details', + 'reconciliationkeys' => 'org_id->name,name', + ), + 'csvdata' => 'name;management_ip;importance;org_id->name;type + truc-machin-bidule;172.15.255.150;high;My Company/Department;switch + 10.15.255.222;10.15.255.222;high;My Company/Department;switch', + ), + array( + 'desc' => 'Load NW ifs', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'NetworkInterface', + 'output' => 'details', + 'reconciliationkeys' => 'device_id->name,name', + ), + 'csvdata' => 'device_id->name;org_id->name;name;ip_address;ip_mask;speed;link_type;mac_address;physical_type + truc-machin-bidule;My Company/Department;"GigabitEthernet44";;;0;downlink;00 12 F2 CB C4 EB ;ethernet + truc-machin-bidule;My Company/Department;"GigabitEthernet38";;;0;downlink;00 12 F2 CB C4 E5 ;ethernet + un-autre;My Company/Department;"GigabitEthernet2/3";;;1000000000;uplink;00 12 F2 20 0F 1A ;ethernet', + ), + array( + 'desc' => 'The simplest data load', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name\nParis", + ), + array( + 'desc' => 'The simplest data load + org', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_id\nParis;2", + ), + array( + 'desc' => 'The simplest data load + org (name)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_name\nParis;Demo", + ), + array( + 'desc' => 'The simplest data load + org (code)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_id->code\nParis;DEMO", + ), + array( + 'desc' => 'Ouput: summary', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'summary', + ), + 'csvdata' => "name;org_id->code\nParis;DEMO", + ), + array( + 'desc' => 'Ouput: retcode', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'retcode', + ), + 'csvdata' => "name;org_id->code\nParis;DEMO", + ), + array( + 'desc' => 'Error in reconciliation list', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'reconciliationkeys' => 'org_id', + ), + 'csvdata' => "org_name;name\nDemo;Paris", + ), + array( + 'desc' => 'Error in attribute list that does not allow to compute reconciliation scheme', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "org_name;country\nDemo;France", + ), + array( + 'desc' => 'Error in attribute list - case A', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org\nParis;2", + ), + array( + 'desc' => 'Error in attribute list - case B1 (key->attcode)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org->code\nParis;DEMO", + ), + array( + 'desc' => 'Error in attribute list - case B2 (key->attcode)', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + ), + 'csvdata' => "name;org_id->duns\nParis;DEMO", + ), + array( + 'desc' => 'Always changing... special comment in change tracking', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'comment' => 'automated testing' + ), + 'csvdata' => "org_name;name;address\nDemo;Le pantheon;Addresse bidon:".((string)microtime(true)), + ), + array( + 'desc' => 'Always changing... but "simulate"', + 'login' => 'admin', + 'password' => 'admin', + 'args' => array( + 'class' => 'Location', + 'output' => 'details', + 'simulate' => '1', + 'comment' => 'SHOULD NEVER APPEAR IN THE HISTORY' + ), + 'csvdata' => "org_name;name;address\nDemo;Le pantheon;restore address?", + ), + ); + + foreach ($aLoads as $aLoadSpec) + { + $this->DoExecSingleLoad($aLoadSpec); + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Test massive data load +/////////////////////////////////////////////////////////////////////////// +define('IMPORT_COUNT', 1000); + +class TestImportRESTMassive extends TestImportREST +{ + static public function GetName() + { + return 'CSV import (REST) - HUGE data set ('.IMPORT_COUNT.' PCs)'; + } + + static public function GetDescription() + { + return 'Stress import.php'; + } + + protected function DoExecute() + { + $aLoadSpec = array( + 'desc' => 'Missing class', + 'args' => array( + 'class' => 'PC', + 'output' => 'summary', + ), + 'csvdata' => "name;org_id;brand\n", + ); + for($i = 0 ; $i <= IMPORT_COUNT ; $i++) + { + $aLoadSpec['csvdata'] .= "pc.import.$i;2;Combodo\n"; + } + $this->DoExecSingleLoad($aLoadSpec); + } +} + + +/////////////////////////////////////////////////////////////////////////// +// Test SOAP services +/////////////////////////////////////////////////////////////////////////// + +$aWebServices = array( + array( + 'verb' => 'GetVersion', + 'expected result' => WebServices::GetVersion(), + 'explain result' => 'no comment!', + 'args' => array(), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'link attribute unknown + a CI not found', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'desc of ticket', /* sDescription */ + 'initial situation blah blah blah', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'InfrastructureCI', + array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), + array(new SOAPAttributeValue('impacting', 'very critical')) + ), + new SOAPLinkCreationSpec( + 'NetworkDevice', + array(new SOAPSearchCondition('name', 'switch01')), + array(new SOAPAttributeValue('impact', 'who cares')) + ), + new SOAPLinkCreationSpec( + 'Server', + array(new SOAPSearchCondition('name', 'thisone')), + array(new SOAPAttributeValue('impact', 'our lives')) + ), + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'caller not specified', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + new SOAPExternalKeySearch(), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'InfrastructureCI', + array(new SOAPSearchCondition('name', 'dbserver1.demo.com')), + array() + ), /* aImpact */ + ), + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong class on CI to attach', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'logInfra', + array(new SOAPSearchCondition('dummyfiltercode', 2)), + array(new SOAPAttributeValue('impact', 'very critical')) + ), + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong search condition on CI to attach', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'PC burning', /* sDescription */ + 'The power supply suddenly started to warm up', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + new SOAPLinkCreationSpec( + 'InfrastructureCI', + array(new SOAPSearchCondition('dummyfiltercode', 2)), + array(new SOAPAttributeValue('impact', 'very critical')) + ), + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (empty array)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'no CI to attach (null)', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + null, /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => true, + 'explain result' => 'caller unknown', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1000))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong values for impact and urgency', + 'args' => array( + 'admin', /* sLogin */ + 'admin', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '6', /* sImpact */ + '7', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong password', + 'args' => array( + 'admin', /* sLogin */ + 'xxxxx', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), + array( + 'verb' => 'CreateIncidentTicket', + 'expected result' => false, + 'explain result' => 'wrong login', + 'args' => array( + 'xxxxx', /* sLogin */ + 'yyyyy', /* sPassword */ + 'Houston not reachable', /* sDescription */ + 'Tried to join the shuttle', /* sInitialSituation */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aCallerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Demo'))), /* aCustomerDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'HW Management'))), /* aServiceDesc */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('id', 1))), /* aServiceSubcategoryDesc */ + 'sub product of the service', /* sProduct */ + new SOAPExternalKeySearch(array(new SOAPSearchCondition('name', 'Hardware support'))), /* aWorkgroupDesc */ + array( + ), /* aImpact */ + '1', /* sImpact */ + '1', /* sUrgency */ + ), + ), +); + + +class TestSoap extends TestSoapWebService +{ + static public function GetName() {return 'Test SOAP';} + static public function GetDescription() {return 'Do basic stuff to test the SOAP capability';} + + protected function DoExecute() + { + echo "

            Note: You may also want to try the sample SOAP client itopsoap.examples.php

            \n"; + + global $aSOAPMapping; + + // this file is generated dynamically with location = here + $sWsdlUri = 'http'.(empty($_SERVER['HTTPS']) ? '' : 's').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php'; + + ini_set("soap.wsdl_cache_enabled","0"); + $this->m_SoapClient = new SoapClient + ( + $sWsdlUri, + array( + 'classmap' => $aSOAPMapping, + 'trace' => 1, + ) + ); + + if (false) + { + self::DumpVariable($this->m_SoapClient->__getTypes()); + } + + global $aWebServices; + foreach ($aWebServices as $iPos => $aWebService) + { + echo "

            SOAP call #$iPos - {$aWebService['verb']}

            \n"; + echo "

            {$aWebService['explain result']}

            \n"; + + try + { + $oRes = call_user_func_array(array($this->m_SoapClient, $aWebService['verb']), $aWebService['args']); + } + catch(SoapFault $e) + { + print "
            \n"; 
            +				print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
            +				print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
            +				print "
            "; + print "Response in HTML:

            ".$this->m_SoapClient->__getLastResponse()."

            "; + throw $e; + } + + self::DumpVariable($oRes); + + print "
            \n"; 
            +			print "Request: \n".htmlspecialchars($this->m_SoapClient->__getLastRequest()) ."\n"; 
            +			print "Response: \n".htmlspecialchars($this->m_SoapClient->__getLastResponse())."\n"; 
            +			print "
            "; + + if ($oRes instanceof SOAPResult) + { + $res = $oRes->status; + } + else + { + $res = $oRes; + } + if ($res != $aWebService['expected result']) + { + echo "Expecting:
            \n"; + var_dump($aWebService['expected result']); + echo "Obtained:
            \n"; + var_dump($res); + throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); + } + } + } +} + +class TestWebServicesDirect extends TestBizModel +{ + static public function GetName() {return 'Test web services locally';} + static public function GetDescription() {return 'Invoke the service directly (troubleshooting)';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function DoExecute() + { + $oWebServices = new WebServices(); + + global $aWebServices; + foreach ($aWebServices as $iPos => $aWebService) + { + echo "

            SOAP call #$iPos - {$aWebService['verb']}

            \n"; + echo "

            {$aWebService['explain result']}

            \n"; + $oRes = call_user_func_array(array($oWebServices, $aWebService['verb']), $aWebService['args']); + self::DumpVariable($oRes); + + if ($oRes instanceof SOAPResult) + { + $res = $oRes->status; + } + else + { + $res = $oRes; + } + if ($res != $aWebService['expected result']) + { + echo "Expecting:
            \n"; + var_dump($aWebService['expected result']); + echo "Obtained:
            \n"; + var_dump($res); + throw new UnitTestException("Expecting result '{$aWebService['expected result']}', but got '$res'"); + } + } + return true; + } +} + +class TestTriggerAndEmail extends TestBizModel +{ + static public function GetName() {return 'Test trigger and email';} + static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';} + + static public function GetConfigFile() {return '../config-itop.php';} + + protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail) + { + $oAction = MetaModel::NewObject("ActionEmail"); + $oAction->Set("status", $sStatus); + $oAction->Set("name", "New server"); + $oAction->Set("test_recipient", $sTesterEmail); + $oAction->Set("from", $sTesterEmail); + $oAction->Set("reply_to", $sTesterEmail); + $oAction->Set("to", $sTo); + $oAction->Set("cc", $sCC); + $oAction->Set("bcc", ""); + $oAction->Set("subject", "New server: '\$this->name()$'"); + $oAction->Set("body", "

            Dear customer,

            We have created the server \$this->hyperlink()$ in the IT infrastructure database.

            You will be further notified when it is in Production.

            The IT infrastructure management team.

            Here are some accentuated characters for french people: 'ééà'

            "); + $oAction->Set("importance", "low"); + $iActionId = $this->ObjectToDB($oAction, true); + + $oLink = MetaModel::NewObject("lnkTriggerAction"); + $oLink->Set("trigger_id", $oTrigger->GetKey()); + $oLink->Set("action_id", $iActionId); + $oLink->Set("order", "1"); + $iLink = $this->ObjectToDB($oLink, true); + } + + protected function DoExecute() + { + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail1"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "romain.quetiez@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail2"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "denis.flaven@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyPerson = MetaModel::NewObject("bizPerson"); + $oMyPerson->Set("name", "testemail3"); + $oMyPerson->Set("org_id", "1"); + $oMyPerson->Set("email", "erwan.taloc@hp.com"); + $iPersonId = $this->ObjectToDB($oMyPerson, true); + + $oMyServer = MetaModel::NewObject("bizServer"); + $oMyServer->Set("name", "wfr.terminator.com"); + $oMyServer->Set("severity", "low"); + $oMyServer->Set("status", "production"); + $oMyServer->Set("org_id", 2); + $oMyServer->Set("location_id", 2); + $iServerId = $this->ObjectToDB($oMyServer, true); + + $oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter"); + $oMyTrigger->Set("description", "Testor"); + $oMyTrigger->Set("target_class", "bizServer"); + $oMyTrigger->Set("state", "Shipped"); + $iTriggerId = $this->ObjectToDB($oMyTrigger, true); + + // Error in OQL field(s) + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE naime = 'Dali'", + "SELECT bizServer", + 'romain.quetiez@hp.com' + ); + + // Error: no recipient + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "", + "", + 'romain.quetiez@hp.com' + ); + + // Test + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "SELECT bizPerson", + 'romain.quetiez@hp.com' + ); + + // Test failing because of a wrong test recipient address + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'test', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'toto@walibi.bg' + ); + + // Normal behavior + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'enabled', + "SELECT bizPerson WHERE name LIKE 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + // Does nothing, because it is disabled + // + $this->CreateEmailSpec + ( + $oMyTrigger, + 'disabled', + "SELECT bizPerson WHERE name = 'testemail%'", + "", + 'romain.quetiez@hp.com' + ); + + $oMyTrigger->DoActivate($oMyServer->ToArgs('this')); + + return true; + } +} +?> From d422d9a8cb0ff671f97daedffc3ec492861a184c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 13 Sep 2010 12:00:50 +0000 Subject: [PATCH 758/970] - Adjusted user statisfaction enums, ticket priority enums (1 => high !) and fixed display lists in Service Management. SVN:trunk[841] --- .../data.sample.Service.xml | 19 +++-- .../data.sample.ServiceSubcategory.xml | 81 ++++++++++++++++++- .../model.itop-service-mgmt.php | 14 ++-- .../en.dict.itop-tickets.php | 28 +++---- .../es_cr.dict.itop-tickets.php | 26 +++--- .../fr.dict.itop-tickets.php | 28 +++---- 6 files changed, 139 insertions(+), 57 deletions(-) diff --git a/modules/itop-service-mgmt-1.0.0/data.sample.Service.xml b/modules/itop-service-mgmt-1.0.0/data.sample.Service.xml index c6329e6bb6..2d8eaa1a09 100644 --- a/modules/itop-service-mgmt-1.0.0/data.sample.Service.xml +++ b/modules/itop-service-mgmt-1.0.0/data.sample.Service.xml @@ -2,16 +2,23 @@ 2 -HW Management -Hardware support and repair -IncidentManagement +Computers and peripherals +Ordering of new hardware (Desktop computer, laptop computer, monitor, mouse, keyboard...) and support in case of hardware failure. +RequestManagement production 2 -NW Management -LAN and WAN support and troubleshooting -IncidentManagement +Telecom and connectivity +Ordering and configuration of new mobile phones, computer connectivity requests, cabling, etc... +RequestManagement +production + + +2 +Software +Management of computer software and applications. Installation, upgrade, troubleshooting and removal of software. +RequestManagement production diff --git a/modules/itop-service-mgmt-1.0.0/data.sample.ServiceSubcategory.xml b/modules/itop-service-mgmt-1.0.0/data.sample.ServiceSubcategory.xml index b2b8e0a25a..718c50efa7 100644 --- a/modules/itop-service-mgmt-1.0.0/data.sample.ServiceSubcategory.xml +++ b/modules/itop-service-mgmt-1.0.0/data.sample.ServiceSubcategory.xml @@ -1,13 +1,88 @@ -Break - +New desktop ordering +Order a new desktop computer, for a new employee or for replacing an old system. 1 +New laptop ordering +Order a new laptop computer, for a new mobile employee or for replacing an old laptop. +1 + + +New LCD monitor ordering +Order a new LCD monitor, for a new employee or for replacing an old monitor. +1 + + +New peripheral +Order a peripheral: keyboard, mouse, personal printer... +1 + + +Repair +Ask for assistance about a hardware failure. +1 + + Troubleshooting - +Ask for help troubleshooting a hardware issue. 2 + +New mobile phone ordering +Order a new mobile phone, for a new employee or for replacing a broken phone. +2 + + +Mobile phone/SIM locking +Request for locking the SIM when a mobile phone has been lost or stolen. +2 + + +Mobile phone/SIM unlocking +Request for unlocking the SIM of your mobile phone. +2 + + +New IP address +Request a new IP address for a fixed system (Desktop computer or server) +2 + + +New DNS name +Request a new DNS name for a fixed system (Desktop computer or server). +2 + + +Network Troubleshooting +Ask for help troubleshooting a network related issue. +2 + + +Software Installation / Upgrade +Ask for installing or upgrading software on a computer. +3 + + +Software removal +Ask for removing software from your computer. +3 + + +Windows installation/ / Upgrade +Ask for installing or upgrading Windows on a computer. +3 + + +Troubleshooting +Ask for assistance about a software related issue. +3 + + +Microsoft Office Support +Request assistance about MS Office software: Word, Excel, PowerPoint, Outlook, SharePoint. +3 + \ No newline at end of file diff --git a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php index 73d15b0947..0f5db8584a 100644 --- a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php @@ -59,7 +59,7 @@ abstract class Contract extends cmdbAbstractObject MetaModel::Init_SetZListItems('details', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'contact_list', 'document_list', 'ci_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency')); - MetaModel::Init_SetZListItems('list', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency')); + MetaModel::Init_SetZListItems('list', array('description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency')); } } class ProviderContract extends Contract @@ -90,7 +90,7 @@ class ProviderContract extends Contract MetaModel::Init_SetZListItems('details', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'contact_list', 'document_list', 'ci_list', 'provider_id', 'sla', 'coverage')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'provider_id', 'sla', 'coverage')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'provider_id', 'sla', 'coverage')); - MetaModel::Init_SetZListItems('list', array('name', 'start_date', 'end_date', 'provider_id', 'sla', 'coverage')); + MetaModel::Init_SetZListItems('list', array('start_date', 'end_date', 'provider_id', 'sla', 'coverage')); } } class CustomerContract extends Contract @@ -125,7 +125,7 @@ class CustomerContract extends Contract MetaModel::Init_SetZListItems('details', array('name', 'org_id', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'contact_list', 'document_list', 'ci_list', 'provider_list','provider_id', 'support_team_id', 'sla_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'org_id', 'support_team_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'org_id', 'start_date', 'end_date', 'cost', 'cost_currency', 'cost_unit', 'billing_frequency', 'provider_id', 'support_team_id')); - MetaModel::Init_SetZListItems('list', array('name', 'org_id', 'start_date', 'end_date', 'provider_id', 'support_team_id')); + MetaModel::Init_SetZListItems('list', array('org_id', 'start_date', 'end_date', 'provider_id', 'support_team_id')); } } class lnkCustomerContractToProviderContract extends cmdbAbstractObject @@ -326,7 +326,7 @@ class Service extends cmdbAbstractObject MetaModel::Init_SetZListItems('details', array('name', 'description', 'org_id', 'type', 'status', 'subcategory_list', 'sla_list', 'document_list', 'contact_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'org_id', 'type', 'status')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'org_id', 'type', 'status')); - MetaModel::Init_SetZListItems('list', array('name', 'description', 'org_id', 'type', 'status')); + MetaModel::Init_SetZListItems('list', array('description', 'org_id', 'type', 'status')); } @@ -371,7 +371,7 @@ class ServiceSubcategory extends cmdbAbstractObject MetaModel::Init_SetZListItems('details', array('name', 'description', 'service_id')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'service_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'service_id')); - MetaModel::Init_SetZListItems('list', array('name', 'description', 'service_id')); + MetaModel::Init_SetZListItems('list', array('description', 'service_id')); } } class SLA extends cmdbAbstractObject @@ -402,7 +402,7 @@ class SLA extends cmdbAbstractObject MetaModel::Init_SetZListItems('details', array('name', 'service_id', 'slt_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'service_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'service_id')); - MetaModel::Init_SetZListItems('list', array('name', 'service_id')); + MetaModel::Init_SetZListItems('list', array('service_id')); } } class SLT extends cmdbAbstractObject @@ -435,7 +435,7 @@ class SLT extends cmdbAbstractObject MetaModel::Init_SetZListItems('details', array('name', 'metric', 'ticket_priority', 'value', 'value_unit', 'sla_list')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'metric', 'ticket_priority', 'value', 'value_unit')); MetaModel::Init_SetZListItems('standard_search', array('name', 'metric', 'ticket_priority', 'value', 'value_unit')); - MetaModel::Init_SetZListItems('list', array('name', 'metric', 'ticket_priority', 'value', 'value_unit')); + MetaModel::Init_SetZListItems('list', array('metric', 'ticket_priority', 'value', 'value_unit')); } } class lnkSLTToSLA extends cmdbAbstractObject diff --git a/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php index 0316201e77..fbb75e8881 100644 --- a/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/en.dict.itop-tickets.php @@ -171,27 +171,27 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:ResponseTicket/Attribute:product+' => '', 'Class:ResponseTicket/Attribute:impact' => 'Impact', 'Class:ResponseTicket/Attribute:impact+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:1' => 'A person', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'A department', 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', 'Class:ResponseTicket/Attribute:impact/Value:2' => 'A service', 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:3' => 'A department', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'A person', 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', 'Class:ResponseTicket/Attribute:urgency' => 'Urgency', 'Class:ResponseTicket/Attribute:urgency+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Low', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'High', 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Medium', 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'High', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Low', 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', 'Class:ResponseTicket/Attribute:priority' => 'Priority', 'Class:ResponseTicket/Attribute:priority+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Low', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'High', 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Medium', 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:3' => 'High', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Low', 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', 'Class:ResponseTicket/Attribute:workgroup_id' => 'Workgroup', 'Class:ResponseTicket/Attribute:workgroup_id+' => '', @@ -239,14 +239,14 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:ResponseTicket/Attribute:solution+' => '', 'Class:ResponseTicket/Attribute:user_satisfaction' => 'User satisfaction', 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Bad', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => 'Bad', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Average', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => 'Average', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Good', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => 'Good', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Very Good', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => 'Very Good', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Very satisfied', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => 'Very satisfied', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Fairly statisfied', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => 'Fairly statisfied', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Rather Dissatified', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => 'Rather Dissatified', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Very Dissatisfied', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => 'Very Dissatisfied', 'Class:ResponseTicket/Attribute:user_commment' => 'User comment', 'Class:ResponseTicket/Attribute:user_commment+' => '', 'Class:ResponseTicket/Stimulus:ev_assign' => 'Assign', diff --git a/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php index 32eb07e319..d6d3919fd5 100644 --- a/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/es_cr.dict.itop-tickets.php @@ -1,4 +1,4 @@ - '', 'Class:ResponseTicket/Attribute:impact' => 'Impacto', 'Class:ResponseTicket/Attribute:impact+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Una Persona', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Un Departamento', 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', 'Class:ResponseTicket/Attribute:impact/Value:2' => 'Un Servicio', 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Un Departamento', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Una Persona', 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:4' => 'Una División', - 'Class:ResponseTicket/Attribute:impact/Value:4+' => '', + //'Class:ResponseTicket/Attribute:impact/Value:4' => 'Una División', + //'Class:ResponseTicket/Attribute:impact/Value:4+' => '', 'Class:ResponseTicket/Attribute:urgency' => 'Urgencia', 'Class:ResponseTicket/Attribute:urgency+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Bajo', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Alto', 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Medio', 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Alto', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Bajo', 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', 'Class:ResponseTicket/Attribute:priority' => 'Priority', 'Class:ResponseTicket/Attribute:priority+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Bajo', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Alto', 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Medio', 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Alto', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Bajo', 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', 'Class:ResponseTicket/Attribute:workgroup_id' => 'Grupo de Trabajo', 'Class:ResponseTicket/Attribute:workgroup_id+' => 'Identificación de Grupo de Trabajo', @@ -215,13 +215,13 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:ResponseTicket/Attribute:solution+' => '', 'Class:ResponseTicket/Attribute:user_satisfaction' => 'Satisfacción del Usuario', 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => '1', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Muy Satisfecho', 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => '1', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => '2', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Bastante Satisfecho', 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => '2', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => '3', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Poco Descontento', 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => '3', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => '4', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Muy Descontento', 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => '4', 'Class:ResponseTicket/Attribute:user_commment' => 'Comentario del Usuario', 'Class:ResponseTicket/Attribute:user_commment+' => '', diff --git a/modules/itop-tickets-1.0.0/fr.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/fr.dict.itop-tickets.php index 1dbab6f616..fd3755b75e 100644 --- a/modules/itop-tickets-1.0.0/fr.dict.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/fr.dict.itop-tickets.php @@ -154,27 +154,27 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:ResponseTicket/Attribute:product+' => '', 'Class:ResponseTicket/Attribute:impact' => 'Impact', 'Class:ResponseTicket/Attribute:impact+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Une personne', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Un département', 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', 'Class:ResponseTicket/Attribute:impact/Value:2' => 'Un service', 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', - 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Un département', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Une personne', 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', 'Class:ResponseTicket/Attribute:urgency' => 'Urgence', 'Class:ResponseTicket/Attribute:urgency+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Basse', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Haute', 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Moyenne', 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', - 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Haute', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Basse', 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', 'Class:ResponseTicket/Attribute:priority' => 'Priorité', 'Class:ResponseTicket/Attribute:priority+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Basse', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Haute', 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Moyenne', 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', - 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Haute', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Basse', 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', 'Class:ResponseTicket/Attribute:workgroup_id' => 'Groupe de travail', 'Class:ResponseTicket/Attribute:workgroup_id+' => '', @@ -214,14 +214,14 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:ResponseTicket/Attribute:solution+' => '', 'Class:ResponseTicket/Attribute:user_satisfaction' => 'Satisfaction utilisateur', 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => '1', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => '1', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => '2', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => '2', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => '3', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => '3', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => '4', - 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => '4', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Très satisfait', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => 'Très satisfait', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Plutôt satisfait', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => 'Plutôt satisfait', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Plutôt mécontent', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => 'Plutôt mécontent', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Très mécontent', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => 'Très mécontent', 'Class:ResponseTicket/Attribute:user_commment' => 'Commentaire utilisateur', 'Class:ResponseTicket/Attribute:user_commment+' => '', 'Class:ResponseTicket/Stimulus:ev_assign' => 'Assigner', From a3ed0441e9bca67d96593d12e96966fd3f94ea34 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 12:02:07 +0000 Subject: [PATCH 759/970] .... continuing the cleanup in test module SVN:trunk[842] --- test/exclude.txt | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 test/exclude.txt diff --git a/test/exclude.txt b/test/exclude.txt deleted file mode 100644 index 1e327f0fd0..0000000000 --- a/test/exclude.txt +++ /dev/null @@ -1,8 +0,0 @@ -# -# The following source files are not re-distributed with the "build" of the application -# since they are used solely for constructing other files during the build process -# -config-test-farm.php -test.class.inc.php -test.php -testlist.inc.php From b4d9389b5efc4c79c16c74f6ee605fe9119d41b3 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 12:03:59 +0000 Subject: [PATCH 760/970] SVN:trunk[843] --- test/benchmark.php | 876 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 876 insertions(+) create mode 100644 test/benchmark.php diff --git a/test/benchmark.php b/test/benchmark.php new file mode 100644 index 0000000000..53d57524a1 --- /dev/null +++ b/test/benchmark.php @@ -0,0 +1,876 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once('../application/application.inc.php'); +require_once('../application/itopwebpage.class.inc.php'); +require_once('../application/wizardhelper.class.inc.php'); +require_once('../application/startup.inc.php'); +require_once('../application/loginwebpage.class.inc.php'); +require_once('../application/utils.inc.php'); +require_once('./setuppage.class.inc.php'); + +//ini_set('memory_limit', '2048M'); + +class BenchmarkDataCreation +{ + var $m_iIfByServer; + var $m_iIfByNWDevice; + var $m_aRequested; + var $m_aPlanned; + var $m_aCreatedByClass = array(); + var $m_aCreatedByDesc = array(); + + var $m_aStatsByClass = array(); + + var $m_oChange; + public function __construct() + { + $this->m_oChange = MetaModel::NewObject("CMDBChange"); + $this->m_oChange->Set("date", time()); + $this->m_oChange->Set("userinfo", "Benchmark setup"); + $iChangeId = $this->m_oChange->DBInsertNoReload(); + } + + public function PlanStructure($iPlannedContacts, $iPlannedContracts) + { + $this->m_aRequested = array( + 'plannedcontacts' => $iPlannedContacts, + 'plannedcontracts' => $iPlannedContracts, + ); + $this->m_aPlanned = array( + 'Contacts' => $iPlannedContacts, + 'Contracts' => $iPlannedContracts, + 'Documents' => $iPlannedContracts * 2, + ); + } + + public function PlanCis($iPlannedCIs) + { + $this->m_aRequested = array( + 'plannedcis' => $iPlannedCIs, + ); + + $this->m_iIfByServer = 2; + $this->m_iIfByNWDevice = 10; + + $iServers = ceil($iPlannedCIs * 9 / 10); + $iNWDevices = ceil($iPlannedCIs / 10); + $iInterfaces = $iServers * $this->m_iIfByServer + $iNWDevices * $this->m_iIfByNWDevice; + $iApplications = $iServers * 5; + $iSolutions = ceil($iApplications / 2); + $iProcesses = ceil($iSolutions / 2); + + $this->m_aPlanned = array( + 'Network devices' => $iNWDevices, + 'Servers' => $iServers, + 'Interfaces' => $iInterfaces, + 'Application SW' => 2, + 'Applications' => $iApplications, + 'Solutions' => $iSolutions, + 'Processes' => $iProcesses, + ); + } + + public function PlanTickets($iPlannedTickets, $iBigTicketCis) + { + $this->m_aRequested = array( + 'plannedtickets' => $iPlannedTickets, + 'plannedbigticketcis' => $iBigTicketCis, + ); + + $this->m_aPlanned = array( + 'Incidents' => ceil($iPlannedTickets / 2), + 'Changes' => ceil($iPlannedTickets / 2), + 'Big ticket: CIs' => $iBigTicketCis, + ); + } + + public function ShowPlans($oP) + { + $oP->add("

            Planned creations

            \n"); + $aPlanned = $this->m_aPlanned; + $aForm = array(); + foreach ($aPlanned as $sKey => $iCount) + { + $aForm[] = array( + 'label' => $sKey, + 'input' => $iCount, + ); + } + $oP->form($aForm); + } + + public function ShowForm($oP, $sNextOperation) + { + $aRequested = $this->m_aRequested; + $oP->add("
            \n"); + $oP->add("\n"); + foreach($this->m_aRequested as $sName => $sValue) + { + $oP->add("\n"); + } + $oP->add("\n"); + $oP->add("
            \n"); + } + + protected function CreateObject($sClass, $aData, $sClassDesc = '') + { + $mu_t1 = MyHelpers::getmicrotime(); + + $oMyObject = MetaModel::NewObject($sClass); + foreach($aData as $sProp => $value) + { + $oMyObject->Set($sProp, $value); + } + + $iId = $oMyObject->DBInsertTrackedNoReload($this->m_oChange, true /* skip security */); + + $sClassId = "$sClass ($sClassDesc)"; + $this->m_aCreatedByDesc[$sClassId][] = $iId; + $this->m_aCreatedByClass[$sClass][] = $iId; + + $mu_t2 = MyHelpers::getmicrotime(); + $this->m_aStatsByClass[$sClass][] = $mu_t2 - $mu_t1; + + return $iId; + } + + static $m_aClassIdCache = array(); + protected function GetClassIds($sClass) + { + if (!isset(self::$m_aClassIdCache[$sClass])) + { + // Load the cache now + self::$m_aClassIdCache[$sClass] = array(); + + $oSet = new DBObjectSet(new DBObjectSearch($sClass)); + while($oObj = $oSet->Fetch()) + { + self::$m_aClassIdCache[$sClass][] = $oObj->GetKey(); + } + } + return self::$m_aClassIdCache[$sClass]; + } + + protected function RandomId($sClass, $sClassDesc = '') + { + $sClassId = "$sClass ($sClassDesc)"; + if (isset($this->m_aCreatedByDesc[$sClassId])) + { + return $this->m_aCreatedByDesc[$sClassId][array_rand($this->m_aCreatedByDesc[$sClassId])]; + } + + $aIds = self::GetClassIds($sClass); + return $aIds[array_rand($aIds)]; + } + + static protected function FindId($sClass) + { + $oSet = new DBObjectSet(new DBObjectSearch($sClass)); + if ($oSet->Count() < 1) + { + return null; + } + + $oObj = $oSet->Fetch(); + return $oObj->GetKey(); + } + + static protected function FindIdFromOQL($sOQL) + { + $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); + if ($oSet->Count() < 1) + { + return null; + } + + $oObj = $oSet->Fetch(); + return $oObj->GetKey(); + } + + protected function my_array_rand($aData, $iCount) + { + if ($iCount == 0) + { + return array(); + } + elseif ($iCount == 1) + { + // array_rand() for one item returns only the key + $key = array_rand($aData); + $aSample = array($key); + } + elseif ($iCount <= count($aData)) + { + $aSample = array_rand($aData, $iCount); + } + else + { + $aSample = array_merge(array_keys($aData), self::my_array_rand($aData, $iCount - count($aData))); + } + return $aSample; + } + + protected function CreateLinks($iFrom, $iCount, $sLinkClass, $sAttCodeFrom, $sAttCodeTo) + { + $oAttTo = MetaModel::GetAttributeDef($sLinkClass, $sAttCodeTo); + $sToClass = $oAttTo->GetTargetClass(); + + $aTargets = self::GetClassIds($sToClass); + $aSample = self::my_array_rand($aTargets, $iCount); + + foreach($aSample as $key) + { + $aData = array( + $sAttCodeFrom => $iFrom, + $sAttCodeTo => $aTargets[$key], + ); + $this->CreateObject($sLinkClass, $aData); + } + } + + public function CreateStructure($oP) + { + $aClasses = MetaModel::GetClasses(); + $aActions = array('Read', 'Bulk Read', 'Delete', 'Bulk Delete', 'Modify', 'Bulk Modify'); + $aStdProfiles = array(2, 3, 4, 5, 6, 7, 8, 9); + + //////////////////////////////////////// + // New specific profile, giving access to everything + // + $aData = array( + 'name' => 'Data guru', + 'description' => 'Could do anything, because everything is granted', + ); + $iGuruProfile = $this->CreateObject('URP_Profiles', $aData); + foreach($aClasses as $sClass) + { + foreach($aActions as $sAction) + { + $aData = array( + 'profileid' => $iGuruProfile, + 'class' => $sClass, + 'permission' => 'yes', + 'action' => $sAction, + ); + $this->CreateObject('URP_ActionGrant', $aData); + } + } + + // User login with super access rights + // + $aData = array( + 'org_id' => self::FindId('Organization'), + 'location_id' => self::FindId('Location'), + 'first_name' => 'Jesus', + 'name' => 'Deus', + 'email' => 'guru@combodo.com', + ); + $iPerson = $this->CreateObject('Person', $aData); + $aData = array( + 'contactid' => $iPerson, + 'login' => 'guru', + 'password' => 'guru', + 'language' => 'EN US', + ); + $iLogin = $this->CreateObject('UserLocal', $aData); + + // Assign the guru profile to the new login + // + $aData = array( + 'userid' => $iLogin, + 'profileid' => $iGuruProfile, + 'reason' => 'he is the one', + ); + $this->CreateObject('URP_UserProfile', $aData); + + //////////////////////////////////////// + // User login having all std profiles + // + $aData = array( + 'org_id' => self::FindId('Organization'), + 'location_id' => self::FindId('Location'), + 'first_name' => 'Little ze', + 'name' => 'Foo', + 'email' => 'foo@combodo.com', + ); + $iPerson = $this->CreateObject('Person', $aData); + $aData = array( + 'contactid' => $iPerson, + 'login' => 'foo', + 'password' => 'foo', + 'language' => 'EN US', + ); + $iLogin = $this->CreateObject('UserLocal', $aData); + + // Assign profiles to the new login + // + foreach($aStdProfiles as $iProfileId) + { + $aData = array( + 'userid' => $iLogin, + 'profileid' => $iProfileId, + 'reason' => '', + ); + $this->CreateObject('URP_UserProfile', $aData); + } + + ///////////////////////// + // + // Organizations + // + $aData = array( + 'name' => 'Benchmark', + ); + $iOrg = $this->CreateObject('Organization', $aData); + + ///////////////////////// + // + // Locations + // + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Rio de Janeiro', + ); + $iLoc = $this->CreateObject('Location', $aData); + + ///////////////////////// + // + // Teams + // + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'Fluminense', + 'email' => 'fluminense@combodo.com', + ); + $iTeam = $this->CreateObject('Team', $aData); + + ///////////////////////// + // + // Persons + // + for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'first_name' => 'Joaõ', + 'name' => 'Ningem #'.$i, + 'email' => 'foo'.$i.'@nowhere.fr', + ); + $iPerson = $this->CreateObject('Person', $aData); + + // Contract/Infra + // + $aData = array( + 'contact_id' => $iPerson, + 'team_id' => $this->RandomId('Team'), + ); + $this->CreateObject('lnkTeamToContact', $aData); + } + + ///////////////////////// + // + // Services + // + $aData = array( + 'org_id' => $iOrg, + 'name' => 'My Service', + ); + $iService = $this->CreateObject('Service', $aData); + + ///////////////////////// + // + // Service subcategories + // + $aData = array( + 'name' => 'My subcategory', + 'service_id' => $iService, + ); + $iOrg = $this->CreateObject('ServiceSubcategory', $aData); + + ///////////////////////// + // + // Contracts + // + for($i = 0 ; $i < $this->m_aPlanned['Contracts'] ; $i++) + { + $aData = array( + 'name' => "Contract #$i", + 'description' => 'Created for benchmarking purposes', + 'org_id' => $this->RandomId('Organization'), + 'provider_id' => $this->RandomId('Organization'), + 'start_date' => '2009-12-25', + 'end_date' => '2019-08-01', + 'support_team_id' => $this->RandomId('Team'), + ); + $iContract = $this->CreateObject('CustomerContract', $aData); + + // Contract/Contact (10% of contacts) + // + $iContactCount = ceil($this->m_aPlanned['Contracts'] / 10); + for($iLinked = 0 ; $iLinked < $iContactCount ; $iLinked++) + { + $aData = array( + 'contact_id' => $this->RandomId('Person'), + 'contract_id' => $iContract, + 'role' => 'role '.$iLinked, + ); + $this->CreateObject('lnkContractToContact', $aData); + } + } + + ///////////////////////// + // + // Documents + // + $sMyDoc = ''; + for($i = 0 ; $i < 1000 ; $i++) + { + // 100 chars + $sMyDoc .= "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"; + } + $oRefDoc = new ormDocument($sMyDoc, 'text/plain'); + + for($i = 0 ; $i < $this->m_aPlanned['Documents'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => "document$i", + 'contents' => $oRefDoc, + ); + $this->CreateObject('FileDoc', $aData); + } + } + + public function CreateCis($oP) + { + $iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'"); + $iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg"); + + ///////////////////////// + // + // Servers + // + for($i = 0 ; $i < $this->m_aPlanned['Servers'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'server'.$i, + 'status' => 'production', + ); + $iServer = $this->CreateObject('Server', $aData); + + // Contract/Infra + $this->CreateLinks($iServer, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + + // Interfaces + for($iLinked = 0 ; $iLinked < $this->m_iIfByServer ; $iLinked++) + { + $aData = array( + 'name' => "eth$iLinked", + 'status' => 'implementation', + 'org_id' => $iOrg, + 'device_id' => $iServer, + 'status' => 'production', + ); + $this->CreateObject('NetworkInterface', $aData, 'server if'); + } + } + + ///////////////////////// + // + // Network devices + // + for($i = 0 ; $i < $this->m_aPlanned['Network devices'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'location_id' => $iLoc, + 'name' => 'equipment #'.$i, + 'status' => 'production', + ); + $iNWDevice = $this->CreateObject('NetworkDevice', $aData); + + // Contract/Infra + $this->CreateLinks($iNWDevice, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + + // Interfaces + // + for($iLinked = 0 ; $iLinked < $this->m_iIfByNWDevice ; $iLinked++) + { + $aData = array( + 'name' => "eth$iLinked", + 'status' => 'implementation', + 'org_id' => $iOrg, + 'device_id' => $iNWDevice, + 'connected_if' => $this->RandomId('NetworkInterface', 'server if'), + 'status' => 'production', + ); + $this->CreateObject('NetworkInterface', $aData, 'equipment if'); + } + } + + ///////////////////////// + // + // Application Software + // + for($i = 0 ; $i < $this->m_aPlanned['Application SW'] ; $i++) + { + $aData = array( + 'name' => 'Software #'.$i, + ); + $iNWDevice = $this->CreateObject('Application', $aData); + } + + ///////////////////////// + // + // Applications + // + for($i = 0 ; $i < $this->m_aPlanned['Applications'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'device_id' => $this->RandomId('Server'), + 'software_id' => $this->RandomId('Application'), + 'name' => 'Application #'.$i, + 'status' => 'production', + ); + $iAppInstance = $this->CreateObject('ApplicationInstance', $aData); + + // Contract/Infra + $this->CreateLinks($iAppInstance, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + } + + ///////////////////////// + // + // Application Solution + // + for($i = 0 ; $i < $this->m_aPlanned['Solutions'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Solution #'.$i, + 'status' => 'production', + ); + $iAppSolution = $this->CreateObject('ApplicationSolution', $aData); + + // Contract/Infra + $this->CreateLinks($iAppSolution, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + } + + ///////////////////////// + // + // Business Process + // + for($i = 0 ; $i < $this->m_aPlanned['Processes'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'name' => 'Process #'.$i, + 'status' => 'production', + ); + $iProcess = $this->CreateObject('BusinessProcess', $aData); + + // Contract/Infra + $this->CreateLinks($iProcess, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); + } + } + + public function CreateTickets($oP) + { + $iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'"); + $iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg"); + + ///////////////////////// + // + // Incident Tickets + // + for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), + 'title' => 'Incident #'.$i, + 'description' => 'O que aconteceu?', + 'ticket_log' => 'Testing...', + ); + $iTicket = $this->CreateObject('Incident', $aData); + + // Incident/Infra + $iInfraCount = rand(1, 6); + $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); + + // Incident/Infra + $iContactCount = rand(1, 6); + $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); + } + + ///////////////////////// + // + // Big Ticket + // + $aData = array( + 'org_id' => $iOrg, + 'caller_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'service_id' => $this->RandomId('Service'), + 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), + 'title' => 'Big ticket', + 'description' => 'O que aconteceu?', + 'ticket_log' => 'Testing...', + ); + $iTicket = $this->CreateObject('Incident', $aData); + + // Incident/Infra + $iInfraCount = $this->m_aPlanned['Big ticket: CIs']; + $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); + + // Incident/Infra + $iContactCount = rand(1, 6); + $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); + + ///////////////////////// + // + // Change Tickets + // + for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++) + { + $aData = array( + 'org_id' => $iOrg, + 'requestor_id' => $this->RandomId('Person'), + 'workgroup_id' => $this->RandomId('Team'), + 'agent_id' => $this->RandomId('Person'), + 'supervisor_group_id' => $this->RandomId('Team'), + 'supervisor_id' => $this->RandomId('Person'), + 'manager_group_id' => $this->RandomId('Team'), + 'manager_id' => $this->RandomId('Person'), + 'title' => 'change #'.$i, + 'description' => "Let's do something there", + ); + $iTicket = $this->CreateObject('NormalChange', $aData); + + // Incident/Infra + $iInfraCount = rand(1, 6); + $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); + + // Incident/Infra + $iContactCount = rand(1, 6); + $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); + } + } + + public function MakeFeedback($oP) + { + foreach($this->m_aCreatedByClass as $sClass => $aClassIds) + { + $iSample = reset($aClassIds); + $sSample = "sample"; + + $iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3); + $fDurationMin = number_format(min($this->m_aStatsByClass[$sClass]), 3); + $fDurationMax = number_format(max($this->m_aStatsByClass[$sClass]), 3); + $fDurationAverage = number_format(array_sum($this->m_aStatsByClass[$sClass]) / count($this->m_aStatsByClass[$sClass]), 3); + + $oP->add("
              "); + $oP->add("
            • "); + $oP->add("$sClass: ".count($this->m_aStatsByClass[$sClass])." - $sSample
              "); + $oP->add("Duration: $fDurationMin => $fDurationMax; Avg:$fDurationAverage; Total: $iDuration"); + $oP->add("
            • "); + $oP->add("
            "); + } + } +} + +/** + * Ask the user what are the settings for the data load + */ +function DisplayStep1(SetupWebPage $oP) +{ + $sNextOperation = 'step2'; + $oP->add("

            iTop benchmarking

            \n"); + + $oP->add("
            \n"); + $oP->add("
            Data load configuration\n"); + $aForm = array(); + $aForm[] = array( + 'label' => "Contacts:", + 'input' => "", + 'help' => '', + ); + $aForm[] = array( + 'label' => "Contracts:", + 'input' => "", + 'help' => '', + ); + $oP->form($aForm); + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); + + $oP->add("
            \n"); + $oP->add("
            Data load configuration\n"); + $aForm = array(); + $aForm[] = array( + 'label' => "Main CIs:", + 'input' => "", + 'help' => ' exclude interfaces, subnets or any other type of secondary CI', + ); + $oP->form($aForm); + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); + + $oP->add("
            \n"); + $oP->add("
            Data load configuration\n"); + $aForm = array(); + $aForm[] = array( + 'label' => "Tickets:", + 'input' => "", + 'help' => ' 50% incidents, 50% changes', + ); + $aForm[] = array( + 'label' => "CIs for the big ticket:", + 'input' => "", + 'help' => 'Number of CI for the single big ticket', + ); + $oP->form($aForm); + $oP->add("
            \n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
            \n"); +} + + +/** + * Main program + */ + +LoginWebPage::DoLogin(); // Check user rights and prompt if needed + +$sOperation = Utils::ReadParam('operation', 'step1'); +$oP = new SetupWebPage('iTop benchmark utility'); + +ExecutionKPI::EnableDuration(); +$oKPI = new ExecutionKPI(); + +try +{ + switch($sOperation) + { + case 'step1': + DisplayStep1($oP); + break; + + case 'create_structure': + $oP->no_cache(); + $iPlannedContacts = Utils::ReadParam('plannedcontacts'); + $iPlannedContracts = Utils::ReadParam('plannedcontracts'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts); + $oDataCreation->ShowPlans($oP); + $oDataCreation->ShowForm($oP, 'create_structure_go'); + break; + + case 'create_structure_go': + $oP->no_cache(); + $iPlannedContacts = Utils::ReadParam('plannedcontacts'); + $iPlannedContracts = Utils::ReadParam('plannedcontracts'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts); + $oDataCreation->CreateStructure($oP); + $oDataCreation->MakeFeedback($oP); + break; + + case 'create_cis': + $oP->no_cache(); + $iPlannedCIs = Utils::ReadParam('plannedcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanCis($iPlannedCIs); + $oDataCreation->ShowPlans($oP); + $oDataCreation->ShowForm($oP, 'create_cis_go'); + break; + + case 'create_cis_go': + $oP->no_cache(); + $iPlannedCIs = Utils::ReadParam('plannedcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanCis($iPlannedCIs); + $oDataCreation->CreateCis($oP); + $oDataCreation->MakeFeedback($oP); + break; + + case 'create_tickets': + $oP->no_cache(); + $iPlannedTickets = Utils::ReadParam('plannedtickets'); + $iBigTicketCis = Utils::ReadParam('plannedbigticketcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis); + $oDataCreation->ShowPlans($oP); + $oDataCreation->ShowForm($oP, 'create_tickets_go'); + break; + + case 'create_tickets_go': + $oP->no_cache(); + $iPlannedTickets = Utils::ReadParam('plannedtickets'); + $iBigTicketCis = Utils::ReadParam('plannedbigticketcis'); + + $oDataCreation = new BenchmarkDataCreation(); + $oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis); + $oDataCreation->CreateTickets($oP); + $oDataCreation->MakeFeedback($oP); + break; + + default: + $oP->error("Error: unsupported operation '$sOperation'"); + } +} +catch(ZZException $e) +{ + $oP->error("Error: '".$e->getMessage()."'"); +} +catch(ZZCoreException $e) +{ + $oP->error("Error: '".$e->getHtmlDesc()."'"); +} +$oKPI->ComputeAndReport('Total execution'); +// too big (showing all queries) ExecutionKPI::ReportStats(); +//MetaModel::ShowQueryTrace(); +$oP->output(); +?> From 8ed58f19c3371f67e5f82d13bd2f44d05d6bb3fd Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 12:07:13 +0000 Subject: [PATCH 761/970] Benchmark page moved into test tools SVN:trunk[844] --- setup/benchmark.php | 876 -------------------------------------------- test/benchmark.php | 2 +- toolkit.php | 1 - 3 files changed, 1 insertion(+), 878 deletions(-) delete mode 100644 setup/benchmark.php diff --git a/setup/benchmark.php b/setup/benchmark.php deleted file mode 100644 index 53d57524a1..0000000000 --- a/setup/benchmark.php +++ /dev/null @@ -1,876 +0,0 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -require_once('../application/application.inc.php'); -require_once('../application/itopwebpage.class.inc.php'); -require_once('../application/wizardhelper.class.inc.php'); -require_once('../application/startup.inc.php'); -require_once('../application/loginwebpage.class.inc.php'); -require_once('../application/utils.inc.php'); -require_once('./setuppage.class.inc.php'); - -//ini_set('memory_limit', '2048M'); - -class BenchmarkDataCreation -{ - var $m_iIfByServer; - var $m_iIfByNWDevice; - var $m_aRequested; - var $m_aPlanned; - var $m_aCreatedByClass = array(); - var $m_aCreatedByDesc = array(); - - var $m_aStatsByClass = array(); - - var $m_oChange; - public function __construct() - { - $this->m_oChange = MetaModel::NewObject("CMDBChange"); - $this->m_oChange->Set("date", time()); - $this->m_oChange->Set("userinfo", "Benchmark setup"); - $iChangeId = $this->m_oChange->DBInsertNoReload(); - } - - public function PlanStructure($iPlannedContacts, $iPlannedContracts) - { - $this->m_aRequested = array( - 'plannedcontacts' => $iPlannedContacts, - 'plannedcontracts' => $iPlannedContracts, - ); - $this->m_aPlanned = array( - 'Contacts' => $iPlannedContacts, - 'Contracts' => $iPlannedContracts, - 'Documents' => $iPlannedContracts * 2, - ); - } - - public function PlanCis($iPlannedCIs) - { - $this->m_aRequested = array( - 'plannedcis' => $iPlannedCIs, - ); - - $this->m_iIfByServer = 2; - $this->m_iIfByNWDevice = 10; - - $iServers = ceil($iPlannedCIs * 9 / 10); - $iNWDevices = ceil($iPlannedCIs / 10); - $iInterfaces = $iServers * $this->m_iIfByServer + $iNWDevices * $this->m_iIfByNWDevice; - $iApplications = $iServers * 5; - $iSolutions = ceil($iApplications / 2); - $iProcesses = ceil($iSolutions / 2); - - $this->m_aPlanned = array( - 'Network devices' => $iNWDevices, - 'Servers' => $iServers, - 'Interfaces' => $iInterfaces, - 'Application SW' => 2, - 'Applications' => $iApplications, - 'Solutions' => $iSolutions, - 'Processes' => $iProcesses, - ); - } - - public function PlanTickets($iPlannedTickets, $iBigTicketCis) - { - $this->m_aRequested = array( - 'plannedtickets' => $iPlannedTickets, - 'plannedbigticketcis' => $iBigTicketCis, - ); - - $this->m_aPlanned = array( - 'Incidents' => ceil($iPlannedTickets / 2), - 'Changes' => ceil($iPlannedTickets / 2), - 'Big ticket: CIs' => $iBigTicketCis, - ); - } - - public function ShowPlans($oP) - { - $oP->add("

            Planned creations

            \n"); - $aPlanned = $this->m_aPlanned; - $aForm = array(); - foreach ($aPlanned as $sKey => $iCount) - { - $aForm[] = array( - 'label' => $sKey, - 'input' => $iCount, - ); - } - $oP->form($aForm); - } - - public function ShowForm($oP, $sNextOperation) - { - $aRequested = $this->m_aRequested; - $oP->add("
            \n"); - $oP->add("\n"); - foreach($this->m_aRequested as $sName => $sValue) - { - $oP->add("\n"); - } - $oP->add("\n"); - $oP->add("
            \n"); - } - - protected function CreateObject($sClass, $aData, $sClassDesc = '') - { - $mu_t1 = MyHelpers::getmicrotime(); - - $oMyObject = MetaModel::NewObject($sClass); - foreach($aData as $sProp => $value) - { - $oMyObject->Set($sProp, $value); - } - - $iId = $oMyObject->DBInsertTrackedNoReload($this->m_oChange, true /* skip security */); - - $sClassId = "$sClass ($sClassDesc)"; - $this->m_aCreatedByDesc[$sClassId][] = $iId; - $this->m_aCreatedByClass[$sClass][] = $iId; - - $mu_t2 = MyHelpers::getmicrotime(); - $this->m_aStatsByClass[$sClass][] = $mu_t2 - $mu_t1; - - return $iId; - } - - static $m_aClassIdCache = array(); - protected function GetClassIds($sClass) - { - if (!isset(self::$m_aClassIdCache[$sClass])) - { - // Load the cache now - self::$m_aClassIdCache[$sClass] = array(); - - $oSet = new DBObjectSet(new DBObjectSearch($sClass)); - while($oObj = $oSet->Fetch()) - { - self::$m_aClassIdCache[$sClass][] = $oObj->GetKey(); - } - } - return self::$m_aClassIdCache[$sClass]; - } - - protected function RandomId($sClass, $sClassDesc = '') - { - $sClassId = "$sClass ($sClassDesc)"; - if (isset($this->m_aCreatedByDesc[$sClassId])) - { - return $this->m_aCreatedByDesc[$sClassId][array_rand($this->m_aCreatedByDesc[$sClassId])]; - } - - $aIds = self::GetClassIds($sClass); - return $aIds[array_rand($aIds)]; - } - - static protected function FindId($sClass) - { - $oSet = new DBObjectSet(new DBObjectSearch($sClass)); - if ($oSet->Count() < 1) - { - return null; - } - - $oObj = $oSet->Fetch(); - return $oObj->GetKey(); - } - - static protected function FindIdFromOQL($sOQL) - { - $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL)); - if ($oSet->Count() < 1) - { - return null; - } - - $oObj = $oSet->Fetch(); - return $oObj->GetKey(); - } - - protected function my_array_rand($aData, $iCount) - { - if ($iCount == 0) - { - return array(); - } - elseif ($iCount == 1) - { - // array_rand() for one item returns only the key - $key = array_rand($aData); - $aSample = array($key); - } - elseif ($iCount <= count($aData)) - { - $aSample = array_rand($aData, $iCount); - } - else - { - $aSample = array_merge(array_keys($aData), self::my_array_rand($aData, $iCount - count($aData))); - } - return $aSample; - } - - protected function CreateLinks($iFrom, $iCount, $sLinkClass, $sAttCodeFrom, $sAttCodeTo) - { - $oAttTo = MetaModel::GetAttributeDef($sLinkClass, $sAttCodeTo); - $sToClass = $oAttTo->GetTargetClass(); - - $aTargets = self::GetClassIds($sToClass); - $aSample = self::my_array_rand($aTargets, $iCount); - - foreach($aSample as $key) - { - $aData = array( - $sAttCodeFrom => $iFrom, - $sAttCodeTo => $aTargets[$key], - ); - $this->CreateObject($sLinkClass, $aData); - } - } - - public function CreateStructure($oP) - { - $aClasses = MetaModel::GetClasses(); - $aActions = array('Read', 'Bulk Read', 'Delete', 'Bulk Delete', 'Modify', 'Bulk Modify'); - $aStdProfiles = array(2, 3, 4, 5, 6, 7, 8, 9); - - //////////////////////////////////////// - // New specific profile, giving access to everything - // - $aData = array( - 'name' => 'Data guru', - 'description' => 'Could do anything, because everything is granted', - ); - $iGuruProfile = $this->CreateObject('URP_Profiles', $aData); - foreach($aClasses as $sClass) - { - foreach($aActions as $sAction) - { - $aData = array( - 'profileid' => $iGuruProfile, - 'class' => $sClass, - 'permission' => 'yes', - 'action' => $sAction, - ); - $this->CreateObject('URP_ActionGrant', $aData); - } - } - - // User login with super access rights - // - $aData = array( - 'org_id' => self::FindId('Organization'), - 'location_id' => self::FindId('Location'), - 'first_name' => 'Jesus', - 'name' => 'Deus', - 'email' => 'guru@combodo.com', - ); - $iPerson = $this->CreateObject('Person', $aData); - $aData = array( - 'contactid' => $iPerson, - 'login' => 'guru', - 'password' => 'guru', - 'language' => 'EN US', - ); - $iLogin = $this->CreateObject('UserLocal', $aData); - - // Assign the guru profile to the new login - // - $aData = array( - 'userid' => $iLogin, - 'profileid' => $iGuruProfile, - 'reason' => 'he is the one', - ); - $this->CreateObject('URP_UserProfile', $aData); - - //////////////////////////////////////// - // User login having all std profiles - // - $aData = array( - 'org_id' => self::FindId('Organization'), - 'location_id' => self::FindId('Location'), - 'first_name' => 'Little ze', - 'name' => 'Foo', - 'email' => 'foo@combodo.com', - ); - $iPerson = $this->CreateObject('Person', $aData); - $aData = array( - 'contactid' => $iPerson, - 'login' => 'foo', - 'password' => 'foo', - 'language' => 'EN US', - ); - $iLogin = $this->CreateObject('UserLocal', $aData); - - // Assign profiles to the new login - // - foreach($aStdProfiles as $iProfileId) - { - $aData = array( - 'userid' => $iLogin, - 'profileid' => $iProfileId, - 'reason' => '', - ); - $this->CreateObject('URP_UserProfile', $aData); - } - - ///////////////////////// - // - // Organizations - // - $aData = array( - 'name' => 'Benchmark', - ); - $iOrg = $this->CreateObject('Organization', $aData); - - ///////////////////////// - // - // Locations - // - $aData = array( - 'org_id' => $iOrg, - 'name' => 'Rio de Janeiro', - ); - $iLoc = $this->CreateObject('Location', $aData); - - ///////////////////////// - // - // Teams - // - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'Fluminense', - 'email' => 'fluminense@combodo.com', - ); - $iTeam = $this->CreateObject('Team', $aData); - - ///////////////////////// - // - // Persons - // - for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'first_name' => 'Joaõ', - 'name' => 'Ningem #'.$i, - 'email' => 'foo'.$i.'@nowhere.fr', - ); - $iPerson = $this->CreateObject('Person', $aData); - - // Contract/Infra - // - $aData = array( - 'contact_id' => $iPerson, - 'team_id' => $this->RandomId('Team'), - ); - $this->CreateObject('lnkTeamToContact', $aData); - } - - ///////////////////////// - // - // Services - // - $aData = array( - 'org_id' => $iOrg, - 'name' => 'My Service', - ); - $iService = $this->CreateObject('Service', $aData); - - ///////////////////////// - // - // Service subcategories - // - $aData = array( - 'name' => 'My subcategory', - 'service_id' => $iService, - ); - $iOrg = $this->CreateObject('ServiceSubcategory', $aData); - - ///////////////////////// - // - // Contracts - // - for($i = 0 ; $i < $this->m_aPlanned['Contracts'] ; $i++) - { - $aData = array( - 'name' => "Contract #$i", - 'description' => 'Created for benchmarking purposes', - 'org_id' => $this->RandomId('Organization'), - 'provider_id' => $this->RandomId('Organization'), - 'start_date' => '2009-12-25', - 'end_date' => '2019-08-01', - 'support_team_id' => $this->RandomId('Team'), - ); - $iContract = $this->CreateObject('CustomerContract', $aData); - - // Contract/Contact (10% of contacts) - // - $iContactCount = ceil($this->m_aPlanned['Contracts'] / 10); - for($iLinked = 0 ; $iLinked < $iContactCount ; $iLinked++) - { - $aData = array( - 'contact_id' => $this->RandomId('Person'), - 'contract_id' => $iContract, - 'role' => 'role '.$iLinked, - ); - $this->CreateObject('lnkContractToContact', $aData); - } - } - - ///////////////////////// - // - // Documents - // - $sMyDoc = ''; - for($i = 0 ; $i < 1000 ; $i++) - { - // 100 chars - $sMyDoc .= "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"; - } - $oRefDoc = new ormDocument($sMyDoc, 'text/plain'); - - for($i = 0 ; $i < $this->m_aPlanned['Documents'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'name' => "document$i", - 'contents' => $oRefDoc, - ); - $this->CreateObject('FileDoc', $aData); - } - } - - public function CreateCis($oP) - { - $iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'"); - $iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg"); - - ///////////////////////// - // - // Servers - // - for($i = 0 ; $i < $this->m_aPlanned['Servers'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'server'.$i, - 'status' => 'production', - ); - $iServer = $this->CreateObject('Server', $aData); - - // Contract/Infra - $this->CreateLinks($iServer, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); - - // Interfaces - for($iLinked = 0 ; $iLinked < $this->m_iIfByServer ; $iLinked++) - { - $aData = array( - 'name' => "eth$iLinked", - 'status' => 'implementation', - 'org_id' => $iOrg, - 'device_id' => $iServer, - 'status' => 'production', - ); - $this->CreateObject('NetworkInterface', $aData, 'server if'); - } - } - - ///////////////////////// - // - // Network devices - // - for($i = 0 ; $i < $this->m_aPlanned['Network devices'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'location_id' => $iLoc, - 'name' => 'equipment #'.$i, - 'status' => 'production', - ); - $iNWDevice = $this->CreateObject('NetworkDevice', $aData); - - // Contract/Infra - $this->CreateLinks($iNWDevice, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); - - // Interfaces - // - for($iLinked = 0 ; $iLinked < $this->m_iIfByNWDevice ; $iLinked++) - { - $aData = array( - 'name' => "eth$iLinked", - 'status' => 'implementation', - 'org_id' => $iOrg, - 'device_id' => $iNWDevice, - 'connected_if' => $this->RandomId('NetworkInterface', 'server if'), - 'status' => 'production', - ); - $this->CreateObject('NetworkInterface', $aData, 'equipment if'); - } - } - - ///////////////////////// - // - // Application Software - // - for($i = 0 ; $i < $this->m_aPlanned['Application SW'] ; $i++) - { - $aData = array( - 'name' => 'Software #'.$i, - ); - $iNWDevice = $this->CreateObject('Application', $aData); - } - - ///////////////////////// - // - // Applications - // - for($i = 0 ; $i < $this->m_aPlanned['Applications'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'device_id' => $this->RandomId('Server'), - 'software_id' => $this->RandomId('Application'), - 'name' => 'Application #'.$i, - 'status' => 'production', - ); - $iAppInstance = $this->CreateObject('ApplicationInstance', $aData); - - // Contract/Infra - $this->CreateLinks($iAppInstance, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); - } - - ///////////////////////// - // - // Application Solution - // - for($i = 0 ; $i < $this->m_aPlanned['Solutions'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'name' => 'Solution #'.$i, - 'status' => 'production', - ); - $iAppSolution = $this->CreateObject('ApplicationSolution', $aData); - - // Contract/Infra - $this->CreateLinks($iAppSolution, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); - } - - ///////////////////////// - // - // Business Process - // - for($i = 0 ; $i < $this->m_aPlanned['Processes'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'name' => 'Process #'.$i, - 'status' => 'production', - ); - $iProcess = $this->CreateObject('BusinessProcess', $aData); - - // Contract/Infra - $this->CreateLinks($iProcess, 1, 'lnkContractToCI', 'ci_id', 'contract_id'); - } - } - - public function CreateTickets($oP) - { - $iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'"); - $iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg"); - - ///////////////////////// - // - // Incident Tickets - // - for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'service_id' => $this->RandomId('Service'), - 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'Incident #'.$i, - 'description' => 'O que aconteceu?', - 'ticket_log' => 'Testing...', - ); - $iTicket = $this->CreateObject('Incident', $aData); - - // Incident/Infra - $iInfraCount = rand(1, 6); - $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); - - // Incident/Infra - $iContactCount = rand(1, 6); - $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); - } - - ///////////////////////// - // - // Big Ticket - // - $aData = array( - 'org_id' => $iOrg, - 'caller_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'service_id' => $this->RandomId('Service'), - 'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'), - 'title' => 'Big ticket', - 'description' => 'O que aconteceu?', - 'ticket_log' => 'Testing...', - ); - $iTicket = $this->CreateObject('Incident', $aData); - - // Incident/Infra - $iInfraCount = $this->m_aPlanned['Big ticket: CIs']; - $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); - - // Incident/Infra - $iContactCount = rand(1, 6); - $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); - - ///////////////////////// - // - // Change Tickets - // - for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++) - { - $aData = array( - 'org_id' => $iOrg, - 'requestor_id' => $this->RandomId('Person'), - 'workgroup_id' => $this->RandomId('Team'), - 'agent_id' => $this->RandomId('Person'), - 'supervisor_group_id' => $this->RandomId('Team'), - 'supervisor_id' => $this->RandomId('Person'), - 'manager_group_id' => $this->RandomId('Team'), - 'manager_id' => $this->RandomId('Person'), - 'title' => 'change #'.$i, - 'description' => "Let's do something there", - ); - $iTicket = $this->CreateObject('NormalChange', $aData); - - // Incident/Infra - $iInfraCount = rand(1, 6); - $this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id'); - - // Incident/Infra - $iContactCount = rand(1, 6); - $this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id'); - } - } - - public function MakeFeedback($oP) - { - foreach($this->m_aCreatedByClass as $sClass => $aClassIds) - { - $iSample = reset($aClassIds); - $sSample = "sample"; - - $iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3); - $fDurationMin = number_format(min($this->m_aStatsByClass[$sClass]), 3); - $fDurationMax = number_format(max($this->m_aStatsByClass[$sClass]), 3); - $fDurationAverage = number_format(array_sum($this->m_aStatsByClass[$sClass]) / count($this->m_aStatsByClass[$sClass]), 3); - - $oP->add("
              "); - $oP->add("
            • "); - $oP->add("$sClass: ".count($this->m_aStatsByClass[$sClass])." - $sSample
              "); - $oP->add("Duration: $fDurationMin => $fDurationMax; Avg:$fDurationAverage; Total: $iDuration"); - $oP->add("
            • "); - $oP->add("
            "); - } - } -} - -/** - * Ask the user what are the settings for the data load - */ -function DisplayStep1(SetupWebPage $oP) -{ - $sNextOperation = 'step2'; - $oP->add("

            iTop benchmarking

            \n"); - - $oP->add("
            \n"); - $oP->add("
            Data load configuration\n"); - $aForm = array(); - $aForm[] = array( - 'label' => "Contacts:", - 'input' => "", - 'help' => '', - ); - $aForm[] = array( - 'label' => "Contracts:", - 'input' => "", - 'help' => '', - ); - $oP->form($aForm); - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
            \n"); - - $oP->add("
            \n"); - $oP->add("
            Data load configuration\n"); - $aForm = array(); - $aForm[] = array( - 'label' => "Main CIs:", - 'input' => "", - 'help' => ' exclude interfaces, subnets or any other type of secondary CI', - ); - $oP->form($aForm); - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
            \n"); - - $oP->add("
            \n"); - $oP->add("
            Data load configuration\n"); - $aForm = array(); - $aForm[] = array( - 'label' => "Tickets:", - 'input' => "", - 'help' => ' 50% incidents, 50% changes', - ); - $aForm[] = array( - 'label' => "CIs for the big ticket:", - 'input' => "", - 'help' => 'Number of CI for the single big ticket', - ); - $oP->form($aForm); - $oP->add("
            \n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
            \n"); -} - - -/** - * Main program - */ - -LoginWebPage::DoLogin(); // Check user rights and prompt if needed - -$sOperation = Utils::ReadParam('operation', 'step1'); -$oP = new SetupWebPage('iTop benchmark utility'); - -ExecutionKPI::EnableDuration(); -$oKPI = new ExecutionKPI(); - -try -{ - switch($sOperation) - { - case 'step1': - DisplayStep1($oP); - break; - - case 'create_structure': - $oP->no_cache(); - $iPlannedContacts = Utils::ReadParam('plannedcontacts'); - $iPlannedContracts = Utils::ReadParam('plannedcontracts'); - - $oDataCreation = new BenchmarkDataCreation(); - $oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts); - $oDataCreation->ShowPlans($oP); - $oDataCreation->ShowForm($oP, 'create_structure_go'); - break; - - case 'create_structure_go': - $oP->no_cache(); - $iPlannedContacts = Utils::ReadParam('plannedcontacts'); - $iPlannedContracts = Utils::ReadParam('plannedcontracts'); - - $oDataCreation = new BenchmarkDataCreation(); - $oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts); - $oDataCreation->CreateStructure($oP); - $oDataCreation->MakeFeedback($oP); - break; - - case 'create_cis': - $oP->no_cache(); - $iPlannedCIs = Utils::ReadParam('plannedcis'); - - $oDataCreation = new BenchmarkDataCreation(); - $oDataCreation->PlanCis($iPlannedCIs); - $oDataCreation->ShowPlans($oP); - $oDataCreation->ShowForm($oP, 'create_cis_go'); - break; - - case 'create_cis_go': - $oP->no_cache(); - $iPlannedCIs = Utils::ReadParam('plannedcis'); - - $oDataCreation = new BenchmarkDataCreation(); - $oDataCreation->PlanCis($iPlannedCIs); - $oDataCreation->CreateCis($oP); - $oDataCreation->MakeFeedback($oP); - break; - - case 'create_tickets': - $oP->no_cache(); - $iPlannedTickets = Utils::ReadParam('plannedtickets'); - $iBigTicketCis = Utils::ReadParam('plannedbigticketcis'); - - $oDataCreation = new BenchmarkDataCreation(); - $oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis); - $oDataCreation->ShowPlans($oP); - $oDataCreation->ShowForm($oP, 'create_tickets_go'); - break; - - case 'create_tickets_go': - $oP->no_cache(); - $iPlannedTickets = Utils::ReadParam('plannedtickets'); - $iBigTicketCis = Utils::ReadParam('plannedbigticketcis'); - - $oDataCreation = new BenchmarkDataCreation(); - $oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis); - $oDataCreation->CreateTickets($oP); - $oDataCreation->MakeFeedback($oP); - break; - - default: - $oP->error("Error: unsupported operation '$sOperation'"); - } -} -catch(ZZException $e) -{ - $oP->error("Error: '".$e->getMessage()."'"); -} -catch(ZZCoreException $e) -{ - $oP->error("Error: '".$e->getHtmlDesc()."'"); -} -$oKPI->ComputeAndReport('Total execution'); -// too big (showing all queries) ExecutionKPI::ReportStats(); -//MetaModel::ShowQueryTrace(); -$oP->output(); -?> diff --git a/test/benchmark.php b/test/benchmark.php index 53d57524a1..759faac4e5 100644 --- a/test/benchmark.php +++ b/test/benchmark.php @@ -29,7 +29,7 @@ require_once('../application/wizardhelper.class.inc.php'); require_once('../application/startup.inc.php'); require_once('../application/loginwebpage.class.inc.php'); require_once('../application/utils.inc.php'); -require_once('./setuppage.class.inc.php'); +require_once('../setup/setuppage.class.inc.php'); //ini_set('memory_limit', '2048M'); diff --git a/toolkit.php b/toolkit.php index f8b8035de9..e8baaabf9a 100644 --- a/toolkit.php +++ b/toolkit.php @@ -16,7 +16,6 @@ echo "Check m echo "Backup and restore (shortcut)
            \n"; echo "Objects schema (shortcut)
            \n"; echo "Setup the email
            \n"; -echo "Generate data for benchmarking purposes
            \n"; echo "

            Web services

            \n"; echo "Available functions
            \n"; echo "WSDL (dynamically generated)
            \n"; From 1d0675ec9e1fb9be14ef5d675f39b2f9b5a31f87 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 13 Sep 2010 12:36:56 +0000 Subject: [PATCH 762/970] - Network Interfaces are back into the sample data... and they can be interconnected ! SVN:trunk[845] --- .../data.sample.FunctionalCI.xml | 2 +- .../data.sample.NetworkInterface.xml | 74 ++++++++++++------- .../module.itop-config-mgmt.php | 2 +- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/modules/itop-config-mgmt-1.0.0/data.sample.FunctionalCI.xml b/modules/itop-config-mgmt-1.0.0/data.sample.FunctionalCI.xml index b2dc6ede8b..aff6a6305a 100644 --- a/modules/itop-config-mgmt-1.0.0/data.sample.FunctionalCI.xml +++ b/modules/itop-config-mgmt-1.0.0/data.sample.FunctionalCI.xml @@ -46,7 +46,7 @@ 0 - +10.1.1.3 router diff --git a/modules/itop-config-mgmt-1.0.0/data.sample.NetworkInterface.xml b/modules/itop-config-mgmt-1.0.0/data.sample.NetworkInterface.xml index cf6e427009..b72e7cb6da 100644 --- a/modules/itop-config-mgmt-1.0.0/data.sample.NetworkInterface.xml +++ b/modules/itop-config-mgmt-1.0.0/data.sample.NetworkInterface.xml @@ -1,6 +1,6 @@ - + A0 production 2 @@ -12,35 +12,35 @@ 4 primary ethernet +10.1.1.2 +255.255.255.0 + +1000 +full +0 +downlink + + +A1 +production +2 +medium + + + + +4 +primary +ethernet full -3 -downlink - - -A1 -production -2 -medium - - - - -4 -primary -ethernet -10.1.1.2 -255.255.255.0 - - -full 0 uplink - + A2 implementation 2 @@ -57,10 +57,10 @@ full -14 + downlink - + eth0 implementation 2 @@ -77,10 +77,10 @@ 00:a2:23:b45:34 full -5 +2 uplink - + eth0 production 2 @@ -97,7 +97,27 @@ full -13 +3 uplink + +A0 +production +2 +medium + + + + +1 +primary +ethernet + + + +1000 +full +1 +downlink + \ No newline at end of file diff --git a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php index 803dca800a..9fcefddd00 100644 --- a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php @@ -41,7 +41,7 @@ SetupWebPage::AddModule( 'data.sample.DBServerInstance.xml', 'data.sample.ApplicationInstance.xml', 'data.sample.DatabaseInstance.xml', - //'data.sample.NetworkInterface.xml', + 'data.sample.NetworkInterface.xml', 'data.sample.lnkCIToContact.xml', 'data.sample.lnkProcessToSolution.xml', 'data.sample.lnkSolutionToCI.xml', From 15293fd2507a68f5ab168bd2b5d2d466974209fd Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 12:36:58 +0000 Subject: [PATCH 763/970] Redirect from /pages/index.php to /index.php (to prevent users from listing the directory) SVN:trunk[846] --- pages/index.php | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pages/index.php diff --git a/pages/index.php b/pages/index.php new file mode 100644 index 0000000000..d326d8048f --- /dev/null +++ b/pages/index.php @@ -0,0 +1,3 @@ + From 5218c00708a91b4932e1b94aa92dbc4e8e0aad83 Mon Sep 17 00:00:00 2001 From: Erwan Taloc Date: Mon, 13 Sep 2010 12:40:26 +0000 Subject: [PATCH 764/970] review data model SVN:trunk[847] --- .../itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php | 2 +- .../itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php | 2 +- .../itop-request-mgmt-1.0.0/model.itop-request-mgmt.php | 4 ++-- .../itop-service-mgmt-1.0.0/model.itop-service-mgmt.php | 4 ++-- modules/itop-tickets-1.0.0/model.itop-tickets.php | 8 +++++--- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index e3f1d2fe9f..49c55dd369 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -47,7 +47,7 @@ class Incident extends ResponseTicket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); } public function ComputeValues() diff --git a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php index 08d65e5bfe..b4d4d1a6ca 100644 --- a/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/model.itop-problem-mgmt.php @@ -72,7 +72,7 @@ class Problem extends Ticket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'ticket_log', 'start_date','knownerrors_list', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'service_id', 'servicesubcategory_id','product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date', 'last_update', 'assignment_date')); MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_change_id', 'close_date')); MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date')); - MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'service_id', 'priority')); // Lifecycle MetaModel::Init_DefineState( diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index 82812b78d6..4edf594643 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -50,7 +50,7 @@ class UserRequest extends ResponseTicket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'description', 'request_type','ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'document_list', 'ci_list', 'contact_list','incident_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment', 'freeze_reason')); MetaModel::Init_SetZListItems('advanced_search', array('ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('standard_search', array('ref', 'title', 'org_id', 'request_type','start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('list', array('ref', 'title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); + MetaModel::Init_SetZListItems('list', array('title', 'org_id', 'start_date', 'status', 'service_id', 'priority', 'workgroup_id', 'agent_id')); MetaModel::Init_OverloadStateAttribute('frozen', 'freeze_reason', OPT_ATT_MANDATORY); @@ -179,7 +179,7 @@ new TemplateMenuNode('UserRequest:Overview', '../modules/itop-request-mgmt-1.0.0 new NewObjectMenuNode('NewUserRequest', 'UserRequest', $oMyMenuGroup->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchUserRequests', 'UserRequest', $oMyMenuGroup->GetIndex(), 2 /* fRank */); $oShortcutNode = new TemplateMenuNode('UserRequest:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -$oNode = new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); +$oNode = new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id AND status NOT IN ("closed","resolved")', $oShortcutNode->GetIndex(), 1 /* fRank */); $oNode->SetParameters(array('auto_reload' => 'fast')); $oNode = new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); $oNode->SetParameters(array('auto_reload' => 'fast')); diff --git a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php index 0f5db8584a..40813bc8c9 100644 --- a/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/model.itop-service-mgmt.php @@ -368,10 +368,10 @@ class ServiceSubcategory extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeExternalKey("service_id", array("targetclass"=>"Service", "jointype"=>null, "allowed_values"=>null, "sql"=>"service_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalField("service_name", array("allowed_values"=>null, "extkey_attcode"=>"service_id", "target_attcode"=>"name", "is_null_allowed"=>true, "depends_on"=>array()))); - MetaModel::Init_SetZListItems('details', array('name', 'description', 'service_id')); + MetaModel::Init_SetZListItems('details', array('name', 'service_id','description')); MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'service_id')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'service_id')); - MetaModel::Init_SetZListItems('list', array('description', 'service_id')); + MetaModel::Init_SetZListItems('list', array('service_id','description')); } } class SLA extends cmdbAbstractObject diff --git a/modules/itop-tickets-1.0.0/model.itop-tickets.php b/modules/itop-tickets-1.0.0/model.itop-tickets.php index 5b4d65ec3c..9d7bfc802d 100644 --- a/modules/itop-tickets-1.0.0/model.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/model.itop-tickets.php @@ -215,7 +215,7 @@ abstract class ResponseTicket extends Ticket MetaModel::Init_SetZListItems('details', array('ref', 'title', 'org_id', 'ticket_log', 'start_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'document_list', 'ci_list', 'contact_list', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('advanced_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'related_problem_id', 'related_change_id', 'close_date', 'last_update', 'assignment_date', 'tto_escalation_deadline', 'ttr_escalation_deadline', 'closure_deadline', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); MetaModel::Init_SetZListItems('standard_search', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'servicesubcategory_id', 'product', 'impact', 'urgency', 'priority', 'workgroup_id', 'agent_id', 'agent_email', 'close_date', 'resolution_code', 'solution', 'user_satisfaction', 'user_commment')); - MetaModel::Init_SetZListItems('list', array('finalclass', 'ref', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'priority', 'workgroup_id', 'agent_id', 'last_update')); + MetaModel::Init_SetZListItems('list', array('finalclass', 'title', 'org_id', 'start_date', 'status', 'caller_id', 'service_id', 'priority', 'workgroup_id', 'agent_id', 'last_update')); // Lifecycle MetaModel::Init_DefineState( @@ -250,6 +250,8 @@ abstract class ResponseTicket extends Ticket 'resolution_code' => OPT_ATT_HIDDEN, 'solution' => OPT_ATT_HIDDEN, 'user_satisfaction' => OPT_ATT_HIDDEN, + 'related_problem_id' => OPT_ATT_HIDDEN, + 'related_change_id' => OPT_ATT_HIDDEN, 'user_commment' => OPT_ATT_HIDDEN, ), ) @@ -277,8 +279,8 @@ abstract class ResponseTicket extends Ticket 'workgroup_id' => OPT_ATT_MUSTPROMPT | OPT_ATT_MANDATORY, 'tto_escalation_deadline' => OPT_ATT_HIDDEN, 'ttr_escalation_deadline' => OPT_ATT_READONLY, - 'related_problem_id' => OPT_ATT_MUSTPROMPT, -// 'related_change_id' => OPT_ATT_MUSTPROMPT, + 'related_problem_id' => OPT_ATT_NORMAL, + 'related_change_id' => OPT_ATT_NORMAL, ), ) ); From 1199109e43e3c33fa2afef0ae122d7e06b207dcc Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 13:13:23 +0000 Subject: [PATCH 765/970] Fixed issue in test page SVN:trunk[848] --- test/test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.php b/test/test.php index 0664aac9b9..c8b3b671fa 100644 --- a/test/test.php +++ b/test/test.php @@ -85,7 +85,7 @@ function DisplayEvents($aEvents, $sTitle) require_once('../application/utils.inc.php'); -require_once('../core/test.class.inc.php'); +require_once('./test.class.inc.php'); require_once('./testlist.inc.php'); require_once('../core/cmdbobject.class.inc.php'); From 9be9a1ef17712cd78b359e7dab9ca38ef76826d6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 13 Sep 2010 13:36:50 +0000 Subject: [PATCH 766/970] - Menu and templates have been reviewed. SVN:trunk[849] --- application/templates/welcome_menu.html | 4 +- modules/itop-config-mgmt-1.0.0/cis_menu.html | 12 ++ .../itop-config-mgmt-1.0.0/contacts_menu.html | 136 +++++++++++++++--- .../en.dict.itop-config-mgmt.php | 4 +- .../es_cr.dict.itop-config-mgmt.php | 2 + .../fr.dict.itop-config-mgmt.php | 2 + .../model.itop-config-mgmt.php | 24 +--- 7 files changed, 146 insertions(+), 38 deletions(-) diff --git a/application/templates/welcome_menu.html b/application/templates/welcome_menu.html index e8bf09bd54..468fe3426f 100644 --- a/application/templates/welcome_menu.html +++ b/application/templates/welcome_menu.html @@ -173,7 +173,7 @@ function RestorePositions()

            UI:WelcomeMenu:MyCalls

            -SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id AND status NOT IN ("closed", "resolved") +SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id AND status NOT IN ("closed", "resolved")
        @@ -191,7 +191,7 @@ function RestorePositions()

        UI:WelcomeMenu:MyIncidents

        -SELECT Incident AS i WHERE i.agent_id = :current_contact_id AND status NOT IN ("closed", "resolved") +SELECT Incident AS i WHERE i.agent_id = :current_contact_id AND status NOT IN ("closed", "resolved")
        diff --git a/modules/itop-config-mgmt-1.0.0/cis_menu.html b/modules/itop-config-mgmt-1.0.0/cis_menu.html index 4162b8d3b7..bac11b61d8 100644 --- a/modules/itop-config-mgmt-1.0.0/cis_menu.html +++ b/modules/itop-config-mgmt-1.0.0/cis_menu.html @@ -167,6 +167,18 @@ a.summary:hover { SELECT Subnet
        +
        +SELECT BusinessProcess +
        + +
        +SELECT ApplicationSolution +
        + +
        +SELECT Group +
        +
        diff --git a/modules/itop-config-mgmt-1.0.0/contacts_menu.html b/modules/itop-config-mgmt-1.0.0/contacts_menu.html index 267a9c438a..541929ff88 100644 --- a/modules/itop-config-mgmt-1.0.0/contacts_menu.html +++ b/modules/itop-config-mgmt-1.0.0/contacts_menu.html @@ -1,22 +1,122 @@ -

        UI:ContactsMenu:Title

        - + +
        + + + +
        - - - - - - - -
        -

        UI-ContactsMenu-ContactsByLocation

        -SELECT Contact +
        + +
        +SELECT Team +
        + +
        +SELECT Person +
        +
        + SELECT Contact
        -

        UI-ContactsMenu-ContactsByType

        -SELECT Contact -
        -

        UI-ContactsMenu-ContactsByStatus

        -SELECT Contact -
        + + + + + + + + + diff --git a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php index 006e249305..0e164e60c0 100644 --- a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php @@ -1046,7 +1046,9 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:ConfigManagement:SWAndApps' => 'Software and Applications', 'Menu:ConfigManagement:Misc' => 'Miscellaneous', 'Menu:Group' => 'Groups of CIs', -'Menu:Group+' => 'Groups of CIs', +'Menu:Group+' => 'Groups of CIs', +'Menu:ConfigManagement:Shortcuts' => 'Shortcuts', +'Menu:ConfigManagement:AllContacts' => 'All contacts: %1$d', )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php index d082b789e5..065aef17c6 100644 --- a/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/es_cr.dict.itop-config-mgmt.php @@ -1015,5 +1015,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:PC+' => 'Todos los PCs (Computadores de Personales', 'Menu:Group' => 'Grupos de ICs', 'Menu:Group+' => 'Grupos de ICs', +'Menu:ConfigManagement:Shortcuts' => 'Atajos', +'Menu:ConfigManagement:AllContacts' => 'Todos los Contactos: %1$d', )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php index ee953afc20..ca1459a10e 100644 --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php @@ -1019,5 +1019,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:ConfigManagement:Misc' => 'Divers', 'Menu:Group' => 'Groupes de CIs', 'Menu:Group+' => 'Groupes de CIs', +'Menu:ConfigManagement:Shortcuts' => 'Raccourcis', +'Menu:ConfigManagement:AllContacts' => 'Tous les contacts: %1$d', )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index b1180a705b..4d319d2e58 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -1627,8 +1627,6 @@ new TemplateMenuNode('ConfigManagementOverview', '../modules/itop-config-mgmt-1. $oContactNode = new TemplateMenuNode('Contact', '../modules/itop-config-mgmt-1.0.0/contacts_menu.html', $oConfigManagementGroup->GetIndex(), 1 /* fRank */); new NewObjectMenuNode('NewContact', 'Contact', $oContactNode->GetIndex(), 1 /* fRank */); new SearchMenuNode('SearchContacts', 'Contact', $oContactNode->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Person', 'SELECT Person', $oContactNode->GetIndex(), 3 /* fRank */); -new OQLMenuNode('Team', 'SELECT Team', $oContactNode->GetIndex(), 4 /* fRank */); new OQLMenuNode('Document', 'SELECT Document', $oConfigManagementGroup->GetIndex(), 2 /* fRank */, true /* bSearch */); new OQLMenuNode('Location', 'SELECT Location', $oConfigManagementGroup->GetIndex(), 3 /* fRank */, true /* bSearch */); @@ -1639,20 +1637,12 @@ $oCINode = new TemplateMenuNode('ConfigManagementCI', '../modules/itop-config-mg new NewObjectMenuNode('NewCI', 'FunctionalCI', $oCINode->GetIndex(), 0 /* fRank */); new SearchMenuNode('SearchCIs', 'FunctionalCI', $oCINode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('BusinessProcess', 'SELECT BusinessProcess', $oCINode->GetIndex(), 2 /* fRank */); -new OQLMenuNode('ApplicationSolution', 'SELECT ApplicationSolution', $oCINode->GetIndex(), 3 /* fRank */); - -//$oSWNode = new TemplateMenuNode('ConfigManagementSoftware', '', $oCINode->GetIndex(), 4 /* fRank */); -//new OQLMenuNode('Licence', 'SELECT Licence', $oSWNode->GetIndex(), 0 /* fRank */); -//new OQLMenuNode('Patch', 'SELECT Patch', $oSWNode->GetIndex(), 1 /* fRank */); -//new OQLMenuNode('ApplicationInstance', 'SELECT SoftwareInstance', $oSWNode->GetIndex(), 2 /* fRank */); - -$oHWNode = new TemplateMenuNode('ConfigManagementHardware', '', $oCINode->GetIndex(), 5 /* fRank */); -//new OQLMenuNode('Subnet', 'SELECT Subnet', $oHWNode->GetIndex(), 0 /* fRank */); -new OQLMenuNode('NetworkDevice', 'SELECT NetworkDevice', $oHWNode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Server', 'SELECT Server', $oHWNode->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Printer', 'SELECT Printer', $oHWNode->GetIndex(), 3 /* fRank */); -//new OQLMenuNode('MobilePhone', 'SELECT MobilePhone', $oHWNode->GetIndex(), 4 /* fRank */); -new OQLMenuNode('PC', 'SELECT PC', $oHWNode->GetIndex(), 5 /* fRank */); +$oShortcutsNode = new TemplateMenuNode('ConfigManagement:Shortcuts', '', $oConfigManagementGroup->GetIndex(), 6 /* fRank */); +new OQLMenuNode('Server', 'SELECT Server', $oShortcutsNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('NetworkDevice', 'SELECT NetworkDevice', $oShortcutsNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('Printer', 'SELECT Printer', $oShortcutsNode->GetIndex(), 3 /* fRank */); +new OQLMenuNode('PC', 'SELECT PC', $oShortcutsNode->GetIndex(), 4 /* fRank */); +new OQLMenuNode('BusinessProcess', 'SELECT BusinessProcess', $oShortcutsNode->GetIndex(), 5 /* fRank */); +new OQLMenuNode('ApplicationSolution', 'SELECT ApplicationSolution', $oShortcutsNode->GetIndex(), 6 /* fRank */); ?> From 7e5681067f66019f41d822607f771cb0ffeeae22 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 13 Sep 2010 13:46:54 +0000 Subject: [PATCH 767/970] Different notification scheme for user requests SVN:trunk[850] --- modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml | 2 +- test/testlist.inc.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml b/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml index 6e980a646b..8706efb19b 100644 --- a/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml +++ b/modules/itop-request-mgmt-1.0.0/data.struct.ta-links.xml @@ -7,7 +7,7 @@ 3 -50 +20 2 diff --git a/test/testlist.inc.php b/test/testlist.inc.php index 55c8872d58..3adfe5dfa5 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -1564,7 +1564,7 @@ class TestImportREST extends TestWebServices /////////////////////////////////////////////////////////////////////////// // Test massive data load /////////////////////////////////////////////////////////////////////////// -define('IMPORT_COUNT', 1000); +define('IMPORT_COUNT', 4000); class TestImportRESTMassive extends TestImportREST { @@ -1581,7 +1581,7 @@ class TestImportRESTMassive extends TestImportREST protected function DoExecute() { $aLoadSpec = array( - 'desc' => 'Missing class', + 'desc' => 'Loading PCs: '.IMPORT_COUNT, 'args' => array( 'class' => 'PC', 'output' => 'summary', From fc2143c7b00061a346ce499f78ba8a5881ce34ed Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 13 Sep 2010 13:52:08 +0000 Subject: [PATCH 768/970] Spanish localization fine tuning... SVN:trunk[851] --- dictionaries/es_cr.dictionary.itop.ui.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index b76596c445..6a14b2a93b 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -298,11 +298,11 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( // Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( - 'Menu:WelcomeMenu' => 'Bievenido', - 'Menu:WelcomeMenu+' => 'Bievenido a iTop', - 'Menu:WelcomeMenuPage' => 'Bievenido', - 'Menu:WelcomeMenuPage+' => 'Bievenido a iTop', - 'UI:WelcomeMenu:Title' => 'Bievenido a iTop', + 'Menu:WelcomeMenu' => 'Bienvenido', + 'Menu:WelcomeMenu+' => 'Bienvenido a iTop', + 'Menu:WelcomeMenuPage' => 'Bienvenido', + 'Menu:WelcomeMenuPage+' => 'Bienvenido a iTop', + 'UI:WelcomeMenu:Title' => 'Bienvenido a iTop', 'UI:WelcomeMenu:LeftBlock' => '

        iTop es un completo; portal IT funcioanl basado en código abierto (OpenSource).

          Incluye: @@ -364,8 +364,9 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Button:ResetPassword' => ' Reset Password ', 'UI:SearchToggle' => 'Search', - 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', - 'UI:NoObjectToDisplay' => 'No object to display.', + 'UI:ClickToCreateNew' => 'Crear un nuevo %1$s', + 'UI:SearchFor_Class' => 'Buscar %1$s objetos', + 'UI:NoObjectToDisplay' => 'Ningún objeto para visualizar.', 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template.', 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template.', 'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by is mandatory. Check the definition of the display template.', @@ -430,7 +431,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:CountOfResults' => '%1$d object(s)', 'UI:ChangesLogTitle' => 'Changes log (%1$d):', 'UI:EmptyChangesLogTitle' => 'Changes log is empty', - 'UI:SearchFor_Class_Objects' => 'Search for %1$s Objects', + 'UI:SearchFor_Class_Objects' => 'Buscar %1$s objetos', 'UI:OQLQueryBuilderTitle' => 'OQL Query Builder', 'UI:OQLQueryTab' => 'OQL Query', 'UI:SimpleSearchTab' => 'Simple Search', From 2e06fae105479041a2fbb927f58adb0b6a37c63f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 13 Sep 2010 17:04:56 +0000 Subject: [PATCH 769/970] - Make the portal web page scrollable. - Support a per-customer stylesheet for tailoring the look of the portal. SVN:trunk[852] --- application/portalwebpage.class.inc.php | 14 ++++++++++---- portal/index.php | 11 ++++++++++- portal/portal.css | 24 +++++++++++++++++------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/application/portalwebpage.class.inc.php b/application/portalwebpage.class.inc.php index b6a443b758..af270379dd 100644 --- a/application/portalwebpage.class.inc.php +++ b/application/portalwebpage.class.inc.php @@ -37,7 +37,7 @@ class PortalWebPage extends NiceWebPage */ protected $m_aMenuButtons; - public function __construct($sTitle) + public function __construct($sTitle, $sAlternateStyleSheet = '') { $this->m_aMenuButtons = array(); parent::__construct($sTitle); @@ -45,7 +45,14 @@ class PortalWebPage extends NiceWebPage $this->add_header("Cache-control: no-cache"); $this->add_linked_stylesheet("../css/jquery.treeview.css"); $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); - $this->add_linked_stylesheet("../portal/portal.css"); + if ($sAlternateStyleSheet != '') + { + $this->add_linked_stylesheet("../portal/$sAlternateStyleSheet/portal.css"); + } + else + { + $this->add_linked_stylesheet("../portal/portal.css"); + } $this->add_linked_script('../js/jquery.layout.min.js'); $this->add_linked_script('../js/jquery.ba-bbq.min.js'); $this->add_linked_script("../js/jquery.tablehover.js"); @@ -158,12 +165,11 @@ EOF public function output() { $this->AddMenuButton('logoff', 'Portal:Disconnect', '../pages/logoff.php?portal=1'); // This menu is always present and is the last one - $sMenu = ' ',a.document);o.appendTo(a.document.getHead());try{b.hc=o.getComputedStyle('background-image')=='none';}catch(p){b.hc=false;}if(b.hc)b.cssClass+=' cke_hc';o.remove();})();j.load(i.corePlugins.split(','),function(){a.status='loaded';a.fire('loaded');var l=a._.pending;if(l){delete a._.pending;for(var m=0;m0){z=A.shift();while(!z.getParent().equals(D))z=z.getParent();if(!z.equals(H))E.push(z);H=z;}while(E.length>0){z=E.shift();if(z.getName()=='blockquote'){var I=new d.documentFragment(q.document);while(z.getFirst()){I.append(z.getFirst().remove());A.push(I.getLast());}I.replace(z);}else A.push(z);}var J=q.document.createElement('blockquote');J.insertBefore(A[0]);while(A.length>0){z=A.shift();J.append(z);}}else if(r==1){var K=[],L={};while(z=y.getNextParagraph()){var M=null,N=null;while(z.getParent()){if(z.getParent().getName()=='blockquote'){M=z.getParent();N=z;break;}z=z.getParent();}if(M&&N&&!N.getCustomData('blockquote_moveout')){K.push(N); +h.setMarker(L,N,'blockquote_moveout',true);}}h.clearAllMarkers(L);var O=[],P=[];L={};while(K.length>0){var Q=K.shift();J=Q.getParent();if(!Q.getPrevious())Q.remove().insertBefore(J);else if(!Q.getNext())Q.remove().insertAfter(J);else{Q.breakParent(Q.getParent());P.push(Q.getNext());}if(!J.getCustomData('blockquote_processed')){P.push(J);h.setMarker(L,J,'blockquote_processed',true);}O.push(Q);}h.clearAllMarkers(L);for(F=P.length-1;F>=0;F--){J=P[F];if(o(J))J.remove();}if(q.config.enterMode==2){var R=true;while(O.length){Q=O.shift();if(Q.getName()=='div'){I=new d.documentFragment(q.document);var S=R&&Q.getPrevious()&&!(Q.getPrevious().type==1&&Q.getPrevious().isBlockBoundary());if(S)I.append(q.document.createElement('br'));var T=Q.getNext()&&!(Q.getNext().type==1&&Q.getNext().isBlockBoundary());while(Q.getFirst())Q.getFirst().remove().appendTo(I);if(T)I.append(q.document.createElement('br'));I.replace(Q);R=false;}}}}s.selectBookmarks(u);q.focus();}};j.add('blockquote',{init:function(q){q.addCommand('blockquote',p);q.ui.addButton('Blockquote',{label:q.lang.blockquote,command:'blockquote'});q.on('selectionChange',n);},requires:['domiterator']});})();j.add('button',{beforeInit:function(m){m.ui.addHandler(1,k.button.handler);}});a.UI_BUTTON=1;k.button=function(m){e.extend(this,m,{title:m.label,className:m.className||m.command&&'cke_button_'+m.command||'',click:m.click||(function(n){n.execCommand(m.command);})});this._={};};k.button.handler={create:function(m){return new k.button(m);}};k.button.prototype={canGroup:true,render:function(m,n){var o=b,p=this._.id=e.getNextId(),q='',r=this.command,s,t;this._.editor=m;var u={id:p,button:this,editor:m,focus:function(){var w=a.document.getById(p);w.focus();},execute:function(){this.button.click(m);}};u.clickFn=s=e.addFunction(u.execute,u);u.index=t=k.button._.instances.push(u)-1;if(this.modes)m.on('mode',function(){this.setState(this.modes[m.mode]?2:0);},this);else if(r){r=m.getCommand(r);if(r){r.on('state',function(){this.setState(r.state);},this);q+='cke_'+(r.state==1?'on':r.state==0?'disabled':'off');}}if(!r)q+='cke_off';if(this.className)q+=' '+this.className;n.push('','=10900&&!o.hc?'':'" href="javascript:void(\''+(this.title||'').replace("'",'')+"')\"",' title="',this.title,'" tabindex="-1" hidefocus="true" role="button" aria-labelledby="'+p+'_label"'+(this.hasArrow?' aria-haspopup="true"':''));if(o.opera||o.gecko&&o.mac)n.push(' onkeypress="return false;"'); +if(o.gecko)n.push(' onblur="this.style.cssText = this.style.cssText;"');n.push(' onkeydown="return CKEDITOR.ui.button._.keydown(',t,', event);" onfocus="return CKEDITOR.ui.button._.focus(',t,', event);" onclick="CKEDITOR.tools.callFunction(',s,', this); return false;"> ',this.label,'');if(this.hasArrow)n.push(''+(b.hc?'▼':' ')+'');n.push('','');if(this.onRender)this.onRender();return u;},setState:function(m){if(this._.state==m)return false;this._.state=m;var n=a.document.getById(this._.id);if(n){n.setState(m);m==0?n.setAttribute('aria-disabled',true):n.removeAttribute('aria-disabled');m==1?n.setAttribute('aria-pressed',true):n.removeAttribute('aria-pressed');return true;}else return false;}};k.button._={instances:[],keydown:function(m,n){var o=k.button._.instances[m];if(o.onkey){n=new d.event(n);return o.onkey(o,n.getKeystroke())!==false;}},focus:function(m,n){var o=k.button._.instances[m],p;if(o.onfocus)p=o.onfocus(o,new d.event(n))!==false;if(b.gecko&&b.version<10900)n.preventBubble();return p;}};k.prototype.addButton=function(m,n){this.add(m,1,n);};a.on('reset',function(){k.button._.instances=[];});(function(){var m=function(t,u){var v=t.document,w=v.getBody(),x=0,y=function(){x=1;};w.on(u,y);(b.version>7?v.$:v.$.selection.createRange()).execCommand(u);w.removeListener(u,y);return x;},n=c?function(t,u){return m(t,u);}:function(t,u){try{return t.document.$.execCommand(u);}catch(v){return false;}},o=function(t){this.type=t;this.canUndo=this.type=='cut';};o.prototype={exec:function(t,u){this.type=='cut'&&s(t);var v=n(t,this.type);if(!v)alert(t.lang.clipboard[this.type+'Error']);return v;}};var p={canUndo:false,exec:c?function(t){t.focus();if(!t.document.getBody().fire('beforepaste')&&!m(t,'paste')){t.fire('pasteDialog');return false;}}:function(t){try{if(!t.document.getBody().fire('beforepaste')&&!t.document.$.execCommand('Paste',false,null))throw 0;}catch(u){setTimeout(function(){t.fire('pasteDialog');},0);return false;}}},q=function(t){if(this.mode!='wysiwyg')return;switch(t.data.keyCode){case 1000+86:case 2000+45:var u=this.document.getBody();if(!c&&u.fire('beforepaste'))t.cancel();else if(b.opera||b.gecko&&b.version<10900)u.fire('paste');return;case 1000+88:case 2000+46:var v=this; +this.fire('saveSnapshot');setTimeout(function(){v.fire('saveSnapshot');},0);}};function r(t,u,v){var w=this.document;if(w.getById('cke_pastebin'))return;if(u=='text'&&t.data&&t.data.$.clipboardData){var x=t.data.$.clipboardData.getData('text/plain');if(x){t.data.preventDefault();v(x);return;}}var y=this.getSelection(),z=new d.range(w),A=new h(u=='text'?'textarea':b.webkit?'body':'div',w);A.setAttribute('id','cke_pastebin');b.webkit&&A.append(w.createText('\xa0'));w.getBody().append(A);A.setStyles({position:'absolute',top:y.getStartElement().getDocumentPosition().y+'px',width:'1px',height:'1px',overflow:'hidden'});A.setStyle(this.config.contentsLangDirection=='ltr'?'left':'right','-1000px');var B=y.createBookmarks();if(u=='text'){if(c){var C=w.getBody().$.createTextRange();C.moveToElementText(A.$);C.execCommand('Paste');t.data.preventDefault();}else{w.$.designMode='off';A.$.focus();}}else{z.setStartAt(A,1);z.setEndAt(A,2);z.select(true);}window.setTimeout(function(){u=='text'&&!c&&(w.$.designMode='on');A.remove();var D;A=b.webkit&&(D=A.getFirst())&&D.is&&D.hasClass('Apple-style-span')?D:A;y.selectBookmarks(B);v(A['get'+(u=='text'?'Value':'Html')]());},0);};function s(t){if(!c||b.quirks)return;var u=t.getSelection(),v;if(u.getType()==3&&(v=u.getSelectedElement())){var w=u.getRanges()[0],x=t.document.createText('');x.insertBefore(v);w.setStartBefore(x);w.setEndAfter(v);u.selectRanges([w]);setTimeout(function(){if(v.getParent()){x.remove();u.selectElement(v);}},0);}};j.add('clipboard',{requires:['dialog','htmldataprocessor'],init:function(t){t.on('paste',function(y){var z=y.data;if(z.html)t.insertHtml(z.html);else if(z.text)t.insertText(z.text);},null,null,1000);t.on('pasteDialog',function(y){setTimeout(function(){t.openDialog('paste');},0);});function u(y,z,A,B){var C=t.lang[z];t.addCommand(z,A);t.ui.addButton(y,{label:C,command:z});if(t.addMenuItems)t.addMenuItem(z,{label:C,command:z,group:'clipboard',order:B});};u('Cut','cut',new o('cut'),1);u('Copy','copy',new o('copy'),4);u('Paste','paste',p,8);a.dialog.add('paste',a.getUrl(this.path+'dialogs/paste.js'));t.on('key',q,t);var v=t.config.forcePasteAsPlainText?'text':'html';t.on('contentDom',function(){var y=t.document.getBody();y.on(v=='text'&&c||b.webkit?'paste':'beforepaste',function(z){if(w)return;r.call(t,z,v,function(A){if(!A)return;var B={};B[v]=A;t.fire('paste',B);});});y.on('beforecut',function(){!w&&s(t);});});if(t.contextMenu){var w;function x(y){c&&(w=1);var z=t.document.$.queryCommandEnabled(y)?2:0; +w=0;return z;};t.contextMenu.addListener(function(y,z){var A=z.getCommonAncestor().isReadOnly();return{cut:!A&&x('Cut'),copy:x('Copy'),paste:!A&&(b.webkit?2:x('Paste'))};});}}});})();j.add('colorbutton',{requires:['panelbutton','floatpanel','styles'],init:function(m){var n=m.config,o=m.lang.colorButton,p;if(!b.hc){q('TextColor','fore',o.textColorTitle);q('BGColor','back',o.bgColorTitle);}function q(s,t,u){m.ui.add(s,4,{label:u,title:u,className:'cke_button_'+s.toLowerCase(),modes:{wysiwyg:1},panel:{css:m.skin.editor.css,attributes:{role:'listbox','aria-label':o.panelTitle}},onBlock:function(v,w){w.autoSize=true;w.element.addClass('cke_colorblock');w.element.setHtml(r(v,t));w.element.getDocument().getBody().setStyle('overflow','hidden');var x=w.keys,y=m.lang.dir=='rtl';x[y?37:39]='next';x[40]='next';x[9]='next';x[y?39:37]='prev';x[38]='prev';x[2000+9]='prev';x[32]='click';}});};function r(s,t){var u=[],v=n.colorButton_colors.split(','),w=v.length+(n.colorButton_enableMore?2:1),x=e.addFunction(function(D,E){if(D=='?'){var F=arguments.callee;function G(I){this.removeListener('ok',G);this.removeListener('cancel',G);I.name=='ok'&&F(this.getContentElement('picker','selectedColor').getValue(),E);};m.openDialog('colordialog',function(){this.on('ok',G);this.on('cancel',G);});return;}m.focus();s.hide();m.fire('saveSnapshot');new a.style(n['colorButton_'+E+'Style'],{color:'inherit'}).remove(m.document);if(D){var H=n['colorButton_'+E+'Style'];H.childRule=E=='back'?function(){return false;}:function(I){return I.getName()!='a';};new a.style(H,{color:D}).apply(m.document);}m.fire('saveSnapshot');});u.push('
          ',o.auto,'
          ');for(var y=0;y');var z=v[y].split('/'),A=z[0],B=z[1]||A;if(!z[1])A='#'+A.replace(/^(.)(.)(.)$/,'$1$1$2$2$3$3');var C=m.lang.colors[B]||B;u.push(''); +}if(n.colorButton_enableMore===undefined||n.colorButton_enableMore)u.push('');u.push('
          ',o.more,'
          ');return u.join('');};}});i.colorButton_colors='000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF';i.colorButton_foreStyle={element:'span',styles:{color:'#(color)'},overrides:[{element:'font',attributes:{color:null}}]};i.colorButton_backStyle={element:'span',styles:{'background-color':'#(color)'}};(function(){j.colordialog={init:function(m){m.addCommand('colordialog',new a.dialogCommand('colordialog'));a.dialog.add('colordialog',this.path+'dialogs/colordialog.js');}};j.add('colordialog',j.colordialog);})();j.add('contextmenu',{requires:['menu'],beforeInit:function(m){m.contextMenu=new j.contextMenu(m);m.addCommand('contextMenu',{exec:function(){m.contextMenu.show(m.document.getBody());}});}});j.contextMenu=e.createClass({$:function(m){this.id=e.getNextId();this.editor=m;this._.listeners=[];this._.functionId=e.addFunction(function(n){this._.panel.hide();m.focus();m.execCommand(n);},this);this.definition={panel:{className:m.skinClass+' cke_contextmenu',attributes:{'aria-label':m.lang.contextmenu.options}}};},_:{onMenu:function(m,n,o,p){var q=this._.menu,r=this.editor;if(q){q.hide();q.removeAll();}else{q=this._.menu=new a.menu(r,this.definition);q.onClick=e.bind(function(A){q.hide();if(A.onClick)A.onClick();else if(A.command)r.execCommand(A.command);},this);q.onEscape=function(A){var B=this.parent;if(B){B._.panel.hideChild();var C=B._.panel._.panel._.currentBlock,D=C._.focusIndex;C._.markItem(D);}else if(A==27){this.hide();r.focus();}return false;};}var s=this._.listeners,t=[],u=this.editor.getSelection(),v=u&&u.getStartElement();q.onHide=e.bind(function(){q.onHide=null;if(c){var A=r.getSelection();A&&A.unlock();}this.onHide&&this.onHide();},this);for(var w=0;w ';j.add('elementspath',{requires:['selection'],init:function(o){var p='cke_path_'+o.name,q,r=function(){if(!q)q=a.document.getById(p);return q;},s='cke_elementspath_'+e.getNextNumber()+'_';o._.elementsPath={idBase:s,filters:[]};o.on('themeSpace',function(t){if(t.data.space=='bottom')t.data.html+=''+o.lang.elementsPath.eleLabel+''+'
          '+n+'
          ';});o.on('selectionChange',function(t){var u=b,v=t.data.selection,w=v.getStartElement(),x=[],y=t.editor,z=y._.elementsPath.list=[],A=y._.elementsPath.filters; +while(w){var B=0;for(var C=0;C',E,''+G+'','');}if(E=='body')break;w=w.getParent();}r().setHtml(x.join('')+n);});o.on('contentDomUnload',function(){q&&q.setHtml(n);});o.addCommand('elementsPathFocus',m.toolbarFocus);}});})();a._.elementsPath={click:function(m,n){var o=a.instances[m];o.focus();var p=o._.elementsPath.list[n];o.getSelection().selectElement(p);return false;},keydown:function(m,n,o){var p=k.button._.instances[n],q=a.instances[m],r=q._.elementsPath.idBase,s;o=new d.event(o);var t=q.lang.dir=='rtl';switch(o.getKeystroke()){case t?39:37:case 9:s=a.document.getById(r+(n+1));if(!s)s=a.document.getById(r+'0');s.focus();return false;case t?37:39:case 2000+9:s=a.document.getById(r+(n-1));if(!s)s=a.document.getById(r+(q._.elementsPath.list.length-1));s.focus();return false;case 27:q.focus();return false;case 13:case 32:this.click(m,n);return false;}return true;}};(function(){j.add('enterkey',{requires:['keystrokes','indent'],init:function(t){var u=t.specialKeys;u[13]=r;u[2000+13]=q;}});j.enterkey={enterBlock:function(t,u,v,w){v=v||s(t);if(!v)return;var x=v.document;if(v.checkStartOfBlock()&&v.checkEndOfBlock()){var y=new d.elementPath(v.startContainer),z=y.block;if(z&&(z.is('li')||z.getParent().is('li'))){t.execCommand('outdent');return;}}var A=u==3?'div':'p',B=v.splitBlock(A);if(!B)return;var C=B.previousBlock,D=B.nextBlock,E=B.wasStartOfBlock,F=B.wasEndOfBlock,G;if(D){G=D.getParent();if(G.is('li')){D.breakParent(G);D.move(D.getNext(),1);}}else if(C&&(G=C.getParent())&&G.is('li')){C.breakParent(G);v.moveToElementEditStart(C.getNext());C.move(C.getPrevious());}if(!E&&!F){if(D.is('li')&&(G=D.getFirst(d.walker.invisible(true)))&&G.is&&G.is('ul','ol'))(c?x.createText('\xa0'):x.createElement('br')).insertBefore(G); +if(D)v.moveToElementEditStart(D);}else{var H;if(C){if(C.is('li')||!p.test(C.getName()))H=C.clone();}else if(D)H=D.clone();if(!H)H=x.createElement(A);else if(w&&!H.is('li'))H.renameNode(A);var I=B.elementPath;if(I)for(var J=0,K=I.elements.length;J0;v--)u[v].deleteContents();return u[0];};})();(function(){var m='nbsp,gt,lt,quot',n='iexcl,cent,pound,curren,yen,brvbar,sect,uml,copy,ordf,laquo,not,shy,reg,macr,deg,plusmn,sup2,sup3,acute,micro,para,middot,cedil,sup1,ordm,raquo,frac14,frac12,frac34,iquest,times,divide,fnof,bull,hellip,prime,Prime,oline,frasl,weierp,image,real,trade,alefsym,larr,uarr,rarr,darr,harr,crarr,lArr,uArr,rArr,dArr,hArr,forall,part,exist,empty,nabla,isin,notin,ni,prod,sum,minus,lowast,radic,prop,infin,ang,and,or,cap,cup,int,there4,sim,cong,asymp,ne,equiv,le,ge,sub,sup,nsub,sube,supe,oplus,otimes,perp,sdot,lceil,rceil,lfloor,rfloor,lang,rang,loz,spades,clubs,hearts,diams,circ,tilde,ensp,emsp,thinsp,zwnj,zwj,lrm,rlm,ndash,mdash,lsquo,rsquo,sbquo,ldquo,rdquo,bdquo,dagger,Dagger,permil,lsaquo,rsaquo,euro',o='Agrave,Aacute,Acirc,Atilde,Auml,Aring,AElig,Ccedil,Egrave,Eacute,Ecirc,Euml,Igrave,Iacute,Icirc,Iuml,ETH,Ntilde,Ograve,Oacute,Ocirc,Otilde,Ouml,Oslash,Ugrave,Uacute,Ucirc,Uuml,Yacute,THORN,szlig,agrave,aacute,acirc,atilde,auml,aring,aelig,ccedil,egrave,eacute,ecirc,euml,igrave,iacute,icirc,iuml,eth,ntilde,ograve,oacute,ocirc,otilde,ouml,oslash,ugrave,uacute,ucirc,uuml,yacute,thorn,yuml,OElig,oelig,Scaron,scaron,Yuml',p='Alpha,Beta,Gamma,Delta,Epsilon,Zeta,Eta,Theta,Iota,Kappa,Lambda,Mu,Nu,Xi,Omicron,Pi,Rho,Sigma,Tau,Upsilon,Phi,Chi,Psi,Omega,alpha,beta,gamma,delta,epsilon,zeta,eta,theta,iota,kappa,lambda,mu,nu,xi,omicron,pi,rho,sigmaf,sigma,tau,upsilon,phi,chi,psi,omega,thetasym,upsih,piv'; +function q(r,s){var t={},u=[],v={nbsp:'\xa0',shy:'­',gt:'>',lt:'<'};r=r.replace(/\b(nbsp|shy|gt|lt|amp)(?:,|$)/g,function(A,B){var C=s?'&'+B+';':v[B],D=s?v[B]:'&'+B+';';t[C]=D;u.push(C);return '';});if(!s){r=r.split(',');var w=document.createElement('div'),x;w.innerHTML='&'+r.join(';&')+';';x=w.innerHTML;w=null;for(var y=0;y'+u+'',u);}},onClick:function(t){m.focus();m.fire('saveSnapshot');q[t].apply(m.document);setTimeout(function(){m.fire('saveSnapshot');},0);},onRender:function(){m.on('selectionChange',function(t){var u=this.getValue(),v=t.data.path;for(var w in q){if(q[w].checkActive(v)){if(w!=u)this.setValue(w,m.lang.format['tag_'+w]);return;}}this.setValue('');},this);}});}});i.format_tags='p;h1;h2;h3;h4;h5;h6;pre;address;div';i.format_p={element:'p'};i.format_div={element:'div'};i.format_pre={element:'pre'};i.format_address={element:'address'};i.format_h1={element:'h1'};i.format_h2={element:'h2'};i.format_h3={element:'h3'};i.format_h4={element:'h4'};i.format_h5={element:'h5'};i.format_h6={element:'h6'};j.add('forms',{init:function(m){var n=m.lang;m.addCss('form{border: 1px dotted #FF0000;padding: 2px;}\n');m.addCss('img.cke_hidden{background-image: url('+a.getUrl(this.path+'images/hiddenfield.gif')+');'+'background-position: center center;'+'background-repeat: no-repeat;'+'border: 1px solid #a9a9a9;'+'width: 16px !important;'+'height: 16px !important;'+'}'); +var o=function(q,r,s){m.addCommand(r,new a.dialogCommand(r));m.ui.addButton(q,{label:n.common[q.charAt(0).toLowerCase()+q.slice(1)],command:r});a.dialog.add(r,s);},p=this.path+'dialogs/';o('Form','form',p+'form.js');o('Checkbox','checkbox',p+'checkbox.js');o('Radio','radio',p+'radio.js');o('TextField','textfield',p+'textfield.js');o('Textarea','textarea',p+'textarea.js');o('Select','select',p+'select.js');o('Button','button',p+'button.js');o('ImageButton','imagebutton',j.getPath('image')+'dialogs/image.js');o('HiddenField','hiddenfield',p+'hiddenfield.js');if(m.addMenuItems)m.addMenuItems({form:{label:n.form.menu,command:'form',group:'form'},checkbox:{label:n.checkboxAndRadio.checkboxTitle,command:'checkbox',group:'checkbox'},radio:{label:n.checkboxAndRadio.radioTitle,command:'radio',group:'radio'},textfield:{label:n.textfield.title,command:'textfield',group:'textfield'},hiddenfield:{label:n.hidden.title,command:'hiddenfield',group:'hiddenfield'},imagebutton:{label:n.image.titleButton,command:'imagebutton',group:'imagebutton'},button:{label:n.button.title,command:'button',group:'button'},select:{label:n.select.title,command:'select',group:'select'},textarea:{label:n.textarea.title,command:'textarea',group:'textarea'}});if(m.contextMenu){m.contextMenu.addListener(function(q){if(q&&q.hasAscendant('form',true)&&!q.isReadOnly())return{form:2};});m.contextMenu.addListener(function(q){if(q&&!q.isReadOnly()){var r=q.getName();if(r=='select')return{select:2};if(r=='textarea')return{textarea:2};if(r=='input'){var s=q.getAttribute('type');if(s=='text'||s=='password')return{textfield:2};if(s=='button'||s=='submit'||s=='reset')return{button:2};if(s=='checkbox')return{checkbox:2};if(s=='radio')return{radio:2};if(s=='image')return{imagebutton:2};}if(r=='img'&&q.getAttribute('_cke_real_element_type')=='hiddenfield')return{hiddenfield:2};}});}m.on('doubleclick',function(q){var r=q.data.element;if(r.is('form'))q.data.dialog='form';else if(r.is('select'))q.data.dialog='select';else if(r.is('textarea'))q.data.dialog='textarea';else if(r.is('img')&&r.getAttribute('_cke_real_element_type')=='hiddenfield')q.data.dialog='hiddenfield';else if(r.is('input')){var s=r.getAttribute('type');switch(s){case 'text':case 'password':q.data.dialog='textfield';break;case 'button':case 'submit':case 'reset':q.data.dialog='button';break;case 'checkbox':q.data.dialog='checkbox';break;case 'radio':q.data.dialog='radio';break;case 'image':q.data.dialog='imagebutton';break;}}});},afterInit:function(m){var n=m.dataProcessor,o=n&&n.htmlFilter,p=n&&n.dataFilter; +if(c)o&&o.addRules({elements:{input:function(q){var r=q.attributes,s=r.type;if(s=='checkbox'||s=='radio')r.value=='on'&&delete r.value;}}});if(p)p.addRules({elements:{input:function(q){if(q.attributes.type=='hidden')return m.createFakeParserElement(q,'cke_hidden','hiddenfield');}}});},requires:['image','fakeobjects']});if(c)h.prototype.hasAttribute=function(m){var p=this;var n=p.$.attributes.getNamedItem(m);if(p.getName()=='input')switch(m){case 'class':return p.$.className.length>0;case 'checked':return!!p.$.checked;case 'value':var o=p.getAttribute('type');if(o=='checkbox'||o=='radio')return p.$.value!='on';break;default:}return!!(n&&n.specified);};(function(){var m={canUndo:false,exec:function(o){o.insertElement(o.document.createElement('hr'));}},n='horizontalrule';j.add(n,{init:function(o){o.addCommand(n,m);o.ui.addButton('HorizontalRule',{label:o.lang.horizontalrule,command:n});}});})();(function(){var m=/^[\t\r\n ]*(?: |\xa0)$/,n='{cke_protected}';function o(T){var U=T.children.length,V=T.children[U-1];while(V&&V.type==3&&!e.trim(V.value))V=T.children[--U];return V;};function p(T,U){var V=T.children,W=o(T);if(W){if((U||!c)&&W.type==1&&W.name=='br')V.pop();if(W.type==3&&m.test(W.value))V.pop();}};function q(T){var U=o(T);return!U||U.type==1&&U.name=='br'||T.name=='form'&&U.name=='input';};function r(T){p(T,true);if(q(T))if(c)T.add(new a.htmlParser.text('\xa0'));else T.add(new a.htmlParser.element('br',{}));};function s(T){p(T);if(q(T))T.add(new a.htmlParser.text('\xa0'));};var t=f,u=e.extend({},t.$block,t.$listItem,t.$tableContent);for(var v in u){if(!('br' in t[v]))delete u[v];}delete u.pre;var w={elements:{},attributeNames:[[/^on/,'_cke_pa_on']]},x={elements:{}};for(v in u)x.elements[v]=r;var y={elementNames:[[/^cke:/,''],[/^\?xml:namespace$/,'']],attributeNames:[[/^_cke_(saved|pa)_/,''],[/^_cke.*/,''],['hidefocus','']],elements:{$:function(T){var U=T.attributes;if(U){if(U.cke_temp)return false;var V=['name','href','src'],W;for(var X=0;X]+)))([^>]*)>/gi,D=/\s_cke_saved_src\s*=/,E=/(?:])[^>]*>[\s\S]*<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,F=/([^<]*)<\/cke:encoded>/gi,G=/(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,H=/(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi,I=/]*?)\/?>(?!\s*<\/cke:\1)/gi;function J(T){return T.replace(C,function(U,V,W,X,Y){if(X=='src'&&D.test(U))return U;else return '<'+V+W+' _cke_saved_'+W+Y+'>';});};function K(T){return T.replace(E,function(U){return ''+encodeURIComponent(U)+'';});};function L(T){return T.replace(F,function(U,V){return decodeURIComponent(V);});};function M(T){return T.replace(G,'$1cke:$2');};function N(T){return T.replace(H,'$1$2');};function O(T){return T.replace(I,'');};function P(T){return T.replace(/(]*>)(\r\n|\n)/g,'$1$2$2');};function Q(T){return T.replace(//g,function(U){return '';});};function R(T){return T.replace(//g,function(U,V){return decodeURIComponent(V);});};function S(T,U){var V=[],W=/<\!--\{cke_temp(comment)?\}(\d*?)-->/g,X=[//gi,//gi].concat(U);T=T.replace(//g,function(Z){return '';});for(var Y=0;Y';});T=T.replace(W,function(Z,aa,ab){return ''; +});return T;};j.add('htmldataprocessor',{requires:['htmlwriter'],init:function(T){var U=T.dataProcessor=new a.htmlDataProcessor(T);U.writer.forceSimpleAmpersand=T.config.forceSimpleAmpersand;U.dataFilter.addRules(w);U.dataFilter.addRules(x);U.htmlFilter.addRules(y);U.htmlFilter.addRules(z);}});a.htmlDataProcessor=function(T){var U=this;U.editor=T;U.writer=new a.htmlWriter();U.dataFilter=new a.htmlParser.filter();U.htmlFilter=new a.htmlParser.filter();};a.htmlDataProcessor.prototype={toHtml:function(T,U){T=S(T,this.editor.config.protectedSource);T=J(T);T=K(T);T=M(T);T=O(T);T=P(T);var V=new h('div');V.setHtml('a'+T);T=V.getHtml().substr(1);T=N(T);T=L(T);T=R(T);var W=a.htmlParser.fragment.fromHtml(T,U),X=new a.htmlParser.basicWriter();W.writeHtml(X,this.dataFilter);T=X.getHtml(true);T=Q(T);return T;},toDataFormat:function(T,U){var V=this.writer,W=a.htmlParser.fragment.fromHtml(T,U);V.reset();W.writeHtml(V,this.htmlFilter);return V.getHtml(true);}};})();j.add('image',{init:function(m){var n='image';a.dialog.add(n,this.path+'dialogs/image.js');m.addCommand(n,new a.dialogCommand(n));m.ui.addButton('Image',{label:m.lang.common.image,command:n});m.on('doubleclick',function(o){var p=o.data.element;if(p.is('img')&&!p.getAttribute('_cke_realelement'))o.data.dialog='image';});if(m.addMenuItems)m.addMenuItems({image:{label:m.lang.image.menu,command:'image',group:'image'}});if(m.contextMenu)m.contextMenu.addListener(function(o,p){if(!o||!o.is('img')||o.getAttribute('_cke_realelement')||o.isReadOnly())return null;return{image:2};});}});i.image_removeLinkByEmptyURL=true;(function(){var m={ol:1,ul:1},n=d.walker.whitespaces(true),o=d.walker.bookmark(false,true);function p(u,v){u.getCommand(this.name).setState(v);};function q(u){var D=this;var v=u.editor,w=u.data.path,x=w&&w.contains(m);if(x)return p.call(D,v,2);if(!D.useIndentClasses&&D.name=='indent')return p.call(D,v,2);var y=u.data.path,z=y.block||y.blockLimit;if(!z)return p.call(D,v,0);if(D.useIndentClasses){var A=z.$.className.match(D.classNameRegex),B=0;if(A){A=A[1];B=D.indentClassMap[A];}if(D.name=='outdent'&&!B||D.name=='indent'&&B==v.config.indentClasses.length)return p.call(D,v,0);return p.call(D,v,2);}else{var C=parseInt(z.getStyle(s(z)),10);if(isNaN(C))C=0;if(C<=0)return p.call(D,v,0);return p.call(D,v,2);}};function r(u,v){var x=this;x.name=v;x.useIndentClasses=u.config.indentClasses&&u.config.indentClasses.length>0;if(x.useIndentClasses){x.classNameRegex=new RegExp('(?:^|\\s+)('+u.config.indentClasses.join('|')+')(?=$|\\s)'); +x.indentClassMap={};for(var w=0;wY;T++)X[T].indent+=U;var aa=M.getAttribute('dir')||M.getStyle('direction'),ab=j.list.arrayToList(X,w,null,u.config.enterMode,aa);if(v.name=='outdent'){var ac;if((ac=M.getParent())&&ac.is('li')){var ad=ab.listNode.getChildren(),ae=[],af=ad.count(),ag;for(T=af-1;T>=0;T--){if((ag=ad.getItem(T))&&ag.is&&ag.is('li'))ae.push(ag);}}}if(ab)ab.listNode.replace(M);if(ae&&ae.length)for(T=0;T=0;A--){x=v[A].createIterator();x.enlargeBr=t!=2;while(y=x.getNextParagraph()){y.removeAttribute('align');y.removeStyle('text-align');var B=w&&(y.$.className=e.ltrim(y.$.className.replace(D.cssClassRegex,''))),C=D.state==2&&(!z||n(y,true)!=D.value);if(w){if(C)y.addClass(w);else if(!B)y.removeAttribute('class');}else if(C)y.setStyle('text-align',D.value);}}r.focus();r.forceNextSelectionCheck();s.selectBookmarks(u);}};j.add('justify',{init:function(r){var s=new p(r,'justifyleft','left'),t=new p(r,'justifycenter','center'),u=new p(r,'justifyright','right'),v=new p(r,'justifyblock','justify');r.addCommand('justifyleft',s);r.addCommand('justifycenter',t);r.addCommand('justifyright',u);r.addCommand('justifyblock',v);r.ui.addButton('JustifyLeft',{label:r.lang.justify.left,command:'justifyleft'});r.ui.addButton('JustifyCenter',{label:r.lang.justify.center,command:'justifycenter'});r.ui.addButton('JustifyRight',{label:r.lang.justify.right,command:'justifyright'});r.ui.addButton('JustifyBlock',{label:r.lang.justify.block,command:'justifyblock'});r.on('selectionChange',e.bind(o,s));r.on('selectionChange',e.bind(o,u));r.on('selectionChange',e.bind(o,t));r.on('selectionChange',e.bind(o,v));r.on('dirChanged',q);},requires:['domiterator']});})();j.add('keystrokes',{beforeInit:function(m){m.keystrokeHandler=new a.keystrokeHandler(m);m.specialKeys={};},init:function(m){var n=m.config.keystrokes,o=m.config.blockedKeystrokes,p=m.keystrokeHandler.keystrokes,q=m.keystrokeHandler.blockedKeystrokes;for(var r=0;rF[H-1].indent+1){var L=F[H-1].indent+1-F[H].indent,M=F[H].indent;while(F[H]&&F[H].indent>=M){F[H].indent+=L;H++;}H--;}}var N=j.list.arrayToList(F,E,null,C.config.enterMode,D.root.getAttribute('dir')),O=N.listNode,P,Q;function R(S){if((P=O[S?'getFirst':'getLast']())&&!(P.is&&P.isBlockBoundary())&&(Q=D.root[S?'getPrevious':'getNext'](d.walker.whitespaces(true)))&&!(Q.is&&Q.isBlockBoundary({br:1})))C.document.createElement('br')[S?'insertBefore':'insertAfter'](P);};R(true);R();O.replace(D.root);};function u(C,D){this.name=C;this.type=D;};u.prototype={exec:function(C){C.focus();var D=C.document,E=C.getSelection(),F=E&&E.getRanges(true);if(!F||F.length<1)return;if(this.state==2){var G=D.getBody();G.trim();if(!G.getFirst()){var H=D.createElement(C.config.enterMode==1?'p':C.config.enterMode==3?'div':'br');H.appendTo(G);F=new d.rangeList([new d.range(D)]);if(H.is('br')){F[0].setStartBefore(H);F[0].setEndAfter(H);}else F[0].selectNodeContents(H);E.selectRanges(F);}else{var I=F.length==1&&F[0],J=I&&I.getEnclosedNode();if(J&&J.is&&this.type==J.getName())o.call(this,C,1);}}var K=E.createBookmarks(true),L=[],M={},N=F.createIterator(),O=0;while((I=N.getNextRange())&&++O){var P=I.getBoundaryNodes(),Q=P.startNode,R=P.endNode;if(Q.type==1&&Q.getName()=='td')I.setStartAt(P.startNode,1);if(R.type==1&&R.getName()=='td')I.setEndAt(P.endNode,2);var S=I.createIterator(),T;S.forceBrBreak=this.state==2;while(T=S.getNextParagraph()){if(T.getCustomData('list_block'))continue;else h.setMarker(M,T,'list_block',1);var U=new d.elementPath(T),V=U.elements,W=V.length,X=null,Y=0,Z=U.blockLimit,aa;for(var ab=W-1;ab>=0&&(aa=V[ab]);ab--){if(m[aa.getName()]&&Z.contains(aa)){Z.removeCustomData('list_group_object_'+O);var ac=aa.getCustomData('list_group_object');if(ac)ac.contents.push(T); +else{ac={root:aa,contents:[T]};L.push(ac);h.setMarker(M,aa,'list_group_object',ac);}Y=1;break;}}if(Y)continue;var ad=Z;if(ad.getCustomData('list_group_object_'+O))ad.getCustomData('list_group_object_'+O).contents.push(T);else{ac={root:ad,contents:[T]};h.setMarker(M,ad,'list_group_object_'+O,ac);L.push(ac);}}}var ae=[];while(L.length>0){ac=L.shift();if(this.state==2){if(m[ac.root.getName()])q.call(this,C,ac,M,ae);else s.call(this,C,ac,ae);}else if(this.state==1&&m[ac.root.getName()])t.call(this,C,ac,M);}for(ab=0;ab0)for(var u=t.length-1;u>=0;u--){var v=t[u][0],w=t[u][1];if(w)v.insertBefore(w);else v.appendTo(s);}};function o(s,t){var u=m(s),v={},w=s.$;if(!t){v['class']=w.className||'';w.className='';}v.inline=w.style.cssText||'';if(!t)w.style.cssText='position: static; overflow: visible';n(u);return v;};function p(s,t){var u=m(s),v=s.$;if('class' in t)v.className=t['class'];if('inline' in t)v.style.cssText=t.inline;n(u);};function q(s){var t=a.instances;for(var u in t){var v=t[u];if(v.mode=='wysiwyg'){var w=v.document.getBody();w.setAttribute('contentEditable',false);w.setAttribute('contentEditable',true);}}if(s.focusManager.hasFocus){s.toolbox.focus();s.focus();}};function r(s){if(!c||b.version>6)return null;var t=h.createFromHtml('');return s.append(t,true);};j.add('maximize',{init:function(s){var t=s.lang,u=a.document,v=u.getWindow(),w,x,y,z;function A(){var C=v.getViewPaneSize();z&&z.setStyles({width:C.width+'px',height:C.height+'px'});s.resize(C.width,C.height,null,true);};var B=2;s.addCommand('maximize',{modes:{wysiwyg:1,source:1},editorFocus:false,exec:function(){var C=s.container.getChild(1),D=s.getThemeSpace('contents');if(s.mode=='wysiwyg'){var E=s.getSelection();w=E&&E.getRanges();x=v.getScrollPosition();}else{var F=s.textarea.$;w=!c&&[F.selectionStart,F.selectionEnd];x=[F.scrollLeft,F.scrollTop];}if(this.state==2){v.on('resize',A);y=v.getScrollPosition();var G=s.container;while(G=G.getParent()){G.setCustomData('maximize_saved_styles',o(G));G.setStyle('z-index',s.config.baseFloatZIndex-1);}D.setCustomData('maximize_saved_styles',o(D,true));C.setCustomData('maximize_saved_styles',o(C,true)); +var H=v.getViewPaneSize(),I={overflow:'hidden',width:(b.opera?H.width:0)+'px',height:(b.opera?H.height-16:0)+'px'};if(c)u.$.documentElement.style.overflow=u.getBody().$.style.overflow='hidden';else u.getBody().setStyles(I);if(b.opera)u.getBody().getParent().setStyles(I);c?setTimeout(function(){v.$.scrollTo(0,0);},0):v.$.scrollTo(0,0);C.setStyle('position','absolute');C.$.offsetLeft;C.setStyles({'z-index':s.config.baseFloatZIndex-1,left:'0px',top:'0px'});z=r(C);C.addClass('cke_maximized');A();var J=C.getDocumentPosition();C.setStyles({left:-1*J.x+'px',top:-1*J.y+'px'});b.gecko&&q(s);}else if(this.state==1){v.removeListener('resize',A);var K=[D,C];for(var L=0;L ');o=m.createFakeElement(o,'cke_pagebreak','div');o.setAttribute('alt',n);o.setAttribute('aria-label',n);var p=m.getSelection().getRanges(true);m.fire('saveSnapshot');for(var q,r=p.length-1;r>=0;r--){q=p[r];if(r1&&n.substr(n.length-1,1)=='%')n=parseInt(window.screen.width*parseInt(n,10)/100,10);if(typeof o=='string'&&o.length>1&&o.substr(o.length-1,1)=='%')o=parseInt(window.screen.height*parseInt(o,10)/100,10);if(n<640)n=640;if(o<420)o=420;var q=parseInt((window.screen.height-o)/2,10),r=parseInt((window.screen.width-n)/2,10);p=(p||'location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes')+',width='+n+',height='+o+',top='+q+',left='+r;var s=window.open('',null,p,true);if(!s)return false;try{s.moveTo(r,q);s.resizeTo(n,o);s.focus();s.location.href=m;}catch(t){s=window.open(m,null,p,true);}return true;}});(function(){var m={modes:{wysiwyg:1,source:1},canUndo:false,exec:function(o){var p,q=o.config,r=q.baseHref?'':'',s=b.isCustomDomain();if(q.fullPage)p=o.getData().replace(//,'$&'+r).replace(/[^>]*(?=<\/title>)/,o.lang.preview);else{var t=''+''+r+''+o.lang.preview+''+e.buildStyleHtml(o.config.contentsCss)+''+t+o.getData()+'';}var v=640,w=420,x=80;try{var y=window.screen;v=Math.round(y.width*0.8);w=Math.round(y.height*0.7);x=Math.round(y.width*0.1);}catch(B){}var z='';if(s){window._cke_htmlToLoad=p;z='javascript:void( (function(){document.open();document.domain="'+document.domain+'";'+'document.write( window.opener._cke_htmlToLoad );'+'document.close();'+'window.opener._cke_htmlToLoad = null;'+'})() )'; +}var A=window.open(z,null,'toolbar=yes,location=no,status=yes,menubar=yes,scrollbars=yes,resizable=yes,width='+v+',height='+w+',left='+x);if(!s){A.document.open();A.document.write(p);A.document.close();}}},n='preview';j.add(n,{init:function(o){o.addCommand(n,m);o.ui.addButton('Preview',{label:o.lang.preview,command:n});}});})();j.add('print',{init:function(m){var n='print',o=m.addCommand(n,j.print);m.ui.addButton('Print',{label:m.lang.print,command:n});}});j.print={exec:function(m){if(b.opera)return;else if(b.gecko)m.window.$.print();else m.document.$.execCommand('Print');},canUndo:false,modes:{wysiwyg:!b.opera}};j.add('removeformat',{requires:['selection'],init:function(m){m.addCommand('removeFormat',j.removeformat.commands.removeformat);m.ui.addButton('RemoveFormat',{label:m.lang.removeFormat,command:'removeFormat'});m._.removeFormat={filters:[]};}});j.removeformat={commands:{removeformat:{exec:function(m){var n=m._.removeFormatRegex||(m._.removeFormatRegex=new RegExp('^(?:'+m.config.removeFormatTags.replace(/,/g,'|')+')$','i')),o=m._.removeAttributes||(m._.removeAttributes=m.config.removeFormatAttributes.split(',')),p=j.removeformat.filter,q=m.getSelection().getRanges(1),r=q.createIterator(),s;while(s=r.getNextRange()){if(s.collapsed)continue;s.enlarge(1);var t=s.createBookmark(),u=t.startNode,v=t.endNode,w=function(z){var A=new d.elementPath(z),B=A.elements;for(var C=1,D;D=B[C];C++){if(D.equals(A.block)||D.equals(A.blockLimit))break;if(n.test(D.getName())&&p(m,D))z.breakParent(D);}};w(u);w(v);var x=u.getNextSourceNode(true,1);while(x){if(x.equals(v))break;var y=x.getNextSourceNode(false,1);if(!(x.getName()=='img'&&x.getAttribute('_cke_realelement'))&&p(m,x))if(n.test(x.getName()))x.remove(1);else{x.removeAttributes(o);m.fire('removeFormatCleanup',x);}x=y;}s.moveToBookmark(t);}m.getSelection().selectRanges(q);}}},filter:function(m,n){var o=m._.removeFormat.filters;for(var p=0;pq.width&&(n.resize_minWidth=q.width);n.resize_minHeight>q.height&&(n.resize_minHeight=q.height);a.document.on('mousemove',t);a.document.on('mouseup',u);if(m.document){m.document.on('mousemove',t);m.document.on('mouseup',u);}});m.on('destroy',function(){e.removeFunction(v);});m.on('themeSpace',function(w){if(w.data.space=='bottom'){var x='';if(r&&!s)x=' cke_resizer_horizontal';if(!r&&s)x=' cke_resizer_vertical';w.data.html+='
          ';}},m,null,100);}}});(function(){var m={modes:{wysiwyg:1,source:1},exec:function(o){var p=o.element.$.form;if(p)try{p.submit();}catch(q){if(p.submit.click)p.submit.click();}}},n='save';j.add(n,{init:function(o){var p=o.addCommand(n,m);p.modes={wysiwyg:!!o.element.$.form};o.ui.addButton('Save',{label:o.lang.save,command:n});}});})();(function(){var m='scaytcheck',n='';function o(t,u){var v=0,w;for(w in u){if(u[w]==t){v=1;break;}}return v;};var p=function(){var t=this,u=function(){var y=t.config,z={};z.srcNodeRef=t.document.getWindow().$.frameElement;z.assocApp='CKEDITOR.'+a.version+'@'+a.revision;z.customerid=y.scayt_customerid||'1:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2';z.customDictionaryIds=y.scayt_customDictionaryIds||'';z.userDictionaryName=y.scayt_userDictionaryName||'';z.sLang=y.scayt_sLang||'en_US';z.onLoad=function(){if(!(c&&b.version<8))this.addStyle(this.selectorCss(),'padding-bottom: 2px !important;');if(t.focusManager.hasFocus&&!q.isControlRestored(t))this.focus();};z.onBeforeChange=function(){if(q.getScayt(t)&&!t.checkDirty())setTimeout(function(){t.resetDirty(); +},0);};var A=window.scayt_custom_params;if(typeof A=='object')for(var B in A)z[B]=A[B];if(q.getControlId(t))z.id=q.getControlId(t);var C=new window.scayt(z);C.afterMarkupRemove.push(function(J){new h(J,C.document).mergeSiblings();});var D=q.instances[t.name];if(D){C.sLang=D.sLang;C.option(D.option());C.paused=D.paused;}q.instances[t.name]=C;var E='scaytButton',F=window.scayt.uiTags,G=[];for(var H=0,I=4;H=0){this.setState(0);q.loadEngine(t);}}};j.add('scayt',{requires:['menubutton'],beforeInit:function(t){var u=t.config.scayt_contextMenuItemsOrder||'suggest|moresuggest|control',v=''; +u=u.split('|');if(u&&u.length)for(var w=0;w tr > td, .%1 table.%2 > tr > th,','.%1 table.%2 > tbody > tr > td, .%1 table.%2 > tbody > tr > th,','.%1 table.%2 > thead > tr > td, .%1 table.%2 > thead > tr > th,','.%1 table.%2 > tfoot > tr > td, .%1 table.%2 > tfoot > tr > th','{','border : #d3d3d3 1px dotted','}']).join('');n=o.replace(/%2/g,m).replace(/%1/g,'cke_show_borders ');var p={preserveState:true,editorFocus:false,exec:function(q){this.toggleState();this.refresh(q);},refresh:function(q){var r=this.state==1?'addClass':'removeClass';q.document.getBody()[r]('cke_show_borders');}};j.add('showborders',{requires:['wysiwygarea'],modes:{wysiwyg:1},init:function(q){var r=q.addCommand('showborders',p);r.canUndo=false;if(q.config.startupShowBorders!==false)r.setState(1);q.addCss(n);q.on('mode',function(){if(r.state!=0)r.refresh(q);},null,null,100);q.on('contentDom',function(){if(r.state!=0)r.refresh(q);});q.on('removeFormatCleanup',function(s){var t=s.data;if(q.getCommand('showborders').state==1&&t.is('table')&&(!t.hasAttribute('border')||parseInt(t.getAttribute('border'),10)<=0))t.addClass(m);});},afterInit:function(q){var r=q.dataProcessor,s=r&&r.dataFilter,t=r&&r.htmlFilter;if(s)s.addRules({elements:{table:function(u){var v=u.attributes,w=v['class'],x=parseInt(v.border,10);if(!x||x<=0)v['class']=(w||'')+' '+m;}}});if(t)t.addRules({elements:{table:function(u){var v=u.attributes,w=v['class'];w&&(v['class']=w.replace(m,'').replace(/\s{2}/,' ').replace(/^\s+|\s+$/,''));}}});}});a.on('dialogDefinition',function(q){var r=q.data.name;if(r=='table'||r=='tableProperties'){var s=q.data.definition,t=s.getContents('info'),u=t.get('txtBorder'),v=u.commit;u.commit=e.override(v,function(y){return function(z,A){y.apply(this,arguments);var B=parseInt(this.getValue(),10);A[!B||B<=0?'addClass':'removeClass'](m); +};});var w=s.getContents('advanced'),x=w&&w.get('advCSSClasses');if(x){x.setup=e.override(x.setup,function(y){return function(){y.apply(this,arguments);this.setValue(this.getValue().replace(/cke_show_border/,''));};});x.commit=e.override(x.commit,function(y){return function(z,A){y.apply(this,arguments);if(!parseInt(A.getAttribute('border'),10))A.addClass('cke_show_border');};});}}});})();j.add('sourcearea',{requires:['editingblock'],init:function(m){var n=j.sourcearea,o=a.document.getWindow();m.on('editingBlockReady',function(){var p,q;m.addMode('source',{load:function(r,s){if(c&&b.version<8)r.setStyle('position','relative');m.textarea=p=new h('textarea');p.setAttributes({dir:'ltr',tabIndex:b.webkit?-1:m.tabIndex,role:'textbox','aria-label':m.lang.editorTitle.replace('%1',m.name)});p.addClass('cke_source');p.addClass('cke_enable_context_menu');var t={width:b.ie7Compat?'99%':'100%',height:'100%',resize:'none',outline:'none','text-align':'left'};if(c){q=function(){p.hide();p.setStyle('height',r.$.clientHeight+'px');p.setStyle('width',r.$.clientWidth+'px');p.show();};m.on('resize',q);o.on('resize',q);setTimeout(q,0);}if(document.addEventListener)p.on('mousedown',function(v){v.data.stopPropagation();});r.setHtml('');r.append(p);p.setStyles(t);m.fire('ariaWidget',p);p.on('blur',function(){m.focusManager.blur();});p.on('focus',function(){m.focusManager.focus();});m.mayBeDirty=true;this.loadData(s);var u=m.keystrokeHandler;if(u)u.attach(p);setTimeout(function(){m.mode='source';m.fire('mode');},b.gecko||b.webkit?100:0);},loadData:function(r){p.setValue(r);m.fire('dataReady');},getData:function(){return p.getValue();},getSnapshotData:function(){return p.getValue();},unload:function(r){p.clearCustomData();m.textarea=p=null;if(q){m.removeListener('resize',q);o.removeListener('resize',q);}if(c&&b.version<8)r.removeStyle('position');},focus:function(){p.focus();}});});m.addCommand('source',n.commands.source);if(m.ui.addButton)m.ui.addButton('Source',{label:m.lang.source,command:'source'});m.on('mode',function(){m.getCommand('source').setState(m.mode=='source'?1:2);});}});j.sourcearea={commands:{source:{modes:{wysiwyg:1,source:1},editorFocus:false,exec:function(m){if(m.mode=='wysiwyg')m.fire('saveSnapshot');m.getCommand('source').setState(0);m.setMode(m.mode=='source'?'wysiwyg':'source');},canUndo:false}}};(function(){j.add('stylescombo',{requires:['richcombo','styles'],init:function(n){var o=n.config,p=n.lang.stylesCombo,q={},r=[];function s(t){n.getStylesSet(function(u){if(!r.length){var v,w; +for(var x=0,y=u.length;x0)return;if(T.type==1&&n.test(T.getName())&&!T.getCustomData('selected_cell')){h.setMarker(K,T,'selected_cell',true);J.push(T);}};for(var M=0;M0&&Q.$.rows[L]||Q.$.parentNode);for(N=J.length;N>=0;N--){if(J[N])s(J[N]);}return K;}else if(G instanceof h){Q=G.getAscendant('table');if(Q.$.rows.length==1)Q.remove();else G.remove();}return 0;};function t(G,H){var I=G.getStartElement(),J=I.getAscendant('td',1)||I.getAscendant('th',1);if(!J)return;var K=J.getAscendant('table'),L=J.$.cellIndex;for(var M=0;M1){L=H[J-1]+1;break;}}if(!L)L=H[0]>0?H[0]-1:H[H.length-1]+1;var N=I.$.rows;for(J=0,K=N.length;J=0;J--){if(H[J])v(H[J]);}return I;}else if(G instanceof h){var K=G.getAscendant('table');if(!K)return null;var L=G.$.cellIndex;for(J=K.$.rows.length-1;J>=0;J--){var M=new h(K.$.rows[J]);if(!L&&M.$.cells.length==1){s(M);continue;}if(M.$.cells[L])M.$.removeChild(M.$.cells[L]);}}return null;};function w(G,H){var I=G.getStartElement(),J=I.getAscendant('td',1)||I.getAscendant('th',1);if(!J)return;var K=J.clone();if(!c)K.appendBogus();if(H)K.insertBefore(J);else K.insertAfter(J);};function x(G){if(G instanceof d.selection){var H=o(G),I=H[0]&&H[0].getAscendant('table'),J=p(H);for(var K=H.length-1;K>=0;K--)x(H[K]);if(J)z(J,true);else if(I)I.remove();}else if(G instanceof h){var L=G.getParent();if(L.getChildCount()==1)L.remove();else G.remove();}};function y(G){var H=G.getBogus();H&&H.remove();G.trim();};function z(G,H){var I=new d.range(G.getDocument());if(!I['moveToElementEdit'+(H?'End':'Start')](G)){I.selectNodeContents(G);I.collapse(H?false:true);}I.select(true);};function A(G,H,I){var J=G[H];if(typeof I=='undefined')return J;for(var K=0;J&&K=Q)M.removeAttribute('rowSpan');else M.$.rowSpan=W; +if(W>=P)M.removeAttribute('colSpan');else M.$.colSpan=X;var ai=new d.nodeList(N.$.rows),aj=ai.count();for(aa=aj-1;aa>=0;aa--){var ak=ai.getItem(aa);if(!ak.$.cells.length){ak.remove();aj++;continue;}}return M;}else return W*X==Z;};function D(G,H){var I=o(G);if(I.length>1)return false;else if(H)return true;var J=I[0],K=J.getParent(),L=K.getAscendant('table'),M=e.buildTableMap(L),N=K.$.rowIndex,O=A(M,N,J),P=J.$.rowSpan,Q,R,S,T;if(P>1){R=Math.ceil(P/2);S=Math.floor(P/2);T=N+R;var U=new h(L.$.rows[T]),V=A(M,T),W;Q=J.clone();for(var X=0;XO){Q.insertBefore(new h(W));break;}else W=null;}if(!W)U.append(Q,true);}else{S=R=1;U=K.clone();U.insertAfter(K);U.append(Q=J.clone());var Y=A(M,N);for(var Z=0;Z1)return false;else if(H)return true;var J=I[0],K=J.getParent(),L=K.getAscendant('table'),M=e.buildTableMap(L),N=K.$.rowIndex,O=A(M,N,J),P=J.$.colSpan,Q,R,S;if(P>1){R=Math.ceil(P/2);S=Math.floor(P/2);}else{S=R=1;var T=B(M,O);for(var U=0;U0?2:0};}},tablecell_insertBefore:{label:H.cell.insertBefore,group:'tablecell',command:'cellInsertBefore',order:5},tablecell_insertAfter:{label:H.cell.insertAfter,group:'tablecell',command:'cellInsertAfter',order:10},tablecell_delete:{label:H.cell.deleteCell,group:'tablecell',command:'cellDelete',order:15},tablecell_merge:{label:H.cell.merge,group:'tablecell',command:'cellMerge',order:16},tablecell_merge_right:{label:H.cell.mergeRight,group:'tablecell',command:'cellMergeRight',order:17},tablecell_merge_down:{label:H.cell.mergeDown,group:'tablecell',command:'cellMergeDown',order:18},tablecell_split_horizontal:{label:H.cell.splitHorizontal,group:'tablecell',command:'cellHorizontalSplit',order:19},tablecell_split_vertical:{label:H.cell.splitVertical,group:'tablecell',command:'cellVerticalSplit',order:20},tablecell_properties:{label:H.cell.title,group:'tablecellproperties',command:'cellProperties',order:21},tablerow:{label:H.row.menu,group:'tablerow',order:1,getItems:function(){return{tablerow_insertBefore:2,tablerow_insertAfter:2,tablerow_delete:2};}},tablerow_insertBefore:{label:H.row.insertBefore,group:'tablerow',command:'rowInsertBefore',order:5},tablerow_insertAfter:{label:H.row.insertAfter,group:'tablerow',command:'rowInsertAfter',order:10},tablerow_delete:{label:H.row.deleteRow,group:'tablerow',command:'rowDelete',order:15},tablecolumn:{label:H.column.menu,group:'tablecolumn',order:1,getItems:function(){return{tablecolumn_insertBefore:2,tablecolumn_insertAfter:2,tablecolumn_delete:2}; +}},tablecolumn_insertBefore:{label:H.column.insertBefore,group:'tablecolumn',command:'columnInsertBefore',order:5},tablecolumn_insertAfter:{label:H.column.insertAfter,group:'tablecolumn',command:'columnInsertAfter',order:10},tablecolumn_delete:{label:H.column.deleteColumn,group:'tablecolumn',command:'columnDelete',order:15}});if(G.contextMenu)G.contextMenu.addListener(function(I,J){if(!I||I.isReadOnly())return null;while(I){if(I.getName() in F)return{tablecell:2,tablerow:2,tablecolumn:2};I=I.getParent();}return null;});},getSelectedCells:o};j.add('tabletools',j.tabletools);})();e.buildTableMap=function(m){var n=m.$.rows,o=-1,p=[];for(var q=0;qp&&(!s||!t||vt){s=v;t=u;}}else{if(q&&u==p){s=v;break;}if(ut)){s=v;t=u;}}}if(s)s.focus();};(function(){j.add('templates',{requires:['dialog'],init:function(o){a.dialog.add('templates',a.getUrl(this.path+'dialogs/templates.js'));o.addCommand('templates',new a.dialogCommand('templates'));o.ui.addButton('Templates',{label:o.lang.templates.button,command:'templates'});}});var m={},n={};a.addTemplates=function(o,p){m[o]=p;};a.getTemplates=function(o){return m[o];};a.loadTemplates=function(o,p){var q=[];for(var r=0,s=o.length;r':' style="display:none">');s.push('',o.lang.toolbar,'');var v=o.toolbox.toolbars,w=o.config.toolbar instanceof Array?o.config.toolbar:o.config['toolbar_'+o.config.toolbar];for(var x=0;x');u=0;}if(y==='/'){s.push('
          ');continue;}s.push('');var B=v.push(A)-1;if(B>0){A.previous=v[B-1];A.previous.next=A;}for(var C=0;C');u=1;}}else if(u){s.push('');u=0;}var F=D.render(o,s);B=A.items.push(F)-1;if(B>0){F.previous=A.items[B-1];F.previous.next=F;}F.toolbar=A;F.onkey=p;F.onfocus=function(){if(!o.toolbox.focusCommandExecuted)o.focus();};}}if(u){s.push('');u=0;}s.push('');}s.push('');if(o.config.toolbarCanCollapse){var G=e.addFunction(function(){o.execCommand('toolbarCollapse');});o.on('destroy',function(){e.removeFunction(G);});var H=e.getNextId();o.addCommand('toolbarCollapse',{exec:function(I){var J=a.document.getById(H),K=J.getPrevious(),L=I.getThemeSpace('contents'),M=K.getParent(),N=parseInt(L.$.style.height,10),O=M.$.offsetHeight,P=!K.isVisible();if(!P){K.hide();J.addClass('cke_toolbox_collapser_min'); +J.setAttribute('title',I.lang.toolbarExpand);}else{K.show();J.removeClass('cke_toolbox_collapser_min');J.setAttribute('title',I.lang.toolbarCollapse);}J.getFirst().setText(P?'▲':'◀');var Q=M.$.offsetHeight-O;L.setStyle('height',N-Q+'px');I.fire('resize');},modes:{wysiwyg:1,source:1}});s.push('','','');}q.data.html+=s.join('');}});o.addCommand('toolbarFocus',n.toolbarFocus);}});})();k.separator={render:function(m,n){n.push('');return{};}};i.toolbarLocation='top';i.toolbar_Basic=[['Bold','Italic','-','NumberedList','BulletedList','-','Link','Unlink','-','About']];i.toolbar_Full=[['Source','-','Save','NewPage','Preview','-','Templates'],['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print','SpellChecker','Scayt'],['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],'/',['Bold','Italic','Underline','Strike','-','Subscript','Superscript'],['NumberedList','BulletedList','-','Outdent','Indent','Blockquote','CreateDiv'],['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],['BidiLtr','BidiRtl'],['Link','Unlink','Anchor'],['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'],'/',['Styles','Format','Font','FontSize'],['TextColor','BGColor'],['Maximize','ShowBlocks','-','About']];i.toolbar='Full';i.toolbarCanCollapse=true;(function(){j.add('undo',{requires:['selection','wysiwygarea'],init:function(s){var t=new o(s),u=s.addCommand('undo',{exec:function(){if(t.undo()){s.selectionChange();this.fire('afterUndo');}},state:0,canUndo:false}),v=s.addCommand('redo',{exec:function(){if(t.redo()){s.selectionChange();this.fire('afterRedo');}},state:0,canUndo:false});t.onChange=function(){u.setState(t.undoable()?2:0);v.setState(t.redoable()?2:0);};function w(x){if(t.enabled&&x.data.command.canUndo!==false)t.save();};s.on('beforeCommandExec',w);s.on('afterCommandExec',w);s.on('saveSnapshot',function(){t.save();});s.on('contentDom',function(){s.document.on('keydown',function(x){if(!x.data.$.ctrlKey&&!x.data.$.metaKey)t.type(x);});});s.on('beforeModeUnload',function(){s.mode=='wysiwyg'&&t.save(true);});s.on('mode',function(){t.enabled=s.mode=='wysiwyg'; +t.onChange();});s.ui.addButton('Undo',{label:s.lang.undo,command:'undo'});s.ui.addButton('Redo',{label:s.lang.redo,command:'redo'});s.resetUndo=function(){t.reset();s.fire('saveSnapshot');};s.on('updateSnapshot',function(){if(t.currentImage&&new m(s).equals(t.currentImage))setTimeout(function(){t.update();},0);});}});j.undo={};var m=j.undo.Image=function(s){this.editor=s;var t=s.getSnapshot(),u=t&&s.getSelection();c&&t&&(t=t.replace(/\s+_cke_expando=".*?"/g,''));this.contents=t;this.bookmarks=u&&u.createBookmarks2(true);},n=/\b(?:href|src|name)="[^"]*?"/gi;m.prototype={equals:function(s,t){var u=this.contents,v=s.contents;if(c&&(b.ie7Compat||b.ie6Compat)){u=u.replace(n,'');v=v.replace(n,'');}if(u!=v)return false;if(t)return true;var w=this.bookmarks,x=s.bookmarks;if(w||x){if(!w||!x||w.length!=x.length)return false;for(var y=0;y25){this.save(false,null,false);this.modifiersCount=1;}}else if(!y){this.modifiersCount=0;this.typesCount++;if(this.typesCount>25){this.save(false,null,false);this.typesCount=1;}}},reset:function(){var s=this;s.lastKeystroke=0;s.snapshots=[];s.index=-1;s.limit=s.editor.config.undoStackSize||20;s.currentImage=null;s.hasUndo=false;s.hasRedo=false;s.resetType();},resetType:function(){var s=this;s.typing=false;delete s.lastKeystroke;s.typesCount=0;s.modifiersCount=0;},fireChange:function(){var s=this;s.hasUndo=!!s.getNextImage(true);s.hasRedo=!!s.getNextImage(false);s.resetType();s.onChange();},save:function(s,t,u){var w=this;var v=w.snapshots;if(!t)t=new m(w.editor);if(t.contents===false)return false;if(w.currentImage&&t.equals(w.currentImage,s))return false; +v.splice(w.index+1,v.length-w.index-1);if(v.length==w.limit)v.shift();w.index=v.push(t)-1;w.currentImage=t;if(u!==false)w.fireChange();return true;},restoreImage:function(s){var u=this;u.editor.loadSnapshot(s.contents);if(s.bookmarks)u.editor.getSelection().selectBookmarks(s.bookmarks);else if(c){var t=u.editor.document.getBody().$.createTextRange();t.collapse(true);t.select();}u.index=s.index;u.update();u.fireChange();},getNextImage:function(s){var x=this;var t=x.snapshots,u=x.currentImage,v,w;if(u)if(s)for(w=x.index-1;w>=0;w--){v=t[w];if(!u.equals(v,true)){v.index=w;return v;}}else for(w=x.index+1;w]*>)\s*<(p|div|address|h\d|center)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi,o=d.walker.whitespaces(true);function p(B){if(B.getType()==3)return B.getSelectedElement().isReadOnly();else return B.getCommonAncestor().isReadOnly();};function q(B){if(this.mode=='wysiwyg'){this.focus();var C=this.getSelection();if(p(C))return;var D=B.data;this.fire('saveSnapshot');if(this.dataProcessor)D=this.dataProcessor.toHtml(D);if(c){var E=C.isLocked;if(E)C.unlock();var F=C.getNative();if(F.type=='Control')F.clear();else if(C.getType()==2){var G=C.getRanges()[0],H=G&&G.endContainer;if(H&&H.type==1&&H.getAttribute('contenteditable')=='false'&&G.checkBoundaryOfElement(H,2)){G.setEndAfter(G.endContainer);G.deleteContents();}}try{F.createRange().pasteHTML(D);}catch(J){}if(E)this.getSelection().lock();}else this.document.$.execCommand('inserthtml',false,D);if(b.webkit){this.document.$.execCommand('inserthtml',false,'');var I=this.document.getById('cke_paste_marker');I.scrollIntoView();I.remove();I=null;}e.setTimeout(function(){this.fire('saveSnapshot');},0,this);}};function r(B){if(this.mode=='wysiwyg'){this.focus();var C=this.getSelection();if(p(C))return;this.fire('saveSnapshot');var D=C.getRanges(),E=B.data,F=E.getName(),G=f.$block[F],H=C.isLocked; +if(H)C.unlock();var I,J,K,L;for(var M=D.length-1;M>=0;M--){I=D[M];I.deleteContents();J=!M&&E||E.clone(1);var N,O;if(G)while((N=I.getCommonAncestor(0,1))&&(O=f[N.getName()])&&!(O&&O[F])){if(N.getName() in f.span)I.splitElement(N);else if(I.checkStartOfBlock()&&I.checkEndOfBlock()){I.setStartBefore(N);I.collapse(true);N.remove();}else I.splitBlock();}I.insertNode(J);if(!K)K=J;}I.moveToPosition(K,4);if(G){var P=K.getNext(o),Q=P&&P.type==1&&P.getName();if(Q&&f.$block[Q]&&f[Q]['#'])I.moveToElementEditStart(P);}C.selectRanges([I]);if(H)this.getSelection().lock();e.setTimeout(function(){this.fire('saveSnapshot');},0,this);}};function s(B){if(!B.checkDirty())setTimeout(function(){B.resetDirty();},0);};var t=d.walker.whitespaces(true),u=d.walker.bookmark(false,true);function v(B){return t(B)&&u(B);};function w(B){return B.type==3&&e.trim(B.getText()).match(/^(?: |\xa0)$/);};function x(B){if(B.isLocked){B.unlock();setTimeout(function(){B.lock();},0);}};function y(B){return B.getOuterHtml().match(n);};t=d.walker.whitespaces(true);function z(B){var C=B.window,D=B.document,E=B.document.getBody(),F=E.getChildren().count();if(!F||F==1&&E.getFirst().hasAttribute('_moz_editor_bogus_node')){s(B);var G=B.element.getDocument(),H=G.getDocumentElement(),I=H.$.scrollTop,J=H.$.scrollLeft,K=D.$.createEvent('KeyEvents');K.initKeyEvent('keypress',true,true,C.$,false,false,false,false,0,32);D.$.dispatchEvent(K);if(I!=H.$.scrollTop||J!=H.$.scrollLeft)G.getWindow().$.scrollTo(J,I);F&&E.getFirst().remove();D.getBody().appendBogus();var L=new d.range(D);L.setStartAt(E,1);L.select();}};function A(B){var C=B.editor,D=B.data.path,E=D.blockLimit,F=B.data.selection,G=F.getRanges()[0],H=C.document.getBody(),I=C.config.enterMode;b.gecko&&z(C);if(I!=2&&G.collapsed&&E.getName()=='body'&&!D.block){C.fire('updateSnapshot');s(C);c&&x(F);var J=G.fixBlock(true,C.config.enterMode==3?'div':'p');if(c){var K=J.getFirst(v);K&&w(K)&&K.remove();}if(y(J)){var L=J.getNext(t);if(L&&L.type==1&&!m[L.getName()]){G.moveToElementEditStart(L);J.remove();}else{L=J.getPrevious(t);if(L&&L.type==1&&!m[L.getName()]){G.moveToElementEditEnd(L);J.remove();}}}G.select();if(!c)C.selectionChange();}var M=new d.range(C.document),N=new d.walker(M);M.selectNodeContents(H);N.evaluator=function(P){return P.type==1&&P.getName() in m;};N.guard=function(P,Q){return!(P.type==3&&t(P)||Q);};if(N.previous()){C.fire('updateSnapshot');s(C);c&&x(F);var O;if(I!=2)O=H.append(new h(I==1?'p':'div'));else O=H;if(!c)O.appendBogus();}};j.add('wysiwygarea',{requires:['editingblock'],init:function(B){var C=B.config.enterMode!=2?B.config.enterMode==3?'div':'p':false,D=B.lang.editorTitle.replace('%1',B.name),E; +B.on('editingBlockReady',function(){var K,L,M,N,O,P,Q=b.isCustomDomain(),R=function(U){if(L)L.remove();var V='document.open();'+(Q?'document.domain="'+document.domain+'";':'')+'document.close();';L=h.createFromHtml('');if(document.location.protocol=='chrome:')a.event.useCapture=true;L.on('load',function(Z){O=1;Z.removeListener();var aa=L.getFrameDocument().$;aa.open('text/html','replace');aa.write(U);aa.close();});if(document.location.protocol=='chrome:')a.event.useCapture=false;var W=B.element,X=b.gecko&&!W.isVisible(),Y={};if(X){W.show();Y={position:W.getStyle('position'),top:W.getStyle('top')};W.setStyles({position:'absolute',top:'-3000px'});}K.append(L);if(X)setTimeout(function(){W.hide();W.setStyles(Y);},1000);};E=e.addFunction(T);var S='';function T(U){if(!O)return;O=0;B.fire('ariaWidget',L);var V=U.document,W=V.body,X=V.getElementById('cke_actscrpt');X.parentNode.removeChild(X);W.spellcheck=!B.config.disableNativeSpellChecker;if(c){W.hideFocus=true;W.disabled=true;W.contentEditable=true;W.removeAttribute('disabled');}else setTimeout(function(){if(b.gecko&&b.version>=10900||b.opera)V.$.body.contentEditable=true;else if(b.webkit)V.$.body.parentNode.contentEditable=true;else V.$.designMode='on';},0);b.gecko&&e.setTimeout(z,0,null,B);U=B.window=new d.window(U);V=B.document=new g(V);V.on('dblclick',function(ac){var ad=ac.data.getTarget(),ae={element:ad,dialog:''};B.fire('doubleclick',ae);ae.dialog&&B.openDialog(ae.dialog);});if(!(c||b.opera))V.on('mousedown',function(ac){var ad=ac.data.getTarget();if(ad.is('img','hr','input','textarea','select'))B.getSelection().selectElement(ad);});if(b.gecko)V.on('mouseup',function(ac){if(ac.data.$.button==2){var ad=ac.data.getTarget();if(!ad.getOuterHtml().replace(n,'')){var ae=new d.range(V);ae.moveToElementEditStart(ad);ae.select(true);}}});V.on('click',function(ac){ac=ac.data;if(ac.getTarget().is('a')&&ac.$.button!=2)ac.preventDefault();});if(b.webkit){V.on('click',function(ac){if(ac.data.getTarget().is('input','select'))ac.data.preventDefault();});V.on('mouseup',function(ac){if(ac.data.getTarget().is('input','textarea'))ac.data.preventDefault(); +});}if(c&&V.$.compatMode=='CSS1Compat'||b.gecko||b.opera){var Y=V.getDocumentElement();Y.on('mousedown',function(ac){if(ac.data.getTarget().equals(Y)){if(b.gecko&&b.version>=10900)I();J.focus();}});}U.on('blur',function(){B.focusManager.blur();});var Z;U.on('focus',function(){var ac=B.document;if(b.gecko&&b.version>=10900)I();else if(b.opera)ac.getBody().focus();else if(b.webkit)if(!Z){B.document.getDocumentElement().focus();Z=1;}B.focusManager.focus();});var aa=B.keystrokeHandler;if(aa)aa.attach(V);if(c){V.getDocumentElement().addClass(V.$.compatMode);V.on('keydown',function(ac){var ad=ac.data.getKeystroke();if(ad in {8:1,46:1}){var ae=B.getSelection(),af=ae.getSelectedElement();if(af){B.fire('saveSnapshot');var ag=ae.getRanges()[0].createBookmark();af.remove();ae.selectBookmarks([ag]);B.fire('saveSnapshot');ac.data.preventDefault();}}});if(V.$.compatMode=='CSS1Compat'){var ab={33:1,34:1};V.on('keydown',function(ac){if(ac.data.getKeystroke() in ab)setTimeout(function(){B.getSelection().scrollIntoView();},0);});}}if(B.contextMenu)B.contextMenu.addTarget(V,B.config.browserContextMenuOnCtrl!==false);setTimeout(function(){B.fire('contentDom');if(P){B.mode='wysiwyg';B.fire('mode');P=false;}M=false;if(N){B.focus();N=false;}setTimeout(function(){B.fire('dataReady');},0);try{B.document.$.execCommand('enableObjectResizing',false,!B.config.disableObjectResizing);}catch(ac){}try{B.document.$.execCommand('enableInlineTableEditing',false,!B.config.disableNativeTableHandles);}catch(ad){}if(c)setTimeout(function(){if(B.document){var ae=B.document.$.body;ae.runtimeStyle.marginBottom='0px';ae.runtimeStyle.marginBottom='';}},1000);},0);};B.addMode('wysiwyg',{load:function(U,V,W){K=U;if(c&&b.quirks)U.setStyle('position','relative');B.mayBeDirty=true;P=true;if(W)this.loadSnapshotData(V);else this.loadData(V);},loadData:function(U){M=true;var V=B.config,W=V.fullPage,X=V.docType,Y='';!W&&(Y=e.buildStyleHtml(B.config.contentsCss)+Y);var Z=V.baseHref?'':'';if(W)U=U.replace(/]*>/i,function(aa){B.docType=X=aa;return '';});if(B.dataProcessor)U=B.dataProcessor.toHtml(U,C);if(W){if(!/]/.test(U))U=''+U;if(!/]/.test(U))U=''+U+'';if(!/]/.test(U))U=U.replace(/]*>/,'$&');else if(!/]/.test(U))U=U.replace(/]*>/,'$&');Z&&(U=U.replace(//,'$&'+Z));U=U.replace(/<\/head\s*>/,Y+'$&'); +U=X+U;}else U=V.docType+''+''+''+D+''+Z+Y+''+''+U+'';U+=S;this.onDispose();R(U);},getData:function(){var U=B.config,V=U.fullPage,W=V&&B.docType,X=L.getFrameDocument(),Y=V?X.getDocumentElement().getOuterHtml():X.getBody().getHtml();if(B.dataProcessor)Y=B.dataProcessor.toDataFormat(Y,C);if(U.ignoreEmptyParagraph)Y=Y.replace(n,function(Z,aa){return aa;});if(W)Y=W+'\n'+Y;return Y;},getSnapshotData:function(){return L.getFrameDocument().getBody().getHtml();},loadSnapshotData:function(U){L.getFrameDocument().getBody().setHtml(U);},onDispose:function(){if(!B.document)return;B.document.getDocumentElement().clearCustomData();B.document.getBody().clearCustomData();B.window.clearCustomData();B.document.clearCustomData();L.clearCustomData();L.remove();},unload:function(U){this.onDispose();B.window=B.document=L=K=N=null;B.fire('contentDomUnload');},focus:function(){if(M)N=true;else if(b.opera&&B.document){var U=B.window.$.frameElement;U.blur(),U.focus();B.document.getBody().focus();B.selectionChange();}else if(!b.opera&&B.window){B.window.focus();B.selectionChange();}}});B.on('insertHtml',q,null,null,20);B.on('insertElement',r,null,null,20);B.on('selectionChange',A,null,null,1);});var F;B.on('contentDom',function(){var K=B.document.getElementsByTag('title').getItem(0);K.setAttribute('_cke_title',B.document.$.title);B.document.$.title=D;});if(b.ie8Compat){B.addCss('html.CSS1Compat [contenteditable=false]{ min-height:0 !important;}');var G=[];for(var H in f.$removeEmpty)G.push('html.CSS1Compat '+H+'[contenteditable=false]');B.addCss(G.join(',')+'{ display:inline-block;}');}else if(b.gecko)B.addCss('html { height: 100% !important; }');function I(K){e.tryThese(function(){B.document.$.designMode='on';setTimeout(function(){B.document.$.designMode='off';if(a.currentInstance==B)B.document.getBody().focus();},50);},function(){B.document.$.designMode='off';var L=B.document.getBody();L.setAttribute('contentEditable',false);L.setAttribute('contentEditable',true);!K&&I(1);});};if(b.gecko||c||b.opera){var J;B.on('uiReady',function(){J=B.container.append(h.createFromHtml(''));J.on('focus',function(){B.focus();});});B.on('destroy',function(){e.removeFunction(E);J.clearCustomData();});}B.on('insertElement',function(K){var L=K.data; +if(L.type==1&&(L.is('input')||L.is('textarea')))if(!L.isReadOnly()){L.setAttribute('contentEditable',false);L.setCustomData('_cke_notReadOnly',1);}});}});if(b.gecko)(function(){var B=document.body;if(!B)window.addEventListener('load',arguments.callee,false);else{var C=B.getAttribute('onpageshow');B.setAttribute('onpageshow',(C?C+';':'')+'event.persisted && (function(){'+'var allInstances = CKEDITOR.instances, editor, doc;'+'for ( var i in allInstances )'+'{'+'\teditor = allInstances[ i ];'+'\tdoc = editor.document;'+'\tif ( doc )'+'\t{'+'\t\tdoc.$.designMode = "off";'+'\t\tdoc.$.designMode = "on";'+'\t}'+'}'+'})();');}})();})();i.disableObjectResizing=false;i.disableNativeTableHandles=true;i.disableNativeSpellChecker=true;i.ignoreEmptyParagraph=true;j.add('wsc',{requires:['dialog'],init:function(m){var n='checkspell',o=m.addCommand(n,new a.dialogCommand(n));o.modes={wysiwyg:!b.opera&&document.domain==window.location.hostname};m.ui.addButton('SpellChecker',{label:m.lang.spellCheck.toolbar,command:n});a.dialog.add(n,this.path+'dialogs/wsc.js');}});i.wsc_customerId=i.wsc_customerId||'1:ua3xw1-2XyGJ3-GWruD3-6OFNT1-oXcuB1-nR6Bp4-hgQHc-EcYng3-sdRXG3-NOfFk';i.wsc_customLoaderScript=i.wsc_customLoaderScript||null;a.DIALOG_RESIZE_NONE=0;a.DIALOG_RESIZE_WIDTH=1;a.DIALOG_RESIZE_HEIGHT=2;a.DIALOG_RESIZE_BOTH=3;(function(){var m=e.cssLength;function n(P){return!!this._.tabs[P][0].$.offsetHeight;};function o(){var T=this;var P=T._.currentTabId,Q=T._.tabIdList.length,R=e.indexOf(T._.tabIdList,P)+Q;for(var S=R-1;S>R-Q;S--){if(n.call(T,T._.tabIdList[S%Q]))return T._.tabIdList[S%Q];}return null;};function p(){var T=this;var P=T._.currentTabId,Q=T._.tabIdList.length,R=e.indexOf(T._.tabIdList,P);for(var S=R+1;S1){U._.tabBarMode=true;U._.tabs[U._.currentTabId][0].focus();Y=1;}else if((ah==37||ah==39)&&U._.tabBarMode){ak=ah==(ai?39:37)?o.call(U):p.call(U);U.selectPage(ak);U._.tabs[ak][0].focus();Y=1;}else if((ah==13||ah==32)&&U._.tabBarMode){al.selectPage(al._.currentTabId); +al._.tabBarMode=false;al._.currentFocusIndex=-1;X(true);Y=1;}if(Y){ag.stop();ag.data.preventDefault();}};function aa(ag){Y&&ag.data.preventDefault();};var ab=this._.element;this.on('show',function(){ab.on('keydown',Z,this,null,0);if(b.opera||b.gecko&&b.mac)ab.on('keypress',aa,this);});this.on('hide',function(){ab.removeListener('keydown',Z);if(b.opera||b.gecko&&b.mac)ab.removeListener('keypress',aa);});this.on('iframeAdded',function(ag){var ah=new g(ag.data.iframe.$.contentWindow.document);ah.on('keydown',Z,this,null,0);});this.on('show',function(){var ak=this;W();if(P.config.dialog_startupFocusTab&&U._.pageCount>1){U._.tabBarMode=true;U._.tabs[U._.currentTabId][0].focus();}else if(!ak._.hasFocus){ak._.currentFocusIndex=-1;if(R.onFocus){var ag=R.onFocus.call(ak);ag&&ag.focus();}else X(true);if(ak._.editor.mode=='wysiwyg'&&c){var ah=P.document.$.selection,ai=ah.createRange();if(ai)if(ai.parentElement&&ai.parentElement().ownerDocument==P.document.$||ai.item&&ai.item(0).ownerDocument==P.document.$){var aj=document.body.createTextRange();aj.moveToElementText(ak.getElement().getFirst().$);aj.collapse(true);aj.select();}}}},this,null,4294967295);if(b.ie6Compat)this.on('load',function(ag){var ah=this.getElement(),ai=ah.getFirst();ai.remove();ai.appendTo(ah);},this);y(this);z(this);new d.text(R.title,a.document).appendTo(this.parts.title);for(var ac=0;ac0?Q:0)+'px',top:(R>0?R:0)+'px'});};})(),getPosition:function(){return e.extend({},this._.position);},show:function(){var P=this._.editor;if(P.mode=='wysiwyg'&&c){var Q=P.getSelection();Q&&Q.lock();}var R=this._.element,S=this.definition;if(!(R.getParent()&&R.getParent().equals(a.document.getBody())))R.appendTo(a.document.getBody());else R.setStyle('display','block');if(b.gecko&&b.version<10900){var T=this.parts.dialog;T.setStyle('position','absolute');setTimeout(function(){T.setStyle('position','fixed');},0);}this.resize(S.minWidth,S.minHeight);this.reset();this.selectPage(this.definition.contents[0].id);if(a.dialog._.currentZIndex===null)a.dialog._.currentZIndex=this._.editor.config.baseFloatZIndex;this._.element.getFirst().setStyle('z-index',a.dialog._.currentZIndex+=10);if(a.dialog._.currentTop===null){a.dialog._.currentTop=this;this._.parentDialog=null;D(this._.editor);R.on('keydown',H);R.on(b.opera?'keypress':'keyup',I);for(var U in {keyup:1,keydown:1,keypress:1})R.on(U,O);}else{this._.parentDialog=a.dialog._.currentTop;var V=this._.parentDialog.getElement().getFirst();V.$.style.zIndex-=Math.floor(this._.editor.config.baseFloatZIndex/2);a.dialog._.currentTop=this;}J(this,this,'\x1b',null,function(){this.getButton('cancel')&&this.getButton('cancel').click();});this._.hasFocus=false;e.setTimeout(function(){var W=a.document.getWindow().getViewPaneSize(),X=this.getSize();this.move((W.width-S.minWidth)/2,(W.height-X.height)/2);this.parts.dialog.setStyle('visibility','');this.fireOnce('load',{});this.fire('show',{});this._.editor.fire('dialogShow',this);this.foreach(function(Y){Y.setInitValue&&Y.setInitValue();});},100,this);},foreach:function(P){var S=this;for(var Q in S._.contents)for(var R in S._.contents[Q])P(S._.contents[Q][R]); +return S;},reset:(function(){var P=function(Q){if(Q.reset)Q.reset(1);};return function(){this.foreach(P);return this;};})(),setupContent:function(){var P=arguments;this.foreach(function(Q){if(Q.setup)Q.setup.apply(Q,P);});},commitContent:function(){var P=arguments;this.foreach(function(Q){if(Q.commit)Q.commit.apply(Q,P);});},hide:function(){if(!this.parts.dialog.isVisible())return;this.fire('hide',{});this._.editor.fire('dialogHide',this);var P=this._.element;P.setStyle('display','none');this.parts.dialog.setStyle('visibility','hidden');K(this);while(a.dialog._.currentTop!=this)a.dialog._.currentTop.hide();if(!this._.parentDialog)E();else{var Q=this._.parentDialog.getElement().getFirst();Q.setStyle('z-index',parseInt(Q.$.style.zIndex,10)+Math.floor(this._.editor.config.baseFloatZIndex/2));}a.dialog._.currentTop=this._.parentDialog;if(!this._.parentDialog){a.dialog._.currentZIndex=null;P.removeListener('keydown',H);P.removeListener(b.opera?'keypress':'keyup',I);for(var R in {keyup:1,keydown:1,keypress:1})P.removeListener(R,O);var S=this._.editor;S.focus();if(S.mode=='wysiwyg'&&c){var T=S.getSelection();T&&T.unlock(true);}}else a.dialog._.currentZIndex-=10;delete this._.parentDialog;this.foreach(function(U){U.resetInitValue&&U.resetInitValue();});},addPage:function(P){var ab=this;var Q=[],R=P.label?' title="'+e.htmlEncode(P.label)+'"':'',S=P.elements,T=a.dialog._.uiElementBuilders.vbox.build(ab,{type:'vbox',className:'cke_dialog_page_contents',children:P.elements,expand:!!P.expand,padding:P.padding,style:P.style||'width: 100%; height: 100%;'},Q),U=h.createFromHtml(Q.join(''));U.setAttribute('role','tabpanel');var V=b,W='cke_'+P.id+'_'+e.getNextNumber(),X=h.createFromHtml(['0?' cke_last':'cke_first',R,!!P.hidden?' style="display:none"':'',' id="',W,'"',V.gecko&&V.version>=10900&&!V.hc?'':' href="javascript:void(0)"',' tabIndex="-1"',' hidefocus="true"',' role="tab">',P.label,''].join(''));U.setAttribute('aria-labelledby',W);ab._.tabs[P.id]=[X,U];ab._.tabIdList.push(P.id);!P.hidden&&ab._.pageCount++;ab._.lastTab=X;ab.updateStyle();var Y=ab._.contents[P.id]={},Z,aa=T.getChild();while(Z=aa.shift()){Y[Z.id]=Z;if(typeof Z.getChild=='function')aa.push.apply(aa,Z.getChild());}U.setAttribute('name',P.id);U.appendTo(ab.parts.contents);X.unselectable();ab.parts.tabs.append(X);if(P.accessKey){J(ab,ab,'CTRL+'+P.accessKey,M,L);ab._.accessKeyMap['CTRL+'+P.accessKey]=P.id;}},selectPage:function(P){if(this._.currentTabId==P)return; +if(this.fire('selectPage',{page:P,currentPage:this._.currentTabId})===true)return;for(var Q in this._.tabs){var R=this._.tabs[Q][0],S=this._.tabs[Q][1];if(Q!=P){R.removeClass('cke_dialog_tab_selected');S.hide();}S.setAttribute('aria-hidden',Q!=P);}var T=this._.tabs[P];T[0].addClass('cke_dialog_tab_selected');if(b.ie6Compat||b.ie7Compat){q(T[1]);T[1].show();setTimeout(function(){q(T[1],1);},0);}else T[1].show();this._.currentTabId=P;this._.currentTabIndex=e.indexOf(this._.tabIdList,P);},updateStyle:function(){this.parts.dialog[(this._.pageCount===1?'add':'remove')+'Class']('cke_single_page');},hidePage:function(P){var R=this;var Q=R._.tabs[P]&&R._.tabs[P][0];if(!Q||R._.pageCount==1)return;else if(P==R._.currentTabId)R.selectPage(o.call(R));Q.hide();R._.pageCount--;R.updateStyle();},showPage:function(P){var R=this;var Q=R._.tabs[P]&&R._.tabs[P][0];if(!Q)return;Q.show();R._.pageCount++;R.updateStyle();},getElement:function(){return this._.element;},getName:function(){return this._.name;},getContentElement:function(P,Q){var R=this._.contents[P];return R&&R[Q];},getValueOf:function(P,Q){return this.getContentElement(P,Q).getValue();},setValueOf:function(P,Q,R){return this.getContentElement(P,Q).setValue(R);},getButton:function(P){return this._.buttons[P];},click:function(P){return this._.buttons[P].click();},disableButton:function(P){return this._.buttons[P].disable();},enableButton:function(P){return this._.buttons[P].enable();},getPageCount:function(){return this._.pageCount;},getParentEditor:function(){return this._.editor;},getSelectedElement:function(){return this.getParentEditor().getSelection().getSelectedElement();},addFocusable:function(P,Q){var S=this;if(typeof Q=='undefined'){Q=S._.focusList.length;S._.focusList.push(new r(S,P,Q));}else{S._.focusList.splice(Q,0,new r(S,P,Q));for(var R=Q+1;Raa.width-Z.width-U)af=aa.width-Z.width+V[1];else af=R.x;if(R.y+V[0]aa.height-Z.height-U)ag=aa.height-Z.height+V[2];else ag=R.y;P.move(af,ag);Y.data.preventDefault();};function X(Y){a.document.removeListener('mousemove',W);a.document.removeListener('mouseup',X);if(b.ie6Compat){var Z=C.getChild(0).getFrameDocument(); +Z.removeListener('mousemove',W);Z.removeListener('mouseup',X);}};P.parts.title.on('mousedown',function(Y){P._.updateSize=true;Q={x:Y.data.$.screenX,y:Y.data.$.screenY};a.document.on('mousemove',W);a.document.on('mouseup',X);R=P.getPosition();if(b.ie6Compat){var Z=C.getChild(0).getFrameDocument();Z.on('mousemove',W);Z.on('mouseup',X);}Y.data.preventDefault();},P);};function z(P){var Q=P.definition,R=Q.minWidth||0,S=Q.minHeight||0,T=Q.resizable,U=P.getParentEditor().skin.margins||[0,0,0,0];function V(ag,ah){ag.y+=ah;};function W(ag,ah){ag.x2+=ah;};function X(ag,ah){ag.y2+=ah;};function Y(ag,ah){ag.x+=ah;};var Z=null,aa=null,ab=P._.editor.config.magnetDistance,ac=['tl','t','tr','l','r','bl','b','br'];function ad(ag){var ah=ag.listenerData.part,ai=P.getSize();aa=P.getPosition();e.extend(aa,{x2:aa.x+ai.width,y2:aa.y+ai.height});Z={x:ag.data.$.screenX,y:ag.data.$.screenY};a.document.on('mousemove',ae,P,{part:ah});a.document.on('mouseup',af,P,{part:ah});if(b.ie6Compat){var aj=C.getChild(0).getFrameDocument();aj.on('mousemove',ae,P,{part:ah});aj.on('mouseup',af,P,{part:ah});}ag.data.preventDefault();};function ae(ag){var ah=ag.data.$.screenX,ai=ag.data.$.screenY,aj=ah-Z.x,ak=ai-Z.y,al=a.document.getWindow().getViewPaneSize(),am=ag.listenerData.part;if(am.search('t')!=-1)V(aa,ak);if(am.search('l')!=-1)Y(aa,aj);if(am.search('b')!=-1)X(aa,ak);if(am.search('r')!=-1)W(aa,aj);Z={x:ah,y:ai};var an,ao,ap,aq;if(aa.x+U[3]al.width-ab)ap=al.width+U[1];else if(am.search('r')!=-1&&aa.x2-aa.xal.height-ab)aq=al.height+U[2];else if(am.search('b')!=-1&&aa.y2-aa.y'];if(b.ie6Compat){var Y=b.isCustomDomain(),Z=""; +X.push('');}X.push('');W=h.createFromHtml(X.join(''));W.setOpacity(T!=undefined?T:0.5);W.appendTo(a.document.getBody());B[V]=W;}else W.show();C=W;var aa=function(){var ad=Q.getViewPaneSize();W.setStyles({width:ad.width+'px',height:ad.height+'px'});},ab=function(){var ad=Q.getScrollPosition(),ae=a.dialog._.currentTop;W.setStyles({left:ad.x+'px',top:ad.y+'px'});do{var af=ae.getPosition();ae.move(af.x,af.y);}while(ae=ae._.parentDialog)};A=aa;Q.on('resize',aa);aa();if(b.ie6Compat){var ac=function(){ab();arguments.callee.prevScrollHandler.apply(this,arguments);};Q.$.setTimeout(function(){ac.prevScrollHandler=window.onscroll||(function(){});window.onscroll=ac;},0);ab();}};function E(){if(!C)return;var P=a.document.getWindow();C.hide();P.removeListener('resize',A);if(b.ie6Compat)P.$.setTimeout(function(){var Q=window.onscroll&&window.onscroll.prevScrollHandler;window.onscroll=Q||null;},0);A=null;};function F(){for(var P in B)B[P].remove();B={};};var G={},H=function(P){var Q=P.data.$.ctrlKey||P.data.$.metaKey,R=P.data.$.altKey,S=P.data.$.shiftKey,T=String.fromCharCode(P.data.$.keyCode),U=G[(Q?'CTRL+':'')+(R?'ALT+':'')+(S?'SHIFT+':'')+T];if(!U||!U.length)return;U=U[U.length-1];U.keydown&&U.keydown.call(U.uiElement,U.dialog,U.key);P.data.preventDefault();},I=function(P){var Q=P.data.$.ctrlKey||P.data.$.metaKey,R=P.data.$.altKey,S=P.data.$.shiftKey,T=String.fromCharCode(P.data.$.keyCode),U=G[(Q?'CTRL+':'')+(R?'ALT+':'')+(S?'SHIFT+':'')+T];if(!U||!U.length)return;U=U[U.length-1];if(U.keyup){U.keyup.call(U.uiElement,U.dialog,U.key);P.data.preventDefault();}},J=function(P,Q,R,S,T){var U=G[R]||(G[R]=[]);U.push({uiElement:P,dialog:Q,key:R,keyup:T||P.accessKeyUp,keydown:S||P.accessKeyDown});},K=function(P){for(var Q in G){var R=G[Q];for(var S=R.length-1;S>=0;S--){if(R[S].dialog==P||R[S].uiElement==P)R.splice(S,1);}if(R.length===0)delete G[Q];}},L=function(P,Q){if(P._.accessKeyMap[Q])P.selectPage(P._.accessKeyMap[Q]);},M=function(P,Q){},N={27:1,13:1},O=function(P){if(P.data.getKeystroke() in N)P.data.stopPropagation();};(function(){k.dialog={uiElement:function(P,Q,R,S,T,U,V){if(arguments.length<4)return; +var W=(S.call?S(Q):S)||'div',X=['<',W,' '],Y=(T&&T.call?T(Q):T)||{},Z=(U&&U.call?U(Q):U)||{},aa=(V&&V.call?V.call(this,P,Q):V)||'',ab=this.domId=Z.id||e.getNextId()+'_uiElement',ac=this.id=Q.id,ad;Z.id=ab;var ae={};if(Q.type)ae['cke_dialog_ui_'+Q.type]=1;if(Q.className)ae[Q.className]=1;var af=Z['class']&&Z['class'].split?Z['class'].split(' '):[];for(ad=0;ad=0;ad--){if(ah[ad]==='')ah.splice(ad,1);}if(ah.length>0)Z.style=(Z.style?Z.style+'; ':'')+ah.join('; ');for(ad in Z)X.push(ad+'="'+e.htmlEncode(Z[ad])+'" ');X.push('>',aa,'');R.push(X.join(''));(this._||(this._={})).dialog=P;if(typeof Q.isChanged=='boolean')this.isChanged=function(){return Q.isChanged;};if(typeof Q.isChanged=='function')this.isChanged=Q.isChanged;a.event.implementOn(this);this.registerEvents(Q);if(this.accessKeyUp&&this.accessKeyDown&&Q.accessKey)J(this,P,'CTRL+'+Q.accessKey);var ai=this;P.on('load',function(){if(ai.getInputElement())ai.getInputElement().on('focus',function(){P._.tabBarMode=false;P._.hasFocus=true;ai.fire('focus');},ai);});if(this.keyboardFocusable){this.tabIndex=Q.tabIndex||0;this.focusIndex=P._.focusList.push(this)-1;this.on('focus',function(){P._.currentFocusIndex=ai.focusIndex;});}e.extend(this,Q);},hbox:function(P,Q,R,S,T){if(arguments.length<4)return;this._||(this._={});var U=this._.children=Q,V=T&&T.widths||null,W=T&&T.height||null,X={},Y,Z=function(){var ab=[''];for(Y=0;Y0)ab.push('style="'+ad.join('; ')+'" ');ab.push('>',R[Y],'');}ab.push('');return ab.join('');},aa={role:'presentation'};T&&T.align&&(aa.align=T.align);k.dialog.uiElement.call(this,P,T||{type:'hbox'},S,'table',X,aa,Z);},vbox:function(P,Q,R,S,T){if(arguments.length<3)return;this._||(this._={});var U=this._.children=Q,V=T&&T.width||null,W=T&&T.heights||null,X=function(){var Y=['');for(var Z=0;Z');}Y.push('
          0)Y.push('style="',aa.join('; '),'" ');Y.push(' class="cke_dialog_ui_vbox_child">',R[Z],'
          ');return Y.join('');};k.dialog.uiElement.call(this,P,T||{type:'vbox'},S,'div',null,{role:'presentation'},X);}};})();k.dialog.uiElement.prototype={getElement:function(){return a.document.getById(this.domId);},getInputElement:function(){return this.getElement();},getDialog:function(){return this._.dialog;},setValue:function(P,Q){this.getInputElement().setValue(P);!Q&&this.fire('change',{value:P});return this;},getValue:function(){return this.getInputElement().getValue();},isChanged:function(){return false;},selectParentTab:function(){var S=this;var P=S.getInputElement(),Q=P,R;while((Q=Q.getParent())&&Q.$.className.search('cke_dialog_page_contents')==-1){}if(!Q)return S;R=Q.getAttribute('name');if(S._.dialog._.currentTabId!=R)S._.dialog.selectPage(R);return S;},focus:function(){this.selectParentTab().getInputElement().focus();return this;},registerEvents:function(P){var Q=/^on([A-Z]\w+)/,R,S=function(U,V,W,X){V.on('load',function(){U.getInputElement().on(W,X,U);});};for(var T in P){if(!(R=T.match(Q)))continue;if(this.eventProcessors[T])this.eventProcessors[T].call(this,this._.dialog,P[T]);else S(this,this._.dialog,R[1].toLowerCase(),P[T]);}return this;},eventProcessors:{onLoad:function(P,Q){P.on('load',Q,this);},onShow:function(P,Q){P.on('show',Q,this);},onHide:function(P,Q){P.on('hide',Q,this);}},accessKeyDown:function(P,Q){this.focus();},accessKeyUp:function(P,Q){},disable:function(){var P=this.getInputElement();P.setAttribute('disabled','true');P.addClass('cke_disabled');},enable:function(){var P=this.getInputElement();P.removeAttribute('disabled');P.removeClass('cke_disabled');},isEnabled:function(){return!this.getInputElement().getAttribute('disabled');},isVisible:function(){return this.getInputElement().isVisible();},isFocusable:function(){if(!this.isEnabled()||!this.isVisible())return false;return true;}};k.dialog.hbox.prototype=e.extend(new k.dialog.uiElement(),{getChild:function(P){var Q=this; +if(arguments.length<1)return Q._.children.concat();if(!P.splice)P=[P];if(P.length<2)return Q._.children[P[0]];else return Q._.children[P[0]]&&Q._.children[P[0]].getChild?Q._.children[P[0]].getChild(P.slice(1,P.length)):null;}},true);k.dialog.vbox.prototype=new k.dialog.hbox();(function(){var P={build:function(Q,R,S){var T=R.children,U,V=[],W=[];for(var X=0;X',Q.name,'');return R.join('');}};a.style.getStyleText=function(Q){var R=Q._ST;if(R)return R;R=Q.styles;var S=Q.attributes&&Q.attributes.style||'',T='';if(S.length)S=S.replace(o,';');for(var U in R){var V=R[U],W=(U+':'+V).replace(o,';');if(V=='inherit')T+=W;else S+=W;}if(S.length)S=M(S);S+=T;return Q._ST=S;};function p(Q){var ap=this;var R=Q.document;if(Q.collapsed){var S=F(ap,R);Q.insertNode(S);Q.moveToPosition(S,2);return;}var T=ap.element,U=ap._.definition,V,W=f[T]||(V=true,f.span);Q.enlarge(1);Q.trim();var X=Q.createBookmark(),Y=X.startNode,Z=X.endNode,aa=Y,ab;while(aa){var ac=false;if(aa.equals(Z)){aa=null;ac=true;}else{var ad=aa.type,ae=ad==1?aa.getName():null;if(ae&&aa.getAttribute('_cke_bookmark')){aa=aa.getNextSourceNode(true);continue;}if(!ae||W[ae]&&(aa.getPosition(Z)|4|0|8)==4+0+8&&(!U.childRule||U.childRule(aa))){var af=aa.getParent();if(af&&((af.getDtd()||f.span)[T]||V)&&(!U.parentRule||U.parentRule(af))){if(!ab&&(!ae||!f.$removeEmpty[ae]||(aa.getPosition(Z)|4|0|8)==4+0+8)){ab=new d.range(R);ab.setStartBefore(aa);}if(ad==3||ad==1&&!aa.getChildCount()){var ag=aa,ah;while(!ag.$.nextSibling&&(ah=ag.getParent(),W[ah.getName()])&&(ah.getPosition(Y)|2|0|8)==2+0+8&&(!U.childRule||U.childRule(ah)))ag=ah;ab.setEndAfter(ag);if(!ag.$.nextSibling)ac=true;}}else ac=true;}else ac=true;aa=aa.getNextSourceNode();}if(ac&&ab&&!ab.collapsed){var ai=F(ap,R),aj=ai.hasAttributes(),ak=ab.getCommonAncestor(),al={styles:{},attrs:{},blockedStyles:{},blockedAttrs:{}},am,an,ao;while(ai&&ak){if(ak.getName()==T){for(am in U.attributes){if(al.blockedAttrs[am]||!(ao=ak.getAttribute(an)))continue;if(ai.getAttribute(am)==ao)al.attrs[am]=1;else al.blockedAttrs[am]=1;}for(an in U.styles){if(al.blockedStyles[an]||!(ao=ak.getStyle(an)))continue;if(ai.getStyle(an)==ao)al.styles[an]=1;else al.blockedStyles[an]=1;}}ak=ak.getParent();}for(am in al.attrs)ai.removeAttribute(am);for(an in al.styles)ai.removeStyle(an);if(aj&&!ai.hasAttributes())ai=null;if(ai){ab.extractContents().appendTo(ai);C(ap,ai);ab.insertNode(ai);ai.mergeSiblings();if(!c)ai.$.normalize();}else{ai=new h('span');ab.extractContents().appendTo(ai); +ab.insertNode(ai);C(ap,ai);ai.remove(true);}ab=null;}}Q.moveToBookmark(X);Q.shrink(2);};function q(Q){Q.enlarge(1);var R=Q.createBookmark(),S=R.startNode;if(Q.collapsed){var T=new d.elementPath(S.getParent()),U;for(var V=0,W;V'+S+'';else Q.setHtml(S);R.remove();};function x(Q){var R=/(\S\s*)\n(?:\s|(]+_cke_bookmark.*?\/span>))*\n(?!$)/gi,S=Q.getName(),T=y(Q.getOuterHtml(),R,function(V,W,X){return W+''+X+'
          ';
          +}),U=[];T.replace(/([\s\S]*?)<\/pre>/gi,function(V,W){U.push(W);});return U;};function y(Q,R,S){var T='',U='';Q=Q.replace(/(^]+_cke_bookmark.*?\/span>)|(]+_cke_bookmark.*?\/span>$)/gi,function(V,W,X){W&&(T=W);X&&(U=X);return '';});return T+Q.replace(R,S)+U;};function z(Q,R){var S=new d.documentFragment(R.getDocument());for(var T=0;T');U=U.replace(/[ \t]{2,}/g,function(W){return e.repeat(' ',W.length-1)+' ';});var V=R.clone();V.setHtml(U);S.append(V);}return S;};function A(Q,R){var S=Q.getHtml();S=y(S,/(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g,'');S=S.replace(/[ \t\r\n]*(]*>)[ \t\r\n]*/gi,'$1');S=S.replace(/([ \t\n\r]+| )/g,' ');S=S.replace(/]*>/gi,'\n');if(c){var T=Q.getDocument().createElement('div');T.append(R);R.$.outerHTML='
          '+S+'
          ';R=T.getFirst().remove();}else R.setHtml(S);return R;};function B(Q,R){var S=Q._.definition,T=e.extend({},S.attributes,K(Q)[R.getName()]),U=S.styles,V=e.isEmpty(T)&&e.isEmpty(U);for(var W in T){if((W=='class'||Q._.definition.fullMatch)&&R.getAttribute(W)!=L(W,T[W]))continue;V=R.hasAttribute(W);R.removeAttribute(W);}for(var X in U){if(Q._.definition.fullMatch&&R.getStyle(X)!=L(X,U[X],true))continue;V=V||!!R.getStyle(X);R.removeStyle(X);}V&&E(R);};function C(Q,R){var S=Q._.definition,T=S.attributes,U=S.styles,V=K(Q),W=R.getElementsByTag(Q.element);for(var X=W.count();--X>=0;)B(Q,W.getItem(X));for(var Y in V){if(Y!=Q.element){W=R.getElementsByTag(Y);for(X=W.count()-1;X>=0;X--){var Z=W.getItem(X);D(Z,V[Y]);}}}};function D(Q,R){var S=R&&R.attributes;if(S)for(var T=0;T0)H+=(F.$.offsetWidth||0)-(F.$.clientWidth||0);H+=4;F.setStyle('width',H+'px');v.element.addClass('cke_frameLoaded');var I=v.element.$.scrollHeight;if(c&&b.quirks&&I>0)I+=(F.$.offsetHeight||0)-(F.$.clientHeight||0);F.setStyle('height',I+'px');u._.currentBlock.element.setStyle('display','none').removeStyle('display');}else F.removeStyle('height');var J=u.element,K=J.getWindow(),L=K.getScrollPosition(),M=K.getViewPaneSize(),N={height:J.$.offsetHeight,width:J.$.offsetWidth};if(A?B<0:B+N.width>M.width+L.x)B+=N.width*(A?1:-1);if(C+N.height>M.height+L.y)C-=N.height;if(c){var O=new h(w.$.offsetParent),P=O;if(P.getName()=='html')P=P.getDocument().getBody();if(P.getComputedStyle('direction')=='rtl')if(b.ie8Compat)B-=w.getDocument().getDocumentElement().$.scrollLeft*2;else B-=O.$.scrollWidth-O.$.clientWidth;}var Q=w.getFirst(),R;if(R=Q.getCustomData('activePanel'))R.onHide&&R.onHide.call(this,1);Q.setCustomData('activePanel',this);w.setStyles({top:C+'px',left:B+'px'});w.setOpacity(1);},this);u.isLoaded?E():u.onLoad=E;e.setTimeout(function(){x.$.contentWindow.focus();this.allowBlur(true); +},0,this);},0,this);this.visible=1;if(this.onShow)this.onShow.call(this);n=0;},hide:function(){var p=this;if(p.visible&&(!p.onHide||p.onHide.call(p)!==true)){p.hideChild();p.element.setStyle('display','none');p.visible=0;p.element.getFirst().removeCustomData('activePanel');}},allowBlur:function(p){var q=this._.panel;if(p!=undefined)q.allowBlur=p;return q.allowBlur;},showAsChild:function(p,q,r,s,t,u){if(this._.activeChild==p&&p._.panel._.offsetParentId==r.getId())return;this.hideChild();p.onHide=e.bind(function(){e.setTimeout(function(){if(!this._.focused)this.hide();},0,this);},this);this._.activeChild=p;this._.focused=false;p.showBlock(q,r,s,t,u);if(b.ie7Compat||b.ie8&&b.ie6Compat)setTimeout(function(){p.element.getChild(0).$.style.cssText+='';},100);},hideChild:function(){var p=this._.activeChild;if(p){delete p.onHide;delete this._.activeChild;p.hide();}}}});a.on('instanceDestroyed',function(){var p=e.isEmpty(a.instances);for(var q in m){var r=m[q];if(p)r.destroy();else r.element.hide();}p&&(m={});});})();j.add('menu',{beforeInit:function(m){var n=m.config.menu_groups.split(','),o=m._.menuGroups={},p=m._.menuItems={};for(var q=0;q'],B=r.length,C=B&&r[0].group;for(var D=0;D');C=E.group;}E.render(this,D,A);}A.push('');u.setHtml(A.join(''));if(this.parent)this.parent._.panel.showAsChild(t,this.id,n,o,p,q);else t.showBlock(this.id,n,o,p,q);s.fire('menuShow',[t]);},hide:function(){this._.panel&&this._.panel.hide();}}});function m(n){n.sort(function(o,p){if(o.groupp.group)return 1;return o.orderp.order?1:0;});};})();a.menuItem=e.createClass({$:function(m,n,o){var p=this;e.extend(p,o,{order:0,className:'cke_button_'+n});p.group=m._.menuGroups[p.group];p.editor=m;p.name=n;},proto:{render:function(m,n,o){var v=this;var p=m.id+String(n),q=typeof v.state=='undefined'?2:v.state,r=' cke_'+(q==1?'on':q==0?'disabled':'off'),s=v.label;if(v.className)r+=' '+v.className;var t=v.getItems;o.push(''+'');if(t)o.push('','&#',v.editor.lang.dir=='rtl'?'9668':'9658',';','');o.push(s,'');}}});i.menu_groups='clipboard,form,tablecell,tablecellproperties,tablerow,tablecolumn,table,anchor,link,image,flash,checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div';(function(){var m=function(o,p){return o._.modes&&o._.modes[p||o.mode];},n;j.add('editingblock',{init:function(o){if(!o.config.editingBlock)return;o.on('themeSpace',function(p){if(p.data.space=='contents')p.data.html+='
          ';});o.on('themeLoaded',function(){o.fireOnce('editingBlockReady');});o.on('uiReady',function(){o.setMode(o.config.startupMode);});o.on('afterSetData',function(){if(!n){function p(){n=true;m(o).loadData(o.getData());n=false;};if(o.mode)p();else o.on('mode',function(){p();o.removeListener('mode',arguments.callee);});}});o.on('beforeGetData',function(){if(!n&&o.mode){n=true;o.setData(m(o).getData());n=false;}});o.on('getSnapshot',function(p){if(o.mode)p.data=m(o).getSnapshotData();});o.on('loadSnapshot',function(p){if(o.mode)m(o).loadSnapshotData(p.data);});o.on('mode',function(p){p.removeListener();b.webkit&&o.container.on('focus',function(){o.focus();});if(o.config.startupFocus)o.focus();setTimeout(function(){o.fireOnce('instanceReady');a.fire('instanceReady',null,o);},0);});}});a.editor.prototype.mode='';a.editor.prototype.addMode=function(o,p){p.name=o;(this._.modes||(this._.modes={}))[o]=p;};a.editor.prototype.setMode=function(o){var p,q=this.getThemeSpace('contents'),r=this.checkDirty();if(this.mode){if(o==this.mode)return;this.fire('beforeModeUnload');var s=m(this);p=s.getData();s.unload(q);this.mode='';}q.setHtml('');var t=m(this,o);if(!t)throw '[CKEDITOR.editor.setMode] Unknown mode "'+o+'".';if(!r)this.on('mode',function(){this.resetDirty();this.removeListener('mode',arguments.callee);});t.load(q,typeof p!='string'?this.getData():p);};a.editor.prototype.focus=function(){var o=m(this);if(o)o.focus();};})();i.startupMode='wysiwyg';i.editingBlock=true;(function(){function m(){var w=this; +try{var t=w.getSelection();if(!t||!t.document.getWindow().$)return;var u=t.getStartElement(),v=new d.elementPath(u);if(!v.compare(w._.selectionPreviousPath)){w._.selectionPreviousPath=v;w.fire('selectionChange',{selection:t,path:v,element:u});}}catch(x){}};var n,o;function p(){o=true;if(n)return;q.call(this);n=e.setTimeout(q,200,this);};function q(){n=null;if(o){e.setTimeout(m,0,this);o=false;}};var r={modes:{wysiwyg:1,source:1},exec:function(t){switch(t.mode){case 'wysiwyg':t.document.$.execCommand('SelectAll',false,null);break;case 'source':var u=t.textarea.$;if(c)u.createTextRange().execCommand('SelectAll');else{u.selectionStart=0;u.selectionEnd=u.value.length;}u.focus();}},canUndo:false};j.add('selection',{init:function(t){t.on('contentDom',function(){var u=t.document,v=u.getBody(),w=u.getDocumentElement();if(c){var x,y,z=1;v.on('focusin',function(D){if(D.data.$.srcElement.nodeName!='BODY')return;if(x){var E=u.getCustomData('cke_locked_selection');if(z&&!E)try{x.select();}catch(F){}x=null;}});v.on('focus',function(){y=1;C();});v.on('beforedeactivate',function(D){if(D.data.$.toElement)return;y=0;z=1;});if(c&&b.version<8)t.on('blur',function(D){try{t.document&&t.document.$.selection.empty();}catch(E){}});w.on('mousedown',function(){z=0;});w.on('mouseup',function(){z=1;});if(c&&(b.ie7Compat||b.version<8||b.quirks))w.on('click',function(D){if(D.data.getTarget().getName()=='html')t.getSelection().getRanges()[0].select();});var A;v.on('mousedown',function(D){if(D.data.$.button==2){var E=t.document.$.selection;if(E.type=='None')A=t.window.getScrollPosition();}B();});v.on('mouseup',function(D){if(D.data.$.button==2&&A){t.document.$.documentElement.scrollLeft=A.x;t.document.$.documentElement.scrollTop=A.y;}A=null;y=1;setTimeout(function(){C(true);},0);});v.on('keydown',B);v.on('keyup',function(){y=1;C();});u.on('selectionchange',C);function B(){y=0;};function C(D){if(y){var E=t.document,F=t.getSelection(),G=F&&F.getNative();if(D&&G&&G.type=='None')if(!E.$.queryCommandEnabled('InsertImage')){e.setTimeout(C,50,this,true);return;}var H;if(G&&G.type&&G.type!='Control'&&(H=G.createRange())&&(H=H.parentElement())&&(H=H.nodeName)&&H.toLowerCase() in {input:1,textarea:1})return;x=G&&F.getRanges()[0];p.call(t);}};}else{u.on('mouseup',p,t);u.on('keyup',p,t);}});t.addCommand('selectAll',r);t.ui.addButton('SelectAll',{label:t.lang.selectAll,command:'selectAll'});t.selectionChange=p;}});a.editor.prototype.getSelection=function(){return this.document&&this.document.getSelection(); +};a.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath;};g.prototype.getSelection=function(){var t=new d.selection(this);return!t||t.isInvalid?null:t;};a.SELECTION_NONE=1;a.SELECTION_TEXT=2;a.SELECTION_ELEMENT=3;d.selection=function(t){var w=this;var u=t.getCustomData('cke_locked_selection');if(u)return u;w.document=t;w.isLocked=0;w._={cache:{}};if(c){var v=w.getNative().createRange();if(!v||v.item&&v.item(0).ownerDocument!=w.document.$||v.parentElement&&v.parentElement().ownerDocument!=w.document.$)w.isInvalid=true;}return w;};var s={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,th:1,thead:1,tfoot:1};d.selection.prototype={getNative:c?function(){return this._.cache.nativeSel||(this._.cache.nativeSel=this.document.$.selection);}:function(){return this._.cache.nativeSel||(this._.cache.nativeSel=this.document.getWindow().$.getSelection());},getType:c?function(){var t=this._.cache;if(t.type)return t.type;var u=1;try{var v=this.getNative(),w=v.type;if(w=='Text')u=2;if(w=='Control')u=3;if(v.createRange().parentElement)u=2;}catch(x){}return t.type=u;}:function(){var t=this._.cache;if(t.type)return t.type;var u=2,v=this.getNative();if(!v)u=1;else if(v.rangeCount==1){var w=v.getRangeAt(0),x=w.startContainer;if(x==w.endContainer&&x.nodeType==1&&w.endOffset-w.startOffset==1&&s[x.childNodes[w.startOffset].nodeName.toLowerCase()])u=3;}return t.type=u;},getRanges:(function(){var t=c?(function(){var u=function(v,w){v=v.duplicate();v.collapse(w);var x=v.parentElement(),y=x.childNodes,z;for(var A=0;A0)break;else if(!C||D==1&&C==-1)return{container:x,offset:A};else if(!D)return{container:x,offset:A+1};z=null;}}if(!z){z=v.duplicate();z.moveToElementText(x);z.collapse(false);}z.setEndPoint('StartToStart',v);var E=z.text.replace(/(\r\n|\r)/g,'\n').length;try{while(E>0)E-=y[--A].nodeValue.length;}catch(F){E=0;}if(E===0)return{container:x,offset:A};else return{container:y[A],offset:-E};};return function(){var F=this;var v=F.getNative(),w=v&&v.createRange(),x=F.getType(),y;if(!v)return[];if(x==2){y=new d.range(F.document);var z=u(w,true);y.setStart(new d.node(z.container),z.offset);z=u(w);y.setEnd(new d.node(z.container),z.offset);if(y.endContainer.getPosition(y.startContainer)&4&&y.endOffset<=y.startContainer.getIndex())y.collapse(); +return[y];}else if(x==3){var A=[];for(var B=0;B=A.getLength())E.setStartAfter(A);else E.setStartBefore(A);if(B&&B.type==3)if(!D)E.setEndBefore(B);else E.setEndAfter(B);var G=new d.walker(E);G.evaluator=function(H){if(H.type==1&&H.getAttribute('contenteditable')=='false'){var I=y.clone();y.setEndBefore(H);if(y.collapsed)w.splice(x--,1);if(!(H.getPosition(E.endContainer)&16)){I.setStartAfter(H);if(!I.collapsed)w.splice(x+1,0,I);}return true;}return false;};G.next();}}return v.ranges;};})(),getStartElement:function(){var A=this;var t=A._.cache;if(t.startElement!==undefined)return t.startElement;var u,v=A.getNative();switch(A.getType()){case 3:return A.getSelectedElement();case 2:var w=A.getRanges()[0];if(w){if(!w.collapsed){w.optimize();while(1){var x=w.startContainer,y=w.startOffset;if(y==(x.getChildCount?x.getChildCount():x.getLength())&&!x.isBlockBoundary())w.setStartAfter(x);else break;}u=w.startContainer;if(u.type!=1)return u.getParent();u=u.getChild(w.startOffset);if(!u||u.type!=1)u=w.startContainer;else{var z=u.getFirst();while(z&&z.type==1){u=z;z=z.getFirst();}}}else{u=w.startContainer;if(u.type!=1)u=u.getParent();}u=u.$;}}return t.startElement=u?new h(u):null;},getSelectedElement:function(){var t=this._.cache;if(t.selectedElement!==undefined)return t.selectedElement;var u=this,v=e.tryThese(function(){return u.getNative().createRange().item(0);},function(){var w=u.getRanges()[0],x,y;for(var z=2;z&&!((x=w.getEnclosedNode())&&x.type==1&&s[x.getName()]&&(y=x));z--)w.shrink(1);return y.$;}); +return t.selectedElement=v?new h(v):null;},lock:function(){var t=this;t.getRanges();t.getStartElement();t.getSelectedElement();t._.cache.nativeSel={};t.isLocked=1;t.document.setCustomData('cke_locked_selection',t);},unlock:function(t){var y=this;var u=y.document,v=u.getCustomData('cke_locked_selection');if(v){u.setCustomData('cke_locked_selection',null);if(t){var w=v.getSelectedElement(),x=!w&&v.getRanges();y.isLocked=0;y.reset();u.getBody().focus();if(w)y.selectElement(w);else y.selectRanges(x);}}if(!v||!t){y.isLocked=0;y.reset();}},reset:function(){this._.cache={};},selectElement:function(t){var w=this;if(w.isLocked){var u=new d.range(w.document);u.setStartBefore(t);u.setEndAfter(t);w._.cache.selectedElement=t;w._.cache.startElement=t;w._.cache.ranges=new d.rangeList(u);w._.cache.type=3;return;}if(c){w.getNative().empty();try{u=w.document.$.body.createControlRange();u.addElement(t.$);u.select();}catch(x){u=w.document.$.body.createTextRange();u.moveToElementText(t.$);u.select();}finally{w.document.fire('selectionchange');}w.reset();}else{u=w.document.$.createRange();u.selectNode(t.$);var v=w.getNative();v.removeAllRanges();v.addRange(u);w.reset();}},selectRanges:function(t){var D=this;if(D.isLocked){D._.cache.selectedElement=null;D._.cache.startElement=t[0]&&t[0].getTouchedStartNode();D._.cache.ranges=new d.rangeList(t);D._.cache.type=2;return;}if(c){if(t.length>1){var u=t[t.length-1];t[0].setEnd(u.endContainer,u.endOffset);t.length=1;}if(t[0])t[0].select();D.reset();}else{var v=D.getNative();if(t.length)v.removeAllRanges();for(var w=0;w=0){s.collapse(true);q.setEnd(s.endContainer.$,s.endOffset);}else throw t;}var r=s.document.getSelection().getNative();r.removeAllRanges();r.addRange(q);};})();(function(){var m={elements:{$:function(n){var o=n.attributes,p=o&&o._cke_realelement,q=p&&new a.htmlParser.fragment.fromHtml(decodeURIComponent(p)),r=q&&q.children[0];if(r&&n.attributes._cke_resizable){var s=n.attributes.style;if(s){var t=/(?:^|\s)width\s*:\s*(\d+)/i.exec(s),u=t&&t[1];t=/(?:^|\s)height\s*:\s*(\d+)/i.exec(s);var v=t&&t[1];if(u)r.attributes.width=u;if(v)r.attributes.height=v;}}return r;}}};j.add('fakeobjects',{requires:['htmlwriter'],afterInit:function(n){var o=n.dataProcessor,p=o&&o.htmlFilter;if(p)p.addRules(m);}});})();a.editor.prototype.createFakeElement=function(m,n,o,p){var q=this.lang.fakeobjects,r={'class':n,src:a.getUrl('images/spacer.gif'),_cke_realelement:encodeURIComponent(m.getOuterHtml()),_cke_real_node_type:m.type,alt:q[o]||q.unknown,align:m.getAttribute('align')||''};if(o)r._cke_real_element_type=o;if(p)r._cke_resizable=p; +return this.document.createElement('img',{attributes:r});};a.editor.prototype.createFakeParserElement=function(m,n,o,p){var q=this.lang.fakeobjects,r,s=new a.htmlParser.basicWriter();m.writeHtml(s);r=s.getHtml();var t={'class':n,src:a.getUrl('images/spacer.gif'),_cke_realelement:encodeURIComponent(r),_cke_real_node_type:m.type,alt:q[o]||q.unknown,align:m.attributes.align||''};if(o)t._cke_real_element_type=o;if(p)t._cke_resizable=p;return new a.htmlParser.element('img',t);};a.editor.prototype.restoreRealElement=function(m){if(m.getAttribute('_cke_real_node_type')!=1)return null;return h.createFromHtml(decodeURIComponent(m.getAttribute('_cke_realelement')),this.document);};j.add('richcombo',{requires:['floatpanel','listblock','button'],beforeInit:function(m){m.ui.addHandler(3,k.richCombo.handler);}});a.UI_RICHCOMBO=3;k.richCombo=e.createClass({$:function(m){var o=this;e.extend(o,m,{title:m.label,modes:{wysiwyg:1}});var n=o.panel||{};delete o.panel;o.id=e.getNextNumber();o.document=n&&n.parent&&n.parent.getDocument()||a.document;n.className=(n.className||'')+' cke_rcombopanel';n.block={multiSelect:n.multiSelect,attributes:n.attributes};o._={panelDefinition:n,items:{},state:2};},statics:{handler:{create:function(m){return new k.richCombo(m);}}},proto:{renderHtml:function(m){var n=[];this.render(m,n);return n.join('');},render:function(m,n){var o=b,p='cke_'+this.id,q=e.addFunction(function(t){var w=this;var u=w._;if(u.state==0)return;w.createPanel(m);if(u.on){u.panel.hide();return;}if(!u.committed){u.list.commit();u.committed=1;}var v=w.getValue();if(v)u.list.mark(v);else u.list.unmarkAll();u.panel.showBlock(w.id,new h(t),4);},this),r={id:p,combo:this,focus:function(){var t=a.document.getById(p).getChild(1);t.focus();},clickFn:q};m.on('mode',function(){this.setState(this.modes[m.mode]?2:0);},this);var s=e.addFunction(function(t,u){t=new d.event(t);var v=t.getKeystroke();switch(v){case 13:case 32:case 40:e.callFunction(q,u);break;default:r.onkey(r,v);}t.preventDefault();});r.keyDownFn=s;n.push('','','',this.label,'','=10900&&!o.hc?'':" href=\"javascript:void('"+this.label+"')\"",' role="button" aria-labelledby="',p,'_label" aria-describedby="',p,'_text" aria-haspopup="true"');if(b.opera||b.gecko&&b.mac)n.push(' onkeypress="return false;"'); +if(b.gecko)n.push(' onblur="this.style.cssText = this.style.cssText;"');n.push(' onkeydown="CKEDITOR.tools.callFunction( ',s,', event, this );" onclick="CKEDITOR.tools.callFunction(',q,', this); return false;">'+this.label+''+''+''+(b.hc?'':'')+''+''+''+'');if(this.onRender)this.onRender();return r;},createPanel:function(m){if(this._.panel)return;var n=this._.panelDefinition,o=this._.panelDefinition.block,p=n.parent||a.document.getBody(),q=new k.floatPanel(m,p,n),r=q.addListBlock(this.id,o),s=this;q.onShow=function(){if(s.className)this.element.getFirst().addClass(s.className+'_panel');s.setState(1);r.focus(!s.multiSelect&&s.getValue());s._.on=1;if(s.onOpen)s.onOpen();};q.onHide=function(t){if(s.className)this.element.getFirst().removeClass(s.className+'_panel');s.setState(2);s._.on=0;if(!t&&s.onClose)s.onClose();};q.onEscape=function(){q.hide();s.document.getById('cke_'+s.id).getFirst().getNext().focus();};r.onClick=function(t,u){s.document.getWindow().focus();if(s.onClick)s.onClick.call(s,t,u);if(u)s.setValue(t,s._.items[t]);else s.setValue('');q.hide();};this._.panel=q;this._.list=r;q.getBlock(this.id).onHide=function(){s._.on=0;s.setState(2);};if(this.init)this.init();},setValue:function(m,n){var p=this;p._.value=m;var o=p.document.getById('cke_'+p.id+'_text');if(!(m||n)){n=p.label;o.addClass('cke_inline_label');}else o.removeClass('cke_inline_label');o.setHtml(typeof n!='undefined'?n:m);},getValue:function(){return this._.value||'';},unmarkAll:function(){this._.list.unmarkAll();},mark:function(m){this._.list.mark(m);},hideItem:function(m){this._.list.hideItem(m);},hideGroup:function(m){this._.list.hideGroup(m);},showAll:function(){this._.list.showAll();},add:function(m,n,o){this._.items[m]=o||m;this._.list.add(m,n,o);},startGroup:function(m){this._.list.startGroup(m);},commit:function(){this._.list.commit();},setState:function(m){var n=this;if(n._.state==m)return;n.document.getById('cke_'+n.id).setState(m);n._.state=m;}}});k.prototype.addRichCombo=function(m,n){this.add(m,3,n);};j.add('htmlwriter');a.htmlWriter=e.createClass({base:a.htmlParser.basicWriter,$:function(){var o=this;o.base();o.indentationChars='\t';o.selfClosingEnd=' />';o.lineBreakChars='\n';o.forceSimpleAmpersand=0;o.sortAttributes=1;o._.indent=0;o._.indentation='';o._.inPre=0;o._.rules={};var m=f;for(var n in e.extend({},m.$nonBodyContent,m.$block,m.$listItem,m.$tableContent))o.setRules(n,{indent:1,breakBeforeOpen:1,breakAfterOpen:1,breakBeforeClose:!m[n]['#'],breakAfterClose:1}); +o.setRules('br',{breakAfterOpen:1});o.setRules('title',{indent:0,breakAfterOpen:0});o.setRules('style',{indent:0,breakBeforeClose:1});o.setRules('pre',{indent:0});},proto:{openTag:function(m,n){var p=this;var o=p._.rules[m];if(p._.indent)p.indentation();else if(o&&o.breakBeforeOpen){p.lineBreak();p.indentation();}p._.output.push('<',m);},openTagClose:function(m,n){var p=this;var o=p._.rules[m];if(n)p._.output.push(p.selfClosingEnd);else{p._.output.push('>');if(o&&o.indent)p._.indentation+=p.indentationChars;}if(o&&o.breakAfterOpen)p.lineBreak();m=='pre'&&(p._.inPre=1);},attribute:function(m,n){if(typeof n=='string'){this.forceSimpleAmpersand&&(n=n.replace(/&/g,'&'));n=e.htmlEncodeAttr(n);}this._.output.push(' ',m,'="',n,'"');},closeTag:function(m){var o=this;var n=o._.rules[m];if(n&&n.indent)o._.indentation=o._.indentation.substr(o.indentationChars.length);if(o._.indent)o.indentation();else if(n&&n.breakBeforeClose){o.lineBreak();o.indentation();}o._.output.push('');m=='pre'&&(o._.inPre=0);if(n&&n.breakAfterClose)o.lineBreak();},text:function(m){var n=this;if(n._.indent){n.indentation();!n._.inPre&&(m=e.ltrim(m));}n._.output.push(m);},comment:function(m){if(this._.indent)this.indentation();this._.output.push('');},lineBreak:function(){var m=this;if(!m._.inPre&&m._.output.length>0)m._.output.push(m.lineBreakChars);m._.indent=1;},indentation:function(){var m=this;if(!m._.inPre)m._.output.push(m._.indentation);m._.indent=0;},setRules:function(m,n){var o=this._.rules[m];if(o)e.extend(o,n,true);else this._.rules[m]=n;}}});j.add('menubutton',{requires:['button','contextmenu'],beforeInit:function(m){m.ui.addHandler(5,k.menuButton.handler);}});a.UI_MENUBUTTON=5;(function(){var m=function(n){var o=this._;if(o.state===0)return;o.previousState=o.state;var p=o.menu;if(!p){p=o.menu=new j.contextMenu(n);p.definition.panel.attributes['aria-label']=n.lang.common.options;p.onHide=e.bind(function(){this.setState(o.previousState);},this);if(this.onMenu)p.addListener(this.onMenu);}if(o.on){p.hide();return;}this.setState(1);p.show(a.document.getById(this._.id),4);};k.menuButton=e.createClass({base:k.button,$:function(n){var o=n.panel;delete n.panel;this.base(n);this.hasArrow=true;this.click=m;},statics:{handler:{create:function(n){return new k.menuButton(n);}}}});})();j.add('dialogui');(function(){var m=function(u){var x=this;x._||(x._={});x._['default']=x._.initValue=u['default']||'';x._.required=u.required||false;var v=[x._];for(var w=1;w',v.label,'','');else{var D={type:'hbox',widths:v.widths,padding:0,children:[{type:'html',html:'
        ');delete this._.started;}},getClick:function(){if(!this._.click)this._.click=e.addFunction(function(m){var o=this;var n=true;if(o.multiSelect)n=o.toggle(m);else o.mark(m);if(o.onClick)o.onClick(m,n);},this);return this._.click;}},proto:{add:function(m,n,o){var r=this;var p=r._.pendingHtml,q=e.getNextId();if(!r._.started){p.push('\n\n
      \n"; + if (MetaModel::DBIsReadOnly()) + { + $sApplicationMode = Dict::S('UI:ApplicationReadOnly'); + } + else + { + $sApplicationMode = ''; + } + //$sLogOffMenu = ""; echo '
      '; @@ -687,8 +696,8 @@ EOF echo '
      '; echo '
      '; - echo ' '; //echo '        
      '; diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 9db9e398e7..0687e9045c 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -134,6 +134,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'read_only' => array( + 'type' => 'bool', + 'description' => 'Freeze the data for administration purposes - administrators can still do anything... in appearance!', + 'default' => false, + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), ); public function IsProperty($sPropCode) diff --git a/core/dbobject.class.php b/core/dbobject.class.php index e3290987b5..4bc995740c 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -849,7 +849,14 @@ abstract class DBObject $sInsertSQL = "INSERT INTO `$sTable` (".join(",", $aFieldsToWrite).") VALUES (".join(", ", $aValuesToWrite).")"; - $iNewKey = CMDBSource::InsertInto($sInsertSQL); + if (MetaModel::DBIsReadOnly()) + { + $iNewKey = -1; + } + else + { + $iNewKey = CMDBSource::InsertInto($sInsertSQL); + } // Note that it is possible to have a key defined here, and the autoincrement expected, this is acceptable in a non root class if (empty($this->m_iKey)) { @@ -1009,7 +1016,10 @@ abstract class DBObject $oFilter->AddCondition('id', $this->m_iKey, '='); $sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges); - CMDBSource::Query($sSQL); + if (!MetaModel::DBIsReadOnly()) + { + CMDBSource::Query($sSQL); + } } $this->DBWriteLinks(); @@ -1053,7 +1063,10 @@ abstract class DBObject $this->OnDelete(); $sSQL = MetaModel::MakeDeleteQuery($oFilter); - CMDBSource::Query($sSQL); + if (!MetaModel::DBIsReadOnly()) + { + CMDBSource::Query($sSQL); + } $this->AfterDelete(); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a8083da8e6..b967979a6e 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1401,6 +1401,7 @@ abstract class MetaModel public static function Init_OverloadStateAttribute($sStateCode, $sAttCode, $iFlags) { + // Warning: this is not sufficient: the flags have to be copied to the states that are inheriting from this state $sTargetClass = self::GetCallersPHPClass("Init"); self::$m_aStates[$sTargetClass][$sStateCode]['attribute_list'][$sAttCode] = $iFlags; } @@ -2535,6 +2536,12 @@ abstract class MetaModel return $aDataDump; } + // Temporary - investigate the cost of such a limitation + public static function DBIsReadOnly() + { + return self::$m_oConfig->Get('read_only'); + } + protected static function MakeDictEntry($sKey, $sValueFromOldSystem, $sDefaultValue, &$bNotInDico) { $sValue = Dict::S($sKey, 'x-no-nothing'); @@ -3505,14 +3512,20 @@ abstract class MetaModel public static function BulkDelete(DBObjectSearch $oFilter) { $sSQL = self::MakeDeleteQuery($oFilter); - CMDBSource::Query($sSQL); + if (!self::DBIsReadOnly()) + { + CMDBSource::Query($sSQL); + } } public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) { // $aValues is an array of $sAttCode => $value $sSQL = self::MakeUpdateQuery($oFilter, $aValues); - CMDBSource::Query($sSQL); + if (!self::DBIsReadOnly()) + { + CMDBSource::Query($sSQL); + } } // Links diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index 016a3c9b67..cfa9817074 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -372,6 +372,11 @@ class UserRights public static function CanChangePassword() { + if (MetaModel::DBIsReadOnly()) + { + return false; + } + if (!is_null(self::$m_oUser)) { return self::$m_oUser->CanChangePassword(); @@ -554,6 +559,14 @@ class UserRights if (self::IsAdministrator($oUser)) return true; + if (MetaModel::DBIsReadOnly()) + { + if ($iActionCode == UR_ACTION_MODIFY) return false; + if ($iActionCode == UR_ACTION_DELETE) return false; + if ($iActionCode == UR_ACTION_BULK_MODIFY) return false; + if ($iActionCode == UR_ACTION_BULK_DELETE) return false; + } + if (MetaModel::HasCategory($sClass, 'bizmodel')) { // #@# Temporary????? @@ -584,6 +597,14 @@ class UserRights if (self::IsAdministrator($oUser)) return true; + if (MetaModel::DBIsReadOnly()) + { + if ($iActionCode == UR_ACTION_MODIFY) return false; + if ($iActionCode == UR_ACTION_DELETE) return false; + if ($iActionCode == UR_ACTION_BULK_MODIFY) return false; + if ($iActionCode == UR_ACTION_BULK_DELETE) return false; + } + if (MetaModel::HasCategory($sClass, 'bizmodel')) { if (is_null($oUser)) @@ -606,6 +627,14 @@ class UserRights if (self::IsAdministrator($oUser)) return true; + if (MetaModel::DBIsReadOnly()) + { + if ($iActionCode == UR_ACTION_MODIFY) return false; + if ($iActionCode == UR_ACTION_DELETE) return false; + if ($iActionCode == UR_ACTION_BULK_MODIFY) return false; + if ($iActionCode == UR_ACTION_BULK_DELETE) return false; + } + // this module is forbidden for non admins if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false; From d8bb6a45b22a79ee4a488a140acf1edc15b19716 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 22 Nov 2010 17:53:52 +0000 Subject: [PATCH 877/970] REVIEWED THE FILE INCLUSION POLICY - includes are relative to the application root folder, aka APPROOT - changed the config file, while preserving the compatibility with older installs SVN:trunk[962] --- application/ajaxwebpage.class.inc.php | 3 +- application/application.inc.php | 14 ++-- application/applicationcontext.class.inc.php | 2 +- application/audit.category.class.inc.php | 2 +- application/audit.rule.class.inc.php | 2 +- application/clipage.class.inc.php | 2 +- application/cmdbabstract.class.inc.php | 14 ++-- application/csvpage.class.inc.php | 2 +- application/displayblock.class.inc.php | 6 +- application/iotask.class.inc.php | 2 +- application/itopwebpage.class.inc.php | 6 +- application/loginwebpage.class.inc.php | 4 +- application/menunode.class.inc.php | 4 +- application/nicewebpage.class.inc.php | 2 +- application/portalwebpage.class.inc.php | 6 +- application/startup.inc.php | 4 +- application/template.class.inc.php | 6 +- .../ui.autocompletewidget.class.inc.php | 4 +- application/ui.linkswidget.class.inc.php | 4 +- application/ui.passwordwidget.class.inc.php | 4 +- application/user.preferences.class.inc.php | 4 +- application/utils.inc.php | 6 +- application/wizardhelper.class.inc.php | 2 +- application/xmlpage.class.inc.php | 2 +- approot.inc.php | 3 + core/action.class.inc.php | 2 +- core/config.class.inc.php | 57 ++++++---------- core/metamodel.class.php | 26 +++++-- core/ormpassword.class.inc.php | 2 +- .../model.itop-config-mgmt.php | 2 - pages/UI.php | 17 ++--- pages/UniversalSearch.php | 12 ++-- pages/ajax.csvimport.php | 17 ++--- pages/ajax.render.php | 21 +++--- pages/audit.php | 13 ++-- pages/csvimport.php | 13 ++-- pages/graphviz.php | 11 +-- pages/logoff.php | 11 +-- pages/navigator.php | 13 ++-- pages/run_query.php | 9 +-- pages/schema.php | 9 +-- pages/xml.navigator.php | 15 +++-- portal/index.php | 16 ++--- setup/ajax.dataloader.php | 17 ++--- setup/email.test.php | 5 +- setup/index.php | 67 ++++++++++--------- setup/setuppage.class.inc.php | 18 +++-- setup/xmldataloader.class.inc.php | 36 +++++----- test/benchmark.php | 15 +++-- test/test.class.inc.php | 35 +++++----- test/test.php | 5 +- test/testlist.inc.php | 41 +++++++++--- webservices/backoffice.dataloader.php | 19 +++--- webservices/check_sla_for_tickets.php | 5 +- webservices/createfrommail.php | 5 +- webservices/export.php | 13 ++-- webservices/import.php | 13 ++-- webservices/soapserver.php | 5 +- webservices/webservices.class.inc.php | 2 +- 59 files changed, 369 insertions(+), 308 deletions(-) create mode 100644 approot.inc.php diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 8b03027235..250eac496f 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -24,7 +24,8 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/webpage.class.inc.php"); +require_once('../approot.inc.php'); +require_once(APPROOT."/application/webpage.class.inc.php"); class ajax_page extends WebPage { diff --git a/application/application.inc.php b/application/application.inc.php index 6d63f8941f..88dcd7c0cd 100644 --- a/application/application.inc.php +++ b/application/application.inc.php @@ -23,13 +23,13 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/applicationcontext.class.inc.php'); -require_once('../application/cmdbabstract.class.inc.php'); -require_once('../application/displayblock.class.inc.php'); -require_once('../application/audit.category.class.inc.php'); -require_once('../application/audit.rule.class.inc.php'); -//require_once('../application/menunode.class.inc.php'); -require_once('../application/utils.inc.php'); +require_once(APPROOT.'/application/applicationcontext.class.inc.php'); +require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); +require_once(APPROOT.'/application/displayblock.class.inc.php'); +require_once(APPROOT.'/application/audit.category.class.inc.php'); +require_once(APPROOT.'/application/audit.rule.class.inc.php'); +//require_once(APPROOT.'/application/menunode.class.inc.php'); +require_once(APPROOT.'/application/utils.inc.php'); class ApplicationException extends CoreException { diff --git a/application/applicationcontext.class.inc.php b/application/applicationcontext.class.inc.php index cac2f2025a..04e43c1bb7 100644 --- a/application/applicationcontext.class.inc.php +++ b/application/applicationcontext.class.inc.php @@ -23,7 +23,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/utils.inc.php"); +require_once(APPROOT."/application/utils.inc.php"); /** * Helper class to store and manipulate the parameters that make the application's context * diff --git a/application/audit.category.class.inc.php b/application/audit.category.class.inc.php index 3da91713da..c07b2e882a 100644 --- a/application/audit.category.class.inc.php +++ b/application/audit.category.class.inc.php @@ -25,7 +25,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/cmdbabstract.class.inc.php'); +require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); class AuditCategory extends cmdbAbstractObject { diff --git a/application/audit.rule.class.inc.php b/application/audit.rule.class.inc.php index 360d23bb5c..01088f6171 100644 --- a/application/audit.rule.class.inc.php +++ b/application/audit.rule.class.inc.php @@ -26,7 +26,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/audit.category.class.inc.php'); +require_once(APPROOT.'/application/audit.category.class.inc.php'); class AuditRule extends cmdbAbstractObject { diff --git a/application/clipage.class.inc.php b/application/clipage.class.inc.php index afeaa16948..ca9ec5e468 100644 --- a/application/clipage.class.inc.php +++ b/application/clipage.class.inc.php @@ -24,7 +24,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/webpage.class.inc.php"); +require_once(APPROOT."/application/webpage.class.inc.php"); class CLIPage { diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 21676f21fc..9a7b9c2ef7 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -31,13 +31,13 @@ define('HILIGHT_CLASS_WARNING', 'orange'); define('HILIGHT_CLASS_OK', 'green'); define('HILIGHT_CLASS_NONE', ''); -require_once('../core/cmdbobject.class.inc.php'); -require_once('../application/utils.inc.php'); -require_once('../application/applicationcontext.class.inc.php'); -require_once('../application/ui.linkswidget.class.inc.php'); -require_once('../application/ui.passwordwidget.class.inc.php'); -require_once('../application/ui.autocompletewidget.class.inc.php'); -require_once('../application/ui.htmleditorwidget.class.inc.php'); +require_once(APPROOT.'/core/cmdbobject.class.inc.php'); +require_once(APPROOT.'/application/utils.inc.php'); +require_once(APPROOT.'/application/applicationcontext.class.inc.php'); +require_once(APPROOT.'/application/ui.linkswidget.class.inc.php'); +require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php'); +require_once(APPROOT.'/application/ui.autocompletewidget.class.inc.php'); +require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php'); abstract class cmdbAbstractObject extends CMDBObject { diff --git a/application/csvpage.class.inc.php b/application/csvpage.class.inc.php index 9ef3655ae3..7ccf83782b 100644 --- a/application/csvpage.class.inc.php +++ b/application/csvpage.class.inc.php @@ -24,7 +24,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/webpage.class.inc.php"); +require_once(APPROOT."/application/webpage.class.inc.php"); class CSVPage extends WebPage { diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 8daa78f482..991284f221 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -23,8 +23,8 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/webpage.class.inc.php'); -require_once('../application/utils.inc.php'); +require_once(APPROOT.'/application/webpage.class.inc.php'); +require_once(APPROOT.'/application/utils.inc.php'); /** * Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects * @@ -757,7 +757,7 @@ EOF break; case 'open_flash_chart_ajax': - include '../pages/php-ofc-library/open-flash-chart.php'; + require_once(APPROOT.'/pages/php-ofc-library/open-flash-chart.php'); $sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie'; $oChart = new open_flash_chart(); diff --git a/application/iotask.class.inc.php b/application/iotask.class.inc.php index a4d8f58e5d..21f002d83c 100644 --- a/application/iotask.class.inc.php +++ b/application/iotask.class.inc.php @@ -23,7 +23,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/cmdbabstract.class.inc.php'); +require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); /** * This class manages the input/output tasks diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index a3a3b5d912..924ab84345 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -23,9 +23,9 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/nicewebpage.class.inc.php"); -require_once("../application/applicationcontext.class.inc.php"); -require_once("../application/user.preferences.class.inc.php"); +require_once(APPROOT."/application/nicewebpage.class.inc.php"); +require_once(APPROOT."/application/applicationcontext.class.inc.php"); +require_once(APPROOT."/application/user.preferences.class.inc.php"); /** * Web page with some associated CSS and scripts (jquery) for a fancier display */ diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 0fb41efbf0..2dfda9eb81 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -23,7 +23,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/nicewebpage.class.inc.php"); +require_once(APPROOT."/application/nicewebpage.class.inc.php"); /** * Web page used for displaying the login form */ @@ -393,7 +393,7 @@ EOF if ($bMustBeAdmin && !UserRights::IsAdministrator()) { - require_once('../setup/setuppage.class.inc.php'); + require_once(APPROOT.'/setup/setuppage.class.inc.php'); $oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

      ".Dict::S('UI:Login:Error:AccessAdmin')."

      \n"); $oP->p("".Dict::S('UI:LogOffMenu').""); diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index 652a3b75d0..3cc59072a3 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -23,8 +23,8 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/utils.inc.php'); -require_once('../application/template.class.inc.php'); +require_once(APPROOT.'/application/utils.inc.php'); +require_once(APPROOT.'/application/template.class.inc.php'); /** diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index 6343279db6..fd9f9795a9 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -23,7 +23,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/webpage.class.inc.php"); +require_once(APPROOT."/application/webpage.class.inc.php"); /** * Web page with some associated CSS and scripts (jquery) for a fancier display */ diff --git a/application/portalwebpage.class.inc.php b/application/portalwebpage.class.inc.php index af270379dd..233d1fe78c 100644 --- a/application/portalwebpage.class.inc.php +++ b/application/portalwebpage.class.inc.php @@ -23,9 +23,9 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once("../application/nicewebpage.class.inc.php"); -require_once("../application/applicationcontext.class.inc.php"); -require_once("../application/user.preferences.class.inc.php"); +require_once(APPROOT."/application/nicewebpage.class.inc.php"); +require_once(APPROOT."/application/applicationcontext.class.inc.php"); +require_once(APPROOT."/application/user.preferences.class.inc.php"); /** * Web page with some associated CSS and scripts (jquery) for a fancier display * of the Portal web page diff --git a/application/startup.inc.php b/application/startup.inc.php index 812ff58285..9c71599b02 100644 --- a/application/startup.inc.php +++ b/application/startup.inc.php @@ -23,8 +23,8 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../core/cmdbobject.class.inc.php'); -require_once('../application/utils.inc.php'); +require_once(APPROOT.'/core/cmdbobject.class.inc.php'); +require_once(APPROOT.'/application/utils.inc.php'); MetaModel::Startup(ITOP_CONFIG_FILE); diff --git a/application/template.class.inc.php b/application/template.class.inc.php index dca6d7a295..b7caf07c96 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -23,7 +23,7 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../application/displayblock.class.inc.php'); +require_once(APPROOT.'/application/displayblock.class.inc.php'); /** * This class manages the special template format used internally to build the iTop web pages */ @@ -218,8 +218,8 @@ class DisplayTemplate */ static public function UnitTest() { - require_once('../application/startup.inc.php'); - require_once("../application/itopwebpage.class.inc.php"); + require_once(APPROOT.'/application/startup.inc.php'); + require_once(APPROOT."/application/itopwebpage.class.inc.php"); $sTemplate = '\n"); - if($iNbCols == 1) - { - $oPage->p(' '.Dict::S('UI:CSVImport:ErrorOnlyOneColumn')); - } - else - { - $oPage->p(' '); - } - } - break; + break; - case 'display_mapping_form': - $oPage = new ajax_page(""); - $oPage->no_cache(); - $sSeparator = utils::ReadParam('separator', ','); - $sTextQualifier = utils::ReadParam('qualifier', '"'); - $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0); - $bFirstLineAsHeader = utils::ReadParam('header_line', false); - $sData = stripslashes(utils::ReadParam('csvdata', '')); - $sClassName = utils::ReadParam('class_name', ''); - $bAdvanced = utils::ReadParam('advanced', false); - $sEncoding = utils::ReadParam('encoding', 'UTF-8'); - $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier); - $aData = $oCSVParser->ToArray($iLinesToSkip); - $iTarget = count($aData); - if ($iTarget == 0) - { - $oPage->p(Dict::S('UI:CSVImport:NoData')); - } - else - { - $oPage->add(""); - $aFirstLine = $aData[0]; // Use the first row to determine the number of columns - $iStartLine = 0; - $iNbColumns = count($aFirstLine); - if ($bFirstLineAsHeader) + case 'display_mapping_form': + $oPage = new ajax_page(""); + $oPage->no_cache(); + $sSeparator = utils::ReadParam('separator', ','); + $sTextQualifier = utils::ReadParam('qualifier', '"'); + $iLinesToSkip = utils::ReadParam('nb_lines_skipped', 0); + $bFirstLineAsHeader = utils::ReadParam('header_line', false); + $sData = stripslashes(utils::ReadParam('csvdata', '')); + $sClassName = utils::ReadParam('class_name', ''); + $bAdvanced = utils::ReadParam('advanced', false); + $sEncoding = utils::ReadParam('encoding', 'UTF-8'); + $oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier); + $aData = $oCSVParser->ToArray($iLinesToSkip); + $iTarget = count($aData); + if ($iTarget == 0) { - $iStartLine = 1; - foreach($aFirstLine as $sField) - { - $aHeader[] = $sField; - } + $oPage->p(Dict::S('UI:CSVImport:NoData')); } else { - // Build some conventional name for the fields: field1...fieldn - $index= 1; - foreach($aFirstLine as $sField) + $oPage->add("
      "); + $aFirstLine = $aData[0]; // Use the first row to determine the number of columns + $iStartLine = 0; + $iNbColumns = count($aFirstLine); + if ($bFirstLineAsHeader) { - $aHeader[] = Dict::Format('UI:CSVImport:FieldName', $index); + $iStartLine = 1; + foreach($aFirstLine as $sField) + { + $aHeader[] = $sField; + } + } + else + { + // Build some conventional name for the fields: field1...fieldn + $index= 1; + foreach($aFirstLine as $sField) + { + $aHeader[] = Dict::Format('UI:CSVImport:FieldName', $index); + $index++; + } + } + $oPage->add("
      \n"); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $index = 1; + foreach($aHeader as $sField) + { + $oPage->add(''); + $oPage->add(""); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); $index++; } - } - $oPage->add("
      '.Dict::S('UI:CSVImport:HeaderFields').''.Dict::S('UI:CSVImport:HeaderMappings').' '.Dict::S('UI:CSVImport:HeaderSearch').''.Dict::S('UI:CSVImport:DataLine1').''.Dict::S('UI:CSVImport:DataLine2').'
      $sField'.GetMappingForField($sClassName, $sField, $index, $bAdvanced).' '.(isset($aData[$iStartLine][$index-1]) ? htmlentities($aData[$iStartLine][$index-1], ENT_QUOTES, 'UTF-8') : ' ').''.(isset($aData[$iStartLine+1][$index-1]) ? htmlentities($aData[$iStartLine+1][$index-1], ENT_QUOTES, 'UTF-8') : ' ').'
      \n"); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $index = 1; - foreach($aHeader as $sField) - { - $oPage->add(''); - $oPage->add(""); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $oPage->add(''); - $index++; - } - $oPage->add("
      '.Dict::S('UI:CSVImport:HeaderFields').''.Dict::S('UI:CSVImport:HeaderMappings').' '.Dict::S('UI:CSVImport:HeaderSearch').''.Dict::S('UI:CSVImport:DataLine1').''.Dict::S('UI:CSVImport:DataLine2').'
      $sField'.GetMappingForField($sClassName, $sField, $index, $bAdvanced).' '.(isset($aData[$iStartLine][$index-1]) ? htmlentities($aData[$iStartLine][$index-1], ENT_QUOTES, 'UTF-8') : ' ').''.(isset($aData[$iStartLine+1][$index-1]) ? htmlentities($aData[$iStartLine+1][$index-1], ENT_QUOTES, 'UTF-8') : ' ').'
      \n"); - $aReconciliationKeys = MetaModel::GetReconcKeys($sClassName); - $aMoreReconciliationKeys = array(); // Store: key => void to automatically remove duplicates - foreach($aReconciliationKeys as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); - if ($oAttDef->IsExternalKey()) + $oPage->add("\n"); + $aReconciliationKeys = MetaModel::GetReconcKeys($sClassName); + $aMoreReconciliationKeys = array(); // Store: key => void to automatically remove duplicates + foreach($aReconciliationKeys as $sAttCode) { - // An external key is specified as a reconciliation key: this means that all the reconciliation - // keys of this class are proposed to identify the target object - $aMoreReconciliationKeys = array_merge($aMoreReconciliationKeys, GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced)); + $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + // An external key is specified as a reconciliation key: this means that all the reconciliation + // keys of this class are proposed to identify the target object + $aMoreReconciliationKeys = array_merge($aMoreReconciliationKeys, GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced)); + } + elseif($oAttDef->IsExternalField()) + { + // An external field is specified as a reconciliation key, translate the field into a field on the target class + // since external fields are not writable, and thus never appears in the mapping form + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $sTargetAttCode = $oAttDef->GetExtAttCode(); + $aMoreReconciliationKeys[$sKeyAttCode.'->'.$sTargetAttCode] = ''; + } } - elseif($oAttDef->IsExternalField()) - { - // An external field is specified as a reconciliation key, translate the field into a field on the target class - // since external fields are not writable, and thus never appears in the mapping form - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $sTargetAttCode = $oAttDef->GetExtAttCode(); - $aMoreReconciliationKeys[$sKeyAttCode.'->'.$sTargetAttCode] = ''; - } - } - $sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys, array_keys($aMoreReconciliationKeys))).'"'; - $oPage->add_ready_script( + $sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys, array_keys($aMoreReconciliationKeys))).'"'; + $oPage->add_ready_script( <<AddCondition('id', 0, '='); // Make sure we create an empty set - $oSet = new CMDBObjectSet($oSearch); - $sResult = cmdbAbstractObject::GetSetAsCSV($oSet, array('showMandatoryFields' => true)); - //$aCSV = explode("\n", $sCSV); - // If there are more than one line, let's assume that the first line is a comment and skip it. - //if (count($aCSV) > 1) - //{ - // $sResult = $aCSV[0]; - //} - //else - //{ - // $sResult = $sCSV; - //} + } + break; - $sClassDisplayName = MetaModel::GetName($sClassName); - $sDisposition = utils::ReadParam('disposition', 'inline'); - if ($sDisposition == 'attachment') - { - $oPage = new CSVPage(""); - $oPage->add_header("Content-type: text/csv; charset=utf-8"); - $oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\""); - $oPage->no_cache(); - $oPage->add($sResult); + case 'get_csv_template': + $sClassName = utils::ReadParam('class_name'); + $oSearch = new DBObjectSearch($sClassName); + $oSearch->AddCondition('id', 0, '='); // Make sure we create an empty set + $oSet = new CMDBObjectSet($oSearch); + $sResult = cmdbAbstractObject::GetSetAsCSV($oSet, array('showMandatoryFields' => true)); + //$aCSV = explode("\n", $sCSV); + // If there are more than one line, let's assume that the first line is a comment and skip it. + //if (count($aCSV) > 1) + //{ + // $sResult = $aCSV[0]; + //} + //else + //{ + // $sResult = $sCSV; + //} + + $sClassDisplayName = MetaModel::GetName($sClassName); + $sDisposition = utils::ReadParam('disposition', 'inline'); + if ($sDisposition == 'attachment') + { + $oPage = new CSVPage(""); + $oPage->add_header("Content-type: text/csv; charset=utf-8"); + $oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\""); + $oPage->no_cache(); + $oPage->add($sResult); + } + else + { + $oPage = new ajax_page(""); + $oPage->no_cache(); + $oPage->add('


      '.$sClassDisplayName.'.csv

      '); + $oPage->add('

      '); + } + break; } - else - { - $oPage = new ajax_page(""); - $oPage->no_cache(); - $oPage->add('


      '.$sClassDisplayName.'.csv

      '); - $oPage->add('

      '); - } - break; + $oPage->output(); } -$oPage->output(); +catch (Exception $e) +{ + IssueLog::Error($e->getMessage()); +} + ?> diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 83b38fcdf0..4be97c77eb 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -31,367 +31,377 @@ require_once(APPROOT.'/application/wizardhelper.class.inc.php'); require_once(APPROOT.'/application/ui.linkswidget.class.inc.php'); require_once(APPROOT.'/application/ui.autocompletewidget.class.inc.php'); -require_once(APPROOT.'/application/startup.inc.php'); -require_once(APPROOT.'/application/user.preferences.class.inc.php'); - -require_once(APPROOT.'/application/loginwebpage.class.inc.php'); -LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed - -$oPage = new ajax_page(""); -$oPage->no_cache(); - -$operation = utils::ReadParam('operation', ''); -$sFilter = stripslashes(utils::ReadParam('filter', '')); -$sEncoding = utils::ReadParam('encoding', 'serialize'); -$sClass = utils::ReadParam('class', 'MissingAjaxParam'); -$sStyle = utils::ReadParam('style', 'list'); - -switch($operation) +try { - case 'addObjects': - require_once(APPROOT.'/application/uilinkswizard.class.inc.php'); - $sClass = utils::ReadParam('class', '', 'get'); - $sLinkedClass = utils::ReadParam('linkedClass', ''); - $sLinkageAttr = utils::ReadParam('linkageAttr', ''); - $iObjectId = utils::ReadParam('objectId', ''); - $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); - $oLinksWizard->DisplayAddForm($oPage); - break; + require_once(APPROOT.'/application/startup.inc.php'); + require_once(APPROOT.'/application/user.preferences.class.inc.php'); - // ui.linkswidget - case 'searchObjectsToAdd': - $sRemoteClass = utils::ReadParam('sRemoteClass', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; - $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); - $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); - $oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked); - break; + require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed - // ui.autocompletewidget - case 'searchObjectsToSelect': - $sTargetClass = utils::ReadParam('sRemoteClass', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $sJson = utils::ReadParam('json', ''); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj)); - $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, $oObj->Get($sAttCode), $iInputId, $sSuffix, ''); - $oWidget->SearchObjectsToSelect($oPage, $sTargetClass); - break; + $oPage = new ajax_page(""); + $oPage->no_cache(); + + $operation = utils::ReadParam('operation', ''); + $sFilter = stripslashes(utils::ReadParam('filter', '')); + $sEncoding = utils::ReadParam('encoding', 'serialize'); + $sClass = utils::ReadParam('class', 'MissingAjaxParam'); + $sStyle = utils::ReadParam('style', 'list'); - // ui.autocompletewidget - case 'objectCreationForm': - $sTargetClass = utils::ReadParam('sRemoteClass', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $sJson = utils::ReadParam('json', ''); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj)); - $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, $oObj->Get($sAttCode), $iInputId, $sSuffix, ''); - $oWidget->GetObjectCreationForm($oPage); - break; - - // ui.autocompletewidget - case 'doCreateObject': - $sTargetClass = utils::ReadParam('sRemoteClass', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $sJson = utils::ReadParam('json', ''); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj)); - // The iInputId of the autocomplete is the prefix for the form used to create the target object - $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, null, $iInputId, $sSuffix, $oWizardHelper->GetFormPrefix()); - $aResult = $oWidget->DoCreateObject($oPage); - echo json_encode($aResult); - break; - - // ui.autocompletewidget - case 'getObjectName': - $sTargetClass = utils::ReadParam('sTargetClass', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $iObjectId = utils::ReadParam('iObjectId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', array(), '', $iInputId, $sSuffix, ''); - $sName = $oWidget->GetObjectName($iObjectId); - echo json_encode(array('name' => $sName)); - break; - - // ui.linkswidget - case 'doAddObjects': - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; - $aLinkedObjectIds = utils::ReadParam('selectObject', array()); - $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); - $oWidget->DoAddObjects($oPage, $aLinkedObjectIds); - break; + switch($operation) + { + case 'addObjects': + require_once(APPROOT.'/application/uilinkswizard.class.inc.php'); + $sClass = utils::ReadParam('class', '', 'get'); + $sLinkedClass = utils::ReadParam('linkedClass', ''); + $sLinkageAttr = utils::ReadParam('linkageAttr', ''); + $iObjectId = utils::ReadParam('objectId', ''); + $oLinksWizard = new UILinksWizard($sClass, $sLinkageAttr, $iObjectId, $sLinkedClass); + $oLinksWizard->DisplayAddForm($oPage); + break; - case 'wizard_helper_preview': - $sJson = utils::ReadParam('json_obj', ''); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $oObj->DisplayBareProperties($oPage); - break; + // ui.linkswidget + case 'searchObjectsToAdd': + $sRemoteClass = utils::ReadParam('sRemoteClass', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); + $oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked); + break; + + // ui.autocompletewidget + case 'searchObjectsToSelect': + $sTargetClass = utils::ReadParam('sRemoteClass', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $sJson = utils::ReadParam('json', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj)); + $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, $oObj->Get($sAttCode), $iInputId, $sSuffix, ''); + $oWidget->SearchObjectsToSelect($oPage, $sTargetClass); + break; - case 'wizard_helper': - $sJson = utils::ReadParam('json_obj', ''); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $sClass = $oWizardHelper->GetTargetClass(); - foreach($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $defaultValue = $oAttDef->GetDefaultValue(); - $oWizardHelper->SetDefaultValue($sAttCode, $defaultValue); - $oObj->Set($sAttCode, $defaultValue); - } - $sFormPrefix = $oWizardHelper->GetFormPrefix(); - foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) - { - $sId = $oWizardHelper->GetIdForField($sAttCode); - if ($sId != '') + // ui.autocompletewidget + case 'objectCreationForm': + $sTargetClass = utils::ReadParam('sRemoteClass', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $sJson = utils::ReadParam('json', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj)); + $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, $oObj->Get($sAttCode), $iInputId, $sSuffix, ''); + $oWidget->GetObjectCreationForm($oPage); + break; + + // ui.autocompletewidget + case 'doCreateObject': + $sTargetClass = utils::ReadParam('sRemoteClass', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $sJson = utils::ReadParam('json', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array('this' => $oObj)); + // The iInputId of the autocomplete is the prefix for the form used to create the target object + $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', $aAllowedValues, null, $iInputId, $sSuffix, $oWizardHelper->GetFormPrefix()); + $aResult = $oWidget->DoCreateObject($oPage); + echo json_encode($aResult); + break; + + // ui.autocompletewidget + case 'getObjectName': + $sTargetClass = utils::ReadParam('sTargetClass', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $iObjectId = utils::ReadParam('iObjectId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $oWidget = new UIAutocompleteWidget($sAttCode, $sClass, '', array(), '', $iInputId, $sSuffix, ''); + $sName = $oWidget->GetObjectName($iObjectId); + echo json_encode(array('name' => $sName)); + break; + + // ui.linkswidget + case 'doAddObjects': + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; + $aLinkedObjectIds = utils::ReadParam('selectObject', array()); + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); + $oWidget->DoAddObjects($oPage, $aLinkedObjectIds); + break; + + case 'wizard_helper_preview': + $sJson = utils::ReadParam('json_obj', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $oObj->DisplayBareProperties($oPage); + break; + + case 'wizard_helper': + $sJson = utils::ReadParam('json_obj', ''); + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $sClass = $oWizardHelper->GetTargetClass(); + foreach($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode) { - // It may happen that the field we'd like to update does not - // exist in the form. For example, if the field should be hidden/read-only - // in the current state of the object - $value = $oObj->Get($sAttCode); - $displayValue = $oObj->GetEditValue($sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $iFlags = MetaModel::GetAttributeFlags($sClass, $oObj->GetState(), $sAttCode); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix)); - // Make sure that we immediatly validate the field when we reload it - $oPage->add_ready_script("$('#$sId').trigger('validate');"); - $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); + $defaultValue = $oAttDef->GetDefaultValue(); + $oWizardHelper->SetDefaultValue($sAttCode, $defaultValue); + $oObj->Set($sAttCode, $defaultValue); } - } - $oPage->add_script("oWizardHelper{$sFormPrefix}.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper{$sFormPrefix}.UpdateFields();\n"); - break; - - case 'ajax': - if ($sFilter != "") - { - $sExtraParams = stripslashes(utils::ReadParam('extra_params', '')); - $aExtraParams = array(); - if (!empty($sExtraParams)) + $sFormPrefix = $oWizardHelper->GetFormPrefix(); + foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) { - $aExtraParams = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); + $sId = $oWizardHelper->GetIdForField($sAttCode); + if ($sId != '') + { + // It may happen that the field we'd like to update does not + // exist in the form. For example, if the field should be hidden/read-only + // in the current state of the object + $value = $oObj->Get($sAttCode); + $displayValue = $oObj->GetEditValue($sAttCode); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $iFlags = MetaModel::GetAttributeFlags($sClass, $oObj->GetState(), $sAttCode); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix)); + // Make sure that we immediatly validate the field when we reload it + $oPage->add_ready_script("$('#$sId').trigger('validate');"); + $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); + } } - if ($sEncoding == 'oql') + $oPage->add_script("oWizardHelper{$sFormPrefix}.m_oData=".$oWizardHelper->ToJSON().";\noWizardHelper{$sFormPrefix}.UpdateFields();\n"); + break; + + case 'ajax': + if ($sFilter != "") { - $oFilter = CMDBSearchFilter::FromOQL($sFilter); + $sExtraParams = stripslashes(utils::ReadParam('extra_params', '')); + $aExtraParams = array(); + if (!empty($sExtraParams)) + { + $aExtraParams = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); + } + if ($sEncoding == 'oql') + { + $oFilter = CMDBSearchFilter::FromOQL($sFilter); + } + else + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + } + $oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false); + $oDisplayBlock->RenderContent($oPage, $aExtraParams); } else { - $oFilter = CMDBSearchFilter::unserialize($sFilter); + $oPage->p("Invalid query (empty filter)."); } - $oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false); - $oDisplayBlock->RenderContent($oPage, $aExtraParams); - } - else - { - $oPage->p("Invalid query (empty filter)."); - } - break; - - case 'details': - $key = utils::ReadParam('id', 0); - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', $key, '='); - $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); - $oDisplayBlock->RenderContent($oPage); - break; - - case 'preview': - $key = utils::ReadParam('id', 0); - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', $key, '='); - $oDisplayBlock = new DisplayBlock($oFilter, 'preview', false); - $oDisplayBlock->RenderContent($oPage); - break; - - case 'pie_chart': - $sGroupBy = utils::ReadParam('group_by', ''); - if ($sFilter != '') - { - if ($sEncoding == 'oql') + break; + + case 'details': + $key = utils::ReadParam('id', 0); + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', $key, '='); + $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); + $oDisplayBlock->RenderContent($oPage); + break; + + case 'preview': + $key = utils::ReadParam('id', 0); + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', $key, '='); + $oDisplayBlock = new DisplayBlock($oFilter, 'preview', false); + $oDisplayBlock->RenderContent($oPage); + break; + + case 'pie_chart': + $sGroupBy = utils::ReadParam('group_by', ''); + if ($sFilter != '') { - $oFilter = CMDBSearchFilter::FromOQL($sFilter); + if ($sEncoding == 'oql') + { + $oFilter = CMDBSearchFilter::FromOQL($sFilter); + } + else + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + } + $oDisplayBlock = new DisplayBlock($oFilter, 'pie_chart_ajax', false); + $oDisplayBlock->RenderContent($oPage, array('group_by' => $sGroupBy)); } else { - $oFilter = CMDBSearchFilter::unserialize($sFilter); - } - $oDisplayBlock = new DisplayBlock($oFilter, 'pie_chart_ajax', false); - $oDisplayBlock->RenderContent($oPage, array('group_by' => $sGroupBy)); - } - else - { - - $oPage->add("\n3d pie\n."); - } - break; - - case 'open_flash_chart': - $aParams = utils::ReadParam('params', array()); - if ($sFilter != '') - { - $oFilter = CMDBSearchFilter::unserialize($sFilter); - $oDisplayBlock = new DisplayBlock($oFilter, 'open_flash_chart_ajax', false); - $oDisplayBlock->RenderContent($oPage, $aParams); - } - else - { - - $oPage->add("\n3d pie\n."); - } - break; - - case 'modal_details': - $key = utils::ReadParam('id', 0); - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', $key, '='); - $oPage->Add("

      Object Details

      \n"); - $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); - $oDisplayBlock->RenderContent($oPage); - $oPage->Add("\n"); - break; - case 'ui.linkswidget': - /* - $sClass = utils::ReadParam('sclass', 'bizContact'); - $sAttCode = utils::ReadParam('attCode', 'name'); - $sOrg = utils::ReadParam('org_id', ''); - $sName = utils::ReadParam('q', ''); - $iMaxCount = utils::ReadParam('max', 30); - UILinksWidget::Autocomplete($oPage, $sClass, $sAttCode, $sName, $iMaxCount); - */ - break; - - case 'ui.linkswidget.linkedset': - /* - $sClass = utils::ReadParam('sclass', 'bizContact'); - $sJSONSet = stripslashes(utils::ReadParam('sset', '')); - $sExtKeyToMe = utils::ReadParam('sextkeytome', ''); - $sExtKeyToRemote = utils::ReadParam('sextkeytoremote', ''); - $iObjectId = utils::ReadParam('id', -1); - UILinksWidget::RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe, $sExtKeyToRemote, $iObjectId); - $iFieldId = utils::ReadParam('myid', '-1'); - $oPage->add_ready_script("$('#{$iFieldId}').trigger('validate');"); - */ - break; - - case 'autocomplete': - $key = utils::ReadParam('id', 0); - $sClass = utils::ReadParam('sclass', 'bizContact'); - $sAttCode = utils::ReadParam('attCode', 'name'); - $sOrg = utils::ReadParam('org_id', ''); - $sName = utils::ReadParam('q', ''); - $iMaxCount = utils::ReadParam('max', 30); - $aArgs = array(); - if (!empty($key)) - { - if ($oThis = MetaModel::GetObject($sClass, $key)) - { - $aArgs['*this*'] = $oThis; - $aArgs['this'] = $oThis; + $oPage->add("\n3d pie\n."); } - } - $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs, $sName); - $iCount = 0; - foreach($aAllowedValues as $key => $value) - { - $oPage->add($value."|".$key."\n"); - } - break; + break; + + case 'open_flash_chart': + $aParams = utils::ReadParam('params', array()); + if ($sFilter != '') + { + $oFilter = CMDBSearchFilter::unserialize($sFilter); + $oDisplayBlock = new DisplayBlock($oFilter, 'open_flash_chart_ajax', false); + $oDisplayBlock->RenderContent($oPage, $aParams); + } + else + { + + $oPage->add("\n3d pie\n."); + } + break; - case 'link': - $sClass = utils::ReadParam('sclass', 'logInfra'); - $sAttCode = utils::ReadParam('attCode', 'name'); - //$sOrg = utils::ReadParam('org_id', ''); - $sName = utils::ReadParam('q', ''); - $iMaxCount = utils::ReadParam('max', 30); - $iCount = 0; - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition($sAttCode, $sName, 'Begins with'); - //$oFilter->AddCondition('org_id', $sOrg, '='); - $oSet = new CMDBObjectSet($oFilter, array($sAttCode => true)); - while( ($iCount < $iMaxCount) && ($oObj = $oSet->fetch()) ) - { - $oPage->add($oObj->GetAsHTML($sAttCode)."|".$oObj->GetKey()."\n"); - $iCount++; - } - break; + case 'modal_details': + $key = utils::ReadParam('id', 0); + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', $key, '='); + $oPage->Add("

      Object Details

      \n"); + $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); + $oDisplayBlock->RenderContent($oPage); + $oPage->Add("\n"); + break; + + case 'ui.linkswidget': + /* + $sClass = utils::ReadParam('sclass', 'bizContact'); + $sAttCode = utils::ReadParam('attCode', 'name'); + $sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + UILinksWidget::Autocomplete($oPage, $sClass, $sAttCode, $sName, $iMaxCount); + */ + break; + + case 'ui.linkswidget.linkedset': + /* + $sClass = utils::ReadParam('sclass', 'bizContact'); + $sJSONSet = stripslashes(utils::ReadParam('sset', '')); + $sExtKeyToMe = utils::ReadParam('sextkeytome', ''); + $sExtKeyToRemote = utils::ReadParam('sextkeytoremote', ''); + $iObjectId = utils::ReadParam('id', -1); + UILinksWidget::RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe, $sExtKeyToRemote, $iObjectId); + $iFieldId = utils::ReadParam('myid', '-1'); + $oPage->add_ready_script("$('#{$iFieldId}').trigger('validate');"); + */ + break; + + case 'autocomplete': + $key = utils::ReadParam('id', 0); + $sClass = utils::ReadParam('sclass', 'bizContact'); + $sAttCode = utils::ReadParam('attCode', 'name'); + $sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + $aArgs = array(); + if (!empty($key)) + { + if ($oThis = MetaModel::GetObject($sClass, $key)) + { + $aArgs['*this*'] = $oThis; + $aArgs['this'] = $oThis; + } + } + $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs, $sName); + $iCount = 0; + foreach($aAllowedValues as $key => $value) + { + $oPage->add($value."|".$key."\n"); + } + break; + + case 'link': + $sClass = utils::ReadParam('sclass', 'logInfra'); + $sAttCode = utils::ReadParam('attCode', 'name'); + //$sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + $iCount = 0; + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition($sAttCode, $sName, 'Begins with'); + //$oFilter->AddCondition('org_id', $sOrg, '='); + $oSet = new CMDBObjectSet($oFilter, array($sAttCode => true)); + while( ($iCount < $iMaxCount) && ($oObj = $oSet->fetch()) ) + { + $oPage->add($oObj->GetAsHTML($sAttCode)."|".$oObj->GetKey()."\n"); + $iCount++; + } + break; + + case 'create': + case 'create_menu': + $sClass = utils::ReadParam('class', ''); + $sFilter = utils::ReadParam('filter', ''); + menuNode::DisplayCreationForm($oPage, $sClass, $sFilter); + break; - case 'create': - case 'create_menu': - $sClass = utils::ReadParam('class', ''); - $sFilter = utils::ReadParam('filter', ''); - menuNode::DisplayCreationForm($oPage, $sClass, $sFilter); - break; + case 'combo_options': + $oFilter = CMDBSearchFilter::FromOQL($sFilter); + $oSet = new CMDBObjectSet($oFilter); + while( $oObj = $oSet->fetch()) + { + $oPage->add(''); + } + break; + + case 'display_document': + $id = utils::ReadParam('id', ''); + $sField = utils::ReadParam('field', ''); + if (!empty($sClass) && !empty($id) && !empty($sField)) + { + DownloadDocument($oPage, $sClass, $id, $sField, 'inline'); + } + break; + + case 'download_document': + $id = utils::ReadParam('id', ''); + $sField = utils::ReadParam('field', ''); + if (!empty($sClass) && !empty($id) && !empty($sField)) + { + DownloadDocument($oPage, $sClass, $id, $sField, 'attachement'); + } + break; + + case 'search_form': + $sClass = utils::ReadParam('className', ''); + $sRootClass = utils::ReadParam('baseClass', ''); + $currentId = utils::ReadParam('currentId', ''); + $oFilter = new DBObjectSearch($sClass); + $oSet = new CMDBObjectSet($oFilter); + $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass)); + $oPage->add($sHtml); + break; + + case 'set_pref': + $sCode = utils::ReadPostedParam('code', ''); + $sValue = utils::ReadPostedParam('value', ''); + appUserPreferences::SetPref($sCode, $sValue); + break; + + case 'erase_all_pref': + // Can be useful in case a user got some corrupted prefs... + appUserPreferences::ClearPreferences(); + break; + + default: + $oPage->p("Invalid query."); + } - case 'combo_options': - $oFilter = CMDBSearchFilter::FromOQL($sFilter); - $oSet = new CMDBObjectSet($oFilter); - while( $oObj = $oSet->fetch()) - { - $oPage->add(''); - } - break; - - case 'display_document': - $id = utils::ReadParam('id', ''); - $sField = utils::ReadParam('field', ''); - if (!empty($sClass) && !empty($id) && !empty($sField)) - { - DownloadDocument($oPage, $sClass, $id, $sField, 'inline'); - } - break; - - case 'download_document': - $id = utils::ReadParam('id', ''); - $sField = utils::ReadParam('field', ''); - if (!empty($sClass) && !empty($id) && !empty($sField)) - { - DownloadDocument($oPage, $sClass, $id, $sField, 'attachement'); - } - break; - - case 'search_form': - $sClass = utils::ReadParam('className', ''); - $sRootClass = utils::ReadParam('baseClass', ''); - $currentId = utils::ReadParam('currentId', ''); - $oFilter = new DBObjectSearch($sClass); - $oSet = new CMDBObjectSet($oFilter); - $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, 'baseClass' => $sRootClass)); - $oPage->add($sHtml); - break; - - case 'set_pref': - $sCode = utils::ReadPostedParam('code', ''); - $sValue = utils::ReadPostedParam('value', ''); - appUserPreferences::SetPref($sCode, $sValue); - break; - - case 'erase_all_pref': - // Can be useful in case a user got some corrupted prefs... - appUserPreferences::ClearPreferences(); - break; - - default: - $oPage->p("Invalid query."); + $oPage->output(); } -$oPage->output(); +catch (Exception $e) +{ + IssueLog::Error($e->getMessage()); +} + + /** * Downloads a document to the browser, either as 'inline' or 'attachment' From 7fbcdc407e7f7c389cc9d0d16bbf7c9b5e97e626 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 24 Nov 2010 16:28:13 +0000 Subject: [PATCH 886/970] Added class DBProperty SVN:trunk[971] --- core/cmdbobject.class.inc.php | 2 + core/dbproperty.class.inc.php | 159 ++++++++++++++++++++++++++++++++++ test/testlist.inc.php | 3 +- 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 core/dbproperty.class.inc.php diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index b501468bfc..6a583f9716 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -59,6 +59,8 @@ require_once('dbobject.class.php'); require_once('dbobjectsearch.class.php'); require_once('dbobjectset.class.php'); +require_once('dbproperty.class.inc.php'); + // db change tracking data model require_once('cmdbchange.class.inc.php'); require_once('cmdbchangeop.class.inc.php'); diff --git a/core/dbproperty.class.inc.php b/core/dbproperty.class.inc.php new file mode 100644 index 0000000000..994de667db --- /dev/null +++ b/core/dbproperty.class.inc.php @@ -0,0 +1,159 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +/** + * A database property + * + * @package iTopORM + */ +class DBProperty extends DBObject +{ + public static function Init() + { + $aParams = array + ( + "category" => "cloud", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_db_properties", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("value", array("allowed_values"=>null, "sql"=>"value", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + MetaModel::Init_AddAttribute(new AttributeDateTime("change_date", array("allowed_values"=>null, "sql"=>"change_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("change_comment", array("allowed_values"=>null, "sql"=>"change_comment", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + } + + /** + * Helper to check wether the table has been created into the DB + * (this table did not exist in 1.0.1 and older versions) + */ + public static function IsInstalled() + { + $sTable = MetaModel::DBGetTable(__CLASS__); + if (CMDBSource::IsTable($sTable)) + { + return true; + } + else + { + return false; + } + return false; + } + + public static function SetProperty($sName, $sValue, $sComment = '', $sDescription = null) + { + try + { + $oSearch = DBObjectSearch::FromOQL('SELECT DBProperty WHERE name = :name'); + $oSet = new DBObjectSet($oSearch, array(), array('name' => $sName)); + if ($oSet->Count() == 0) + { + $oProp = new DBProperty(); + $oProp->Set('name', $sName); + $oProp->Set('description', $sDescription); + $oProp->Set('value', $sValue); + $oProp->Set('change_date', time()); + $oProp->Set('change_comment', $sComment); + $oProp->DBInsert(); + } + elseif ($oSet->Count() == 1) + { + $oProp = $oSet->fetch(); + if (!is_null($sDescription)) + { + $oProp->Set('description', $sDescription); + } + $oProp->Set('value', $sValue); + $oProp->Set('change_date', time()); + $oProp->Set('change_comment', $sComment); + $oProp->DBUpdate(); + } + else + { + // Houston... + throw new CoreException('duplicate db property'); + } + } + catch (MySQLException $e) + { + // This might be because the table could not be found, + // let's check it and discard silently if this is really the case + if (self::IsInstalled()) + { + throw $e; + } + IssueLog::Error('Attempting to write a DBProperty while the module has not been installed'); + } + } + + public static function GetProperty($sName, $default = null) + { + try + { + $oSearch = DBObjectSearch::FromOQL('SELECT DBProperty WHERE name = :name'); + $oSet = new DBObjectSet($oSearch, array(), array('name' => $sName)); + $iCount = $oSet->Count(); + if ($iCount == 0) + { + //throw new CoreException('unknown db property', array('name' => $sName)); + $sValue = $default; + } + elseif ($iCount == 1) + { + $oProp = $oSet->fetch(); + $sValue = $oProp->Get('value'); + } + else + { + // $iCount > 1 + // Houston... + throw new CoreException('duplicate db property', array('name' => $sName, 'count' => $iCount)); + } + } + catch (MySQLException $e) + { + // This might be because the table could not be found, + // let's check it and discard silently if this is really the case + if (self::IsInstalled()) + { + throw $e; + } + $sValue = $default; + } + return $sValue; + } +} + +?> diff --git a/test/testlist.inc.php b/test/testlist.inc.php index a15ac59926..6a5a08f091 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -1904,6 +1904,7 @@ $aManageCloudUsersSpecs = array( 'args' => array( 'admin', /* sAdminLogin */ 'admin', /* sAdminPassword */ + 'http://myserver.mydomain.fr:8080', /* sCloudMgrUrl */ 'andros@combodo.com', /* sLogin */ 'André', /* sFirstName */ 'Dupont', /* sLastName */ @@ -2311,7 +2312,7 @@ class TestDBProperties extends TestBizModel { $sName = 'test'; DBProperty::SetProperty($sName, 'unix time:'.time(), 'means nothing', 'done with the automated test utility'); - $sValue = DBProperty::GetProperty($sName); + $sValue = DBProperty::GetProperty($sName, 'defaults to this because the table has not been created (1.0.1 install?)'); echo "

      Write... then read property $sName, found: '$sValue'

      \n"; } } From c63d25e223da74a79d370be7cf3f4afd4f3f429c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 24 Nov 2010 16:52:55 +0000 Subject: [PATCH 887/970] Read-only mode relying successively on a DB property, and an application setting SVN:trunk[972] --- core/config.class.inc.php | 2 +- core/metamodel.class.php | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index c1205d9645..bdefc69405 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -135,7 +135,7 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), - 'read_only' => array( + 'database_read_only' => array( 'type' => 'bool', 'description' => 'Freeze the data for administration purposes - administrators can still do anything... in appearance!', 'default' => false, diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 26c8f60893..a91f4605e6 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2536,10 +2536,30 @@ abstract class MetaModel return $aDataDump; } - // Temporary - investigate the cost of such a limitation + /* + * Determines wether the target DB is frozen or not + * 1 - consider the DB property 'status' + * 2 - check the setting 'database_read_only' + */ public static function DBIsReadOnly() { - return self::$m_oConfig->Get('read_only'); + $sStatus = DBProperty::GetProperty('status', null); + if (!is_null($sStatus)) + { + switch (strtolower(trim($sStatus))) + { + case 'fullaccess': + $ret = false; + break; + default: + $ret = true; + } + } + else + { + $ret = self::$m_oConfig->Get('database_read_only'); + } + return $ret; } protected static function MakeDictEntry($sKey, $sValueFromOldSystem, $sDefaultValue, &$bNotInDico) From a60709a354593eb30ac8812482df9d5ef3a34ce2 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 25 Nov 2010 09:44:42 +0000 Subject: [PATCH 888/970] #331 Import.php could not be run in HTTP mode (when PHP running in CGI mode) SVN:trunk[973] --- application/utils.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 23eb1085c8..2778b66bc5 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -45,8 +45,8 @@ class utils public static function IsModeCLI() { - global $argv; - if (isset($argv)) + $sSAPIName = php_sapi_name(); + if ($sSAPIName == 'cli') { return true; } From ebf9910579802502c5e5c36eec956889c3972a8c Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 25 Nov 2010 09:52:22 +0000 Subject: [PATCH 889/970] #331 Import.php could not be run in HTTP mode (when PHP running in CGI mode) - fix getting more bullet proof SVN:trunk[974] --- application/utils.inc.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/utils.inc.php b/application/utils.inc.php index 2778b66bc5..41b47438ea 100644 --- a/application/utils.inc.php +++ b/application/utils.inc.php @@ -46,7 +46,8 @@ class utils public static function IsModeCLI() { $sSAPIName = php_sapi_name(); - if ($sSAPIName == 'cli') + $sCleanName = strtolower(trim($sSAPIName)); + if ($sCleanName == 'cli') { return true; } From 64044e47e14a0c0f8186a8351d808a89f5b70dab Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 25 Nov 2010 17:15:12 +0000 Subject: [PATCH 890/970] Modularity: allow a module to execute some specific installation procedures (customize the config file, do something in the database) SVN:trunk[975] --- setup/index.php | 32 +++++++++++++++++++++--- setup/moduleinstaller.class.inc.php | 38 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 setup/moduleinstaller.class.inc.php diff --git a/setup/index.php b/setup/index.php index 152dcc05c8..a2a2b11bf9 100644 --- a/setup/index.php +++ b/setup/index.php @@ -30,6 +30,7 @@ require_once(APPROOT.'/core/log.class.inc.php'); require_once(APPROOT.'/core/kpi.class.inc.php'); require_once(APPROOT.'/core/cmdbsource.class.inc.php'); require_once(APPROOT.'/setup/setuppage.class.inc.php'); +require_once(APPROOT.'/setup/moduleinstaller.class.inc.php'); define('TMP_CONFIG_FILE', APPROOT.'/tmp-config-itop.php'); define('FINAL_CONFIG_FILE', APPROOT.'/config-itop.php'); @@ -661,9 +662,8 @@ function GetAvailableModules(SetupWebpage $oP) /** * Build the config file from the parameters (especially the selected modules) */ -function BuildConfig(SetupWebpage $oP, Config &$oConfig, $aParamValues) +function BuildConfig(SetupWebpage $oP, Config &$oConfig, $aParamValues, $aAvailableModules) { - $aAvailableModules = GetAvailableModules($oP); // Initialize the arrays below with default values for the application... $aAddOns = $oConfig->GetAddOns(); $aAppModules = $oConfig->GetAppModules(); @@ -695,6 +695,20 @@ function BuildConfig(SetupWebpage $oP, Config &$oConfig, $aParamValues) $oConfig->SetModuleSetting($sName, $sProperty, $value); } } + if (isset($aAvailableModules[$sModuleId]['installer'])) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + if (!class_exists($sModuleInstallerClass)) + { + throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aAvailableModules[$sModuleId]['label']); + } + if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) + { + throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aAvailableModules[$sModuleId]['label']); + } + $aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig'); + $oConfig = call_user_func_array($aCallSpec, array($oConfig)); + } } $oConfig->SetAddOns($aAddOns); $oConfig->SetAppModules($aAppModules); @@ -971,10 +985,22 @@ function AdminAccountDefinition(SetupWebPage $oP, $aParamValues, $iCurrentStep, $sDBPrefix = $aParamValues['db_prefix']; $oConfig->SetDBName($sDBName); $oConfig->SetDBSubname($sDBPrefix); - BuildConfig($oP, $oConfig, $aParamValues); // Load all the includes based on the modules selected + $aAvailableModules = GetAvailableModules($oP); + BuildConfig($oP, $oConfig, $aParamValues, $aAvailableModules); // Load all the includes based on the modules selected $oConfig->WriteToFile(TMP_CONFIG_FILE); if (CreateDatabaseStructure($oP, $oConfig, $sDBName, $sDBPrefix, $aParamValues['module'])) { + foreach($aParamValues['module'] as $sModuleId) + { + if (isset($aAvailableModules[$sModuleId]['installer'])) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + // The validity of the sModuleInstallerClass has been established in BuildConfig() + $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); + call_user_func_array($aCallSpec, array($oConfig)); + } + } + $sRedStar = "*"; $oP->add("

      Default language for the application:

      \n"); // Possible languages (depends on the dictionaries loaded in the config) diff --git a/setup/moduleinstaller.class.inc.php b/setup/moduleinstaller.class.inc.php new file mode 100644 index 0000000000..cf77814ad3 --- /dev/null +++ b/setup/moduleinstaller.class.inc.php @@ -0,0 +1,38 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +abstract class ModuleInstallerAPI +{ + public static function BeforeWritingConfig(Config $oConfiguration) + { + return $oConfiguration; + } + + public static function AfterDatabaseCreation(Config $oConfiguration) + { + } +} +?> From 51fa310664218801e95b59ffa7042cd8daea96fd Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Nov 2010 10:43:25 +0000 Subject: [PATCH 891/970] - Fixed Trac #332: improved usability of the CSV import wizard with IE8. SVN:trunk[976] --- pages/csvimport.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 4da0f60edd..73a5d3efcb 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -620,7 +620,7 @@ EOF $oPage->add('
      '); $oPage->add('
      '.Dict::S('UI:CSVImport:SelectClass').' '); $oPage->add(GetClassesSelect('class_name', $sClassName, 300, UR_ACTION_BULK_MODIFY)); - $oPage->add(' '.Dict::S('UI:CSVImport:AdvancedMode').'
      '); + $oPage->add(' '.Dict::S('UI:CSVImport:AdvancedMode').''); $oPage->add(''); $oPage->add('

      '.Dict::S('UI:CSVImport:SelectAClassFirst').'

      '); $oPage->add(''); @@ -896,21 +896,21 @@ EOF $oPage->add('
      '); $oPage->add(''); $oPage->add('

      '.Dict::S('UI:CSVImport:SeparatorCharacter').'

      '); - $oPage->add('

      '.Dict::S('UI:CSVImport:SeparatorComma+').'
      '); - $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorSemicolon+').'
      '); - $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorTab+').'
      '); - $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorOther').' '); + $oPage->add('

      '.Dict::S('UI:CSVImport:SeparatorComma+').'
      '); + $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorSemicolon+').'
      '); + $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorTab+').'
      '); + $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorOther').' '); $oPage->add('

      '); $oPage->add('
      '); $oPage->add('

      '.Dict::S('UI:CSVImport:TextQualifierCharacter').'

      '); - $oPage->add('

      '.Dict::S('UI:CSVImport:QualifierDoubleQuote+').'
      '); - $oPage->add(' '.Dict::S('UI:CSVImport:QualifierSimpleQuote+').'
      '); - $oPage->add(' '.Dict::S('UI:CSVImport:QualifierOther').' '); + $oPage->add('

      '.Dict::S('UI:CSVImport:QualifierDoubleQuote+').'
      '); + $oPage->add(' '.Dict::S('UI:CSVImport:QualifierSimpleQuote+').'
      '); + $oPage->add(' '.Dict::S('UI:CSVImport:QualifierOther').' '); $oPage->add('

      '); $oPage->add('
      '); $oPage->add('

      '.Dict::S('UI:CSVImport:CommentsAndHeader').'

      '); - $oPage->add('

      '.Dict::S('UI:CSVImport:TreatFirstLineAsHeader').'

      '); - $oPage->add('

      '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '').'

      '); + $oPage->add('

      '.Dict::S('UI:CSVImport:TreatFirstLineAsHeader').'

      '); + $oPage->add('

      '.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '').'

      '); $oPage->add('

      '); $oPage->add(''); $oPage->add(''); @@ -1023,7 +1023,7 @@ EOF '

      '; $sFileLoadHtml .= '

      '.Dict::S('UI:CSVImport:Encoding').': '; - $sFileLoadHtml .= ''; // IE 8 has some troubles if the font is different foreach($aPossibleEncodings as $sIconvCode => $sDisplayName ) { $sSelected = ''; @@ -1048,7 +1048,7 @@ EOF $sPasteDataHtml = '

      '.Dict::S('UI:CSVImport:PasteData').'

      '. '

      '; $sPasteDataHtml .= '

      '.Dict::S('UI:CSVImport:Encoding').': '; - $sPasteDataHtml .= ''; // IE8 has some troubles if the font is different foreach($aPossibleEncodings as $sIconvCode => $sDisplayName ) { $sSelected = ''; From ced4bf4259f4766c7805dddde713b25dc79888f7 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Nov 2010 11:16:55 +0000 Subject: [PATCH 892/970] - Fixed Trac #333: organizations' drop-down list is truncated on IE when the name of an organization is too long. SVN:trunk[977] --- application/itopwebpage.class.inc.php | 4 ++-- css/light-grey.css | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 924ab84345..72a31ce78b 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -463,7 +463,7 @@ EOF $oAppContext = new ApplicationContext(); $iCurrentOrganization = $oAppContext->GetCurrentValue('org_id'); $sHtml = '

      '; - $sHtml .= ''; $sSelected = ($iCurrentOrganization == '') ? ' selected' : ''; $sHtml .= ''; while($oOrg = $oSet->Fetch()) @@ -478,7 +478,7 @@ EOF { $sSelected = ""; } - $sHtml .= ''; + $sHtml .= ''; } $sHtml .= ''; // Add other dimensions/context information to this form diff --git a/css/light-grey.css b/css/light-grey.css index e746c687ac..a8652373ad 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -889,4 +889,10 @@ tr.row_added td { } a.truncated { cursor: pointer; +} +.org_combo +{ + font-size:x-small; + width: auto; + max-width: 200px; } \ No newline at end of file From 9e70feb29dcd3345044ababeeea891c2c5ec67c5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Nov 2010 13:19:13 +0000 Subject: [PATCH 893/970] - Fixed bug #334: proper handling of the "remove objects" button (was working only for the first linkset in the object). - Improved feedback while searching and reloading added objects. SVN:trunk[978] --- application/cmdbabstract.class.inc.php | 2 +- application/ui.linkswidget.class.inc.php | 26 ++++++++++-------------- js/linkswidget.js | 6 ++++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 9a7b9c2ef7..7423f2cb1f 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -201,7 +201,7 @@ abstract class cmdbAbstractObject extends CMDBObject $sLinkedClass = $oAttDef->GetLinkedClass(); $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); $sTargetClass = $oLinkingAttDef->GetTargetClass(); - $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); + $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription().''); $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index 4cec000177..d9aec58a7e 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -37,7 +37,6 @@ class UILinksWidget protected $m_sLinkedClass; protected $m_sRemoteClass; protected $m_bDuplicatesAllowed; - protected static $iWidgetIndex = 0; public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false) { @@ -47,7 +46,6 @@ class UILinksWidget $this->m_iInputId = $iInputId; $this->m_bDuplicatesAllowed = $bDuplicatesAllowed; $this->m_aEditableFields = array(); - self::$iWidgetIndex++; $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode); $this->m_sLinkedClass = $oAttDef->GetLinkedClass(); @@ -60,7 +58,7 @@ class UILinksWidget $this->m_aEditableFields = array(); $this->m_aTableConfig = array(); - $this->m_aTableConfig['form::checkbox'] = array( 'label' => "m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".self::$iWidgetIndex.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+')); + $this->m_aTableConfig['form::checkbox'] = array( 'label' => "m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+')); foreach(MetaModel::ListAttributeDefs($this->m_sLinkedClass) as $sAttCode=>$oAttDef) { @@ -109,7 +107,7 @@ class UILinksWidget $sPrefix .= "[$key]["; $sNameSuffix = "]"; // To make a tabular form $aArgs['prefix'] = $sPrefix; - $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] = "m_iInputId.".OnSelectChange();\" value=\"$key\">"; $aRow['form::checkbox'] .= ""; foreach($this->m_aEditableFields as $sFieldCode) { @@ -123,7 +121,7 @@ class UILinksWidget $sPrefix .= "[$linkObjOrId]["; $sNameSuffix = "]"; // To make a tabular form $aArgs['prefix'] = $sPrefix; - $aRow['form::checkbox'] = ""; + $aRow['form::checkbox'] = "m_iInputId.".OnSelectChange();\" value=\"$linkObjOrId\">"; $aRow['form::checkbox'] .= ""; foreach($this->m_aEditableFields as $sFieldCode) { @@ -209,7 +207,6 @@ class UILinksWidget */ public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array()) { - $iWidgetIndex = self::$iWidgetIndex; $sHtmlValue = ''; $sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode); $sHtmlValue .= "
      m_sAttCode}{$this->m_sNameSuffix}\">\n"; @@ -226,12 +223,12 @@ class UILinksWidget $sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm); $sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false'; $oPage->add_ready_script(<<m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates); - oWidget$iWidgetIndex.Init(); + oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates); + oWidget{$this->m_iInputId}.Init(); EOF ); - $sHtmlValue .= "     m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget$iWidgetIndex.RemoveSelected();\" >"; - $sHtmlValue .= "   m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget$iWidgetIndex.AddObjects();\">\n"; + $sHtmlValue .= "     m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >"; + $sHtmlValue .= "   m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\">\n"; $sHtmlValue .= "

       

      \n"; $sHtmlValue .= "
      \n"; $oPage->add_at_the_end($this->GetObjectPickerDialog($oPage)); // To prevent adding forms inside the main form @@ -348,12 +345,11 @@ EOF { $sHtml = "
      m_sAttCode}{$this->m_sNameSuffix}\">"; $sHtml .= "
      \n"; - $iWidgetIndex = self::$iWidgetIndex; $oFilter = new DBObjectSearch($this->m_sRemoteClass); $oSet = new CMDBObjectSet($oFilter); $oBlock = new DisplayBlock($oFilter, 'search', false); $sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true)); - $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget$iWidgetIndex.DoAddObjects(this.id);\">\n"; + $sHtml .= "m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n"; $sHtml .= "
      m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n"; $sHtml .= "

      ".Dict::S('UI:Message:EmptyList:UseSearchForm')."

      \n"; $sHtml .= "
      \n"; @@ -361,10 +357,10 @@ EOF $sHtml .= "
      \n"; $sHtml .= "\n"; $sHtml .= "
      \n"; - $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget$iWidgetIndex.UpdateSizes });"); + $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });"); $oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."'});"); - $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget$iWidgetIndex.SearchObjectsToAdd);"); - $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget$iWidgetIndex.UpdateSizes);"); + $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->m_iInputId}.SearchObjectsToAdd);"); + $oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget{$this->m_iInputId}.UpdateSizes);"); return $sHtml; } diff --git a/js/linkswidget.js b/js/linkswidget.js index e137ab97b6..4102e0c4a0 100644 --- a/js/linkswidget.js +++ b/js/linkswidget.js @@ -93,6 +93,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates) theMap.operation = 'searchObjectsToAdd'; // Override what is defined in the form itself sSearchAreaId = '#SearchResultsToAdd_'+me.id; + $(sSearchAreaId).block(); // Run the query and display the results $.post( 'ajax.render.php', theMap, @@ -101,7 +102,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates) $(sSearchAreaId).html(data); $(sSearchAreaId+' .listResults').tableHover(); $(sSearchAreaId+' .listResults').tablesorter( { headers: {0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables - + $(sSearchAreaId).unblock(); }, 'html' ); @@ -144,7 +145,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates) } ); theMap['operation'] = 'doAddObjects'; - + $('#busy_'+me.iInputId).html(' '); // Run the query and display the results $.post( 'ajax.render.php', theMap, function(data) @@ -158,6 +159,7 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates) $('#linkedset_'+me.id+' .listResults').tableHover(); $('#linkedset_'+me.id+' .listResults').trigger('update').trigger("applyWidgets"); // table is already sortable, just refresh it $('#linkedset_'+me.id+' :input').each( function() { $(this).trigger('validate', ''); }); // Validate newly added form fields... + $('#busy_'+me.iInputId).html(''); } }, 'html' From c8384cc8a4624b6ad3dd182935547ca81bb327bd Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Nov 2010 13:25:00 +0000 Subject: [PATCH 894/970] Handle filtering of non-existent attributes in hierarchical ZLists. SVN:trunk[979] --- core/metamodel.class.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a91f4605e6..6877746958 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1350,14 +1350,23 @@ abstract class MetaModel // Discard attributes that do not make sense // (missing classes in the current module combination, resulting in irrelevant ext key or link set) // - foreach($aItems as $iFoo => $sAttCode) + self::Init_CheckZListItems($aItems, $sTargetClass); + self::$m_aListData[$sTargetClass][$sListCode] = $aItems; + } + + protected static function Init_CheckZListItems(&$aItems, $sTargetClass) + { + foreach($aItems as $iFoo => $attCode) { - if (isset(self::$m_aIgnoredAttributes[$sTargetClass][$sAttCode])) + if (is_array($attCode)) + { + self::Init_CheckZListItems($attCode, $sTargetClass); + } + else if (isset(self::$m_aIgnoredAttributes[$sTargetClass][$attCode])) { unset($aItems[$iFoo]); } } - self::$m_aListData[$sTargetClass][$sListCode] = $aItems; } public static function Init_DefineState($sStateCode, $aStateDef) From 7d13ec00e43530d61e5ab6cac49a3e461d7e0d3d Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 26 Nov 2010 15:55:38 +0000 Subject: [PATCH 895/970] User profiles: created in dedicated module itop-profiles-itil SVN:trunk[980] --- .../userrights/userrightsmatrix.class.inc.php | 1 + .../userrights/userrightsnull.class.inc.php | 5 - .../userrightsprofile.class.inc.php | 267 ------------- .../userrightsprojection.class.inc.php | 347 ---------------- core/userrights.class.inc.php | 10 - .../module.itop-profiles-itil.php | 370 ++++++++++++++++++ 6 files changed, 371 insertions(+), 629 deletions(-) create mode 100644 modules/itop-profiles-itil/module.itop-profiles-itil.php diff --git a/addons/userrights/userrightsmatrix.class.inc.php b/addons/userrights/userrightsmatrix.class.inc.php index 7c7e9ca9ee..3f4f9369b7 100644 --- a/addons/userrights/userrightsmatrix.class.inc.php +++ b/addons/userrights/userrightsmatrix.class.inc.php @@ -149,6 +149,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI return ($oUser->GetKey() == 1); } + // Deprecated - create a new module ! public function Setup() { // Users must be added manually diff --git a/addons/userrights/userrightsnull.class.inc.php b/addons/userrights/userrightsnull.class.inc.php index f31ce3b253..760b93ab7a 100644 --- a/addons/userrights/userrightsnull.class.inc.php +++ b/addons/userrights/userrightsnull.class.inc.php @@ -42,11 +42,6 @@ class UserRightsNull extends UserRightsAddOnAPI return true; } - public function Setup() - { - return true; - } - public function Init() { return true; diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 0226ffde5c..5cdad21c89 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -471,14 +471,6 @@ class UserRightsProfile extends UserRightsAddOnAPI return true; } - public function Setup() - { - SetupProfiles::ComputeITILProfiles(); - //SetupProfiles::ComputeBasicProfiles(); - SetupProfiles::DoCreateProfiles(); - return true; - } - public function Init() { MetaModel::RegisterPlugin('userrights', 'ACbyProfile'); @@ -825,265 +817,6 @@ exit; } } -// -// Create simple profiles into our user management model: -// - administrator -// - readers -// - contributors -// -class SetupProfiles -{ - protected static $m_aActions = array( - UR_ACTION_READ => 'Read', - UR_ACTION_MODIFY => 'Modify', - UR_ACTION_DELETE => 'Delete', - UR_ACTION_BULK_READ => 'Bulk Read', - UR_ACTION_BULK_MODIFY => 'Bulk Modify', - UR_ACTION_BULK_DELETE => 'Bulk Delete', - ); - - // Note: It is possible to specify the same class in several modules - // - protected static $m_aModules = array(); - protected static $m_aProfiles = array(); - - - protected static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) - { - $oNewObj = MetaModel::NewObject("URP_ActionGrant"); - $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); - $oNewObj->Set('class', $sClass); - $oNewObj->Set('action', self::$m_aActions[$iAction]); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - protected static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) - { - $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); - $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('permission', 'yes'); - $oNewObj->Set('class', $sClass); - $oNewObj->Set('stimulus', $sStimulusCode); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - protected static function DoCreateOneProfile($sName, $aProfileData) - { - $sDescription = $aProfileData['description']; - if (strlen(trim($aProfileData['write_modules'])) == 0) - { - $aWriteModules = array(); - } - else - { - $aWriteModules = explode(',', trim($aProfileData['write_modules'])); - } - $aStimuli = $aProfileData['stimuli']; - - $oNewObj = MetaModel::NewObject("URP_Profiles"); - $oNewObj->Set('name', $sName); - $oNewObj->Set('description', $sDescription); - $iProfile = $oNewObj->DBInsertNoReload(); - - // Grant read rights for everything - // - foreach (MetaModel::GetClasses('bizmodel') as $sClass) - { - self::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass); - self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass); - } - - // Grant write for given modules - // Start by compiling the information, because some modules may overlap - $aWriteableClasses = array(); - foreach ($aWriteModules as $sModule) - { - //$oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); - foreach (self::$m_aModules[$sModule] as $sClass) - { - $aWriteableClasses[$sClass] = true; - } - } - foreach ($aWriteableClasses as $sClass => $foo) - { - if (!MetaModel::IsValidClass($sClass)) - { - throw new CoreException("Invalid class name '$sClass'"); - } - self::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass); - self::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass); - self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass); - // By default, do not allow bulk deletion operations for standard users - // self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_DELETE, $sClass); - } - - // Grant stimuli for given classes - foreach ($aStimuli as $sClass => $sAllowedStimuli) - { - if (!MetaModel::IsValidClass($sClass)) - { - // Could be a class defined in a module that wasn't installed - continue; - //throw new CoreException("Invalid class name '$sClass'"); - } - - if ($sAllowedStimuli == 'any') - { - $aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass)); - } - elseif ($sAllowedStimuli == 'none') - { - $aAllowedStimuli = array(); - } - else - { - $aAllowedStimuli = explode(',', $sAllowedStimuli); - } - foreach ($aAllowedStimuli as $sStimulusCode) - { - self::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass); - } - } - } - - public static function DoCreateProfiles() - { - URP_Profiles::DoCreateAdminProfile(); - URP_Profiles::DoCreateUserPortalProfile(); - - foreach(self::$m_aProfiles as $sName => $aProfileData) - { - self::DoCreateOneProfile($sName, $aProfileData); - } - } - - public static function ComputeBasicProfiles() - { - // In this profiling scheme, one single module represents all the classes - // - self::$m_aModules = array( - 'UserData' => MetaModel::GetClasses('bizmodel'), - ); - - self::$m_aProfiles = array( - 'Reader' => array( - 'description' => 'Person having a ready-only access to the data', - 'write_modules' => '', - 'stimuli' => array( - ), - ), - 'Writer' => array( - 'description' => 'Contributor to the contents (read + write access)', - 'write_modules' => 'UserData', - 'stimuli' => array( - // any class => 'any' - ), - ), - ); - } - - public static function ComputeITILProfiles() - { - // In this profiling scheme, modules are based on ITIL recommendations - // - self::$m_aModules = array( - /* - 'WriteModule' => array( - 'someclass', - 'anotherclass', - ), - */ - 'General' => MetaModel::GetClasses('structure'), - 'Documentation' => MetaModel::GetClasses('documentation'), - 'Configuration' => MetaModel::GetClasses('configmgmt'), - 'Incident' => MetaModel::GetClasses('incidentmgmt'), - 'Problem' => MetaModel::GetClasses('problemmgmt'), - 'Change' => MetaModel::GetClasses('changemgmt'), - 'Service' => MetaModel::GetClasses('servicemgmt'), - 'Call' => MetaModel::GetClasses('requestmgmt'), - 'KnownError' => MetaModel::GetClasses('knownerrormgmt'), - ); - - self::$m_aProfiles = array( - 'Configuration Manager' => array( - 'description' => 'Person in charge of the documentation of the managed CIs', - 'write_modules' => 'General,Documentation,Configuration', - 'stimuli' => array( - //'bizServer' => 'none', - //'bizContract' => 'none', - //'bizIncidentTicket' => 'none', - //'bizChangeTicket' => 'any', - ), - ), - 'Service Desk Agent' => array( - 'description' => 'Person in charge of creating incident reports', - 'write_modules' => 'Incident,Call', - 'stimuli' => array( - 'Incident' => 'ev_assign', - 'UserRequest' => 'ev_assign', - ), - ), - 'Support Agent' => array( - 'description' => 'Person analyzing and solving the current incidents', - 'write_modules' => 'Incident', - 'stimuli' => array( - 'Incident' => 'ev_assign,ev_reassign,ev_resolve,ev_close', - 'UserRequest' => 'ev_assign,ev_reassign,ev_resolve,ev_close,ev_freeze', - ), - ), - 'Problem Manager' => array( - 'description' => 'Person analyzing and solving the current problems', - 'write_modules' => 'Problem,KnownError', - 'stimuli' => array( - 'Problem' => 'ev_assign,ev_reassign,ev_resolve,ev_close', - ), - ), - - 'Change Implementor' => array( - 'description' => 'Person executing the changes', - 'write_modules' => 'Change', - 'stimuli' => array( - 'NormalChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - 'EmergencyChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - 'RoutineChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - ), - ), - 'Change Supervisor' => array( - 'description' => 'Person responsible for the overall change execution', - 'write_modules' => 'Change', - 'stimuli' => array( - 'NormalChange' => 'ev_validate,ev_reject,ev_assign,ev_reopen,ev_finish', - 'EmergencyChange' => 'ev_assign,ev_reopen,ev_finish', - 'RoutineChange' => 'ev_assign,ev_reopen,ev_finish', - ), - ), - 'Change Approver' => array( - 'description' => 'Person who could be impacted by some changes', - 'write_modules' => 'Change', - 'stimuli' => array( - 'NormalChange' => 'ev_approve,ev_notapprove', - 'EmergencyChange' => 'ev_approve,ev_notapprove', - 'RoutineChange' => 'none', - ), - ), - 'Service Manager' => array( - 'description' => 'Person responsible for the service delivered to the [internal] customer', - 'write_modules' => 'Service', - 'stimuli' => array( - ), - ), - 'Document author' => array( - 'description' => 'Any person who could contribute to documentation', - 'write_modules' => 'Documentation', - 'stimuli' => array( - ), - ), - ); - } -} UserRights::SelectModule('UserRightsProfile'); diff --git a/addons/userrights/userrightsprojection.class.inc.php b/addons/userrights/userrightsprojection.class.inc.php index 5c4d9f79a4..4e771051e8 100644 --- a/addons/userrights/userrightsprojection.class.inc.php +++ b/addons/userrights/userrightsprojection.class.inc.php @@ -658,16 +658,6 @@ class UserRightsProjection extends UserRightsAddOnAPI // See implementation of userrightsprofile } - public function Setup() - { - SetupProfiles::ComputeITILProfiles(); - //SetupProfiles::ComputeBasicProfiles(); - - SetupProfiles::DoCreateDimensions(); - SetupProfiles::DoCreateProfiles(); - return true; - } - public function Init() { MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData')); @@ -1256,343 +1246,6 @@ exit; } } -// -// Create simple profiles into our user management model: -// - administrator -// - readers -// - contributors -// -class SetupProfiles -{ - protected static $m_aDimensions = array( - 'organization' => array( - 'description' => '', - 'type' => 'Organization', - ), - ); - - protected static $m_aActions = array( - UR_ACTION_READ => 'Read', - UR_ACTION_MODIFY => 'Modify', - UR_ACTION_DELETE => 'Delete', - UR_ACTION_BULK_READ => 'Bulk Read', - UR_ACTION_BULK_MODIFY => 'Bulk Modify', - UR_ACTION_BULK_DELETE => 'Bulk Delete', - ); - - // Note: It is possible to specify the same class in several modules - // - protected static $m_aModules = array(); - protected static $m_aProfiles = array(); - - protected static function DoCreateClassProjection($iDimension, $sClass) - { - $oNewObj = MetaModel::NewObject("URP_ClassProjection"); - $oNewObj->Set('dimensionid', $iDimension); - $oNewObj->Set('class', $sClass); - $oNewObj->Set('attribute', ''); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - protected static function DoCreateDimension($sName, $aDimensionData) - { - $oNewObj = MetaModel::NewObject("URP_Dimensions"); - $oNewObj->Set('name', $sName); - $oNewObj->Set('description', $aDimensionData['description']); - $oNewObj->Set('type', $aDimensionData['type']); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - - protected static function DoCreateProfileProjection($iProfile, $iDimension) - { - $oNewObj = MetaModel::NewObject("URP_ProfileProjection"); - $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('dimensionid', $iDimension); - $oNewObj->Set('value', ''); - $oNewObj->Set('attribute', ''); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - - protected static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) - { - $oNewObj = MetaModel::NewObject("URP_ActionGrant"); - $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); - $oNewObj->Set('class', $sClass); - $oNewObj->Set('action', self::$m_aActions[$iAction]); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - protected static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) - { - $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); - $oNewObj->Set('profileid', $iProfile); - $oNewObj->Set('permission', 'yes'); - $oNewObj->Set('class', $sClass); - $oNewObj->Set('stimulus', $sStimulusCode); - $iId = $oNewObj->DBInsertNoReload(); - return $iId; - } - - protected static function DoCreateAdminProfile() - { - $oNewObj = MetaModel::NewObject("URP_Profiles"); - $oNewObj->Set('name', 'Administrator'); - $oNewObj->Set('description', 'Has the rights on everything (bypassing any control)'); - $iNewId = $oNewObj->DBInsertNoReload(); - if ($iNewId != ADMIN_PROFILE_ID) - { - throw new CoreException('Admin profile could not be created with its standard id', array('requested'=>ADMIN_PROFILE_ID, 'obtained'=>$iNewId)); - } - } - - protected static function DoCreateOneProfile($sName, $aProfileData) - { - $sDescription = $aProfileData['description']; - if (strlen(trim($aProfileData['write_modules'])) == 0) - { - $aWriteModules = array(); - } - else - { - $aWriteModules = explode(',', trim($aProfileData['write_modules'])); - } - $aStimuli = $aProfileData['stimuli']; - - $oNewObj = MetaModel::NewObject("URP_Profiles"); - $oNewObj->Set('name', $sName); - $oNewObj->Set('description', $sDescription); - $iProfile = $oNewObj->DBInsertNoReload(); - - // Project in every dimension - // - $oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions")); - while ($oDimension = $oDimensionSet->Fetch()) - { - $iDimension = $oDimension->GetKey(); - self::DoCreateProfileProjection($iProfile, $iDimension); - } - - // Grant read rights for everything - // - foreach (MetaModel::GetClasses('bizmodel') as $sClass) - { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - - self::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass); - self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass); - } - - // Grant write for given modules - // Start by compiling the information, because some modules may overlap - $aWriteableClasses = array(); - foreach ($aWriteModules as $sModule) - { - //$oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); - foreach (self::$m_aModules[$sModule] as $sClass) - { - $aWriteableClasses[$sClass] = true; - } - } - foreach ($aWriteableClasses as $sClass => $foo) - { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - - if (!MetaModel::IsValidClass($sClass)) - { - throw new CoreException("Invalid class name '$sClass'"); - } - self::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass); - self::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass); - self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass); - // By default, do not allow bulk deletion operations for standard users - // self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_DELETE, $sClass); - } - - // Grant stimuli for given classes - foreach ($aStimuli as $sClass => $sAllowedStimuli) - { - if (!MetaModel::IsValidClass($sClass)) - { - // Could be a class defined in a module that wasn't installed - continue; - //throw new CoreException("Invalid class name '$sClass'"); - } - - if ($sAllowedStimuli == 'any') - { - $aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass)); - } - elseif ($sAllowedStimuli == 'none') - { - $aAllowedStimuli = array(); - } - else - { - $aAllowedStimuli = explode(',', $sAllowedStimuli); - } - foreach ($aAllowedStimuli as $sStimulusCode) - { - self::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass); - } - } - } - - public static function DoCreateDimensions() - { - $aClass = MetaModel::GetClasses(); - foreach(self::$m_aDimensions as $sName => $aDimensionData) - { - $iDimension = self::DoCreateDimension($sName, $aDimensionData); - - foreach($aClass as $sClass) - { - // Skip non instantiable classes - if (MetaModel::IsAbstract($sClass)) continue; - - if (!MetaModel::IsValidClass($sClass)) - { - throw new CoreException("Invalid class name '$sClass'"); - } - self::DoCreateClassProjection($iDimension, $sClass); - } - } - } - - public static function DoCreateProfiles() - { - self::DoCreateAdminProfile(); - - foreach(self::$m_aProfiles as $sName => $aProfileData) - { - self::DoCreateOneProfile($sName, $aProfileData); - } - } - - public static function ComputeBasicProfiles() - { - // In this profiling scheme, one single module represents all the classes - // - self::$m_aModules = array( - 'UserData' => MetaModel::GetClasses('bizmodel'), - ); - - self::$m_aProfiles = array( - 'Reader' => array( - 'description' => 'Person having a ready-only access to the data', - 'write_modules' => '', - 'stimuli' => array( - ), - ), - 'Writer' => array( - 'description' => 'Contributor to the contents (read + write access)', - 'write_modules' => 'UserData', - 'stimuli' => array( - // any class => 'any' - ), - ), - ); - } - - public static function ComputeITILProfiles() - { - // In this profiling scheme, modules are based on ITIL recommendations - // - self::$m_aModules = array( - /* - 'WriteModule' => array( - 'someclass', - 'anotherclass', - ), - */ - 'General' => MetaModel::GetClasses('structure'), - 'Documentation' => MetaModel::GetClasses('documentation'), - 'Configuration' => MetaModel::GetClasses('configmgmt'), - 'Incident' => MetaModel::GetClasses('incidentmgmt'), - 'Problem' => MetaModel::GetClasses('problemmgmt'), - 'Change' => MetaModel::GetClasses('changemgmt'), - 'Service' => MetaModel::GetClasses('servicemgmt'), - 'Call' => MetaModel::GetClasses('requestmgmt'), - 'KnownError' => MetaModel::GetClasses('knownerrormgmt'), - ); - - self::$m_aProfiles = array( - 'Configuration Manager' => array( - 'description' => 'Person in charge of the documentation of the managed CIs', - 'write_modules' => 'General,Documentation,Configuration', - 'stimuli' => array( - //'bizServer' => 'none', - //'bizContract' => 'none', - //'bizIncidentTicket' => 'none', - //'bizChangeTicket' => 'any', - ), - ), - 'Service Desk Agent' => array( - 'description' => 'Person in charge of creating incident reports', - 'write_modules' => 'Incident,Call', - 'stimuli' => array( - 'Incident' => 'ev_assign', - 'UserRequest' => 'ev_assign', - ), - ), - 'Support Agent' => array( - 'description' => 'Person analyzing and solving the current incidents or problems', - 'write_modules' => 'Incident,Problem,KnownError', - 'stimuli' => array( - 'Incident' => 'ev_assign,ev_reassign,ev_resolve,ev_close', - 'UserRequest' => 'ev_assign,ev_reassign,ev_resolve,ev_close,ev_freeze', - ), - ), - 'Change Implementor' => array( - 'description' => 'Person executing the changes', - 'write_modules' => 'Change', - 'stimuli' => array( - 'NormalChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - 'EmergencyChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - 'RoutineChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', - ), - ), - 'Change Supervisor' => array( - 'description' => 'Person responsible for the overall change execution', - 'write_modules' => 'Change', - 'stimuli' => array( - 'NormalChange' => 'ev_validate,ev_reject,ev_assign,ev_reopen,ev_finish', - 'EmergencyChange' => 'ev_assign,ev_reopen,ev_finish', - 'RoutineChange' => 'ev_assign,ev_reopen,ev_finish', - ), - ), - 'Change Approver' => array( - 'description' => 'Person who could be impacted by some changes', - 'write_modules' => 'Change', - 'stimuli' => array( - 'NormalChange' => 'ev_approve,ev_notapprove', - 'EmergencyChange' => 'ev_approve,ev_notapprove', - 'RoutineChange' => 'none', - ), - ), - 'Service Manager' => array( - 'description' => 'Person responsible for the service delivered to the [internal] customer', - 'write_modules' => 'Service', - 'stimuli' => array( - ), - ), - 'Document author' => array( - 'description' => 'Any person who could contribute to documentation', - 'write_modules' => 'Documentation', - 'stimuli' => array( - ), - ), - ); - } -} UserRights::SelectModule('UserRightsProjection'); diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index e4698c5a6d..bd3bb81e2d 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -50,7 +50,6 @@ define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions */ abstract class UserRightsAddOnAPI { - abstract public function Setup(); // initial installation abstract public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US'); // could be used during initial installation abstract public function Init(); // loads data (possible optimizations) @@ -310,15 +309,6 @@ class UserRights return $bRes; } - // Installation (e.g: give default values for users) - public static function Setup() - { - // to be discussed... - $bRes = self::$m_oAddOn->Setup(); - self::FlushPrivileges(true /* reset admin cache */); - return $bRes; - } - protected static function IsLoggedIn() { if (self::$m_oUser == null) diff --git a/modules/itop-profiles-itil/module.itop-profiles-itil.php b/modules/itop-profiles-itil/module.itop-profiles-itil.php new file mode 100644 index 0000000000..abdfa1e8a3 --- /dev/null +++ b/modules/itop-profiles-itil/module.itop-profiles-itil.php @@ -0,0 +1,370 @@ + 'Create standard ITIL profiles', + 'category' => 'create_profiles', + + // Setup + // + 'dependencies' => array( + ), + 'mandatory' => true, + 'visible' => false, + 'installer' => 'CreateITILProfilesInstaller', + + // Components + // + 'datamodel' => array( + //'model.itop-profiles-itil.php', + ), + 'webservice' => array( + //'webservices.itop-profiles-itil.php', + ), + 'dictionary' => array( + //'en.dict.itop-profiles-itil.php', + //'fr.dict.itop-profiles-itil.php', + //'de.dict.itop-profiles-itil.php', + ), + 'data.struct' => array( + //'data.struct.itop-profiles-itil.xml', + ), + 'data.sample' => array( + //'data.sample.itop-profiles-itil.xml', + ), + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => array( + //'some_setting' => 'some value', + ), + ) +); + + +// Module installation handler +// +class CreateITILProfilesInstaller extends ModuleInstallerAPI +{ + public static function BeforeWritingConfig(Config $oConfiguration) + { + //$oConfiguration->SetModuleSetting('user-rigths-profile', 'myoption', 'myvalue'); + return $oConfiguration; + } + + public static function AfterDatabaseCreation(Config $oConfiguration) + { + self::ComputeITILProfiles(); + //self::ComputeBasicProfiles(); + self::DoCreateProfiles(); + UserRights::FlushPrivileges(true /* reset admin cache */); + } + + protected static $m_aActions = array( + UR_ACTION_READ => 'Read', + UR_ACTION_MODIFY => 'Modify', + UR_ACTION_DELETE => 'Delete', + UR_ACTION_BULK_READ => 'Bulk Read', + UR_ACTION_BULK_MODIFY => 'Bulk Modify', + UR_ACTION_BULK_DELETE => 'Bulk Delete', + ); + + // Note: It is possible to specify the same class in several modules + // + protected static $m_aModules = array(); + protected static $m_aProfiles = array(); + + + protected static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) + { + $oNewObj = MetaModel::NewObject("URP_ActionGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('action', self::$m_aActions[$iAction]); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) + { + $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', 'yes'); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('stimulus', $sStimulusCode); + $iId = $oNewObj->DBInsertNoReload(); + return $iId; + } + + protected static function DoCreateOneProfile($sName, $aProfileData) + { + $sDescription = $aProfileData['description']; + if (strlen(trim($aProfileData['write_modules'])) == 0) + { + $aWriteModules = array(); + } + else + { + $aWriteModules = explode(',', trim($aProfileData['write_modules'])); + } + if (strlen(trim($aProfileData['delete_modules'])) == 0) + { + $aDeleteModules = array(); + } + else + { + $aDeleteModules = explode(',', trim($aProfileData['delete_modules'])); + } + $aStimuli = $aProfileData['stimuli']; + + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', $sName); + $oNewObj->Set('description', $sDescription); + $iProfile = $oNewObj->DBInsertNoReload(); + + // Grant read rights for everything + // + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + self::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass); + } + + // Grant write for given modules + // Start by compiling the information, because some modules may overlap + $aWriteableClasses = array(); + foreach ($aWriteModules as $sModule) + { + //$oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); + foreach (self::$m_aModules[$sModule] as $sClass) + { + $aWriteableClasses[$sClass] = true; + } + } + foreach ($aWriteableClasses as $sClass => $foo) + { + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } + self::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass); + self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass); + } + + // Grant delete for given modules + // Start by compiling the information, because some modules may overlap + $aDeletableClasses = array(); + foreach ($aDeleteModules as $sModule) + { + //$oPage->p('Granting delete access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes'); + foreach (self::$m_aModules[$sModule] as $sClass) + { + $aDeletableClasses[$sClass] = true; + } + } + foreach ($aDeletableClasses as $sClass => $foo) + { + if (!MetaModel::IsValidClass($sClass)) + { + throw new CoreException("Invalid class name '$sClass'"); + } + self::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass); + // By default, do not allow bulk deletion operations for standard users + // self::DoCreateActionGrant($iProfile, UR_ACTION_BULK_DELETE, $sClass); + } + + // Grant stimuli for given classes + foreach ($aStimuli as $sClass => $sAllowedStimuli) + { + if (!MetaModel::IsValidClass($sClass)) + { + // Could be a class defined in a module that wasn't installed + continue; + //throw new CoreException("Invalid class name '$sClass'"); + } + + if ($sAllowedStimuli == 'any') + { + $aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass)); + } + elseif ($sAllowedStimuli == 'none') + { + $aAllowedStimuli = array(); + } + else + { + $aAllowedStimuli = explode(',', $sAllowedStimuli); + } + foreach ($aAllowedStimuli as $sStimulusCode) + { + self::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass); + } + } + } + + public static function DoCreateProfiles() + { + URP_Profiles::DoCreateAdminProfile(); + URP_Profiles::DoCreateUserPortalProfile(); + + foreach(self::$m_aProfiles as $sName => $aProfileData) + { + self::DoCreateOneProfile($sName, $aProfileData); + } + } + + public static function ComputeBasicProfiles() + { + // In this profiling scheme, one single module represents all the classes + // + self::$m_aModules = array( + 'UserData' => MetaModel::GetClasses('bizmodel'), + ); + + self::$m_aProfiles = array( + 'Reader' => array( + 'description' => 'Person having a ready-only access to the data', + 'write_modules' => '', + 'delete_modules' => '', + 'stimuli' => array( + ), + ), + 'Writer' => array( + 'description' => 'Contributor to the contents (read + write access)', + 'write_modules' => 'UserData', + 'delete_modules' => 'UserData', + 'stimuli' => array( + // any class => 'any' + ), + ), + ); + } + + public static function ComputeITILProfiles() + { + // In this profiling scheme, modules are based on ITIL recommendations + // + self::$m_aModules = array( + 'General' => MetaModel::GetClasses('structure'), + 'Documentation' => MetaModel::GetClasses('documentation'), + 'Configuration' => MetaModel::GetClasses('configmgmt'), + 'Incident' => MetaModel::GetClasses('incidentmgmt'), + 'Problem' => MetaModel::GetClasses('problemmgmt'), + 'Change' => MetaModel::GetClasses('changemgmt'), + 'Service' => MetaModel::GetClasses('servicemgmt'), + 'Call' => MetaModel::GetClasses('requestmgmt'), + 'KnownError' => MetaModel::GetClasses('knownerrormgmt'), + ); + + self::$m_aProfiles = array( + 'Configuration Manager' => array( + 'description' => 'Person in charge of the documentation of the managed CIs', + 'write_modules' => 'General,Documentation,Configuration', + 'delete_modules' => 'General,Documentation,Configuration', + 'stimuli' => array( + //'Server' => 'none', + //'Contract' => 'none', + //'IncidentTicket' => 'none', + //'ChangeTicket' => 'any', + ), + ), + 'Service Desk Agent' => array( + 'description' => 'Person in charge of creating incident reports', + 'write_modules' => 'Incident,Call', + 'delete_modules' => 'Incident,Call', + 'stimuli' => array( + 'Incident' => 'ev_assign', + 'UserRequest' => 'ev_assign', + ), + ), + 'Support Agent' => array( + 'description' => 'Person analyzing and solving the current incidents', + 'write_modules' => 'Incident', + 'delete_modules' => 'Incident', + 'stimuli' => array( + 'Incident' => 'ev_assign,ev_reassign,ev_resolve,ev_close', + 'UserRequest' => 'ev_assign,ev_reassign,ev_resolve,ev_close,ev_freeze', + ), + ), + 'Problem Manager' => array( + 'description' => 'Person analyzing and solving the current problems', + 'write_modules' => 'Problem,KnownError', + 'delete_modules' => 'Problem,KnownError', + 'stimuli' => array( + 'Problem' => 'ev_assign,ev_reassign,ev_resolve,ev_close', + ), + ), + + 'Change Implementor' => array( + 'description' => 'Person executing the changes', + 'write_modules' => 'Change', + 'delete_modules' => 'Change', + 'stimuli' => array( + 'NormalChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + 'EmergencyChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + 'RoutineChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor', + ), + ), + 'Change Supervisor' => array( + 'description' => 'Person responsible for the overall change execution', + 'write_modules' => 'Change', + 'delete_modules' => 'Change', + 'stimuli' => array( + 'NormalChange' => 'ev_validate,ev_reject,ev_assign,ev_reopen,ev_finish', + 'EmergencyChange' => 'ev_assign,ev_reopen,ev_finish', + 'RoutineChange' => 'ev_assign,ev_reopen,ev_finish', + ), + ), + 'Change Approver' => array( + 'description' => 'Person who could be impacted by some changes', + 'write_modules' => 'Change', + 'delete_modules' => 'Change', + 'stimuli' => array( + 'NormalChange' => 'ev_approve,ev_notapprove', + 'EmergencyChange' => 'ev_approve,ev_notapprove', + 'RoutineChange' => 'none', + ), + ), + 'Service Manager' => array( + 'description' => 'Person responsible for the service delivered to the [internal] customer', + 'write_modules' => 'Service', + 'delete_modules' => 'Service', + 'stimuli' => array( + ), + ), + 'Document author' => array( + 'description' => 'Any person who could contribute to documentation', + 'write_modules' => 'Documentation', + 'delete_modules' => 'Documentation', + 'stimuli' => array( + ), + ), + ); + } +} + +?> From 9f000c15ff6c9a5a6199a05de614eaed17fa4ff8 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 26 Nov 2010 15:57:11 +0000 Subject: [PATCH 896/970] Cosmetic on setup: perform post DB install in the same page as admin account creation, and hide ticket prerequisites module SVN:trunk[981] --- .../module.itop-tickets.php | 2 +- setup/index.php | 33 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index 7b3c143922..000723d5c4 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -16,7 +16,7 @@ SetupWebPage::AddModule( 'itop-config-mgmt/1.0.0', ), 'mandatory' => true, - 'visible' => true, + 'visible' => false, // Components // diff --git a/setup/index.php b/setup/index.php index a2a2b11bf9..d6aaa6ce5b 100644 --- a/setup/index.php +++ b/setup/index.php @@ -511,12 +511,6 @@ function CreateDatabaseStructure(SetupWebPage $oP, Config $oConfig, $sDBName, $s function CreateAdminAccount(SetupWebPage $oP, Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) { $oP->log('Info - CreateAdminAccount'); - InitDataModel($oP, TMP_CONFIG_FILE, false); // load data model and connect to the database - - if (!UserRights::Setup()) - { - return false; - } if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) { @@ -990,17 +984,6 @@ function AdminAccountDefinition(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConfig->WriteToFile(TMP_CONFIG_FILE); if (CreateDatabaseStructure($oP, $oConfig, $sDBName, $sDBPrefix, $aParamValues['module'])) { - foreach($aParamValues['module'] as $sModuleId) - { - if (isset($aAvailableModules[$sModuleId]['installer'])) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - // The validity of the sModuleInstallerClass has been established in BuildConfig() - $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); - call_user_func_array($aCallSpec, array($oConfig)); - } - } - $sRedStar = "*"; $oP->add("

      Default language for the application:

      \n"); // Possible languages (depends on the dictionaries loaded in the config) @@ -1059,7 +1042,23 @@ function SampleDataSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, Con $oP->add("
      \n"); $oP->add("\n"); AddParamsToForm($oP, $aParamValues, array('sample_data')); + + InitDataModel($oP, TMP_CONFIG_FILE, false); // load data model and connect to the database + // Perform here additional DB setup + // Moved here to spread the setup duration between two steps of the wizard (timeouts...) + $aAvailableModules = GetAvailableModules($oP); + foreach($aParamValues['module'] as $sModuleId) + { + if (isset($aAvailableModules[$sModuleId]['installer'])) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + // The validity of the sModuleInstallerClass has been established in BuildConfig() + $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); + call_user_func_array($aCallSpec, array($oConfig)); + } + } + if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd, $sLanguage)) { $oP->add("

      Loading of sample data

      \n"); From e37f6a0954de5d970b58cf4672476de6e63f5064 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 26 Nov 2010 15:59:41 +0000 Subject: [PATCH 897/970] Welcome page moved out the application, into a dedicated module: itop-welcome-itil SVN:trunk[982] --- application/itopwebpage.class.inc.php | 2 - application/templates/welcome_menu.html | 201 ------------------ .../model.itop-welcome-itil.php | 20 ++ .../module.itop-welcome-itil.php | 54 +++++ 4 files changed, 74 insertions(+), 203 deletions(-) delete mode 100644 application/templates/welcome_menu.html create mode 100644 modules/itop-welcome-itil/model.itop-welcome-itil.php create mode 100644 modules/itop-welcome-itil/module.itop-welcome-itil.php diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 72a31ce78b..73513f7c50 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -412,8 +412,6 @@ EOF * Data Model * Universal Search */ - $oWelcomeMenu = new MenuGroup('WelcomeMenu', 10 /* fRank */); - new TemplateMenuNode('WelcomeMenuPage', '../application/templates/welcome_menu.html', $oWelcomeMenu->GetIndex() /* oParent */, 1 /* fRank */); $oToolsMenu = new MenuGroup('DataAdministration', 70 /* fRank */, 'Organization', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS); new WebPageMenuNode('CSVImportMenu', '../pages/csvimport.php', $oToolsMenu->GetIndex(), 1 /* fRank */); diff --git a/application/templates/welcome_menu.html b/application/templates/welcome_menu.html deleted file mode 100644 index 468fe3426f..0000000000 --- a/application/templates/welcome_menu.html +++ /dev/null @@ -1,201 +0,0 @@ - - - diff --git a/modules/itop-welcome-itil/model.itop-welcome-itil.php b/modules/itop-welcome-itil/model.itop-welcome-itil.php new file mode 100644 index 0000000000..7ffd1b569f --- /dev/null +++ b/modules/itop-welcome-itil/model.itop-welcome-itil.php @@ -0,0 +1,20 @@ +GetIndex() /* oParent */, 1 /* fRank */); + +?> diff --git a/modules/itop-welcome-itil/module.itop-welcome-itil.php b/modules/itop-welcome-itil/module.itop-welcome-itil.php new file mode 100644 index 0000000000..7064a79123 --- /dev/null +++ b/modules/itop-welcome-itil/module.itop-welcome-itil.php @@ -0,0 +1,54 @@ + 'ITIL skin', + 'category' => 'skin', + + // Setup + // + 'dependencies' => array( + ), + 'mandatory' => true, + 'visible' => false, + //'installer' => 'MyInstaller', + + // Components + // + 'datamodel' => array( + 'model.itop-welcome-itil.php', + ), + 'webservice' => array( + //'webservices.itop-welcome-itil.php', + ), + 'dictionary' => array( + //'en.dict.itop-welcome-itil.php', + //'fr.dict.itop-welcome-itil.php', + //'de.dict.itop-welcome-itil.php', + ), + 'data.struct' => array( + //'data.struct.itop-welcome-itil.xml', + ), + 'data.sample' => array( + //'data.sample.itop-welcome-itil.xml', + ), + + // Documentation + // + 'doc.manual_setup' => '', + 'doc.more_information' => '', + + // Default settings + // + 'settings' => array( + //'some_setting' => 'some value', + ), + ) +); + +?> From 4bd217d17814c0414909a74c8d285ada1fe0d797 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 26 Nov 2010 16:22:20 +0000 Subject: [PATCH 898/970] Keep backward compatibility with *old* config files without the 'webservices' entry in the config-file. SVN:trunk[983] --- core/config.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index bdefc69405..aa85e2edb2 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -409,7 +409,7 @@ class Config } $this->m_aAppModules = $MyModules['application']; $this->m_aDataModels = $MyModules['business']; - $this->m_aWebServiceCategories = $MyModules['webservices']; + $this->m_aWebServiceCategories = isset($MyModules['webservices']) ? $MyModules['webservices'] : array(); $this->m_aAddons = $MyModules['addons']; $this->m_aDictionaries = $MyModules['dictionaries']; From 699679318abfff8935f0c3bd704334d437892e94 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 26 Nov 2010 16:39:41 +0000 Subject: [PATCH 899/970] Welcome menu (forgot at previous commit) SVN:trunk[984] --- modules/itop-welcome-itil/welcome_menu.html | 201 ++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 modules/itop-welcome-itil/welcome_menu.html diff --git a/modules/itop-welcome-itil/welcome_menu.html b/modules/itop-welcome-itil/welcome_menu.html new file mode 100644 index 0000000000..468fe3426f --- /dev/null +++ b/modules/itop-welcome-itil/welcome_menu.html @@ -0,0 +1,201 @@ + + + From 540eb5f272b00fb8c4faef00cfbd158e77e0fc91 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 29 Nov 2010 09:17:44 +0000 Subject: [PATCH 900/970] Moved the standards menus into the "welcome" module SVN:trunk[985] --- application/itopwebpage.class.inc.php | 35 ------------------ .../model.itop-welcome-itil.php | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 73513f7c50..046e92fc06 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -391,41 +391,6 @@ EOF EOF ); - // Add the standard menus - /* - * +--------------------+ - * | Welcome | - * +--------------------+ - * Welcome To iTop - * +--------------------+ - * | Tools | - * +--------------------+ - * CSV Import - * +--------------------+ - * | Admin Tools | << Only present if the user is an admin - * +--------------------+ - * User Accounts - * Profiles - * Notifications - * Run Queries - * Export - * Data Model - * Universal Search - */ - - $oToolsMenu = new MenuGroup('DataAdministration', 70 /* fRank */, 'Organization', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS); - new WebPageMenuNode('CSVImportMenu', '../pages/csvimport.php', $oToolsMenu->GetIndex(), 1 /* fRank */); - - // Add the admin menus - $oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */); - new OQLMenuNode('UserAccountsMenu', 'SELECT User', $oAdminMenu->GetIndex(), 1 /* fRank */); - new OQLMenuNode('ProfilesMenu', 'SELECT URP_Profiles', $oAdminMenu->GetIndex(), 2 /* fRank */); - new TemplateMenuNode('NotificationsMenu', '../application/templates/notifications_menu.html', $oAdminMenu->GetIndex(), 3 /* fRank */); - new OQLMenuNode('AuditCategories', 'SELECT AuditCategory', $oAdminMenu->GetIndex(), 4 /* fRank */); - new WebPageMenuNode('RunQueriesMenu', '../pages/run_query.php', $oAdminMenu->GetIndex(), 8 /* fRank */); - new WebPageMenuNode('ExportMenu', '../webservices/export.php', $oAdminMenu->GetIndex(), 9 /* fRank */); - new WebPageMenuNode('DataModelMenu', '../pages/schema.php', $oAdminMenu->GetIndex(), 10 /* fRank */); - new WebPageMenuNode('UniversalSearchMenu', '../pages/UniversalSearch.php', $oAdminMenu->GetIndex(), 11 /* fRank */); } public function AddToMenu($sHtml) diff --git a/modules/itop-welcome-itil/model.itop-welcome-itil.php b/modules/itop-welcome-itil/model.itop-welcome-itil.php index 7ffd1b569f..99ef84026b 100644 --- a/modules/itop-welcome-itil/model.itop-welcome-itil.php +++ b/modules/itop-welcome-itil/model.itop-welcome-itil.php @@ -14,7 +14,43 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// Add the standard menus +/* + * +--------------------+ + * | Welcome | + * +--------------------+ + * Welcome To iTop + * +--------------------+ + * | Tools | + * +--------------------+ + * CSV Import + * +--------------------+ + * | Admin Tools | << Only present if the user is an admin + * +--------------------+ + * User Accounts + * Profiles + * Notifications + * Run Queries + * Export + * Data Model + * Universal Search + */ + $oWelcomeMenu = new MenuGroup('WelcomeMenu', 10 /* fRank */); new TemplateMenuNode('WelcomeMenuPage', APPROOT.'modules/itop-welcome-itil/welcome_menu.html', $oWelcomeMenu->GetIndex() /* oParent */, 1 /* fRank */); +$oToolsMenu = new MenuGroup('DataAdministration', 70 /* fRank */, 'Organization', UR_ACTION_MODIFY, UR_ALLOWED_YES|UR_ALLOWED_DEPENDS); +new WebPageMenuNode('CSVImportMenu', '../pages/csvimport.php', $oToolsMenu->GetIndex(), 1 /* fRank */); + +// Add the admin menus +$oAdminMenu = new MenuGroup('AdminTools', 80 /* fRank */); +new OQLMenuNode('UserAccountsMenu', 'SELECT User', $oAdminMenu->GetIndex(), 1 /* fRank */); +new OQLMenuNode('ProfilesMenu', 'SELECT URP_Profiles', $oAdminMenu->GetIndex(), 2 /* fRank */); +new TemplateMenuNode('NotificationsMenu', '../application/templates/notifications_menu.html', $oAdminMenu->GetIndex(), 3 /* fRank */); +new OQLMenuNode('AuditCategories', 'SELECT AuditCategory', $oAdminMenu->GetIndex(), 4 /* fRank */); +new WebPageMenuNode('RunQueriesMenu', '../pages/run_query.php', $oAdminMenu->GetIndex(), 8 /* fRank */); +new WebPageMenuNode('ExportMenu', '../webservices/export.php', $oAdminMenu->GetIndex(), 9 /* fRank */); +new WebPageMenuNode('DataModelMenu', '../pages/schema.php', $oAdminMenu->GetIndex(), 10 /* fRank */); +new WebPageMenuNode('UniversalSearchMenu', '../pages/UniversalSearch.php', $oAdminMenu->GetIndex(), 11 /* fRank */); + ?> From c428e07c4b219e400618f0647d6098b3e4aaca45 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 29 Nov 2010 10:19:33 +0000 Subject: [PATCH 901/970] XML data loader to request for credentials SVN:trunk[986] --- setup/xmldataloader.class.inc.php | 7 +++++-- webservices/backoffice.dataloader.php | 23 ++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index bf323158d6..b81dff815b 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -46,13 +46,16 @@ class XMLDataLoader protected $m_aWarnings; protected $m_iCountCreated; - public function __construct($sConfigFileName) + public function __construct($sConfigFileName = null) { $this->m_aKeys = array(); $this->m_aObjectsCache = array(); $this->m_oChange = null; $this->m_sCacheFileName = KEYS_CACHE_FILE; - $this->InitDataModel($sConfigFileName); + if ($sConfigFileName != null) + { + $this->InitDataModel($sConfigFileName); + } $this->LoadKeysCache(); $this->m_bSessionActive = true; $this->m_aErrors = array(); diff --git a/webservices/backoffice.dataloader.php b/webservices/backoffice.dataloader.php index 5b955a0eba..ebe74bd582 100644 --- a/webservices/backoffice.dataloader.php +++ b/webservices/backoffice.dataloader.php @@ -29,11 +29,18 @@ * 'file' string Name of the file to load */ define('SAFE_MINIMUM_MEMORY', 256*1024*1024); + require_once('../approot.inc.php'); -require_once(APPROOT.'/application/utils.inc.php'); -require_once(APPROOT."/application/nicewebpage.class.inc.php"); +require_once(APPROOT.'/application/application.inc.php'); + +require_once(APPROOT.'/application/startup.inc.php'); + +require_once(APPROOT.'/application/loginwebpage.class.inc.php'); +LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (must be admin) + // required because the class xmldataloader is reporting errors in the setup.log file require_once(APPROOT.'/setup/setuppage.class.inc.php'); +require_once(APPROOT.'/setup/xmldataloader.class.inc.php'); function SetMemoryLimit($oP) @@ -72,20 +79,10 @@ function SetMemoryLimit($oP) // //////////////////////////////////////////////////////////////////////////////// -require_once(APPROOT.'/core/config.class.inc.php'); -require_once(APPROOT.'/core/log.class.inc.php'); -require_once(APPROOT.'/core/kpi.class.inc.php'); -require_once(APPROOT.'/core/cmdbsource.class.inc.php'); -require_once(APPROOT.'/setup/xmldataloader.class.inc.php'); - -define('FINAL_CONFIG_FILE', APPROOT.'/config-itop.php'); - // Never cache this page header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past -Utils::SpecifyConfigFile(FINAL_CONFIG_FILE); - /** * Main program */ @@ -97,7 +94,7 @@ $oP = new WebPage("iTop - Backoffice data loader"); try { // Note: the data model must be loaded first - $oDataLoader = new XMLDataLoader(FINAL_CONFIG_FILE); // When called by the wizard, the final config is not yet there + $oDataLoader = new XMLDataLoader(); if (empty($sFileName)) { From 4a6bc8a896129148bc8cf5a0314c3d347f06679e Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 29 Nov 2010 10:35:18 +0000 Subject: [PATCH 902/970] - added the ability to create objects pointed by ExtKeys even when the edit mode is a drop-down list and not an autocomplete - made this behavior configurable globally or per external key, using the config-flag/option: allow_target_creation. SVN:trunk[987] --- application/cmdbabstract.class.inc.php | 41 ++---- .../ui.autocompletewidget.class.inc.php | 119 ++++++++++-------- core/attributedef.class.inc.php | 6 + core/config.class.inc.php | 8 ++ js/autocompletewidget.js | 41 ++++-- pages/ajax.render.php | 17 +-- 6 files changed, 124 insertions(+), 108 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 7423f2cb1f..ee6c4ac04d 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1052,10 +1052,10 @@ EOF if (!$oAttDef->IsExternalField()) { - $sMandatory = 'false'; + $bMandatory = 'false'; if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { - $sMandatory = 'true'; + $bMandatory = 'true'; } $sValidationField = ""; $sHelpText = $oAttDef->GetHelpOnEdition(); @@ -1085,7 +1085,7 @@ EOF break; case 'HTML': - $oWidget = new UIHTMLEditorWidget($iId, $sAttCode, $sNameSuffix, $sHelpText, $sValidationField, $value, $sMandatory); + $oWidget = new UIHTMLEditorWidget($iId, $sAttCode, $sNameSuffix, $sHelpText, $sValidationField, $value, $bMandatory); $sHTMLValue = $oWidget->Display($oPage, $aArgs); break; @@ -1128,38 +1128,11 @@ EOF $aEventsList[] ='validate'; $aEventsList[] ='change'; - // #@# todo - add context information (depending on dimensions) $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); $iFieldSize = $oAttDef->GetMaxSize(); $iMaxComboLength = $oAttDef->GetMaximumComboLength(); - if (count($aAllowedValues) >= $iMaxComboLength) - { - // too many choices, use an autocomplete - $oWidget = new UIAutoCompleteWidget($sAttCode, $sClass, $oAttDef->GetLabel(), $aAllowedValues, $value, $iId, $sNameSuffix, $sFieldPrefix, $sFormPrefix); - $sHTMLValue = $oWidget->Display($oPage, $aArgs); - - } - else - { - // Few choices, use a normal 'select' - // In case there are no valid values, the select will be empty, thus blocking the user from validating the form - $sHTMLValue = " {$sValidationField}\n"; - } + $oWidget = new UIExtKeyWidget($sAttCode, $sClass, $oAttDef->GetLabel(), $aAllowedValues, $value, $iId, $bMandatory, $sNameSuffix, $sFieldPrefix, $sFormPrefix); + $sHTMLValue = $oWidget->Display($oPage, $aArgs); break; case 'String': @@ -1175,7 +1148,7 @@ EOF $sHTMLValue .= "\n"; foreach($aAllowedValues as $key => $display_value) { - if ((count($aAllowedValues) == 1) && ($sMandatory == 'true') ) + if ((count($aAllowedValues) == 1) && ($bMandatory == 'true') ) { // When there is only once choice, select it by default $sSelected = ' selected'; @@ -1205,7 +1178,7 @@ EOF { $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number } - $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $sMandatory, sFormId, $sNullValue) } );\n"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId, $sNullValue) } );\n"); // Bind to a custom event: validate } $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one if (count($aDependencies) > 0) diff --git a/application/ui.autocompletewidget.class.inc.php b/application/ui.autocompletewidget.class.inc.php index 1d53c373ab..ad7bd6644b 100644 --- a/application/ui.autocompletewidget.class.inc.php +++ b/application/ui.autocompletewidget.class.inc.php @@ -14,7 +14,7 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA /** - * Class UIAutoCompleteWidget + * Class UIExtKeyWidget * UI wdiget for displaying and editing external keys when * A simple drop-down list is not enough... * @@ -61,7 +61,7 @@ require_once(APPROOT.'/application/webpage.class.inc.php'); require_once(APPROOT.'/application/displayblock.class.inc.php'); -class UIAutoCompleteWidget +class UIExtKeyWidget { protected static $iWidgetIndex = 0; protected $sAttCode; @@ -69,7 +69,7 @@ class UIAutoCompleteWidget protected $iId; protected $sTitle; - public function __construct($sAttCode, $sClass, $sTitle, $aAllowedValues, $value, $iInputId, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '') + public function __construct($sAttCode, $sClass, $sTitle, $aAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '') { self::$iWidgetIndex++; $this->sAttCode = $sAttCode; @@ -83,6 +83,7 @@ class UIAutoCompleteWidget $this->sTargetClass = $this->oAttDef->GetTargetClass(); $this->sTitle = $sTitle; $this->sFormPrefix = $sFormPrefix; + $this->bMandatory = $bMandatory; } /** @@ -93,50 +94,74 @@ class UIAutoCompleteWidget */ public function Display(WebPage $oPage, $aArgs = array()) { - if ($this->oAttDef->IsNull($this->value)) // Null values are displayed as '' + $bCreate = (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $this->oAttDef->AllowTargetCreation()); + if (count($this->aAllowedValues) < $this->oAttDef->GetMaximumComboLength()) { - $sDisplayValue = ''; + // Few choices, use a normal 'select' + $sSelectMode = 'true'; + // In case there are no valid values, the select will be empty, thus blocking the user from validating the form + $sHTMLValue = "\n"; } else { - $sDisplayValue = $this->GetObjectName($this->value); - } - $bCreate = UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY); - $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm'); - $sFormPrefix = $this->sFormPrefix; - $oPage->add_ready_script( -<<iId} = new AutocompleteWidget('$this->iId', '$this->sClass', '$this->sAttCode', '$this->sNameSuffix', oWizardHelper$sFormPrefix); - oACWidget_{$this->iId}.emptyHtml = "

      $sMessage

      "; -EOF -); - $iMinChars = $this->oAttDef->GetMinAutoCompleteChars(); - $iFieldSize = $this->oAttDef->GetMaxSize(); + // Too many choices, use an autocomplete + $sSelectMode = 'false'; + + if ($this->oAttDef->IsNull($this->value)) // Null values are displayed as '' + { + $sDisplayValue = ''; + } + else + { + $sDisplayValue = $this->GetObjectName($this->value); + } + $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm'); + $sFormPrefix = $this->sFormPrefix; + $iMinChars = $this->oAttDef->GetMinAutoCompleteChars(); + $iFieldSize = $this->oAttDef->GetMaxSize(); + + // the input for the auto-complete + $sHTMLValue = "aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/> "; + $sHTMLValue .= "iId}.Search();\"> "; + + // another hidden input to store & pass the object's Id + $sHTMLValue .= "iId\" name=\"attr_{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" value=\"$this->value\" />\n"; + + // Scripts to start the autocomplete and bind some events to it + $oPage->add_ready_script("\$('#label_$this->iId').autocomplete('./ajax.render.php', { scroll:true, minChars:{$iMinChars}, formatItem:formatItem, autoFill:false, matchContains:true, keyHolder:'#{$this->iId}', extraParams:{operation:'autocomplete', sclass:'$this->sClass',attCode:'".$this->sAttCode."'}});"); + $oPage->add_ready_script("\$('#label_$this->iId').blur(function() { $(this).search(); } );"); + $oPage->add_ready_script("\$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('$this->iId', event, data, formatted); } );"); + $oPage->add_ready_script("\$('#ac_dlg_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$this->sTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });\n"); + $oPage->add_at_the_end($this->GetSearchDialog($oPage)); // To prevent adding forms inside the main form - // the input for the auto-complete - $sHTMLValue = "aAllowedValues)."\" type=\"text\" id=\"label_$this->iId\" size=\"30\" maxlength=\"$iFieldSize\" value=\"$sDisplayValue\"/> "; - $sHTMLValue .= "iId}.Search();\"> "; + } if ($bCreate) { $sHTMLValue .= "iId}.CreateObject();\"> "; + $oPage->add_at_the_end('
      '); } $sHTMLValue .= "iId}\">"; - - // another hidden input to store & pass the object's Id - $sHTMLValue .= "iId\" name=\"attr_{$this->sFieldPrefix}{$this->sAttCode}{$this->sNameSuffix}\" value=\"$this->value\" />\n"; - - // Scripts to start the autocomplete and bind some events to it - $oPage->add_ready_script("\$('#label_$this->iId').autocomplete('./ajax.render.php', { scroll:true, minChars:{$iMinChars}, formatItem:formatItem, autoFill:false, matchContains:true, keyHolder:'#{$this->iId}', extraParams:{operation:'autocomplete', sclass:'$this->sClass',attCode:'".$this->sAttCode."'}});"); - $oPage->add_ready_script("\$('#label_$this->iId').blur(function() { $(this).search(); } );"); - $oPage->add_ready_script("\$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('$this->iId', event, data, formatted); } );"); - $oPage->add_ready_script("\$('#ac_dlg_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$this->sTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });\n"); - $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$this->sTitle'});\n"); - $oPage->add_at_the_end($this->GetSearchDialog($oPage)); // To prevent adding forms inside the main form - if ($bCreate) - { - $oPage->add_at_the_end($this->GetCreationDialog($oPage)); // To prevent adding forms inside the main form - } - + $oPage->add_ready_script( +<<iId} = new ExtKeyWidget('$this->iId', '$this->sClass', '$this->sAttCode', '$this->sNameSuffix', $sSelectMode, oWizardHelper{$this->sFormPrefix}); + oACWidget_{$this->iId}.emptyHtml = "

      $sMessage

      "; +EOF +); return $sHTMLValue; } @@ -163,23 +188,6 @@ EOF return $sHTML; } - - protected function GetCreationDialog(WebPage $oPage) - { - $sHTML = '
      '; - - //$sHTML .= "iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoCreate();\">\n"; - //cmdbAbstractObject::DisplayCreationForm($oPage, 'Person', null, array()); - //$sHTML .= "iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_create_{$this->iId}').dialog('close');\">  "; - //$sHTML .= "iId}\" value=\"".Dict::S('UI:Button:New')."\" onClick=\"oACWidget_{$this->iId}.DoCreate();\">"; - //$sHTML .= "\n"; - $sHTML .= '
      '; - - //$oPage->add_ready_script("$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);"); - - return $sHTML; - } - /** * Search for objects to be selected * @param WebPage $oP The page used for the output (usually an AjaxWebPage) @@ -218,8 +226,11 @@ EOF */ public function GetObjectCreationForm(WebPage $oPage) { + $oPage->add('
      '); $oPage->add("

      ".MetaModel::GetClassIcon($this->sTargetClass)." ".Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sTargetClass))."

      \n"); cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, null, array(), array('formPrefix' => $this->iId, 'noRelations' => true)); + $oPage->add('
      '); + $oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: $(window).width()*0.8, height: 'auto', autoOpen: false, modal: true, title: '$this->sTitle'});\n"); $oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');"); $oPage->add_ready_script("$('#dcr_{$this->iId} form').bind('submit.uilinksWizard', oACWidget_{$this->iId}.DoCreateObject);"); } diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index c4a9e64581..81febee9b5 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1747,6 +1747,12 @@ class AttributeExternalKey extends AttributeDBFieldVoid { return $this->GetOptional('min_autocomplete_chars', utils::GetConfig()->Get('min_autocomplete_chars')); } + + public function AllowTargetCreation() + { + return $this->GetOptional('allow_target_creation', utils::GetConfig()->Get('allow_target_creation')); + } + } /** diff --git a/core/config.class.inc.php b/core/config.class.inc.php index aa85e2edb2..6ec467d7e1 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -135,6 +135,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'allow_target_creation' => array( + 'type' => 'bool', + 'description' => 'Displays the + button on external keys to create target objects', + 'default' => true, + 'value' => true, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), 'database_read_only' => array( 'type' => 'bool', 'description' => 'Freeze the data for administration purposes - administrators can still do anything... in appearance!', diff --git a/js/autocompletewidget.js b/js/autocompletewidget.js index 93377dd001..048627fd7e 100644 --- a/js/autocompletewidget.js +++ b/js/autocompletewidget.js @@ -13,7 +13,7 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -function AutocompleteWidget(id, sClass, sAttCode, sSuffix, oWizHelper) +function ExtKeyWidget(id, sClass, sAttCode, sSuffix, bSelectMode, oWizHelper) { this.id = id; this.sClass = sClass; @@ -23,6 +23,7 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix, oWizHelper) this.emptyOnClose = true; // Workaround for the JQuery dialog being very slow when opening and closing if the content contains many INPUT tags this.oWizardHelper = oWizHelper; this.ajax_request = null; + this.bSelectMode = bSelectMode; // true if the edited field is a SELECT, false if it's an autocomplete var me = this; this.Init = function() @@ -194,16 +195,13 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix, oWizHelper) // since responses may arrive in arbitrary order me.StopPendingRequest(); - // Run the query and get the result back directly in JSON + // Run the query and get the result back directly in HTML me.ajax_request = $.post( 'ajax.render.php', theMap, function(data) { - $('#dcr_'+me.id).html(data); - // Adjust the height of the dialog + $('#ajax_'+me.id).html(data); $('#ac_create_'+me.id).dialog('open'); - var h = $('#ac_create_'+me.id+' .wizContainer').outerHeight(); - $('#ac_create_'+me.id).dialog( "option", "height", h+55 ); // space for dialog title and padding... - $('#ac_create_'+me.id).dialog( "option", "close", function() { $('#label_'+me.id).removeClass('ac_loading'); $('#label_'+me.id).focus(); } ); + $('#ac_create_'+me.id).dialog( "option", "close", me.OnCloseCreateObject ); // Modify the action of the cancel button $('#ac_create_'+me.id+' button.cancel').unbind('click').click( me.CloseCreateObject ); me.ajax_request = null; @@ -215,7 +213,15 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix, oWizHelper) this.CloseCreateObject = function() { $('#ac_create_'+me.id).dialog( "close" ); - $('#dcr_'+me.id).html(''); + } + + this.OnCloseCreateObject = function() + { + $('#label_'+me.id).removeClass('ac_loading'); + $('#label_'+me.id).focus(); + $('#ac_create_'+me.id).dialog("destroy"); + $('#ac_create_'+me.id).remove(); + $('#ajax_'+me.id).html(''); } this.DoCreateObject = function() @@ -256,11 +262,22 @@ function AutocompleteWidget(id, sClass, sAttCode, sSuffix, oWizHelper) me.ajax_request = $.post( 'ajax.render.php', theMap, function(data) { - $('#label_'+me.id).val(data.name); - $('#'+me.id).val(data.id); + if (me.bSelectMode) + { + // Add the newly created object to the drop-down list and select it + $('
      '; $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste'), $sPasteDataHtml); if (!empty($sCSVData)) From dc96cbc482a18c3c1b32650a85eb37f9efe9a9cb Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 1 Dec 2010 15:21:29 +0000 Subject: [PATCH 919/970] - Ask for confirmation when doing a CSV import/synchro that is considered as "risky" (based on thresholds from the config file) - Added a "Restart" button to quickly start over a CSV import/synchro SVN:trunk[1004] --- core/config.class.inc.php | 33 +++++ dictionaries/dictionary.itop.ui.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 1 + pages/csvimport.php | 165 ++++++++++++++++++++++++- 4 files changed, 197 insertions(+), 3 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index a72c952b0a..51ef331925 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -151,6 +151,39 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + // Levels that trigger a confirmation in the CSV import/synchro wizard + 'csv_import_min_object_confirmation' => array( + 'type' => 'integer', + 'description' => 'Minimum number of objects to check for the confirmation percentages', + 'default' => 3, + 'value' => 3, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), + 'csv_import_errors_percentage' => array( + 'type' => 'integer', + 'description' => 'Percentage of errors that trigger a confirmation in the CSV import', + 'default' => 50, + 'value' => 50, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), + 'csv_import_modifications_percentage' => array( + 'type' => 'integer', + 'description' => 'Percentage of modifications that trigger a confirmation in the CSV import', + 'default' => 50, + 'value' => 50, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), + 'csv_import_creations_percentage' => array( + 'type' => 'integer', + 'description' => 'Percentage of creations that trigger a confirmation in the CSV import', + 'default' => 50, + 'value' => 50, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), ); public function IsProperty($sPropCode) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index a213e742c2..db93edd045 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -336,6 +336,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Button:Cancel' => 'Cancel', 'UI:Button:Apply' => 'Apply', 'UI:Button:Back' => ' << Back ', + 'UI:Button:Restart' => ' |<< Restart ', 'UI:Button:Next' => ' Next >> ', 'UI:Button:Finish' => ' Finish ', 'UI:Button:DoImport' => ' Run the Import ! ', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 3ce4c7df09..e189be5516 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -338,6 +338,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Button:Cancel' => 'Annuler', 'UI:Button:Apply' => 'Appliquer', 'UI:Button:Back' => ' << Retour ', + 'UI:Button:Restart' => ' |<< Recommencer ', 'UI:Button:Next' => ' Suite >> ', 'UI:Button:Finish' => ' Terminer ', 'UI:Button:DoImport' => ' Lancer l\'import ! ', diff --git a/pages/csvimport.php b/pages/csvimport.php index 92a7409fd7..d2ff49f00c 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -524,6 +524,8 @@ try $sHtml .= "$sMessage"; $sHtml .= ''; } + + $iUnchanged = count($aRes) - $iErrors - $iModified - $iCreated; $sHtml .= ''; $oPage->add('
      '); $oPage->add('
      '); @@ -578,10 +580,43 @@ try $oPage->add('
      '); $oPage->add($sHtml); $oPage->add('
      '); - $oPage->add('

        '); + $oPage->add('

      '); + if($bSimulate) + { + $oPage->add('  '); + } + $oPage->add('  '); + + $bShouldConfirm = false; if ($bSimulate) { - $oPage->add('

      '); + // if there are *too many* changes, we should ask the user for a confirmation + if (count($aRes) >= utils::GetConfig()->Get('csv_import_min_object_confirmation')) + { + $fErrorsPercentage = (100.0*$iErrors)/count($aRes); + if ($fErrorsPercentage >= utils::GetConfig()->Get('csv_import_errors_percentage')) + { + $sMessage = sprintf("%.0f %% of the loaded objects will be modified.", $fErrorsPercentage); + $bShouldConfirm = true; + } + $fCreatedPercentage = (100.0*$iCreated)/count($aRes); + if ($fCreatedPercentage >= utils::GetConfig()->Get('csv_import_creations_percentage')) + { + $sMessage = sprintf("%.0f %% of the loaded objects will be modified.", $fCreatedPercentage); + $bShouldConfirm = true; + } + $fModifiedPercentage = (100.0*$iModified)/count($aRes); + if ($fModifiedPercentage >= utils::GetConfig()->Get('csv_import_modifications_percentage')) + { + $sMessage = sprintf("%.0f %% of the loaded objects will be modified.", $fModifiedPercentage); + $bShouldConfirm = true; + } + + } + $iCount = count($aRes); + //$oPage->add('

      '); + $sConfirm = $bShouldConfirm ? 'true' : 'false'; + $oPage->add('

      "); } else { @@ -589,6 +624,40 @@ try } $oPage->add('
      '); $oPage->add('
      '); + + + if ($bShouldConfirm) + { + $oPage->add('
      '); + $oPage->add('

      '.$sMessage.'

      '); + $oPage->add('

      Are you sure you want to do this ?

      '); + $oPage->add('
      '); + $oPage->add('
      '); + $oPage->add_ready_script( +<<add_script( <<< EOF function CSVGoBack() @@ -598,10 +667,92 @@ function CSVGoBack() } +function CSVRestart() +{ + $('input[name=step]').val(1); + $('#wizForm').submit(); + +} + function ToggleRows(sCSSClass) { $('.'+sCSSClass).toggle(); } + +function DoSubmit(bConfirm) +{ + if (bConfirm) //Ask for a confirmation + { + $('#dlg_confirmation').dialog('open'); + } + else + { + // Submit the form + $('#wizForm').block(); + $('#wizForm').submit(); + } + return false; +} + +function CancelImport() +{ + $('#dlg_confirmation').dialog('close'); +} + +function RunImport() +{ + $('#dlg_confirmation').dialog('close'); + // Submit the form + $('#wizForm').block(); + $('#wizForm').submit(); +} + +function open_flash_chart_data() +{ + var oResult = { + "elements": [ + { + "type": "pie", + "tip": "#label# - #val# (#percent#)", + "font-size": 14, + "colours": + [ + "#FF6666", + "#6666FF", + "#66FF66", + "#666666", + ], + "values": + [ + { + "value": $iErrors, + "label": "Errors", + "alpha": 0.9 + }, + { + "value": $iModified, + "label": "Modified", + "alpha": 0.9 + }, + { + "value": $iCreated, + "label": "Created", + "alpha": 0.9 + }, + { + "value": $iUnchanged, + "label": "Unchanged", + "alpha": 0.9 + } + ] + } + ], + "x_axis": null, + "font-size": 14, + "bg_colour": "#EEEEEE" + }; + return JSON.stringify(oResult); +} EOF ); if ($iErrors > 0) @@ -716,7 +867,8 @@ EOF $oPage->add(''); } } - $oPage->add('

        '); + $oPage->add('

        '); + $oPage->add('  '); $oPage->add('

      '); $oPage->add(''); $oPage->add('
      '); @@ -746,6 +898,13 @@ EOF } + function CSVRestart() + { + $('input[name=step]').val(1); + $('#wizForm').submit(); + + } + var ajax_request = null; function DoMapping() From d4eaf43257d11597d7217dfd3ecca505f2fb8ba7 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 1 Dec 2010 16:27:11 +0000 Subject: [PATCH 920/970] Fixed cosmetic issues in the CSV import (related to the recent changes) SVN:trunk[1005] --- core/bulkchange.class.inc.php | 4 ++-- pages/csvimport.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index af1564bd8f..d25e1aac71 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -516,7 +516,7 @@ class BulkChange { $sErrors = implode(', ', $aErrors); $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)"); - return; + return $oTargetObj; } // Check that any external key will have a value proposed @@ -535,7 +535,7 @@ class BulkChange { $sMissingKeys = implode(', ', $aMissingKeys); $aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Could not be created, due to missing external key(s): $sMissingKeys"); - return; + return $oTargetObj; } // Optionaly record the results diff --git a/pages/csvimport.php b/pages/csvimport.php index d2ff49f00c..6b9aef28d8 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -596,13 +596,13 @@ try $fErrorsPercentage = (100.0*$iErrors)/count($aRes); if ($fErrorsPercentage >= utils::GetConfig()->Get('csv_import_errors_percentage')) { - $sMessage = sprintf("%.0f %% of the loaded objects will be modified.", $fErrorsPercentage); + $sMessage = sprintf("%.0f %% of the loaded objects have errors and will be ignored.", $fErrorsPercentage); $bShouldConfirm = true; } $fCreatedPercentage = (100.0*$iCreated)/count($aRes); if ($fCreatedPercentage >= utils::GetConfig()->Get('csv_import_creations_percentage')) { - $sMessage = sprintf("%.0f %% of the loaded objects will be modified.", $fCreatedPercentage); + $sMessage = sprintf("%.0f %% of the loaded objects will be created.", $fCreatedPercentage); $bShouldConfirm = true; } $fModifiedPercentage = (100.0*$iModified)/count($aRes); From 53d5867b9387af488d0f99172c4dae160f8c6cf0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 3 Dec 2010 09:10:15 +0000 Subject: [PATCH 921/970] Patch to support a different data model where contacts are not part of an org SVN:trunk[1006] --- addons/userrights/userrightsprofile.class.inc.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index 5cdad21c89..fc25b778c3 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -442,7 +442,11 @@ class UserRightsProfile extends UserRightsAddOnAPI $oContact->Set('name', 'My last name'); $oContact->Set('first_name', 'My first name'); //$oContact->Set('status', 'available'); - $oContact->Set('org_id', $iOrgId); + if (MetaModel::IsValidAttCode('Person', 'org_id')) + { + // Protect for a different data model where contacts are not part of an org + $oContact->Set('org_id', $iOrgId); + } $oContact->Set('email', 'my.email@foo.org'); //$oContact->Set('phone', ''); //$oContact->Set('location_id', $iLocationId); From d85aba8ebc860b05605bde17f1e1d8bfda538701 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 3 Dec 2010 10:18:28 +0000 Subject: [PATCH 922/970] Improved change tracking: user login replaced by the full name if available Added a tab into the CSV import: browse the CSV imports history Finalized the read-only mode (distinguish between users and everybody, admin message displayed on top of the main screen) SVN:trunk[1007] --- application/itopwebpage.class.inc.php | 28 +- application/ui.extkeywidget.class.inc.php | 9 +- core/bulkchange.class.inc.php | 266 ++++++++++++++++++ core/cmdbchange.class.inc.php | 28 ++ core/config.class.inc.php | 29 +- core/metamodel.class.php | 26 +- core/userrights.class.inc.php | 68 ++++- css/light-grey.css | 10 +- dictionaries/de.dictionary.itop.core.php | 3 +- dictionaries/de.dictionary.itop.ui.php | 8 + dictionaries/dictionary.itop.core.php | 1 + dictionaries/dictionary.itop.ui.php | 11 + dictionaries/es_cr.dictionary.itop.ui.php | 11 + dictionaries/fr.dictionary.itop.core.php | 1 + dictionaries/fr.dictionary.itop.ui.php | 11 + dictionaries/pt_br.dictionary.itop.ui.php | 9 + images/locked.png | Bin 0 -> 4233 bytes images/unlocked.png | Bin 0 -> 4116 bytes modules/authent-local/model.authent-local.php | 9 +- pages/UI.php | 54 +--- pages/ajax.render.php | 5 + pages/csvimport.php | 21 +- portal/index.php | 18 +- webservices/import.php | 9 +- 24 files changed, 503 insertions(+), 132 deletions(-) create mode 100644 images/locked.png create mode 100644 images/unlocked.png diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 3fc98dc15e..76ecbc1553 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -632,13 +632,32 @@ EOF } $sLogOffMenu .= "
    \n\n\n"; - if (MetaModel::DBIsReadOnly()) + $sRestrictions = ''; + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) { - $sApplicationMode = Dict::S('UI:ApplicationReadOnly'); + if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE)) + { + $sRestrictions = Dict::S('UI:AccessRO-All'); + } + } + elseif (!MetaModel::DBHasAccess(ACCESS_USER_WRITE)) + { + $sRestrictions = Dict::S('UI:AccessRO-Users'); + } + + if (strlen($sRestrictions) > 0) + { + $sAdminMessage = trim(utils::GetConfig()->Get('access_message')); + $sApplicationBanner = ''; + $sApplicationBanner .= ' '.$sRestrictions.''; + if (strlen($sAdminMessage) > 0) + { + $sApplicationBanner .= ' '.$sAdminMessage.''; + } } else { - $sApplicationMode = ''; + $sApplicationBanner = ''; } //$sLogOffMenu = ""; @@ -667,8 +686,9 @@ EOF echo '
    '; echo '
    '; + echo '
    '.$sApplicationBanner.'
    '; echo ' '; //echo '        
    '; diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index 10374b6566..b4c32690cb 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -258,14 +258,7 @@ EOF $oObj->UpdateObject($this->sFormPrefix.$this->iId); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index d25e1aac71..ea071dfaee 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -789,6 +789,272 @@ class BulkChange return $aResult; } + + /** + * Display the history of bulk imports + */ + static function DisplayImportHistory(WebPage $oPage, $bFromAjax = false, $bShowAll = false) + { + $sAjaxDivId = "CSVImportHistory"; + if (!$bFromAjax) + { + $oPage->add('
    '); + } + + $oPage->p(Dict::S('UI:History:BulkImports+')); + + $oBulkChangeSearch = DBObjectSearch::FromOQL("SELECT CMDBChange WHERE userinfo LIKE '%(CSV)'"); + + $iQueryLimit = $bShowAll ? 0 : utils::GetConfig()->GetMaxDisplayLimit() + 1; + $oBulkChanges = new DBObjectSet($oBulkChangeSearch, array('date' => false), array(), $iQueryLimit); + + $oAppContext = new ApplicationContext(); + + $bLimitExceeded = false; + if ($oBulkChanges->Count() > utils::GetConfig()->GetMaxDisplayLimit()) + { + $bLimitExceeded = true; + if (!$bShowAll) + { + $iMaxObjects = utils::GetConfig()->GetMinDisplayLimit(); + $oBulkChanges->SetLimit($iMaxObjects); + } + } + $oBulkChanges->Seek(0); + + $aDetails = array(); + while ($oChange = $oBulkChanges->Fetch()) + { + $sDate = ''.$oChange->Get('date').''; + $sUser = $oChange->GetUserName(); + if (preg_match('/^(.*)\\(CSV\\)$/i', $oChange->Get('userinfo'), $aMatches)) + { + $sUser = $aMatches[1]; + } + else + { + $sUser = $oChange->Get('userinfo'); + } + + $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOpCreate WHERE change = :change_id"); + $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $oChange->GetKey())); + $iCreated = $oOpSet->Count(); + + //while ($oCreated = $oOpSet->Fetch()) + //{ + // $oPage->p("Created ".$oCreated->Get('objclass')."::".$oCreated->Get('objkey')); + //} + + $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOpSetAttribute WHERE change = :change_id"); + $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $oChange->GetKey())); + + $aModified = array(); + $aAttList = array(); + while ($oModified = $oOpSet->Fetch()) + { + $sClass = $oModified->Get('objclass'); + $iKey = $oModified->Get('objkey'); + $sAttCode = $oModified->Get('attcode'); + + $aAttList[$sClass][$sAttCode] = true; + $aModified["$sClass::$iKey"] = true; + } + $iModified = count($aModified); + + // Assumption: there is only one class of objects being loaded + // Then the last class found gives us the class for every object + + $aDetails[] = array('date' => $sDate, 'user' => $sUser, 'class' => $sClass, 'created' => $iCreated, 'modified' => $iModified); + + } + + $aConfig = array( 'date' => array('label' => Dict::S('UI:History:Date'), 'description' => Dict::S('UI:History:Date+')), + 'user' => array('label' => Dict::S('UI:History:User'), 'description' => Dict::S('UI:History:User+')), + 'class' => array('label' => Dict::S('Core:AttributeClass'), 'description' => Dict::S('Core:AttributeClass+')), + 'created' => array('label' => Dict::S('UI:History:StatsCreations'), 'description' => Dict::S('UI:History:StatsCreations+')), + 'modified' => array('label' => Dict::S('UI:History:StatsModifs'), 'description' => Dict::S('UI:History:StatsModifs+')), + ); + + if ($bLimitExceeded) + { + if ($bShowAll) + { + // Collapsible list + $oPage->add('

    '.Dict::Format('UI:CountOfResults', $oBulkChanges->Count()).'  '.Dict::S('UI:CollapseList').'

    '); + } + else + { + // Truncated list + $iMinDisplayLimit = utils::GetConfig()->GetMinDisplayLimit(); + $sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oBulkChanges->Count()); + $sLinkLabel = Dict::S('UI:DisplayAll'); + $oPage->add('

    '.$sCollapsedLabel.'  '.$sLinkLabel.'

    '); + + $oPage->add_ready_script( +<<GetForLink(); + $oPage->add_script( +<<table($aConfig, $aDetails); + + if (!$bFromAjax) + { + $oPage->add('
    '); + } + } + + /** + * Display the details of an import + */ + static function DisplayImportHistoryDetails(iTopWebPage $oPage, $iChange) + { + if ($iChange == 0) + { + throw new Exception("Missing parameter changeid"); + } + $oChange = MetaModel::GetObject('CMDBChange', $iChange, false); + if (is_null($oChange)) + { + throw new Exception("Unknown change: $iChange"); + } + $oPage->add("

    ".Dict::Format('UI:History:BulkImportDetails', $oChange->Get('date'), $oChange->GetUserName())."

    \n"); + + // Assumption : change made one single class of objects + $aObjects = array(); + $aAttributes = array(); // array of attcode => occurences + + $oOpSearch = DBObjectSearch::FromOQL("SELECT CMDBChangeOp WHERE change = :change_id"); + $oOpSet = new DBObjectSet($oOpSearch, array(), array('change_id' => $iChange)); + while ($oOperation = $oOpSet->Fetch()) + { + $sClass = $oOperation->Get('objclass'); + $iKey = $oOperation->Get('objkey'); + $iObjId = "$sClass::$iKey"; + if (!isset($aObjects[$iObjId])) + { + $aObjects[$iObjId] = array(); + $aObjects[$iObjId]['__class__'] = $sClass; + $aObjects[$iObjId]['__id__'] = $iKey; + } + if (get_class($oOperation) == 'CMDBChangeOpCreate') + { + $aObjects[$iObjId]['__created__'] = true; + } + elseif (is_subclass_of($oOperation, 'CMDBChangeOpSetAttribute')) + { + $sAttCode = $oOperation->Get('attcode'); + + if (get_class($oOperation) == 'CMDBChangeOpSetAttributeScalar') + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsExternalKey()) + { + $oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue')); + $oNewTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('newvalue')); + $sOldValue = $oOldTarget->GetHyperlink(); + $sNewValue = $oNewTarget->GetHyperlink(); + } + else + { + $sOldValue = $oOperation->GetAsHTML('oldvalue'); + $sNewValue = $oOperation->GetAsHTML('newvalue'); + } + $aObjects[$iObjId][$sAttCode] = $sOldValue.' -> '.$sNewValue; + } + else + { + $aObjects[$iObjId][$sAttCode] = 'n/a'; + } + + if (isset($aAttributes[$sAttCode])) + { + $aAttributes[$sAttCode]++; + } + else + { + $aAttributes[$sAttCode] = 1; + } + } + } + + $aDetails = array(); + foreach($aObjects as $iUId => $aObjData) + { + $aRow = array(); + $oObject = MetaModel::GetObject($aObjData['__class__'], $aObjData['__id__'], false); + if (is_null($oObject)) + { + $aRow['object'] = $aObjData['__class__'].'::'.$aObjData['__id__'].' (deleted)'; + } + else + { + $aRow['object'] = $oObject->GetHyperlink(); + } + if (isset($aObjData['__created__'])) + { + $aRow['operation'] = Dict::S('Change:ObjectCreated'); + } + else + { + $aRow['operation'] = Dict::S('Change:ObjectModified'); + } + foreach ($aAttributes as $sAttCode => $iOccurences) + { + if (isset($aObjData[$sAttCode])) + { + $aRow[$sAttCode] = $aObjData[$sAttCode]; + } + elseif (!is_null($oObject)) + { + // This is the current vaslue: $oObject->GetAsHtml($sAttCode) + // whereas we are displaying the value that was set at the time + // the object was created + // This requires addtional coding...let's do that later + $aRow[$sAttCode] = ''; + } + else + { + $aRow[$sAttCode] = ''; + } + } + $aDetails[] = $aRow; + } + + $aConfig = array(); + $aConfig['object'] = array('label' => MetaModel::GetName($sClass), 'description' => MetaModel::GetClassDescription($sClass)); + $aConfig['operation'] = array('label' => Dict::S('UI:History:Changes'), 'description' => Dict::S('UI:History:Changes+')); + foreach ($aAttributes as $sAttCode => $iOccurences) + { + $aConfig[$sAttCode] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'description' => MetaModel::GetDescription($sClass, $sAttCode)); + } + $oPage->table($aConfig, $aDetails); + } } diff --git a/core/cmdbchange.class.inc.php b/core/cmdbchange.class.inc.php index 4f7a0c80c9..db2a9f84b4 100644 --- a/core/cmdbchange.class.inc.php +++ b/core/cmdbchange.class.inc.php @@ -49,6 +49,34 @@ class CMDBChange extends DBObject MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); } + + // Helper to keep track of the author of a given change, + // taking into account a variety of cases (contact attached or not, impersonation) + static public function GetCurrentUserName() + { + if (UserRights::IsImpersonated()) + { + $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUserFriendlyName(), UserRights::GetUserFriendlyName()); + } + else + { + $sUserString = UserRights::GetUserFriendlyName(); + } + return $sUserString; + } + + public function GetUserName() + { + if (preg_match('/^(.*)\\(CSV\\)$/i', $this->Get('userinfo'), $aMatches)) + { + $sUser = $aMatches[1]; + } + else + { + $sUser = $this->Get('userinfo'); + } + return $sUser; + } } ?> diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 51ef331925..87de78e8de 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -18,6 +18,11 @@ define('ITOP_VERSION', '$ITOP_VERSION$'); define('ITOP_REVISION', '$WCREV$'); define('ITOP_BUILD_DATE', '$WCNOW$'); +define('ACCESS_USER_WRITE', 1); +define('ACCESS_ADMIN_WRITE', 2); +define('ACCESS_FULL', ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE); +define('ACCESS_READONLY', 0); + /** * Configuration read/write * @@ -143,14 +148,6 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), - 'database_read_only' => array( - 'type' => 'bool', - 'description' => 'Freeze the data for administration purposes - administrators can still do anything... in appearance!', - 'default' => false, - 'value' => '', - 'source_of_value' => '', - 'show_in_conf_sample' => false, - ), // Levels that trigger a confirmation in the CSV import/synchro wizard 'csv_import_min_object_confirmation' => array( 'type' => 'integer', @@ -184,6 +181,22 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'access_mode' => array( + 'type' => 'integer', + 'description' => 'Combination of flags (ACCESS_USER_WRITE | ACCESS_ADMIN_WRITE, or ACCESS_FULL)', + 'default' => ACCESS_FULL, + 'value' => ACCESS_FULL, + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), + 'access_message' => array( + 'type' => 'string', + 'description' => 'Message displayed to the users when there is any access restriction', + 'default' => 'iTop is temporarily frozen, please wait... (the admin team)', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), ); public function IsProperty($sPropCode) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6877746958..40de91821b 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -2547,28 +2547,26 @@ abstract class MetaModel /* * Determines wether the target DB is frozen or not - * 1 - consider the DB property 'status' - * 2 - check the setting 'database_read_only' */ public static function DBIsReadOnly() { - $sStatus = DBProperty::GetProperty('status', null); - if (!is_null($sStatus)) + // Improvement: check the mySQL variable -> Read-only + + if (UserRights::IsAdministrator()) { - switch (strtolower(trim($sStatus))) - { - case 'fullaccess': - $ret = false; - break; - default: - $ret = true; - } + return (!self::DBHasAccess(ACCESS_ADMIN_WRITE)); } else { - $ret = self::$m_oConfig->Get('database_read_only'); + return (!self::DBHasAccess(ACCESS_USER_WRITE)); } - return $ret; + } + + public static function DBHasAccess($iRequested = ACCESS_FULL) + { + $iMode = self::$m_oConfig->Get('access_mode'); + if (($iMode & $iRequested) == 0) return false; + return true; } protected static function MakeDictEntry($sKey, $sValueFromOldSystem, $sDefaultValue, &$bNotInDico) diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php index bd3bb81e2d..7ef26d117d 100644 --- a/core/userrights.class.inc.php +++ b/core/userrights.class.inc.php @@ -110,6 +110,35 @@ abstract class User extends cmdbAbstractObject abstract public function CanChangePassword(); abstract public function ChangePassword($sOldPassword, $sNewPassword); + /* + * Compute a name in best effort mode + */ + public function GetFriendlyName() + { + if (!MetaModel::IsValidAttCode(get_class($this), 'contactid')) + { + return $this->Get('login'); + } + if ($this->Get('contactid') != 0) + { + $sFirstName = $this->Get('first_name'); + $sLastName = $this->Get('last_name'); + $sEmail = $this->Get('email'); + if (strlen($sFirstName) > 0) + { + return "$sFirstName $sLastName"; + } + elseif (strlen($sEmail) > 0) + { + return "$sLastName <$sEmail>"; + } + else + { + return $sLastName; + } + } + } + /* * Overload the standard behavior */ @@ -490,6 +519,24 @@ class UserRights return $oUser->Get('contactid'); } + // Render the user name in best effort mode + public static function GetUserFriendlyName($sName = '') + { + if (empty($sName)) + { + $oUser = self::$m_oUser; + } + else + { + $oUser = FindUser($sName); + } + if (is_null($oUser)) + { + return ''; + } + return $oUser->GetFriendlyName(); + } + public static function IsImpersonated() { if (is_null(self::$m_oRealUser)) @@ -517,6 +564,15 @@ class UserRights return self::$m_oRealUser->GetKey(); } + public static function GetRealUserFriendlyName() + { + if (is_null(self::$m_oRealUser)) + { + return ''; + } + return self::$m_oRealUser->GetFriendlyName(); + } + protected static function CheckLogin() { if (!self::IsLoggedIn()) @@ -551,8 +607,6 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; - if (self::IsAdministrator($oUser)) return true; - if (MetaModel::DBIsReadOnly()) { if ($iActionCode == UR_ACTION_MODIFY) return false; @@ -561,6 +615,8 @@ class UserRights if ($iActionCode == UR_ACTION_BULK_DELETE) return false; } + if (self::IsAdministrator($oUser)) return true; + if (MetaModel::HasCategory($sClass, 'bizmodel')) { // #@# Temporary????? @@ -589,8 +645,6 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; - if (self::IsAdministrator($oUser)) return true; - if (MetaModel::DBIsReadOnly()) { if ($iActionCode == UR_ACTION_MODIFY) return false; @@ -599,6 +653,8 @@ class UserRights if ($iActionCode == UR_ACTION_BULK_DELETE) return false; } + if (self::IsAdministrator($oUser)) return true; + if (MetaModel::HasCategory($sClass, 'bizmodel')) { if (is_null($oUser)) @@ -619,8 +675,6 @@ class UserRights // When initializing, we need to let everything pass trough if (!self::CheckLogin()) return true; - if (self::IsAdministrator($oUser)) return true; - if (MetaModel::DBIsReadOnly()) { if ($iActionCode == UR_ACTION_MODIFY) return false; @@ -629,6 +683,8 @@ class UserRights if ($iActionCode == UR_ACTION_BULK_DELETE) return false; } + if (self::IsAdministrator($oUser)) return true; + // this module is forbidden for non admins if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false; diff --git a/css/light-grey.css b/css/light-grey.css index df86f51355..0cdd244161 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -807,6 +807,14 @@ div#logo div { background: url(../images/banner-bkg.png) repeat-x scroll 0 0 transparent; text-align: right; } +#admin-banner { + float: left; + margin-top: 2px; + padding: 8px; + border: 1px solid #c33; + background-color: #fee; + -moz-border-radius: 0.5em; +} #global-search { height: 55px; float: right; @@ -908,4 +916,4 @@ span.form_validation { text-align:center; width: 95%; -moz-border-radius: 0.5em; -} \ No newline at end of file +} diff --git a/dictionaries/de.dictionary.itop.core.php b/dictionaries/de.dictionary.itop.core.php index 70d7f591c2..b55bf3bf56 100644 --- a/dictionaries/de.dictionary.itop.core.php +++ b/dictionaries/de.dictionary.itop.core.php @@ -203,7 +203,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( // Used by CMDBChangeOp... & derived classes Dict::Add('DE DE', 'German', 'Deutsch', array( 'Change:ObjectCreated' => 'Objekt erstellt', - 'Change:ObjectDeleted' => 'Objekt erstellt', + 'Change:ObjectDeleted' => 'Object deleted', + 'Change:ObjectModified' => 'Object modified', 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s geändert zu %2$s (vorheriger Wert: %3$s)', 'Change:Text_AppendedTo_AttName' => '%1$s zugefügt an %2$s', 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifiziert, vorheriger Wert: %2$s', diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 45d82e464a..784f624029 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -439,6 +439,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:History:User+' => 'Benutzer, der die Änderung durchführte', 'UI:History:Changes' => 'Änderungen', 'UI:History:Changes+' => 'Änderungen, die am Objekt durchgeführt wurden', + 'UI:History:StatsCreations' => 'Created', + 'UI:History:StatsCreations+' => 'Count of objects created', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Laden...', 'UI:Menu:Actions' => 'Aktionen', 'UI:Menu:New' => 'Neu...', @@ -481,6 +487,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'UI:LogOff:ThankYou' => 'Vielen Dank dafür, dass Sie iTop benutzen!', 'UI:LogOff:ClickHereToLoginAgain' => 'Klicken Sie hier, um sich wieder anzumelden...', 'UI:ChangePwdMenu' => 'Passwort ändern...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'Neues Passwort und das wiederholte Passwort entsprechen nicht überein!', 'UI:Button:Login' => 'iTop betreten', 'UI:Login:Error:AccessRestricted' => 'Der iTop-Zugang ist gesperrt. Bitte kontaktieren Sie einen iTop-Administrator.', diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index 5c1047a2f1..67b9eb2908 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -203,6 +203,7 @@ Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array( 'Change:ObjectCreated' => 'Object created', 'Change:ObjectDeleted' => 'Object deleted', + 'Change:ObjectModified' => 'Object modified', 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s set to %2$s (previous value: %3$s)', 'Change:Text_AppendedTo_AttName' => '%1$s appended to %2$s', 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modified, previous value: %2$s', diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index db93edd045..294582d009 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -399,12 +399,21 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', 'UI:NotificationsTab' => 'Notifications', + 'UI:History:BulkImports' => 'History', + 'UI:History:BulkImports+' => 'List of CSV imports (last first)', + 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date of the change', 'UI:History:User' => 'User', 'UI:History:User+' => 'User who made the change', 'UI:History:Changes' => 'Changes', 'UI:History:Changes+' => 'Changes made to the object', + 'UI:History:StatsCreations' => 'Created', + 'UI:History:StatsCreations+' => 'Count of objects created', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Loading...', 'UI:Menu:Actions' => 'Actions', 'UI:Menu:New' => 'New...', @@ -447,6 +456,8 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:LogOff:ThankYou' => 'Thank you for using iTop', 'UI:LogOff:ClickHereToLoginAgain' => 'Click here to login again...', 'UI:ChangePwdMenu' => 'Change Password...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 22531fe3b8..9c04ad7098 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -406,12 +406,21 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', + 'UI:History:BulkImports' => 'History', + 'UI:History:BulkImports+' => 'List of CSV imports (last first)', + 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date of the change', 'UI:History:User' => 'User', 'UI:History:User+' => 'User who made the change', 'UI:History:Changes' => 'Changes', 'UI:History:Changes+' => 'Changes made to the object', + 'UI:History:StatsCreations' => 'Created', + 'UI:History:StatsCreations+' => 'Count of objects created', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Loading...', 'UI:Menu:Actions' => 'Actions', 'UI:Menu:New' => 'New...', @@ -451,6 +460,8 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Login:IncorrectOldPassword' => 'Error: the old password is incorrect', 'UI:LogOffMenu' => 'Log off', 'UI:ChangePwdMenu' => 'Change Password...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 575a66aa18..b5d0e64344 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -204,6 +204,7 @@ Dict::Add('FR FR', 'French', 'Français', array( Dict::Add('FR FR', 'French', 'Français', array( 'Change:ObjectCreated' => 'Elément créé', 'Change:ObjectDeleted' => 'Elément effacé', + 'Change:ObjectModified' => 'Elément modifié', 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s modifié en %2$s (ancienne valeur: %3$s)', 'Change:Text_AppendedTo_AttName' => '%1$s ajouté à %2$s', 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifié, ancienne valeur: %2$s', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index e189be5516..80ddb3f8b3 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -400,12 +400,21 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:History:LastModified_On_By' => 'Dernière modification par %2$s le %1$s.', 'UI:HistoryTab' => 'Historique', 'UI:NotificationsTab' => 'Notifications', + 'UI:History:BulkImports' => 'Historique', + 'UI:History:BulkImports+' => 'Liste des imports CSV (le dernier est en haut de la liste)', + 'UI:History:BulkImportDetails' => 'Changements résultant de l\'import CSV du %1$s (auteur: %2$s)', 'UI:History:Date' => 'Date', 'UI:History:Date+' => 'Date de modification', 'UI:History:User' => 'Utilisateur', 'UI:History:User+' => 'Utilisateur qui a fait la modification', 'UI:History:Changes' => 'Changements', 'UI:History:Changes+' => 'Changements sur cet objet', + 'UI:History:StatsCreations' => 'Créés', + 'UI:History:StatsCreations+' => 'Nombre d\'objets créés', + 'UI:History:StatsModifs' => 'Modifiés', + 'UI:History:StatsModifs+' => 'Nombre d\'objets modifiés', + 'UI:History:StatsDeletes' => 'Effacés', + 'UI:History:StatsDeletes+' => 'Nombre d\'objets effacés', 'UI:Loading' => 'Chargement...', 'UI:Menu:Actions' => 'Actions', 'UI:Menu:New' => 'Créer...', @@ -448,6 +457,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:LogOff:ThankYou' => 'Merci d\'avoir utilisé iTop', 'UI:LogOff:ClickHereToLoginAgain' => 'Cliquez ici pour vous reconnecter...', 'UI:ChangePwdMenu' => 'Changer de mot de passe...', + 'UI:AccessRO-All' => 'iTop est en lecture seule', + 'UI:AccessRO-Users' => 'iTop est en lecture seule pour les utilisateurs finaux', 'UI:Login:RetypePwdDoesNotMatch' => 'Les deux saisies du nouveau mot de passe ne sont pas identiques !', 'UI:Button:Login' => 'Entrer dans iTop', 'UI:Login:Error:AccessRestricted' => 'L\'accès à iTop est soumis à autorisation. Merci de contacter votre administrateur iTop.', diff --git a/dictionaries/pt_br.dictionary.itop.ui.php b/dictionaries/pt_br.dictionary.itop.ui.php index 3ad8ccd737..0262db4f6a 100644 --- a/dictionaries/pt_br.dictionary.itop.ui.php +++ b/dictionaries/pt_br.dictionary.itop.ui.php @@ -408,12 +408,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:NoObject_Class_ToDisplay' => 'Nenhum %1$s para mostrar', 'UI:History:LastModified_On_By' => 'Ultima modificacao de %1$s por %2$s.', 'UI:HistoryTab' => 'Historico', + 'UI:History:BulkImports' => 'History', + 'UI:History:BulkImports+' => 'List of CSV imports (last first)', + 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', 'UI:History:Date' => 'Data', 'UI:History:Date+' => 'Data da alteração', 'UI:History:User' => 'Usuario', 'UI:History:User+' => 'Usuario que fez alteração', 'UI:History:Changes' => 'Alterações', 'UI:History:Changes+' => 'Alterações feita no objeto', + 'UI:History:StatsModifs' => 'Modified', + 'UI:History:StatsModifs+' => 'Count of objects modified', + 'UI:History:StatsDeletes' => 'Deleted', + 'UI:History:StatsDeletes+' => 'Count of objects deleted', 'UI:Loading' => 'Carregando...', 'UI:Menu:Actions' => 'Ações', 'UI:Menu:New' => 'Novo...', @@ -453,6 +460,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'UI:Login:IncorrectOldPassword' => 'Erro: senha incorreta', 'UI:LogOffMenu' => 'Sair', 'UI:ChangePwdMenu' => 'Alterar senha...', + 'UI:AccessRO-All' => 'iTop is read-only', + 'UI:AccessRO-Users' => 'iTop is read-only for end-users', 'UI:Login:RetypePwdDoesNotMatch' => 'A nova senha nao confere!', 'UI:Button:Login' => 'Enter iTop', 'UI:Login:Error:AccessRestricted' => 'iTop accesso restrito. Por favor, contate o suporte.', diff --git a/images/locked.png b/images/locked.png new file mode 100644 index 0000000000000000000000000000000000000000..b00530a3258f4e728b050f7ec969ba5bbcc79c02 GIT binary patch literal 4233 zcmV;45O(j0P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000HFNklWZ0hkk8u}&Yu*^FN{ZrKGSA>tZ5ns{oa48?aj9?U4HkK zFMj(6R}ZqD90cIPZ(jdeuk61v-q;vY?NCi8)OE%9@DYxmdV-yuJF{z-F1~Q~`LF-t zp#%Kkx4-&MQIxOdwwAIG3Y>cJ|EcSWl=JEBn>U;|&r_fHtX;kQ@9%!>?B{;I%DQfq zq&I%~vomdLFHdgYlIQ~=3|}jI{YRgA=F5+N{Mm0jerK}zXn%d}we_P%`OAejpm1ey!P(J zINJEYIp9kVtiVry{9-?)^xXApSD4rHi#`-D?YWr9OJ&)+xOwFwYokNY{pt7T`wt9I zPbW`~*VoJGPB(!5YpvRw39I;Mm5P7y`}*T-d*BeH=h+Xmcj( zLjZ`uff>Wm28Y&VNVD?XfwC9v|;b zZe3lEg^R0~zyxsxDuLu8zJTFyL2eo(fOr@jar6g55U>PMz?VjhP(;I=(Y67yqPa!Y zR`-Aa&VhFjU51qLjp7&J6#9K|5uy{Ef@MPltP;!_QbKTs_ZgK$&I{@)tqzbIC8vyK zCCxLrfs}-%g_OYcg>2vzP(i$b2g{urqMeF&+fZ|2u{a1o+X4YKL$jgU$)d?fl|V9F z0gB+95eg$j$n(4M(aj7K5RU+R9Xsv|P|sTyRfT8?3eE{&SQ46<;j$1!PzTl%5<;pC zFA)16-mq3MOPDsqqR)xrgVh0I84<~-DyE=~p|znE=mN})as-+oRfYiGzBr!379IC=w(AZ4K{*(1Y0P3@Iw$Kmkhzsm5$Z+f>wbyE;JIEXeaY+RU+e zMjOzyWXjOa!2s!lFTwW(r-m##MJz2%XNFltyV#+s56}gvy_;-k0&O8T(9EFOfyG?N zL2xDH4AkIM@MhiiRj^=Py%5YY*$yy>ieAK(5%WSAG>{sY8fpH1-Ng*=KoG3yB$r}Y zJ`ATUeWM_bvh=G1^wtK1sQ9Qz&m&bQzogDwy*}U}ln@HA))4E=N9bOx4WJ9m8W7nN z;J&?~Dwt~^dYoUnzwFFr3EFDuc7aqY?T+EhhZ@BJQ@;g;~Bl fbZ_KkuKjNSldIeK0^+nu00000NkvXXu0mjfA>jwE literal 0 HcmV?d00001 diff --git a/images/unlocked.png b/images/unlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..69a09fd824970f9ab148a599a17a2f9839075bf5 GIT binary patch literal 4116 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'password', $sNewPassword); $oChange = MetaModel::NewObject("CMDBChange"); $oChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oChange->Set("userinfo", $sUserString); $oChange->DBInsert(); $this->DBUpdateTracked($oChange, true); diff --git a/pages/UI.php b/pages/UI.php index 5ef83c13a4..e6a42faf78 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -130,14 +130,7 @@ function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bDeleteConfirmed) // $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $oMyChange->DBInsert(); @@ -918,14 +911,7 @@ try $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); @@ -1055,14 +1041,7 @@ try { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); @@ -1111,14 +1090,7 @@ try $sClassLabel = MetaModel::GetName($sClass); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBInsertTracked($oMyChange); @@ -1276,14 +1248,7 @@ EOF { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oObj->DBUpdateTracked($oMyChange); @@ -1328,14 +1293,7 @@ EOF $sLinkingAttCode = utils::ReadPostedParam('linking_attcode', ''); $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index c51c3c6623..41579f8c49 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -206,6 +206,11 @@ try } break; + case 'displayCSVHistory': + $bShowAll = (utils::ReadParam('showall', 'false') == 'true'); + BulkChange::DisplayImportHistory($oPage, true, $bShowAll); + break; + case 'details': $key = utils::ReadParam('id', 0); $oFilter = new DBObjectSearch($sClass); diff --git a/pages/csvimport.php b/pages/csvimport.php index 6b9aef28d8..9844889870 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -35,7 +35,6 @@ try require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed - $oAppContext = new ApplicationContext(); $iStep = utils::ReadParam('step', 1); $oPage = new iTopWebPage(Dict::S('UI:Title:BulkImport')); @@ -344,14 +343,7 @@ try // We're doing it for real, let's create a change $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $sUserString .= ' (CSV)'; $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); @@ -1246,7 +1238,7 @@ EOF ); $oPage->add_ready_script('DoPreview();'); } - + /** * Prompt for the data to be loaded (either via a file or a copy/paste) * @param WebPage $oPage The current web page @@ -1412,10 +1404,19 @@ $('#select_template_class').change( function() { }); EOF ); + + $oPage->SetCurrentTabContainer('tabs1'); + $oPage->SetCurrentTab(Dict::S('UI:History:BulkImports')); + BulkChange::DisplayImportHistory($oPage); } switch($iStep) { + case 10: + $iChange = (int)utils::ReadParam('changeid', 0); + BulkChange::DisplayImportHistoryDetails($oPage, $iChange); + break; + case 5: LoadData($oPage); break; diff --git a/portal/index.php b/portal/index.php index 31f10776c1..f61b2c2558 100644 --- a/portal/index.php +++ b/portal/index.php @@ -326,14 +326,7 @@ function DoCreateRequest($oP, $oUserOrg) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oRequest->DBInsertTracked($oMyChange); @@ -622,14 +615,7 @@ function DoCloseRequest($oP, UserRequest $oRequest) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); $oMyChange->Set("userinfo", $sUserString); $iChangeId = $oMyChange->DBInsert(); $oRequest->DBUpdateTracked($oMyChange); diff --git a/webservices/import.php b/webservices/import.php index 4e04bc7aa7..dee36f50bc 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -483,14 +483,7 @@ try { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); - if (UserRights::IsImpersonated()) - { - $sUserString = Dict::Format('UI:Archive_User_OnBehalfOf_User', UserRights::GetRealUser(), UserRights::GetUser()); - } - else - { - $sUserString = UserRights::GetUser(); - } + $sUserString = CMDBChange::GetCurrentUserName(); if (strlen($sComment) > 0) { $sMoreInfo = 'Web Service (CSV) - '.$sComment; From b225b621b7c8ecdc99f8a82fa11a06b9353df8ab Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 3 Dec 2010 10:28:25 +0000 Subject: [PATCH 923/970] Cosmetic issue with the banner when the application is fully accessible SVN:trunk[1008] --- application/itopwebpage.class.inc.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 76ecbc1553..d65942c4f4 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -648,12 +648,14 @@ EOF if (strlen($sRestrictions) > 0) { $sAdminMessage = trim(utils::GetConfig()->Get('access_message')); - $sApplicationBanner = ''; + $sApplicationBanner = '
    '; + $sApplicationBanner .= ''; $sApplicationBanner .= ' '.$sRestrictions.''; if (strlen($sAdminMessage) > 0) { $sApplicationBanner .= ' '.$sAdminMessage.''; } + $sApplicationBanner .= '
    '; } else { @@ -686,7 +688,7 @@ EOF echo '
    '; echo '
    '; - echo '
    '.$sApplicationBanner.'
    '; + echo $sApplicationBanner; echo ' '; echo '
    '; diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 6215a09653..b4f4d40e30 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -197,6 +197,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => true, ), + 'online_help' => array( + 'type' => 'string', + 'description' => 'Hyperlink to the online-help web page', + 'default' => 'http://www.combodo.com/itop-help', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), ); public function IsProperty($sPropCode) From 6a72ceb685d08d449b3fdbee669349b121d837c0 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Dec 2010 15:24:16 +0000 Subject: [PATCH 934/970] Patch for supporting a data model without any Person. SVN:trunk[1019] --- addons/userrights/userrightsprofile.class.inc.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/userrights/userrightsprofile.class.inc.php b/addons/userrights/userrightsprofile.class.inc.php index ec51235c67..e8a89b0bc3 100644 --- a/addons/userrights/userrightsprofile.class.inc.php +++ b/addons/userrights/userrightsprofile.class.inc.php @@ -465,7 +465,10 @@ class UserRightsProfile extends UserRightsAddOnAPI $oUser = new UserLocal(); $oUser->Set('login', $sAdminUser); $oUser->Set('password', $sAdminPwd); - $oUser->Set('contactid', $iContactId); + if (MetaModel::IsValidAttCode('UserLocal', 'contactid')) + { + $oUser->Set('contactid', $iContactId); + } $oUser->Set('language', $sLanguage); // Language was chosen during the installation // Add this user to the very specific 'admin' profile From 25ea40922898fc47a7dd606173a9a932a56fc52a Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 7 Dec 2010 19:54:27 +0000 Subject: [PATCH 935/970] Build the menus BEFORE the actual display of the page content... since the content is the result of a menu ! SVN:trunk[1020] --- application/itopwebpage.class.inc.php | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 941232a65f..ab25a98ea8 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -399,6 +399,16 @@ EOF EOF ); + // Build menus from module handlers + // + foreach(get_declared_classes() as $sPHPClass) + { + if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI')) + { + $aCallSpec = array($sPHPClass, 'OnMenuCreation'); + call_user_func($aCallSpec); + } + } } public function AddToMenu($sHtml) @@ -464,17 +474,6 @@ EOF public function DisplayMenu() { - // Build menus from module handlers - // - foreach(get_declared_classes() as $sPHPClass) - { - if (is_subclass_of($sPHPClass, 'ModuleHandlerAPI')) - { - $aCallSpec = array($sPHPClass, 'OnMenuCreation'); - call_user_func($aCallSpec); - } - } - // Display the menu $oAppContext = new ApplicationContext(); $iAccordionIndex = 0; From 63387755064ee197c764fc35b76373fd7365c3d3 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 8 Dec 2010 09:43:56 +0000 Subject: [PATCH 936/970] Split the usage of the iTop logo: one logo to be displayed internally (once logged) one externally (login/logoff). SVN:trunk[1021] --- application/loginwebpage.class.inc.php | 2 +- images/itop-logo-external.png | Bin 0 -> 2466 bytes pages/logoff.php | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100755 images/itop-logo-external.png diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index ca8b6b09f8..c680505722 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -103,7 +103,7 @@ EOF $sAuthPwd = utils::ReadParam('suggest_pwd', ''); $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); - $this->add("
    \n"); + $this->add("
    \n"); $this->add("
    \n"); $this->add("

    ".Dict::S('UI:Login:Welcome')."

    \n"); if ($bFailedLogin) diff --git a/images/itop-logo-external.png b/images/itop-logo-external.png new file mode 100755 index 0000000000000000000000000000000000000000..41797152f3a8053662c7b724c24656ce924814dc GIT binary patch literal 2466 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 literal 0 HcmV?d00001 diff --git a/pages/logoff.php b/pages/logoff.php index a34487f611..4d49de70b7 100644 --- a/pages/logoff.php +++ b/pages/logoff.php @@ -30,7 +30,7 @@ session_start(); LoginWebPage::ResetSession(); $oPage = new LoginWebPage(); $sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_VERSION); -$oPage->add("
    \n"); +$oPage->add("
    \n"); $oPage->add("
    \n"); $oPage->add("

    ".Dict::S('UI:LogOff:ThankYou')."

    \n"); $bPortal = utils::ReadParam('portal', false); From b6883ed3b4c6ddf430059a360d7ae010e57c6de5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Wed, 8 Dec 2010 15:29:30 +0000 Subject: [PATCH 937/970] Internal: added an helper to get the currently loaded config file SVN:trunk[1022] --- core/config.class.inc.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index b4f4d40e30..9c54e89533 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -832,6 +832,10 @@ class Config { return is_writable($this->m_sFile); } + public function GetLoadedFile() + { + return $this->m_sFile; + } /** * Render the configuration as an associative array From b89a4d32d17cdd5daa4e4fdc43757f8fa87d4d83 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Thu, 9 Dec 2010 10:04:38 +0000 Subject: [PATCH 938/970] Improved the detection of MySQL Slave status SVN:trunk[1023] --- core/cmdbsource.class.inc.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 4b78f08861..cf91820f62 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -562,19 +562,21 @@ class CMDBSource { return false; } - if ($aRow['Slave_IO_Running'] != 'Yes') - { - return false; - } if (!isset($aRow['Slave_SQL_Running'])) { return false; } - if ($aRow['Slave_SQL_Running'] != 'Yes') + + // If at least one slave thread is running, then we consider that the slave is enabled + if ($aRow['Slave_IO_Running'] == 'Yes') { - return false; + return true; } - return true; + if ($aRow['Slave_SQL_Running'] == 'Yes') + { + return true; + } + return false; } } From 028f23214de7026dd80fbca926e4c83ea6823bc3 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 10 Dec 2010 13:43:55 +0000 Subject: [PATCH 939/970] - Fixed the CSV "confirmation pie chart" to work under IE & enhanced the look of the chart by removing unused values. SVN:trunk[1024] --- pages/csvimport.php | 98 +++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 31 deletions(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index ac9336684e..7cdbc6dfd7 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -700,48 +700,84 @@ function RunImport() function open_flash_chart_data() { + var iErrors = $iErrors; + var iModified = $iModified; + var iCreated = $iCreated; + var iUnchanged = $iUnchanged; + var fAlpha = 0.9; + var oResult = { "elements": [ { "type": "pie", - "tip": "#label# - #val# (#percent#)", + "tip": "#label# (#percent#)", + "gradient-fill": true, "font-size": 14, - "colours": - [ - "#FF6666", - "#6666FF", - "#66FF66", - "#666666", - ], - "values": - [ - { - "value": $iErrors, - "label": "Errors", - "alpha": 0.9 - }, - { - "value": $iModified, - "label": "Modified", - "alpha": 0.9 - }, - { - "value": $iCreated, - "label": "Created", - "alpha": 0.9 - }, - { - "value": $iUnchanged, - "label": "Unchanged", - "alpha": 0.9 - } - ] + "colours":[], + "values": [], + "animate":[ + { + "type": "fade" + }, + ] } ], "x_axis": null, "font-size": 14, "bg_colour": "#EEEEEE" }; + + if (iErrors > 0) + { + var oErrors = + { + "value": iErrors, + "label": "Errors: "+iErrors, + "alpha": fAlpha, + "label-colour": "#CC3333", + }; + oResult.elements[0].values.push(oErrors); + oResult.elements[0].colours.push('#FF6666'); + } + if (iModified > 0) + { + var oModified = + { + "value": iModified, + "label": "Modified: "+iModified, + "alpha": fAlpha, + "label-colour": "#3333CC", + }; + oResult.elements[0].values.push(oModified); + oResult.elements[0].colours.push('#6666FF'); + } + if (iCreated > 0) + { + var oCreated = + { + "value": iCreated, + "label": "Created: "+iCreated, + "alpha": fAlpha, + "label-colour": "#33CC33", + + }; + oResult.elements[0].values.push(oCreated); + oResult.elements[0].colours.push('#66FF66'); + } + if (iUnchanged > 0) + { + var oUnchanged = + { + "value": iUnchanged, + "label": "Unchanged: "+iUnchanged, + "alpha": fAlpha, + "label-colour": "#333333", + + }; + oResult.elements[0].values.push(oUnchanged); + oResult.elements[0].colours.push('#666666'); + } + return JSON.stringify(oResult); } EOF From 203887589fe29d0d6b8810af8c923b40841ac08c Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 10 Dec 2010 13:58:12 +0000 Subject: [PATCH 940/970] - Yet another IE fix... SVN:trunk[1025] --- pages/csvimport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/csvimport.php b/pages/csvimport.php index 7cdbc6dfd7..23551d5356 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -718,7 +718,7 @@ function open_flash_chart_data() "animate":[ { "type": "fade" - }, + } ] } ], From 21722b9e806998ce1d626da9e66d1b32f73aad33 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Fri, 10 Dec 2010 15:19:18 +0000 Subject: [PATCH 941/970] Internal: added an API for logging tools events SVN:trunk[1026] --- core/log.class.inc.php | 266 +++++++++++++++++++++------------------ core/metamodel.class.php | 2 + 2 files changed, 148 insertions(+), 120 deletions(-) diff --git a/core/log.class.inc.php b/core/log.class.inc.php index 80e2635c04..f6d72e8440 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -1,120 +1,146 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -class FileLog -{ - protected $m_sFile = ''; // log is disabled if this is empty - - public function __construct($sFileName = '') - { - $this->m_sFile = $sFileName; - } - - public function Error($sText) - { - self::Write("Error | ".$sText); - } - - public function Warning($sText) - { - self::Write("Warning | ".$sText); - } - - public function Info($sText) - { - self::Write("Info | ".$sText); - } - - public function Ok($sText) - { - self::Write("Ok | ".$sText); - } - - protected function Write($sText) - { - if (strlen($this->m_sFile) == 0) return; - - $hLogFile = @fopen($this->m_sFile, 'a'); - if ($hLogFile !== false) - { - $sDate = date('Y-m-d H:i:s'); - fwrite($hLogFile, "$sDate | $sText\n"); - fclose($hLogFile); - } - } -} - -class SetupLog -{ - protected static $m_oFileLog; - - public static function Enable($sTargetFile) - { - self::$m_oFileLog = new FileLog($sTargetFile); - } - public static function Error($sText) - { - self::$m_oFileLog->Error($sText); - } - public static function Warning($sText) - { - self::$m_oFileLog->Warning($sText); - } - public static function Info($sText) - { - self::$m_oFileLog->Info($sText); - } - public static function Ok($sText) - { - self::$m_oFileLog->Ok($sText); - } -} - -class IssueLog -{ - protected static $m_oFileLog; - - public static function Enable($sTargetFile) - { - self::$m_oFileLog = new FileLog($sTargetFile); - } - public static function Error($sText) - { - self::$m_oFileLog->Error($sText); - } - public static function Warning($sText) - { - self::$m_oFileLog->Warning($sText); - } - public static function Info($sText) - { - self::$m_oFileLog->Info($sText); - } - public static function Ok($sText) - { - self::$m_oFileLog->Ok($sText); - } -} -?> + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +class FileLog +{ + protected $m_sFile = ''; // log is disabled if this is empty + + public function __construct($sFileName = '') + { + $this->m_sFile = $sFileName; + } + + public function Error($sText) + { + self::Write("Error | ".$sText); + } + + public function Warning($sText) + { + self::Write("Warning | ".$sText); + } + + public function Info($sText) + { + self::Write("Info | ".$sText); + } + + public function Ok($sText) + { + self::Write("Ok | ".$sText); + } + + protected function Write($sText) + { + if (strlen($this->m_sFile) == 0) return; + + $hLogFile = @fopen($this->m_sFile, 'a'); + if ($hLogFile !== false) + { + $sDate = date('Y-m-d H:i:s'); + fwrite($hLogFile, "$sDate | $sText\n"); + fclose($hLogFile); + } + } +} + +class SetupLog +{ + protected static $m_oFileLog; + + public static function Enable($sTargetFile) + { + self::$m_oFileLog = new FileLog($sTargetFile); + } + public static function Error($sText) + { + self::$m_oFileLog->Error($sText); + } + public static function Warning($sText) + { + self::$m_oFileLog->Warning($sText); + } + public static function Info($sText) + { + self::$m_oFileLog->Info($sText); + } + public static function Ok($sText) + { + self::$m_oFileLog->Ok($sText); + } +} + +class IssueLog +{ + protected static $m_oFileLog; + + public static function Enable($sTargetFile) + { + self::$m_oFileLog = new FileLog($sTargetFile); + } + public static function Error($sText) + { + self::$m_oFileLog->Error($sText); + } + public static function Warning($sText) + { + self::$m_oFileLog->Warning($sText); + } + public static function Info($sText) + { + self::$m_oFileLog->Info($sText); + } + public static function Ok($sText) + { + self::$m_oFileLog->Ok($sText); + } +} + +class ToolsLog +{ + protected static $m_oFileLog; + + public static function Enable($sTargetFile) + { + self::$m_oFileLog = new FileLog($sTargetFile); + } + public static function Error($sText) + { + self::$m_oFileLog->Error($sText); + } + public static function Warning($sText) + { + self::$m_oFileLog->Warning($sText); + } + public static function Info($sText) + { + self::$m_oFileLog->Info($sText); + } + public static function Ok($sText) + { + self::$m_oFileLog->Ok($sText); + } +} +?> diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 11840d386b..19c681218f 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3294,6 +3294,8 @@ abstract class MetaModel } self::$m_bLogNotification = self::$m_oConfig->GetLogNotification(); self::$m_bLogWebService = self::$m_oConfig->GetLogWebService(); + + ToolsLog::Enable(APPROOT.'/tools.log'); } else { From 511c703b614dc054099ef2d500fab26e80d26e69 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 15 Dec 2010 16:33:08 +0000 Subject: [PATCH 942/970] Bug fix: history of encrypted fields was not recorded properly. SVN:trunk[1027] --- core/cmdbobject.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 877232da42..2e6e370073 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -152,7 +152,7 @@ abstract class CMDBObject extends DBObject { $original = ''; } - $oMyChangeOp->Set("prevdata", $original); + $oMyChangeOp->Set("prevstring", $original); $iId = $oMyChangeOp->DBInsertNoReload(); } elseif ($oAttDef instanceOf AttributeBlob) From 6dd3eb9970dda11d1dc2a7ed4adb647946cdaa0f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 20 Dec 2010 11:13:54 +0000 Subject: [PATCH 943/970] - Added the russian localization (thanks to Vladimir Shilov) SVN:trunk[1028] --- core/config.class.inc.php | 2 + dictionaries/ru.dictionary.itop.core.php | 385 ++++++ dictionaries/ru.dictionary.itop.ui.php | 870 ++++++++++++++ .../module.authent-external.php | 1 + .../ru.dict.authent-external.php | 43 + modules/authent-ldap/module.authent-ldap.php | 1 + modules/authent-ldap/ru.dict.authent-ldap.php | 45 + .../authent-local/module.authent-local.php | 1 + .../authent-local/ru.dict.authent-local.php | 45 + .../module.itop-change-mgmt.php | 1 + .../ru.dict.itop-change-mgmt.php | 345 ++++++ .../module.itop-config-mgmt.php | 1 + .../ru.dict.itop-config-mgmt.php | 1052 +++++++++++++++++ .../module.itop-incident-mgmt.php | 1 + .../ru.dict.itop-incident-mgmt.php | 73 ++ .../module.itop-knownerror-mgmt.php | 1 + .../ru.dict.itop-knownerror-mgmt.php | 147 +++ .../module.itop-problem-mgmt.php | 1 + .../ru.dict.itop-problem-mgmt.php | 165 +++ .../module.itop-request-mgmt.php | 1 + .../ru.dict.itop-request-mgmt.php | 84 ++ .../module.itop-service-mgmt.php | 1 + .../ru.dict.itop-service-mgmt.php | 452 +++++++ .../module.itop-tickets.php | 1 + .../ru.dict.itop-tickets.php | 262 ++++ 25 files changed, 3981 insertions(+) create mode 100644 dictionaries/ru.dictionary.itop.core.php create mode 100644 dictionaries/ru.dictionary.itop.ui.php create mode 100644 modules/authent-external/ru.dict.authent-external.php create mode 100644 modules/authent-ldap/ru.dict.authent-ldap.php create mode 100644 modules/authent-local/ru.dict.authent-local.php create mode 100644 modules/itop-change-mgmt-1.0.0/ru.dict.itop-change-mgmt.php create mode 100644 modules/itop-config-mgmt-1.0.0/ru.dict.itop-config-mgmt.php create mode 100644 modules/itop-incident-mgmt-1.0.0/ru.dict.itop-incident-mgmt.php create mode 100644 modules/itop-knownerror-mgmt-1.0.0/ru.dict.itop-knownerror-mgmt.php create mode 100644 modules/itop-problem-mgmt-1.0.0/ru.dict.itop-problem-mgmt.php create mode 100644 modules/itop-request-mgmt-1.0.0/ru.dict.itop-request-mgmt.php create mode 100644 modules/itop-service-mgmt-1.0.0/ru.dict.itop-service-mgmt.php create mode 100644 modules/itop-tickets-1.0.0/ru.dict.itop-tickets.php diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 9c54e89533..0ca0b0b448 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -362,6 +362,8 @@ class Config 'dictionaries/de.dictionary.itop.core.php', // Support for German 'dictionaries/pt_br.dictionary.itop.ui.php', // Support for Brazilian Portuguese 'dictionaries/pt_br.dictionary.itop.core.php', // Support for Brazilian Portuguese + 'dictionaries/ru.dictionary.itop.ui.php', // Support for Russian + 'dictionaries/ru.dictionary.itop.core.php', // Support for Russian ); foreach($this->m_aSettings as $sPropCode => $aSettingInfo) { diff --git a/dictionaries/ru.dictionary.itop.core.php b/dictionaries/ru.dictionary.itop.core.php new file mode 100644 index 0000000000..99de23519f --- /dev/null +++ b/dictionaries/ru.dictionary.itop.core.php @@ -0,0 +1,385 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +////////////////////////////////////////////////////////////////////// +// Classes in 'core/cmdb' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: CMDBChange +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChange' => 'Изменение', + 'Class:CMDBChange+' => 'Отслеживание изменений', + 'Class:CMDBChange/Attribute:date' => 'дата', + 'Class:CMDBChange/Attribute:date+' => 'дата и время регистрации изменений', + 'Class:CMDBChange/Attribute:userinfo' => 'разная информация', + 'Class:CMDBChange/Attribute:userinfo+' => 'изменение определённые -вызвавшим-', +)); + +// +// Class: CMDBChangeOp +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOp' => 'Операция изменения', + 'Class:CMDBChangeOp+' => 'Отслеживание операции изменения', + 'Class:CMDBChangeOp/Attribute:change' => 'изменение', + 'Class:CMDBChangeOp/Attribute:change+' => 'изменение', + 'Class:CMDBChangeOp/Attribute:date' => 'дата', + 'Class:CMDBChangeOp/Attribute:date+' => 'дата и время изменения', + 'Class:CMDBChangeOp/Attribute:userinfo' => 'пользователь', + 'Class:CMDBChangeOp/Attribute:userinfo+' => 'кто сделал изменение', + 'Class:CMDBChangeOp/Attribute:objclass' => 'класс объекта', + 'Class:CMDBChangeOp/Attribute:objclass+' => 'класс объекта', + 'Class:CMDBChangeOp/Attribute:objkey' => 'id объекта', + 'Class:CMDBChangeOp/Attribute:objkey+' => 'id объекта', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'тип', + 'Class:CMDBChangeOp/Attribute:finalclass+' => '', +)); + +// +// Class: CMDBChangeOpCreate +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOpCreate' => 'создание объекта', + 'Class:CMDBChangeOpCreate+' => 'Отслеживание создания объекта', +)); + +// +// Class: CMDBChangeOpDelete +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOpDelete' => 'удаление объекта', + 'Class:CMDBChangeOpDelete+' => 'Отслеживание удаления объекта', +)); + +// +// Class: CMDBChangeOpSetAttribute +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOpSetAttribute' => 'изменение объекта', + 'Class:CMDBChangeOpSetAttribute+' => 'Отслеживание изменения объекта', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Атрибут', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => 'код изменённого свойства', +)); + +// +// Class: CMDBChangeOpSetAttributeScalar +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOpSetAttributeScalar' => 'изменение свойства', + 'Class:CMDBChangeOpSetAttributeScalar+' => 'Отслеживание изменения скалярного свойства объекта', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Предыдущее значение', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => 'предыдущее значение атрибута', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Новое значение', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'новое значение атрибута', +)); +// Used by CMDBChangeOp... & derived classes +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Change:ObjectCreated' => 'Объект создан', + 'Change:ObjectDeleted' => 'Объект удалён', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s установлено в %2$s (предыдущее значение: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s добавлено к %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s изменено, предыдущее значение: %2$s', + 'Change:AttName_Changed' => '%1$s изменено', +)); + +// +// Class: CMDBChangeOpSetAttributeBlob +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOpSetAttributeBlob' => 'изменение данных', + 'Class:CMDBChangeOpSetAttributeBlob+' => 'отслеживание изменения данных', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Предыдущие данные', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'предыдущее содержимое атрибута', +)); + +// +// Class: CMDBChangeOpSetAttributeText +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CMDBChangeOpSetAttributeText' => 'изменение текста', + 'Class:CMDBChangeOpSetAttributeText+' => 'отслеживание изменения текста', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Предыдущие данные', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'предыдущее содержимое атрибута', +)); + +// +// Class: Event +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Event' => 'Журнал событий', + 'Class:Event+' => 'Внутренние событие приложения', + 'Class:Event/Attribute:message' => 'сообщение', + 'Class:Event/Attribute:message+' => 'короткое описание собітия', + 'Class:Event/Attribute:date' => 'дата', + 'Class:Event/Attribute:date+' => 'дата и время регистрации изменений', + 'Class:Event/Attribute:userinfo' => 'информация о пользователе', + 'Class:Event/Attribute:userinfo+' => 'идентификация пользователя, действия которого вызвали это событие', + 'Class:Event/Attribute:finalclass' => 'тип', + 'Class:Event/Attribute:finalclass+' => '', +)); + +// +// Class: EventNotification +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:EventNotification' => 'Уведомление о событии', + 'Class:EventNotification+' => 'Отслеживание отосланных уведомлений', + 'Class:EventNotification/Attribute:trigger_id' => 'Триггер', + 'Class:EventNotification/Attribute:trigger_id+' => 'учётная запись пользователя', + 'Class:EventNotification/Attribute:action_id' => 'пользователь', + 'Class:EventNotification/Attribute:action_id+' => 'учётная запись пользователя', + 'Class:EventNotification/Attribute:object_id' => 'id объекта', + 'Class:EventNotification/Attribute:object_id+' => 'id объекта (класс заданный тригером ?)', +)); + +// +// Class: EventNotificationEmail +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:EventNotificationEmail' => 'Отправка сообщений на e-mail', + 'Class:EventNotificationEmail+' => 'Отслеживание отправленных писем', + 'Class:EventNotificationEmail/Attribute:to' => 'Кому', + 'Class:EventNotificationEmail/Attribute:to+' => 'Кому', + 'Class:EventNotificationEmail/Attribute:cc' => 'Копия', + 'Class:EventNotificationEmail/Attribute:cc+' => 'Копия', + 'Class:EventNotificationEmail/Attribute:bcc' => 'Скрытая копия', + 'Class:EventNotificationEmail/Attribute:bcc+' => 'Скрытая копия', + 'Class:EventNotificationEmail/Attribute:from' => 'От', + 'Class:EventNotificationEmail/Attribute:from+' => 'Отправитель сообщения', + 'Class:EventNotificationEmail/Attribute:subject' => 'Тема', + 'Class:EventNotificationEmail/Attribute:subject+' => 'Тема', + 'Class:EventNotificationEmail/Attribute:body' => 'Тело', + 'Class:EventNotificationEmail/Attribute:body+' => 'Тело', +)); + +// +// Class: EventIssue +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:EventIssue' => 'Выпуск события', + 'Class:EventIssue+' => 'Отслеживание выпуска (warning, error, др.)', + 'Class:EventIssue/Attribute:issue' => 'Выпуск', + 'Class:EventIssue/Attribute:issue+' => 'Что произошло', + 'Class:EventIssue/Attribute:impact' => 'Воздействие', + 'Class:EventIssue/Attribute:impact+' => 'Последствия', + 'Class:EventIssue/Attribute:page' => 'Страница', + 'Class:EventIssue/Attribute:page+' => 'Точка входа HTTP', + 'Class:EventIssue/Attribute:arguments_post' => 'Отправленные аргументы', + 'Class:EventIssue/Attribute:arguments_post+' => 'Аргументы HTTP POST', + 'Class:EventIssue/Attribute:arguments_get' => 'Аргументы URL', + 'Class:EventIssue/Attribute:arguments_get+' => 'Аргументы HTTP GET', + 'Class:EventIssue/Attribute:callstack' => 'Стек?вызовов', + 'Class:EventIssue/Attribute:callstack+' => 'Стек вызовов', + 'Class:EventIssue/Attribute:data' => 'Данные', + 'Class:EventIssue/Attribute:data+' => 'Подробнее', +)); + +// +// Class: EventWebService +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:EventWebService' => 'События Web сервиса', + 'Class:EventWebService+' => 'Trace of an web service call', + 'Class:EventWebService/Attribute:verb' => 'Verb', + 'Class:EventWebService/Attribute:verb+' => 'Название операции', + 'Class:EventWebService/Attribute:result' => 'Результат', + 'Class:EventWebService/Attribute:result+' => 'Overall success/failure', + 'Class:EventWebService/Attribute:log_info' => 'Info log', + 'Class:EventWebService/Attribute:log_info+' => 'Result info log', + 'Class:EventWebService/Attribute:log_warning' => 'Warning log', + 'Class:EventWebService/Attribute:log_warning+' => 'Result warning log', + 'Class:EventWebService/Attribute:log_error' => 'Error log', + 'Class:EventWebService/Attribute:log_error+' => 'Result error log', + 'Class:EventWebService/Attribute:data' => 'Данные', + 'Class:EventWebService/Attribute:data+' => 'Result data', +)); + +// +// Class: Action +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Action' => 'Заказное действие', + 'Class:Action+' => 'Действие определённое пользователем', + 'Class:Action/Attribute:name' => 'Имя', + 'Class:Action/Attribute:name+' => '', + 'Class:Action/Attribute:description' => 'Описание', + 'Class:Action/Attribute:description+' => '', + 'Class:Action/Attribute:status' => 'Статус', + 'Class:Action/Attribute:status+' => 'В производстве или ?', + 'Class:Action/Attribute:status/Value:test' => 'Проходит проверку', + 'Class:Action/Attribute:status/Value:test+' => 'Проходит проверку', + 'Class:Action/Attribute:status/Value:enabled' => 'В производстве', + 'Class:Action/Attribute:status/Value:enabled+' => 'В производстве', + 'Class:Action/Attribute:status/Value:disabled' => 'Неактивный', + 'Class:Action/Attribute:status/Value:disabled+' => 'Неактивный', + 'Class:Action/Attribute:trigger_list' => 'Связанные триггеры', + 'Class:Action/Attribute:trigger_list+' => 'Триггеры привызанные к этому действию', + 'Class:Action/Attribute:finalclass' => 'Тип', + 'Class:Action/Attribute:finalclass+' => '', +)); + +// +// Class: ActionNotification +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ActionNotification' => 'Уведомление', + 'Class:ActionNotification+' => 'Уведомление (выдержка)', +)); + +// +// Class: ActionEmail +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ActionEmail' => 'Уведомление по e-mail', + 'Class:ActionEmail+' => '', + 'Class:ActionEmail/Attribute:test_recipient' => 'Проверка получателя', + 'Class:ActionEmail/Attribute:test_recipient+' => 'Назначение если статус "Test"', + 'Class:ActionEmail/Attribute:from' => 'От', + 'Class:ActionEmail/Attribute:from+' => 'Будет отослано в заголовке e-mail', + 'Class:ActionEmail/Attribute:reply_to' => 'Ответить на', + 'Class:ActionEmail/Attribute:reply_to+' => 'Будет отослано в заголовке e-mail', + 'Class:ActionEmail/Attribute:to' => 'Кому', + 'Class:ActionEmail/Attribute:to+' => 'Получатель e-mail', + 'Class:ActionEmail/Attribute:cc' => 'Копия', + 'Class:ActionEmail/Attribute:cc+' => 'Копия', + 'Class:ActionEmail/Attribute:bcc' => 'Скр. копия', + 'Class:ActionEmail/Attribute:bcc+' => 'Скрытая копия', + 'Class:ActionEmail/Attribute:subject' => 'тема', + 'Class:ActionEmail/Attribute:subject+' => 'Заголовок письма', + 'Class:ActionEmail/Attribute:body' => 'тело', + 'Class:ActionEmail/Attribute:body+' => 'Содержимое письма', + 'Class:ActionEmail/Attribute:importance' => 'значение', + 'Class:ActionEmail/Attribute:importance+' => 'Флаг значения', + 'Class:ActionEmail/Attribute:importance/Value:low' => 'низкий', + 'Class:ActionEmail/Attribute:importance/Value:low+' => 'низкий', + 'Class:ActionEmail/Attribute:importance/Value:normal' => 'нормальный', + 'Class:ActionEmail/Attribute:importance/Value:normal+' => 'нормальный', + 'Class:ActionEmail/Attribute:importance/Value:high' => 'высокий', + 'Class:ActionEmail/Attribute:importance/Value:high+' => 'высокий', +)); + +// +// Class: Trigger +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Trigger' => 'Триггер', + 'Class:Trigger+' => 'Заказной триггер события', + 'Class:Trigger/Attribute:description' => 'Описание', + 'Class:Trigger/Attribute:description+' => 'однострочное описание', + 'Class:Trigger/Attribute:action_list' => 'Действия триггера', + 'Class:Trigger/Attribute:action_list+' => 'Действия, выполняемые при активации триггера', + 'Class:Trigger/Attribute:finalclass' => 'Тип', + 'Class:Trigger/Attribute:finalclass+' => '', +)); + +// +// Class: TriggerOnObject +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:TriggerOnObject' => 'Триггер (в зависимости класс)', + 'Class:TriggerOnObject+' => 'Триггер по даному классу объектов', + 'Class:TriggerOnObject/Attribute:target_class' => 'Целевой класс', + 'Class:TriggerOnObject/Attribute:target_class+' => '', +)); + +// +// Class: TriggerOnStateChange +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:TriggerOnStateChange' => 'Триггер (на изменение состояния)', + 'Class:TriggerOnStateChange+' => 'Триггер на изменение состояния объекта', + 'Class:TriggerOnStateChange/Attribute:state' => 'Статус', + 'Class:TriggerOnStateChange/Attribute:state+' => '', +)); + +// +// Class: TriggerOnStateEnter +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:TriggerOnStateEnter' => 'Триггер (на начало состояния)', + 'Class:TriggerOnStateEnter+' => 'Триггер на изменению состояния объекта - начало', +)); + +// +// Class: TriggerOnStateLeave +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:TriggerOnStateLeave' => 'Триггер (на окончание состояния)', + 'Class:TriggerOnStateLeave+' => 'Триггер на изменению состояния объекта - окончание', +)); + +// +// Class: TriggerOnObjectCreate +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:TriggerOnObjectCreate' => 'Триггер (на создание объекта)', + 'Class:TriggerOnObjectCreate+' => 'Триггер на создание объекта [дочерний класс] данного класса', +)); + +// +// Class: lnkTriggerAction +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkTriggerAction' => 'Действие/Триггер', + 'Class:lnkTriggerAction+' => 'Связь между триггером и действий', + 'Class:lnkTriggerAction/Attribute:action_id' => 'Действие', + 'Class:lnkTriggerAction/Attribute:action_id+' => 'Выполняемое действие', + 'Class:lnkTriggerAction/Attribute:action_name' => 'Действие', + 'Class:lnkTriggerAction/Attribute:action_name+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_id' => 'Триггер', + 'Class:lnkTriggerAction/Attribute:trigger_id+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_name' => 'Триггер', + 'Class:lnkTriggerAction/Attribute:trigger_name+' => '', + 'Class:lnkTriggerAction/Attribute:order' => 'Порядок', + 'Class:lnkTriggerAction/Attribute:order+' => 'Порядок выполнения действий', +)); + + +?> diff --git a/dictionaries/ru.dictionary.itop.ui.php b/dictionaries/ru.dictionary.itop.ui.php new file mode 100644 index 0000000000..17d33e677e --- /dev/null +++ b/dictionaries/ru.dictionary.itop.ui.php @@ -0,0 +1,870 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +////////////////////////////////////////////////////////////////////// +// Classes in 'gui' +////////////////////////////////////////////////////////////////////// +// + +////////////////////////////////////////////////////////////////////// +// Classes in 'application' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: AuditCategory +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:AuditCategory' => 'Категория аудита', + 'Class:AuditCategory+' => 'Раздел внутри общего аудита', + 'Class:AuditCategory/Attribute:name' => 'Название категории', + 'Class:AuditCategory/Attribute:name+' => 'Краткое название для этой категории', + 'Class:AuditCategory/Attribute:description' => 'Описание категории аудита', + 'Class:AuditCategory/Attribute:description+' => 'Полное описание категории аудита', + 'Class:AuditCategory/Attribute:definition_set' => 'Набор определений', + 'Class:AuditCategory/Attribute:definition_set+' => 'OQL выражение, определяющее набор объектов для проверки', + 'Class:AuditCategory/Attribute:rules_list' => 'Правила аудита', + 'Class:AuditCategory/Attribute:rules_list+' => 'Правила аудита для этой категории', +)); + +// +// Class: AuditRule +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:AuditRule' => 'Правило аудита', + 'Class:AuditRule+' => 'Правило для проверки данной категории аудита', + 'Class:AuditRule/Attribute:name' => 'Название правила', + 'Class:AuditRule/Attribute:name+' => 'Краткое название этого правила', + 'Class:AuditRule/Attribute:description' => 'Описание правила аудита', + 'Class:AuditRule/Attribute:description+' => 'Полное описание этого правила аудита', + 'Class:AuditRule/Attribute:query' => 'Запрос на исполнение', + 'Class:AuditRule/Attribute:query+' => 'OQL выражение на исполнение', + 'Class:AuditRule/Attribute:valid_flag' => 'Действительные объекты?', + 'Class:AuditRule/Attribute:valid_flag+' => 'Истина, если правило возвращает действительный объект, иначе ложь', + 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'истина', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => 'истина', + 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'ложь', + 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'ложь', + 'Class:AuditRule/Attribute:category_id' => 'Категория', + 'Class:AuditRule/Attribute:category_id+' => 'Категория этого правила', + 'Class:AuditRule/Attribute:category_name' => 'Категория', + 'Class:AuditRule/Attribute:category_name+' => 'Название категории для этого правила', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'addon/userrights' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: User +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:User' => 'Пользователь', + 'Class:User+' => 'Пользовательский логин', + 'Class:User/Attribute:finalclass' => 'Тип счёта', + 'Class:User/Attribute:finalclass+' => '', + 'Class:User/Attribute:contactid' => 'Контакт (человек)', + 'Class:User/Attribute:contactid+' => 'Личные данные из бизнес-данных', + 'Class:User/Attribute:last_name' => 'Фамилия', + 'Class:User/Attribute:last_name+' => 'Фамилия соответсвующего контакта', + 'Class:User/Attribute:first_name' => 'Имя', + 'Class:User/Attribute:first_name+' => 'Имя соответсвующего контакта', + 'Class:User/Attribute:email' => 'e-mail', + 'Class:User/Attribute:email+' => 'e-mail соответсвующего контакта', + 'Class:User/Attribute:login' => 'Логин', + 'Class:User/Attribute:login+' => 'строка идентификации пользователя', + 'Class:User/Attribute:language' => 'Язык', + 'Class:User/Attribute:language+' => 'язык пользователя', + 'Class:User/Attribute:language/Value:RU RU' => 'Русский', + 'Class:User/Attribute:language/Value:RU RU+' => 'Русский (Россия)', + 'Class:User/Attribute:language/Value:EN US' => 'English', + 'Class:User/Attribute:language/Value:EN US+' => 'English (U.S.)', + 'Class:User/Attribute:language/Value:FR FR' => 'French', + 'Class:User/Attribute:language/Value:FR FR+' => 'French (France)', + 'Class:User/Attribute:profile_list' => 'Профили', + 'Class:User/Attribute:profile_list+' => 'Роли, предоставление прав этому человеку', + 'Class:User/Attribute:allowed_org_list' => 'Разрешённые организации', + 'Class:User/Attribute:allowed_org_list+' => 'Конечный пользователь имеет право видеть данные, принадлежащие к следующим организациям. Если ни одна организация не указан, нет никаких ограничений.', + + 'Class:User/Error:LoginMustBeUnique' => 'Логин должен быть уникальным - "%1s" уже используется.', + 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'По крайней мере, один профиль должен быть отнесен к этому пользователю.', +)); + +// +// Class: URP_Profiles +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_Profiles' => 'Профиль', + 'Class:URP_Profiles+' => 'Пользовательский профиль', + 'Class:URP_Profiles/Attribute:name' => 'Название', + 'Class:URP_Profiles/Attribute:name+' => 'метка', + 'Class:URP_Profiles/Attribute:description' => 'Описание', + 'Class:URP_Profiles/Attribute:description+' => 'однострочное описание', + 'Class:URP_Profiles/Attribute:user_list' => 'Пользователи', + 'Class:URP_Profiles/Attribute:user_list+' => 'лица, имеющие эту роль', +)); + +// +// Class: URP_Dimensions +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_Dimensions' => 'размерность', + 'Class:URP_Dimensions+' => 'применение размерности (определение силосов)', + 'Class:URP_Dimensions/Attribute:name' => 'Название', + 'Class:URP_Dimensions/Attribute:name+' => 'метка', + 'Class:URP_Dimensions/Attribute:description' => 'Описание', + 'Class:URP_Dimensions/Attribute:description+' => 'краткое описание', + 'Class:URP_Dimensions/Attribute:type' => 'Тип', + 'Class:URP_Dimensions/Attribute:type+' => 'имя класса или типа данных (проекционный блок)', +)); + +// +// Class: URP_UserProfile +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_UserProfile' => 'Пользователь в профиль', + 'Class:URP_UserProfile+' => 'профили пользователей', + 'Class:URP_UserProfile/Attribute:userid' => 'Пользователь', + 'Class:URP_UserProfile/Attribute:userid+' => 'учетная запись пользователя', + 'Class:URP_UserProfile/Attribute:userlogin' => 'Логин', + 'Class:URP_UserProfile/Attribute:userlogin+' => 'Логин пользователя', + 'Class:URP_UserProfile/Attribute:profileid' => 'Профиль', + 'Class:URP_UserProfile/Attribute:profileid+' => 'использование профиля', + 'Class:URP_UserProfile/Attribute:profile' => 'Профиль', + 'Class:URP_UserProfile/Attribute:profile+' => 'Название профиля', + 'Class:URP_UserProfile/Attribute:reason' => 'Причина', + 'Class:URP_UserProfile/Attribute:reason+' => 'объяснение, почему этому человеку назначена эта роль', +)); + +// +// Class: URP_UserOrg +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_UserOrg' => 'Организации пользователя', + 'Class:URP_UserOrg+' => 'Разрешённые организации', + 'Class:URP_UserOrg/Attribute:userid' => 'Пользователь', + 'Class:URP_UserOrg/Attribute:userid+' => 'учетная запись пользователя', + 'Class:URP_UserOrg/Attribute:userlogin' => 'Логин', + 'Class:URP_UserOrg/Attribute:userlogin+' => 'Логин пользователя', + 'Class:URP_UserOrg/Attribute:allowed_org_id' => 'Организация', + 'Class:URP_UserOrg/Attribute:allowed_org_id+' => 'Разрешённая организация', + 'Class:URP_UserOrg/Attribute:allowed_org_name' => 'Организация', + 'Class:URP_UserOrg/Attribute:allowed_org_name+' => 'Разрешённая организация', + 'Class:URP_UserOrg/Attribute:reason' => 'Причина', + 'Class:URP_UserOrg/Attribute:reason+' => 'объяснение, почему этот человек имеет право видеть данные, принадлежащие к этой организации', +)); + +// +// Class: URP_ProfileProjection +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_ProfileProjection' => 'проэктирование профилей', + 'Class:URP_ProfileProjection+' => 'проэктирование профилей', + 'Class:URP_ProfileProjection/Attribute:dimensionid' => 'Размерность', + 'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'применение размерности', + 'Class:URP_ProfileProjection/Attribute:dimension' => 'Размерность', + 'Class:URP_ProfileProjection/Attribute:dimension+' => 'применение размерности', + 'Class:URP_ProfileProjection/Attribute:profileid' => 'Профиль', + 'Class:URP_ProfileProjection/Attribute:profileid+' => 'использование профиля', + 'Class:URP_ProfileProjection/Attribute:profile' => 'Профиль', + 'Class:URP_ProfileProjection/Attribute:profile+' => 'Название профиля', + 'Class:URP_ProfileProjection/Attribute:value' => 'Значение выражения', + 'Class:URP_ProfileProjection/Attribute:value+' => 'OQL выражение (используя $user) | константа | | +атрибут кода', + 'Class:URP_ProfileProjection/Attribute:attribute' => 'Атрибут', + 'Class:URP_ProfileProjection/Attribute:attribute+' => 'Целевой атрибут кода (необязательный)', +)); + +// +// Class: URP_ClassProjection +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_ClassProjection' => 'прожктирование классов', + 'Class:URP_ClassProjection+' => 'прожктирование классов', + 'Class:URP_ClassProjection/Attribute:dimensionid' => 'Размерность', + 'Class:URP_ClassProjection/Attribute:dimensionid+' => 'применение размерности', + 'Class:URP_ClassProjection/Attribute:dimension' => 'Размерность', + 'Class:URP_ClassProjection/Attribute:dimension+' => 'применение размерности', + 'Class:URP_ClassProjection/Attribute:class' => 'Класс', + 'Class:URP_ClassProjection/Attribute:class+' => 'Целевой класс', + 'Class:URP_ClassProjection/Attribute:value' => 'Значение выражения', + 'Class:URP_ClassProjection/Attribute:value+' => 'OQL выражение (используя $this) | константа | | +атрибут кода', + 'Class:URP_ClassProjection/Attribute:attribute' => 'Атрибут', + 'Class:URP_ClassProjection/Attribute:attribute+' => 'Целевой атрибут кода (необязательный)', +)); + +// +// Class: URP_ActionGrant +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_ActionGrant' => 'действие разрешений', + 'Class:URP_ActionGrant+' => 'разрешения на классы', + 'Class:URP_ActionGrant/Attribute:profileid' => 'Профиль', + 'Class:URP_ActionGrant/Attribute:profileid+' => 'использование профиля', + 'Class:URP_ActionGrant/Attribute:profile' => 'Профиль', + 'Class:URP_ActionGrant/Attribute:profile+' => 'использование профиля', + 'Class:URP_ActionGrant/Attribute:class' => 'Класс', + 'Class:URP_ActionGrant/Attribute:class+' => 'Целевой класс', + 'Class:URP_ActionGrant/Attribute:permission' => 'Разрешения', + 'Class:URP_ActionGrant/Attribute:permission+' => 'разрешено или нет?', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes' => 'да', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => 'да', + 'Class:URP_ActionGrant/Attribute:permission/Value:no' => 'нет', + 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => 'нет', + 'Class:URP_ActionGrant/Attribute:action' => 'Действие', + 'Class:URP_ActionGrant/Attribute:action+' => 'действие выполняемое на данном классе', +)); + +// +// Class: URP_StimulusGrant +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_StimulusGrant' => 'разрешения стимулов', + 'Class:URP_StimulusGrant+' => 'разрешения на стимулы в жизненном цикле объекта', + 'Class:URP_StimulusGrant/Attribute:profileid' => 'Профиль', + 'Class:URP_StimulusGrant/Attribute:profileid+' => 'использование профиля', + 'Class:URP_StimulusGrant/Attribute:profile' => 'Профиль', + 'Class:URP_StimulusGrant/Attribute:profile+' => 'использование профиля', + 'Class:URP_StimulusGrant/Attribute:class' => 'Класс', + 'Class:URP_StimulusGrant/Attribute:class+' => 'Целевой класс', + 'Class:URP_StimulusGrant/Attribute:permission' => 'Разрешения', + 'Class:URP_StimulusGrant/Attribute:permission+' => 'разрешено или нет?', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'да', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => 'да', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'нет', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => 'нет', + 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Стимулы', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'код стимулов', +)); + +// +// Class: URP_AttributeGrant +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:URP_AttributeGrant' => 'разрешения атрибутов', + 'Class:URP_AttributeGrant+' => 'разрешения на уровне атрибутов', + 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'Действие предоставления', + 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => 'действие предоставления', + 'Class:URP_AttributeGrant/Attribute:attcode' => 'Атрибут', + 'Class:URP_AttributeGrant/Attribute:attcode+' => 'Код атрибута', +)); + +// +// String from the User Interface: menu, messages, buttons, etc... +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:WelcomeMenu' => 'Добро пожаловать', + 'Menu:WelcomeMenu+' => 'Добро пожаловать в iTop', + 'Menu:WelcomeMenuPage' => 'Добро пожаловать', + 'Menu:WelcomeMenuPage+' => 'Добро пожаловать в iTop', + 'UI:WelcomeMenu:Title' => 'Добро пожаловать в iTop', + + 'UI:WelcomeMenu:LeftBlock' => '

    iTop is a complete, OpenSource, IT Operational Portal.

    +
      Он включает: +
    • A complete CMDB (Configuration management database) to document and manage the IT inventory.
    • +
    • Модуль управления инцидентами для отслеживания и общения по вопросам IT.
    • +
    • Модуль управления изменениями для планирования и отслеживания изменений в IT.
    • +
    • База данных известных ошибок для ускорения устранения инцидентов.
    • +
    • Модуль простоев для документирования всех запланированных простоев и оповещения соответстсвующих контактов.
    • +
    • Панели для быстрого обзора IT.
    • +
    +

    Все модули могут быть настроены, шаг за шагом, независмо друг от друга.

    ', + + 'UI:WelcomeMenu:RightBlock' => '

    iTop ориентирован на предоставления сервисов, он позволяет IT специалистам легко управляться с несколькими заказчиками или организациями. +

      iTop обеспечивает многофункциональный набор бизнес-процессов, которые: +
    • Повышает эффективность управления IT
    • +
    • Повышает производительность IT-операция
    • +
    • Улучшает удовлетворенность клиентов и обеспечивает понимание бизнес-процессов.
    • +
    +

    +

    iTop полностью открыт для интеграции в рамках текущего управления ИТ-инфраструктурой.

    +

    +

      Внедрение ИТ-портала нового поколения поможет вам: +
    • Лучше управлять более и более сложными ИТ-окружениями.
    • +
    • Реализовывать процессы ITIL в ваем собственном темпе.
    • +
    • Управлять наиболее важнім активом ИТ: документацией.
    • +
    +

    ', + 'UI:WelcomeMenu:AllOpenRequests' => 'Открытые запросы: %1$d', + 'UI:WelcomeMenu:MyCalls' => 'Мои запросы', + 'UI:WelcomeMenu:OpenIncidents' => 'Открытые инциденты: %1$d', + 'UI:WelcomeMenu:AllConfigItems' => 'Кофигурационные единицы: %1$d', + 'UI:WelcomeMenu:MyIncidents' => 'Инциденты назначенные на меня', + 'UI:AllOrganizations' => ' Все организации ', + 'UI:YourSearch' => 'Ваш поиск', + 'UI:LoggedAsMessage' => 'Вы вошли как %1$s', + 'UI:LoggedAsMessage+Admin' => 'Вы вошли как %1$s (Администратор)', + 'UI:Button:Logoff' => 'Выход', + 'UI:Button:GlobalSearch' => 'Поиск', + 'UI:Button:Search' => ' Поиск ', + 'UI:Button:Query' => ' Запрос ', + 'UI:Button:Ok' => 'Ok', + 'UI:Button:Cancel' => 'Отмена', + 'UI:Button:Apply' => 'Применить', + 'UI:Button:Back' => ' << Назад ', + 'UI:Button:Next' => ' Вперёд >> ', + 'UI:Button:Finish' => ' Конец ', + 'UI:Button:DoImport' => ' Выполнить импорт ! ', + 'UI:Button:Done' => ' Сделать ', + 'UI:Button:SimulateImport' => ' Эмулировать импорт ', + 'UI:Button:Test' => 'Тестировать!', + 'UI:Button:Evaluate' => ' Оценка ', + 'UI:Button:AddObject' => ' Добавить... ', + 'UI:Button:BrowseObjects' => ' Обзор... ', + 'UI:Button:Add' => ' Добавить ', + 'UI:Button:AddToList' => ' << Добавить ', + 'UI:Button:RemoveFromList' => ' Удалить >> ', + 'UI:Button:FilterList' => ' Фильтр... ', + 'UI:Button:Create' => ' Создать ', + 'UI:Button:Delete' => ' Удалить ! ', + 'UI:Button:ChangePassword' => ' Сменить пароль ', + 'UI:Button:ResetPassword' => ' Сбросить пароль ', + + 'UI:SearchToggle' => 'Поиск', + 'UI:ClickToCreateNew' => 'Создать новый %1$s', + 'UI:SearchFor_Class' => 'Поиск для %1$s объектов', + 'UI:NoObjectToDisplay' => 'Нет объектов для отображения.', + 'UI:Error:MandatoryTemplateParameter_object_id' => 'Параметр object_id является обязательным если указан link_attr. Проверьте определение отображения шаблона.', + 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Параметр object_id является обязательным если указан link_attr. Проверьте определение отображения шаблона', + 'UI:Error:MandatoryTemplateParameter_group_by' => 'Параметр group_by является обязательным. Проверьте определение отображения шаблона.', + 'UI:Error:InvalidGroupByFields' => 'Неверный список полей для группировки: "%1$s".', + 'UI:Error:UnsupportedStyleOfBlock' => 'Ошибка: неподдерживаемый стиль блока: "%1$s".', + 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Неправильное определение ссылки: класс объектов для управления: %1$s не был найден в качестве внешнего ключа в классе %2$s', + 'UI:Error:Object_Class_Id_NotFound' => 'Объект: %1$s:%2$d не найден.', + 'UI:Error:WizardCircularReferenceInDependencies' => 'Ошибка: Циклическая ссылка в зависимостях между полями, проверить модель данных.', + 'UI:Error:UploadedFileTooBig' => 'Загружаемый файл слишком большой. (Максимально разрешённый размер %1$s). Проверьте в конфинурации PHP параметры upload_max_filesize и post_max_size.', + 'UI:Error:UploadedFileTruncated.' => 'Загруженный файл был усечен !', + 'UI:Error:NoTmpDir' => 'Временный каталог не определен.', + 'UI:Error:CannotWriteToTmp_Dir' => ' Невозможно записать временный файл на диск. upload_tmp_dir = "%1$s".', + 'UI:Error:UploadStoppedByExtension_FileName' => 'Загрузка остановлена по расширению. (Имя файла = "%1$s").', + 'UI:Error:UploadFailedUnknownCause_Code' => 'Загрузка файла не удалась по неизвестной причине. (Код ошибки = "%1$s").', + + 'UI:Error:1ParametersMissing' => 'Ошибка: следующий параметр должен быть указан для этой операции: %1$s.', + 'UI:Error:2ParametersMissing' => 'Ошибка: следующие параметры должен быть указан для этой операции: %1$s и %2$s.', + 'UI:Error:3ParametersMissing' => 'Ошибка: следующие параметры должен быть указан для этой операции: %1$s, %2$s и %3$s.', + 'UI:Error:4ParametersMissing' => 'Ошибка: следующие параметры должен быть указан для этой операции: %1$s, %2$s, %3$s и %4$s.', + 'UI:Error:IncorrectOQLQuery_Message' => 'Ошибка: неправильній запрос OQL: %1$s', + 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Ошибка при выполнении запроса: %1$s', + 'UI:Error:ObjectAlreadyUpdated' => 'Ошибка: объект уже обновлён.', + 'UI:Error:ObjectCannotBeUpdated' => 'Ошибка: объект не может быть обновлён.', + 'UI:Error:ObjectsAlreadyDeleted' => 'Ошибка: объект уже удалён!', + 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'Вам не разрешено выполнять массовое удаления объектов класса %1$s', + 'UI:Error:DeleteNotAllowedOn_Class' => 'Вы не можете удалять объекты класса %1$s', + 'UI:Error:BulkModifyNotAllowedOn_Class' => 'Вам не разрешено выполнять массовое обновление объектов класса %1$s', + 'UI:Error:ObjectAlreadyCloned' => 'Ошибка: объект уже клонирован!', + 'UI:Error:ObjectAlreadyCreated' => 'Ошибка: объект уже создан!', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Ошибка: недействительный стимул "%1$s" на объекте %2$s в состоянии "%3$s".', + + + 'UI:GroupBy:Count' => 'Счётчик', + 'UI:GroupBy:Count+' => 'Количество элементов', + 'UI:CountOfObjects' => '%1$d объектов соответствует критериям.', + 'UI_CountOfObjectsShort' => '%1$d объектов.', + 'UI:NoObject_Class_ToDisplay' => 'Нечего отображать %1$s', + 'UI:History:LastModified_On_By' => 'Последнее изменение %1$s by %2$s.', + 'UI:HistoryTab' => 'История', + 'UI:NotificationsTab' => 'Оповещения', + 'UI:History:Date' => 'Дата', + 'UI:History:Date+' => 'Дата изменения', + 'UI:History:User' => 'Пользователь', + 'UI:History:User+' => 'Пользователь сделавший изменение', + 'UI:History:Changes' => 'Изменения', + 'UI:History:Changes+' => 'Изменения, внесенные в объект', + 'UI:Loading' => 'Загрузка...', + 'UI:Menu:Actions' => 'Действия', + 'UI:Menu:New' => 'Новый...', + 'UI:Menu:Add' => 'Добавить...', + 'UI:Menu:Manage' => 'Управление...', + 'UI:Menu:EMail' => 'eMail', + 'UI:Menu:CSVExport' => 'Экспорт CSV', + 'UI:Menu:Modify' => 'Изменить...', + 'UI:Menu:Delete' => 'Удалить...', + 'UI:Menu:Manage' => 'Управление...', + 'UI:Menu:BulkDelete' => 'Удалить...', + 'UI:UndefinedObject' => 'неопределённый', + 'UI:Document:OpenInNewWindow:Download' => 'Открыть в новом окне: %1$s, Загрузка: %2$s', + 'UI:SelectAllToggle+' => 'Выбрать / Отменить всё', + 'UI:TruncatedResults' => '%1$d объектов отображено из %2$d', + 'UI:DisplayAll' => 'Показать всё', + 'UI:CollapseList' => 'Свернуть', + 'UI:CountOfResults' => '%1$d объект(ы)', + 'UI:ChangesLogTitle' => 'Журнал изменений (%1$d):', + 'UI:EmptyChangesLogTitle' => 'Журнал изменений пустой', + 'UI:SearchFor_Class_Objects' => 'Поиск объекта %1$s', + 'UI:OQLQueryBuilderTitle' => 'Коструктор запросов OQL', + 'UI:OQLQueryTab' => 'Запрос OQL', + 'UI:SimpleSearchTab' => 'Простой поиск', + 'UI:Details+' => 'Подробности', + 'UI:SearchValue:Any' => '* Любой *', + 'UI:SearchValue:Mixed' => '* смешанный *', + 'UI:SelectOne' => '-- выбрать один --', + 'UI:Login:Welcome' => 'Добро пожаловать в iTop!', + 'UI:Login:IncorrectLoginPassword' => 'Неправильный логин/пароль. Пожалуйста, попробуйте еще раз.', + 'UI:Login:IdentifyYourself' => 'Представтесть, прежде чем продолжить', + 'UI:Login:UserNamePrompt' => 'Имя пользователя', + 'UI:Login:PasswordPrompt' => 'Пароль', + 'UI:Login:ChangeYourPassword' => 'Изменение пароля', + 'UI:Login:OldPasswordPrompt' => 'Старый пароль', + 'UI:Login:NewPasswordPrompt' => 'Новый пароль', + 'UI:Login:RetypeNewPasswordPrompt' => 'Повтор нового пароля', + 'UI:Login:IncorrectOldPassword' => 'Ошибка: старый пароль неверный', + 'UI:LogOffMenu' => 'Выход', + 'UI:LogOff:ThankYou' => 'Спасибо за использование iTop', + 'UI:LogOff:ClickHereToLoginAgain' => 'Нажмите здесь, чтобы снова войти...', + 'UI:ChangePwdMenu' => 'Изменить пароль...', + 'UI:Login:RetypePwdDoesNotMatch' => 'Новый пароль и повторный пароль не совпадают!', + 'UI:Button:Login' => 'Введите iTop', + 'UI:Login:Error:AccessRestricted' => 'Доступ к iTop ограничен. Пожалуйста, свяжитесь с администратором iTop.', + 'UI:Login:Error:AccessAdmin' => 'Доступ ограничен для лиц с административными привилегиями. Пожалуйста, свяжитесь с администратором iTop.', + 'UI:CSVImport:MappingSelectOne' => '-- выбрать один --', + 'UI:CSVImport:MappingNotApplicable' => '-- игнорировать это поле --', + 'UI:CSVImport:NoData' => 'Пустой набор данных..., пожалуйста введите что-нибудь!', + 'UI:Title:DataPreview' => 'Предпросмотр данных', + 'UI:CSVImport:ErrorOnlyOneColumn' => 'Ошибка: Данные содежат только одну колонку. Выбран правильный разделитель?', + 'UI:CSVImport:FieldName' => 'Поле %1$d', + 'UI:CSVImport:DataLine1' => 'Строка данных 1', + 'UI:CSVImport:DataLine2' => 'Строка данных 2', + 'UI:CSVImport:idField' => 'id (Первичный ключ)', + 'UI:Title:BulkImport' => 'iTop - Пакетный импорт', + 'UI:Title:BulkImport+' => 'Мастер импорта CSV', + 'UI:CSVImport:ClassesSelectOne' => '-- выбрать один --', + 'UI:CSVImport:ErrorExtendedAttCode' => 'Внутренняя ошибка: "%1$s" некорректный код потому, что "%2$s" НЕ являеться внешним ключём класса "%3$s"', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d объект(ы) останеться неизменным.', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d объект(ы) будет изменён.', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d объект(ы) будет добавлен.', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d объект(ы) будут ошибочны.', + 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d объект(ы) остался неизменённым.', + 'UI:CSVImport:ObjectsWereModified' => '%1$d объект(ы) изменён.', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d объект(ы) был добавлен.', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d объект(ы) содержит ошибки.', + 'UI:Title:CSVImportStep2' => 'Step 2 of 5: Опции данных CSV', + 'UI:Title:CSVImportStep3' => 'Step 3 of 5: Распределение данных', + 'UI:Title:CSVImportStep4' => 'Step 4 of 5: Симуляция импорта', + 'UI:Title:CSVImportStep5' => 'Step 5 of 5: Импорт завершён', + 'UI:CSVImport:LinesNotImported' => 'Строки небыли загружены:', + 'UI:CSVImport:LinesNotImported+' => 'Следующие строки не были импортированы, потому что они содержат ошибки', + 'UI:CSVImport:SeparatorComma+' => ', (запятая)', + 'UI:CSVImport:SeparatorSemicolon+' => '; (точка с запятой)', + 'UI:CSVImport:SeparatorTab+' => 'табулятор', + 'UI:CSVImport:SeparatorOther' => 'другое:', + 'UI:CSVImport:QualifierDoubleQuote+' => '" (двойная кавычка)', + 'UI:CSVImport:QualifierSimpleQuote+' => '\' (одинарная кавычка)', + 'UI:CSVImport:QualifierOther' => 'другое:', + 'UI:CSVImport:TreatFirstLineAsHeader' => 'Использовать первую строку как заголовок (названия столбцов)', + 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'Пропустить %1$s строк(у) от начала файла', + 'UI:CSVImport:CSVDataPreview' => 'Предпросмотр данных CSV', + 'UI:CSVImport:SelectFile' => 'Выбор файла для иморта:', + 'UI:CSVImport:Tab:LoadFromFile' => 'Загрузить из файла', + 'UI:CSVImport:Tab:CopyPaste' => 'Копировать и вставить данные', + 'UI:CSVImport:Tab:Templates' => 'Шаблоны', + 'UI:CSVImport:PasteData' => 'Вставить данные для импорта:', + 'UI:CSVImport:PickClassForTemplate' => 'Выбор шаблона для загрузки: ', + 'UI:CSVImport:SeparatorCharacter' => 'Символ разделителя:', + 'UI:CSVImport:TextQualifierCharacter' => 'Символ экранирования текста', + 'UI:CSVImport:CommentsAndHeader' => 'Коментарии и заголовок', + 'UI:CSVImport:SelectClass' => 'Выбор класса импорта:', + 'UI:CSVImport:AdvancedMode' => 'Расширенный режим', + 'UI:CSVImport:AdvancedMode+' => 'В расширенном режиме "id" (первичный ключ) объекта может быть использован для обновления и переименования объектов.' . + 'Однако колонка "id" (if present) может быть использовать только как критерий поиска и не модет быть совмещена с любым другим критерием поиска.', + 'UI:CSVImport:SelectAClassFirst' => 'Для настройки рапределения, в первую очередь выберите класс.', + 'UI:CSVImport:HeaderFields' => 'Поля', + 'UI:CSVImport:HeaderMappings' => 'Распределение', + 'UI:CSVImport:HeaderSearch' => 'Поиск?', + 'UI:CSVImport:AlertIncompleteMapping' => 'Необходимо выбрать распределение для каждой ячейки.', + 'UI:CSVImport:AlertNoSearchCriteria' => 'Необходимо выбрать, по крайней мере один критерий', + 'UI:CSVImport:Encoding' => 'Кодировка символов', + 'UI:UniversalSearchTitle' => 'iTop - Универсальный поиск', + 'UI:UniversalSearch:Error' => 'Ошибка: %1$s', + 'UI:UniversalSearch:LabelSelectTheClass' => 'Выбор класса для поиска: ', + + 'UI:Audit:Title' => 'iTop - Аудит CMDB', + 'UI:Audit:InteractiveAudit' => 'Интерактивный аудит', + 'UI:Audit:HeaderAuditRule' => 'Правило аудита', + 'UI:Audit:HeaderNbObjects' => '# Объекты', + 'UI:Audit:HeaderNbErrors' => '# Ошибки', + 'UI:Audit:PercentageOk' => '% Ok', + + 'UI:RunQuery:Title' => 'iTop - Оценка запросов OQL', + 'UI:RunQuery:QueryExamples' => 'Примеры запросов', + 'UI:RunQuery:HeaderPurpose' => 'Цель', + 'UI:RunQuery:HeaderPurpose+' => 'Объяснение запросов', + 'UI:RunQuery:HeaderOQLExpression' => 'Выражение OQL', + 'UI:RunQuery:HeaderOQLExpression+' => 'Запрос в синтаксисе OQL', + 'UI:RunQuery:ExpressionToEvaluate' => 'Оценка віражения: ', + 'UI:RunQuery:MoreInfo' => 'Подробная информация о запросе: ', + 'UI:RunQuery:DevelopedQuery' => 'Переработанное выражение запроса: ', + 'UI:RunQuery:SerializedFilter' => 'Сериализованные фильты: ', + 'UI:RunQuery:Error' => 'Ошибка при выполнении запроса: %1$s', + + 'UI:Schema:Title' => 'iTop схема объектов', + 'UI:Schema:CategoryMenuItem' => 'Категория %1$s', + 'UI:Schema:Relationships' => 'Отношения', + 'UI:Schema:AbstractClass' => 'Абстрактный класс: ни один объект из этого класса может быть создан.', + 'UI:Schema:NonAbstractClass' => 'Не абстрактный класс: объекты этого класса могут быть созданы.', + 'UI:Schema:ClassHierarchyTitle' => 'Иерархия классов', + 'UI:Schema:AllClasses' => 'Все классы', + 'UI:Schema:ExternalKey_To' => 'Внешний ключ %1$s', + 'UI:Schema:Columns_Description' => 'Столбцы: %1$s', + 'UI:Schema:Default_Description' => 'По умолчанию: "%1$s"', + 'UI:Schema:NullAllowed' => 'Null разрешён', + 'UI:Schema:NullNotAllowed' => 'Null НЕ разрешён', + 'UI:Schema:Attributes' => 'Атрибуты', + 'UI:Schema:AttributeCode' => 'Код атрибута', + 'UI:Schema:AttributeCode+' => 'Внутренний код атрибута', + 'UI:Schema:Label' => 'Метка', + 'UI:Schema:Label+' => 'Метка атрибута', + 'UI:Schema:Type' => 'Тип', + + 'UI:Schema:Type+' => 'Тип данных атрибута', + 'UI:Schema:Origin' => 'Происхождение', + 'UI:Schema:Origin+' => 'Базовый класс, в котором этот атрибут определен', + 'UI:Schema:Description' => 'Описание', + 'UI:Schema:Description+' => 'Описание атрибута', + 'UI:Schema:AllowedValues' => 'Допустимые значения', + 'UI:Schema:AllowedValues+' => 'Ограничения на возможные значения для этого атрибута', + 'UI:Schema:MoreInfo' => 'Подробнее', + 'UI:Schema:MoreInfo+' => 'Более подробная информация о поле, определённом в базе данных', + 'UI:Schema:SearchCriteria' => 'Критерий поиска', + 'UI:Schema:FilterCode' => 'Код фильтра', + 'UI:Schema:FilterCode+' => 'Код критерия поиска', + 'UI:Schema:FilterDescription' => 'Описание', + 'UI:Schema:FilterDescription+' => 'Описание еритерия поиска', + 'UI:Schema:AvailOperators' => 'Доступные операторы', + 'UI:Schema:AvailOperators+' => 'Возможные операторы для этого критерия поиска', + 'UI:Schema:ChildClasses' => 'Дочерние классы', + 'UI:Schema:ReferencingClasses' => 'Привязки классов', + 'UI:Schema:RelatedClasses' => 'Зависимые классы', + 'UI:Schema:LifeCycle' => 'Жизненный цикл', + 'UI:Schema:Triggers' => 'Триггеры', + 'UI:Schema:Relation_Code_Description' => 'Зависимость %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Вниз: %1$s', + 'UI:Schema:RelationUp_Description' => 'Вверх: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: распространяется на %2$d уровней, запрос: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: не распространяется (%2$d уровней), запрос: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s связан с классом %2$s через поле %3$s', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s связан с %2$s через %3$s::%4$s', + 'UI:Schema:Links:1-n' => 'Классы, указывающие на %1$s (1:n ссылки):', + 'UI:Schema:Links:n-n' => 'Классы связаны с %1$s (n:n сслыки):', + 'UI:Schema:Links:All' => 'График всех связанных классов', + 'UI:Schema:NoLifeCyle' => 'Не определён жизненный цикл для этих классов.', + 'UI:Schema:LifeCycleTransitions' => 'Переходы', + 'UI:Schema:LifeCyleAttributeOptions' => 'Варианты атрибутов', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Скрытый', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Только для чтения', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Обязательный', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Необходимо изменить', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Пользователю будет предложено изменить значение', + 'UI:Schema:LifeCycleEmptyList' => 'пустой список', + + 'UI:LinksWidget:Autocomplete+' => 'Введите первые 3 символа...', + 'UI:Combo:SelectValue' => '--- выбор значения ---', + 'UI:Label:SelectedObjects' => 'Выбранные объекты: ', + 'UI:Label:AvailableObjects' => 'Доступные объекты: ', + 'UI:Link_Class_Attributes' => '%1$s атрибуты', + 'UI:SelectAllToggle+' => 'Выбрать всё / Отменить всё', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Добавить %1$s объекты связанные с %2$s: %3$s', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => 'Добавть %1$s объекты для связи с %2$s', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Управление %1$s объектами связанными с %2$s: %3$s', + 'UI:AddLinkedObjectsOf_Class' => 'Добавить %1$ss...', + 'UI:RemoveLinkedObjectsOf_Class' => 'Удалить выбранные объекты', + 'UI:Message:EmptyList:UseAdd' => 'Список пуст, используй кнопку "Добавить ...", для добавения элементов.', + 'UI:Message:EmptyList:UseSearchForm' => 'Используйте форму поиска выше для поиска объектов, которые будут добавлены.', + + 'UI:Wizard:FinalStepTitle' => 'Последний шаг: подтверждение', + 'UI:Title:DeletionOf_Object' => 'Удаление %1$s', + 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Пакетное удаление %1$d объектов класса %2$s', + 'UI:Delete:NotAllowedToDelete' => 'Вы не можете удалить этот объект', + 'UI:Delete:NotAllowedToUpdate_Fields' => 'Вы не можете обновить следующее(ие) поле(я): %1$s', + 'UI:Error:NotEnoughRightsToDelete' => 'Этот объект не может быть удален, потому что текущий пользователь не имеет достаточных прав', + 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Этот объект не может быть удален, потому что некоторые ручные операции должны быть выполнены до этого', + 'UI:Archive_User_OnBehalfOf_User' => '%1$s от имени %2$s', + 'UI:Delete:AutomaticallyDeleted' => 'автоматически удалён', + 'UI:Delete:AutomaticResetOf_Fields' => 'автоматически сброшено поле(я): %1$s', + 'UI:Delete:CleaningUpRefencesTo_Object' => 'Очищенны все ссылки(связи?) на %1$s...', + 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Очищенны все ссылки(связи?) на %1$d объектов класса %2$s...', + 'UI:Delete:Done+' => 'Что было сделано...', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s удалено.', + 'UI:Delete:ConfirmDeletionOf_Name' => 'Удаление %1$s', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Удаление %1$d объектов класса %2$s', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => 'Должно быть автоматичски удалено, но вы не можете это сделать', + 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => 'Необходимо удалить вручную - но вы не можете удалить этот объект, свяжитесь с администратором вашего приложения', + 'UI:Delete:WillBeDeletedAutomatically' => 'Будет удалено автоматически', + 'UI:Delete:MustBeDeletedManually' => 'Необходимо удалить вручную', + 'UI:Delete:CannotUpdateBecause_Issue' => 'Должно быть автоматически обновлено, но: %1$s', + 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'будет автоматически обновлено (сброс: %1$s)', + 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d объектов/связей ссылаются(связаны?) %2$s', + 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d объектов/связей ссылаются на объекты, которые будут удалены', + 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Для обеспечения целостности базы данных, необходимо устранить все связи', + 'UI:Delete:Consequence+' => 'Что будет сделано', + 'UI:Delete:SorryDeletionNotAllowed' => 'К сожалению, вы не можете удалить этот объект, см. подробное объяснение выше', + 'UI:Delete:PleaseDoTheManualOperations' => 'Необходимо выполнить указанные ручные операции до запроса на удаление этого объекта', + 'UI:Delect:Confirm_Object' => 'Подтвердите удаление %1$s.', + 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Подтвердите удаление %1$d объектов класса %2$s.', + 'UI:WelcomeToITop' => 'Добро пожаловать в iTop', + 'UI:DetailsPageTitle' => 'iTop - %1$s - %2$s подробности', + 'UI:ErrorPageTitle' => 'iTop - Ошибка', + 'UI:ObjectDoesNotExist' => 'Извните, этот объект не существует (или вы не можете его видеть).', + 'UI:SearchResultsPageTitle' => 'iTop - Результаты поиска', + 'UI:Search:NoSearch' => 'Ничего не найдено', + 'UI:FullTextSearchTitle_Text' => 'Результаты для "%1$s":', + 'UI:Search:Count_ObjectsOf_Class_Found' => '%1$d объект(ы) класса %2$s найдено.', + 'UI:Search:NoObjectFound' => 'Объекты не найдены.', + 'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s модификации', + 'UI:ModificationTitle_Class_Object' => 'Модификации %1$s: %2$s', + 'UI:ClonePageTitle_Object_Class' => 'iTop - Клон %1$s - %2$s модификация', + 'UI:CloneTitle_Class_Object' => 'Клон %1$s: %2$s', + 'UI:CreationPageTitle_Class' => 'iTop - Создание нового %1$s ', + 'UI:CreationTitle_Class' => 'Создание нового %1$s', + 'UI:SelectTheTypeOf_Class_ToCreate' => 'Выбор типа %1$s для создания:', + 'UI:Class_Object_NotUpdated' => 'Изменений не обнаружено, %1$s (%2$s) не был изменён.', + 'UI:Class_Object_Updated' => '%1$s (%2$s) обновлён.', + 'UI:BulkDeletePageTitle' => 'iTop - Пакетное удаление', + 'UI:BulkDeleteTitle' => 'Выбор объектов для удаления:', + 'UI:PageTitle:ObjectCreated' => 'iTop Объект создан.', + 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s создан.', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Применение %1$s на объект: %2$s в состоянии %3$s для целевого класса: %4$s.', + 'UI:ObjectCouldNotBeWritten' => 'Объект не может быть записан: %1$s', + 'UI:PageTitle:FatalError' => 'iTop - Фатальная ошибка', + 'UI:SystemIntrusion' => 'Доступ запрещён. Вы пытаетесь выполнить неразрешённую операцию.', + 'UI:FatalErrorMessage' => 'Фатальная ошибка, iTop не может продолжать.', + 'UI:Error_Details' => 'Ошибка: %1$s.', + + 'UI:PageTitle:ClassProjections' => 'iTop управление пользователями - проектирование классов', + 'UI:PageTitle:ProfileProjections' => 'iTop управление пользователями - проектирование профилей', + 'UI:UserManagement:Class' => 'Классs', + 'UI:UserManagement:Class+' => 'Класс объектов', + 'UI:UserManagement:ProjectedObject' => 'Объект', + 'UI:UserManagement:ProjectedObject+' => 'Проектируемый объект', + 'UI:UserManagement:AnyObject' => '* любой *', + 'UI:UserManagement:User' => 'Пользователь', + 'UI:UserManagement:User+' => 'Пользователь учавствует', + 'UI:UserManagement:Profile' => 'Профиль', + 'UI:UserManagement:Profile+' => 'Профиль, указанный в проектировании', + 'UI:UserManagement:Action:Read' => 'Чтение', + 'UI:UserManagement:Action:Read+' => 'Чтение/отображение объектов', + 'UI:UserManagement:Action:Modify' => 'Modify', + 'UI:UserManagement:Action:Modify+' => 'Создание и редактирование (изменение) объектов', + 'UI:UserManagement:Action:Delete' => 'Удаление', + 'UI:UserManagement:Action:Delete+' => 'Удаление объектов', + 'UI:UserManagement:Action:BulkRead' => 'Пакетное чтение (Экспорт)', + 'UI:UserManagement:Action:BulkRead+' => 'Список оъектов или массовый экспорт', + 'UI:UserManagement:Action:BulkModify' => 'Пакетное изменение', + 'UI:UserManagement:Action:BulkModify+' => 'Массовое создание/редактирование (импорт CSV)', + 'UI:UserManagement:Action:BulkDelete' => 'Пакетное удаление', + 'UI:UserManagement:Action:BulkDelete+' => 'Массовое удаление объектов', + 'UI:UserManagement:Action:Stimuli' => 'Стимулы', + 'UI:UserManagement:Action:Stimuli+' => 'Допустимые (составные) действия', + 'UI:UserManagement:Action' => 'Действие', + 'UI:UserManagement:Action+' => 'Действие, выполняемое пользователем', + 'UI:UserManagement:TitleActions' => 'Действия', + 'UI:UserManagement:Permission' => 'Разрешения', + 'UI:UserManagement:Permission+' => 'Пользовательские разрешения', + 'UI:UserManagement:Attributes' => 'Атрибуты', + 'UI:UserManagement:ActionAllowed:Yes' => 'Да', + 'UI:UserManagement:ActionAllowed:No' => 'Нет', + 'UI:UserManagement:AdminProfile+' => 'Администраторы имеют полный доступ на чтение/запись всех объектов в базе данных.', + 'UI:UserManagement:NoLifeCycleApplicable' => 'не определено', + 'UI:UserManagement:NoLifeCycleApplicable+' => 'Не определён жизненній цикл для данного класса', + 'UI:UserManagement:GrantMatrix' => 'Матрица разрешений', + 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Связь между %1$s и %2$s', + 'UI:UserManagement:LinkBetween_User_And_Org' => 'Связь между %1$s и %2$s', + + 'Menu:AdminTools' => 'Инструменты админа', + 'Menu:AdminTools+' => 'Административные инструменты', + 'Menu:AdminTools?' => 'Инструменты доступны только для пользователей, имеющих профиль администратора', + + 'UI:ChangeManagementMenu' => 'Управление изменениями', + 'UI:ChangeManagementMenu+' => 'Управление изменениями', + 'UI:ChangeManagementMenu:Title' => 'Обзор изменений', + 'UI-ChangeManagementMenu-ChangesByType' => 'Изменения по типу', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Изменения по статутсу', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Изменения по рабочей группе', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Не назначенные изменения', + + 'UI:ConfigurationItemsMenu'=> 'Элементы конфигурации', + 'UI:ConfigurationItemsMenu+'=> 'Все устройства', + 'UI:ConfigurationItemsMenu:Title' => 'Обзор элементов конфигурации', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Серверы по критичности', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'ПК по критичности', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Сетевые устройства по критичности', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Приложения по критичности', + + 'UI:ConfigurationManagementMenu' => 'Управление конфигурациями', + 'UI:ConfigurationManagementMenu+' => 'Управление конфигурациями', + 'UI:ConfigurationManagementMenu:Title' => 'Обзор инфраструктуры', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Объекты инфраструктуры по типу', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Объекты инфраструктуры по статусу', + +'UI:ConfigMgmtMenuOverview:Title' => 'Панель управления конфигурациями', +'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => 'Элементы конфигурации по статусу', +'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => 'Элементы конфигурации по типу', + +'UI:RequestMgmtMenuOverview:Title' => 'Панель управления запросами', +'UI-RequestManagementOverview-RequestByService' => 'Пользовательские запросы по сервису', +'UI-RequestManagementOverview-RequestByPriority' => 'Пользовательские запросы по приоритету', +'UI-RequestManagementOverview-RequestUnassigned' => 'Пользовательские запросы не назначенные не на один агент', + +'UI:IncidentMgmtMenuOverview:Title' => 'Панель управления инцидентами', +'UI-IncidentManagementOverview-IncidentByService' => 'Инциденты по сервису', +'UI-IncidentManagementOverview-IncidentByPriority' => 'Инциденты по приоритету', +'UI-IncidentManagementOverview-IncidentUnassigned' => 'Инциденты не назначенные не на один агент', + +'UI:ChangeMgmtMenuOverview:Title' => 'Панель управления изменениями', +'UI-ChangeManagementOverview-ChangeByType' => 'Изменения по типу', +'UI-ChangeManagementOverview-ChangeUnassigned' => 'Изменения не назначенные не на один агент', +'UI-ChangeManagementOverview-ChangeWithOutage' => 'Отключения в связи с изменениями', + +'UI:ServiceMgmtMenuOverview:Title' => 'Панель управления сервисами', +'UI-ServiceManagementOverview-CustomerContractToRenew' => 'Договора с клиентами, которые будут обновлены в течении 30 дней', +'UI-ServiceManagementOverview-ProviderContractToRenew' => 'Договора с поставщиками, которые будут обновлены в течении 30 дней', + + 'UI:ContactsMenu' => 'Договора', + 'UI:ContactsMenu+' => 'Договора', + 'UI:ContactsMenu:Title' => 'Обзор договоров', + 'UI-ContactsMenu-ContactsByLocation' => 'Договора по размещению', + 'UI-ContactsMenu-ContactsByType' => 'Договора по типу', + 'UI-ContactsMenu-ContactsByStatus' => 'Договора по статусу', + + 'Menu:CSVImportMenu' => 'Импорт CSV', + 'Menu:CSVImportMenu+' => 'Пакетное создание или обновление', + + 'Menu:DataModelMenu' => 'Модель данных', + 'Menu:DataModelMenu+' => 'Обзор модели данных', + + 'Menu:ExportMenu' => 'Экспорт', + 'Menu:ExportMenu+' => 'Экспорт результатов любого запроса в HTML, CSV или XML', + + 'Menu:NotificationsMenu' => 'Уведомления', + 'Menu:NotificationsMenu+' => 'Конфигурация уведомлений', + 'UI:NotificationsMenu:Title' => 'Конфигурация Уведомлений', + 'UI:NotificationsMenu:Help' => 'Помощь', + 'UI:NotificationsMenu:HelpContent' => '

    В iTop уведомления полностью настраиваемые. Они основаны на двух наборах объектов: триггеры и действия.

    +

    Триггеры оперделяют когда уведомление будет выполнено. Есть 3 типа триггеров обробатывающих 3 разных фазы жизненного цикла объекта: +

      +
    1. the "OnCreate" триггеры сработают когда объект заданного класса будет создан
    2. +
    3. the "OnStateEnter" триггеры сработают перед тем как объект заданного класса войдёт в заданное состояние (выйдет из другого состояния)
    4. +
    5. the "OnStateLeave" триггеры сработают когда объекты заданного класса выйдут из заданного состояния
    6. +
    +

    +

    +Действия определяют, какое действие будет выполнено при срабатывании триггера. Пока есть только одно действие, которое состоит в отправке сообщения на электронную почту. +Эти действия также определяют шаблон, который будет использован для отправки электронного сообщения, а также другие параметры сообщения, такие как получатель, важность и т.д. +

    +

    Специальная страница: email.test.php доступна для тестирования и устранения неполадок в настройка почты в PHP.

    +

    Чтобы быть выполненными, действия необходимо ассоциировать с триггерами. +При ассоциации с триггером, каждое действие получает "порядковый" номер, который определяет порядок выполнения действий.

    ', + 'UI:NotificationsMenu:Triggers' => 'Триггеры', + 'UI:NotificationsMenu:AvailableTriggers' => 'Доступные триггеры', + 'UI:NotificationsMenu:OnCreate' => 'При создании объекта', + 'UI:NotificationsMenu:OnStateEnter' => 'При входе объекта в заданное состояние', + 'UI:NotificationsMenu:OnStateLeave' => 'При выходе объекта из заданного состояния', + 'UI:NotificationsMenu:Actions' => 'Действия', + 'UI:NotificationsMenu:AvailableActions' => 'Доступные действия', + + 'Menu:AuditCategories' => 'Категории аудита', + 'Menu:AuditCategories+' => 'Категории аудита', + 'Menu:Notifications:Title' => 'Категории аудита', + + 'Menu:RunQueriesMenu' => 'Выполнение запросов', + 'Menu:RunQueriesMenu+' => 'Выполнение любых запросов', + + 'Menu:DataAdministration' => 'Административные данные', + 'Menu:DataAdministration+' => 'Административные данные', + + 'Menu:UniversalSearchMenu' => 'Универсальный поиск', + 'Menu:UniversalSearchMenu+' => 'Поиск чего угодно...', + + 'Menu:ApplicationLogMenu' => 'Логгирование приложения', + 'Menu:ApplicationLogMenu+' => 'Логгирование приложения', + 'Menu:ApplicationLogMenu:Title' => 'Логгирование приложения', + + 'Menu:UserManagementMenu' => 'Управление пользователями', + 'Menu:UserManagementMenu+' => 'Управление пользователями', + + 'Menu:ProfilesMenu' => 'Профили', + 'Menu:ProfilesMenu+' => 'Профили', + 'Menu:ProfilesMenu:Title' => 'Профили', + + 'Menu:UserAccountsMenu' => 'Учетные записи пользователей', + 'Menu:UserAccountsMenu+' => 'Учетные записи пользователей', + 'Menu:UserAccountsMenu:Title' => 'Учетные записи пользователей', + + 'UI:iTopVersion:Short' => 'iTop версия %1$s', + 'UI:iTopVersion:Long' => 'iTop версия %1$s-%2$s основан на %3$s', + 'UI:PropertiesTab' => 'Свойства', + + 'UI:OpenDocumentInNewWindow_' => 'Открыть этот документ в новом окне: %1$s', + 'UI:DownloadDocument_' => 'Скачать этот документ: %1$s', + 'UI:Document:NoPreview' => 'Не доступен предварительный просомтр для документов данного типа', + + 'UI:DeadlineMissedBy_duration' => 'Пропущен %1$s', + 'UI:Deadline_LessThan1Min' => '< 1 мин', + 'UI:Deadline_Minutes' => '%1$d мин', + 'UI:Deadline_Hours_Minutes' => '%1$dч %2$dмин', + 'UI:Deadline_Days_Hours_Minutes' => '%1$dд %2$dч %3$dмин', + 'UI:Help' => 'Помощь', + 'UI:PasswordConfirm' => '(Подтвердить)', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Перед добавлением %1$s объектво, сохраните этот объект.', + 'UI:DisplayThisMessageAtStartup' => 'Показать это сообщение при запуске', + 'UI:RelationshipGraph' => 'Графический вид', + 'UI:RelationshipList' => 'Список', + + 'Portal:Title' => 'Пользовательский iTop портал', + 'Portal:Refresh' => 'Обновить', + 'Portal:Back' => 'Назад', + 'Portal:CreateNewRequest' => 'Создать новый запрос', + 'Portal:ChangeMyPassword' => 'Изменить мой пароль', + 'Portal:Disconnect' => 'Отключить', + 'Portal:OpenRequests' => 'Мои открытые запросы', + 'Portal:ResolvedRequests' => 'Мои решённые запросы', + 'Portal:SelectService' => 'Выбери сервис из каталога:', + 'Portal:PleaseSelectOneService' => 'Необходимо выбрать хотя-бы один сервис', + 'Portal:SelectSubcategoryFrom_Service' => 'Выбери под-категорию для сервиса %1$s:', + 'Portal:PleaseSelectAServiceSubCategory' => 'Необходимо выбрать одну под-категорию', + 'Portal:DescriptionOfTheRequest' => 'Введи описание запроса:', + 'Portal:TitleRequestDetailsFor_Request' => 'Подробности запроса %1$s:', + 'Portal:NoOpenRequest' => 'Нет запросов в этой категории.', + 'Portal:Button:CloseTicket' => 'Закрыть этот "тикет"', + 'Portal:EnterYourCommentsOnTicket' => 'Введите ваши каментарии по решению этого "тикета":', + 'Portal:ErrorNoContactForThisUser' => 'Ошибка: текющий пользователь не ассоциирован с Контактом/Человеком. Пожалуйста свяжитесь с вашим администратором.', + + 'Enum:Undefined' => 'Неопределён', +)); + + + +?> diff --git a/modules/authent-external/module.authent-external.php b/modules/authent-external/module.authent-external.php index 71f7024b8c..2e15fb6d51 100644 --- a/modules/authent-external/module.authent-external.php +++ b/modules/authent-external/module.authent-external.php @@ -49,6 +49,7 @@ SetupWebPage::AddModule( 'en.dict.authent-external.php', 'fr.dict.authent-external.php', 'de.dict.authent-external.php', + 'ru.dict.authent-external.php', ), 'data.struct' => array( //'data.struct.authent-ldap.xml', diff --git a/modules/authent-external/ru.dict.authent-external.php b/modules/authent-external/ru.dict.authent-external.php new file mode 100644 index 0000000000..4956ff4691 --- /dev/null +++ b/modules/authent-external/ru.dict.authent-external.php @@ -0,0 +1,43 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserExternal +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:UserExternal' => 'Внешний пользователь', + 'Class:UserExternal+' => 'Пользователь аутентифицированный вне iTop', +)); + +?> diff --git a/modules/authent-ldap/module.authent-ldap.php b/modules/authent-ldap/module.authent-ldap.php index 544689ef34..edaa38003b 100644 --- a/modules/authent-ldap/module.authent-ldap.php +++ b/modules/authent-ldap/module.authent-ldap.php @@ -32,6 +32,7 @@ SetupWebPage::AddModule( 'en.dict.authent-ldap.php', 'fr.dict.authent-ldap.php', 'de.dict.authent-ldap.php', + 'ru.dict.authent-ldap.php', ), 'data.struct' => array( //'data.struct.authent-ldap.xml', diff --git a/modules/authent-ldap/ru.dict.authent-ldap.php b/modules/authent-ldap/ru.dict.authent-ldap.php new file mode 100644 index 0000000000..37fbeb85da --- /dev/null +++ b/modules/authent-ldap/ru.dict.authent-ldap.php @@ -0,0 +1,45 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserLDAP +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:UserLDAP' => 'Пользователь LDAP', + 'Class:UserLDAP+' => 'Пользователь аутентифицированный в LDAP', + 'Class:UserLDAP/Attribute:password' => 'Пароль', + 'Class:UserLDAP/Attribute:password+' => 'строка аутентификации пользователя', +)); + +?> diff --git a/modules/authent-local/module.authent-local.php b/modules/authent-local/module.authent-local.php index 0517d514b4..f0ee27eb3e 100644 --- a/modules/authent-local/module.authent-local.php +++ b/modules/authent-local/module.authent-local.php @@ -26,6 +26,7 @@ SetupWebPage::AddModule( 'en.dict.authent-local.php', 'fr.dict.authent-local.php', 'de.dict.authent-local.php', + 'ru.dict.authent-local.php', ), 'data.struct' => array( //'data.struct.authent-local.xml', diff --git a/modules/authent-local/ru.dict.authent-local.php b/modules/authent-local/ru.dict.authent-local.php new file mode 100644 index 0000000000..9975d8b2fa --- /dev/null +++ b/modules/authent-local/ru.dict.authent-local.php @@ -0,0 +1,45 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserLocal +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:UserLocal' => 'Пользователь iTop', + 'Class:UserLocal+' => 'Пользователь аутентифицированный iTop', + 'Class:UserLocal/Attribute:password' => 'Пароль', + 'Class:UserLocal/Attribute:password+' => 'строка аутентификации пользователя', +)); + +?> diff --git a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php index d3c9f880f0..db9a87f92d 100644 --- a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php @@ -30,6 +30,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-change-mgmt.php', 'de.dict.itop-change-mgmt.php', 'pt_br.dict.itop-change-mgmt.php', + 'ru.dict.itop-change-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-change-mgmt.xml', diff --git a/modules/itop-change-mgmt-1.0.0/ru.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/ru.dict.itop-change-mgmt.php new file mode 100644 index 0000000000..d6ac7489e0 --- /dev/null +++ b/modules/itop-change-mgmt-1.0.0/ru.dict.itop-change-mgmt.php @@ -0,0 +1,345 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:ChangeManagement' => 'Управление изменениями', + 'Menu:Change:Overview' => 'Обзор', + 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'Новые изменения', + 'Menu:NewChange+' => 'Создание нового запроса на изменение', + 'Menu:SearchChanges' => 'Поиск изменений', + 'Menu:SearchChanges+' => 'Поиск запросов на изменения', + 'Menu:Change:Shortcuts' => 'Ярлыки', + 'Menu:Change:Shortcuts+' => '', + 'Menu:WaitingAcceptance' => 'Изменения ожидающие принятия', + 'Menu:WaitingAcceptance+' => '', + 'Menu:WaitingApproval' => 'Изменения ожидающие утверждения', + 'Menu:WaitingApproval+' => '', + 'Menu:Changes' => 'Открытые изменения', + 'Menu:Changes+' => '', + 'Menu:MyChanges' => 'Изменения назначенные на меня', + 'Menu:MyChanges+' => 'Изменения назначенные на меня (как агент)', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Change +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Change' => 'Изменение', + 'Class:Change+' => '', + 'Class:Change/Attribute:start_date' => 'Планируеый запуск', + 'Class:Change/Attribute:start_date+' => '', + 'Class:Change/Attribute:status' => 'Статус', + 'Class:Change/Attribute:status+' => '', + 'Class:Change/Attribute:status/Value:new' => 'Новый', + 'Class:Change/Attribute:status/Value:new+' => '', + 'Class:Change/Attribute:status/Value:validated' => 'Проверен', + 'Class:Change/Attribute:status/Value:validated+' => '', + 'Class:Change/Attribute:status/Value:rejected' => 'Отклонён', + 'Class:Change/Attribute:status/Value:rejected+' => '', + 'Class:Change/Attribute:status/Value:assigned' => 'Назначен', + 'Class:Change/Attribute:status/Value:assigned+' => '', + 'Class:Change/Attribute:status/Value:plannedscheduled' => 'Намечен и запланирован', + 'Class:Change/Attribute:status/Value:plannedscheduled+' => '', + 'Class:Change/Attribute:status/Value:approved' => 'Утверждён', + 'Class:Change/Attribute:status/Value:approved+' => '', + 'Class:Change/Attribute:status/Value:notapproved' => 'Не утверждём', + 'Class:Change/Attribute:status/Value:notapproved+' => '', + 'Class:Change/Attribute:status/Value:implemented' => 'Реализован', + 'Class:Change/Attribute:status/Value:implemented+' => '', + 'Class:Change/Attribute:status/Value:monitored' => 'Контролируемый', + 'Class:Change/Attribute:status/Value:monitored+' => '', + 'Class:Change/Attribute:status/Value:closed' => 'Закрыт', + 'Class:Change/Attribute:status/Value:closed+' => '', + 'Class:Change/Attribute:reason' => 'Причина', + 'Class:Change/Attribute:reason+' => '', + 'Class:Change/Attribute:requestor_id' => 'Инициатор запроса', + 'Class:Change/Attribute:requestor_id+' => '', + 'Class:Change/Attribute:requestor_email' => 'Инициатор запроса', + 'Class:Change/Attribute:requestor_email+' => '', + 'Class:Change/Attribute:org_id' => 'Клиент', + 'Class:Change/Attribute:org_id+' => '', + 'Class:Change/Attribute:org_name' => 'Клиент', + 'Class:Change/Attribute:org_name+' => '', + 'Class:Change/Attribute:workgroup_id' => 'Рабоча группа', + 'Class:Change/Attribute:workgroup_id+' => '', + 'Class:Change/Attribute:workgroup_name' => 'Рабочая группа', + 'Class:Change/Attribute:workgroup_name+' => '', + 'Class:Change/Attribute:creation_date' => 'Создан', + 'Class:Change/Attribute:creation_date+' => '', + 'Class:Change/Attribute:last_update' => 'Последнее обновление', + 'Class:Change/Attribute:last_update+' => '', + 'Class:Change/Attribute:end_date' => 'Дата окончания', + 'Class:Change/Attribute:end_date+' => '', + 'Class:Change/Attribute:close_date' => 'Закрыт', + 'Class:Change/Attribute:close_date+' => '', + 'Class:Change/Attribute:impact' => 'Воздействие', + 'Class:Change/Attribute:impact+' => '', + 'Class:Change/Attribute:agent_id' => 'Агент', + 'Class:Change/Attribute:agent_id+' => '', + 'Class:Change/Attribute:agent_name' => 'Агент', + 'Class:Change/Attribute:agent_name+' => '', + 'Class:Change/Attribute:agent_email' => 'Агент', + 'Class:Change/Attribute:agent_email+' => '', + 'Class:Change/Attribute:supervisor_group_id' => 'Руководитель команды', + 'Class:Change/Attribute:supervisor_group_id+' => '', + 'Class:Change/Attribute:supervisor_group_name' => 'Руководитель команды', + 'Class:Change/Attribute:supervisor_group_name+' => '', + 'Class:Change/Attribute:supervisor_id' => 'Руководитель', + 'Class:Change/Attribute:supervisor_id+' => '', + 'Class:Change/Attribute:supervisor_email' => 'Руководитель', + 'Class:Change/Attribute:supervisor_email+' => '', + 'Class:Change/Attribute:manager_group_id' => 'Менеджер команды', + 'Class:Change/Attribute:manager_group_id+' => '', + 'Class:Change/Attribute:manager_group_name' => 'Менеджер команды', + 'Class:Change/Attribute:manager_group_name+' => '', + 'Class:Change/Attribute:manager_id' => 'Менеджер', + 'Class:Change/Attribute:manager_id+' => '', + 'Class:Change/Attribute:manager_email' => 'Менеджер', + 'Class:Change/Attribute:manager_email+' => '', + 'Class:Change/Attribute:outage' => 'Отключение', + 'Class:Change/Attribute:outage+' => '', + 'Class:Change/Attribute:outage/Value:yes' => 'Отключение', + 'Class:Change/Attribute:outage/Value:yes+' => '', + 'Class:Change/Attribute:outage/Value:no' => 'Нет', + 'Class:Change/Attribute:outage/Value:no+' => '', + 'Class:Change/Attribute:change_request' => 'Запрос', + 'Class:Change/Attribute:change_request+' => '', + 'Class:Change/Attribute:fallback' => 'Резервный план', + 'Class:Change/Attribute:fallback+' => '', + 'Class:Change/Stimulus:ev_validate' => 'Проверка', + 'Class:Change/Stimulus:ev_validate+' => '', + 'Class:Change/Stimulus:ev_reject' => 'Отклонить', + 'Class:Change/Stimulus:ev_reject+' => '', + 'Class:Change/Stimulus:ev_assign' => 'Назначить', + 'Class:Change/Stimulus:ev_assign+' => '', + 'Class:Change/Stimulus:ev_reopen' => 'Переоткрыть', + 'Class:Change/Stimulus:ev_reopen+' => '', + 'Class:Change/Stimulus:ev_plan' => 'План', + 'Class:Change/Stimulus:ev_plan+' => '', + 'Class:Change/Stimulus:ev_approve' => 'Утвердить', + 'Class:Change/Stimulus:ev_approve+' => '', + 'Class:Change/Stimulus:ev_replan' => 'Перепланировать', + 'Class:Change/Stimulus:ev_replan+' => '', + 'Class:Change/Stimulus:ev_notapprove' => 'Отклонить', + 'Class:Change/Stimulus:ev_notapprove+' => '', + 'Class:Change/Stimulus:ev_implement' => 'Реализовать', + 'Class:Change/Stimulus:ev_implement+' => '', + 'Class:Change/Stimulus:ev_monitor' => 'Наблюдение', + 'Class:Change/Stimulus:ev_monitor+' => '', + 'Class:Change/Stimulus:ev_finish' => 'Закончить', + 'Class:Change/Stimulus:ev_finish+' => '', +)); + +// +// Class: RoutineChange +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:RoutineChange' => 'Регулярное изменение', + 'Class:RoutineChange+' => '', + 'Class:RoutineChange/Attribute:status/Value:new' => 'Новое', + 'Class:RoutineChange/Attribute:status/Value:new+' => '', + 'Class:RoutineChange/Attribute:status/Value:assigned' => 'Назначено', + 'Class:RoutineChange/Attribute:status/Value:assigned+' => '', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled' => 'Намечено и запланировано', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:RoutineChange/Attribute:status/Value:approved' => 'Утверждено', + 'Class:RoutineChange/Attribute:status/Value:approved+' => '', + 'Class:RoutineChange/Attribute:status/Value:implemented' => 'Реализовано', + 'Class:RoutineChange/Attribute:status/Value:implemented+' => '', + 'Class:RoutineChange/Attribute:status/Value:monitored' => 'Контролируемое', + 'Class:RoutineChange/Attribute:status/Value:monitored+' => '', + 'Class:RoutineChange/Attribute:status/Value:closed' => 'Закрытое', + 'Class:RoutineChange/Attribute:status/Value:closed+' => '', + 'Class:RoutineChange/Stimulus:ev_validate' => 'Проверить', + 'Class:RoutineChange/Stimulus:ev_validate+' => '', + 'Class:RoutineChange/Stimulus:ev_assign' => 'Назначить', + 'Class:RoutineChange/Stimulus:ev_assign+' => '', + 'Class:RoutineChange/Stimulus:ev_reopen' => 'Переоткрыть', + 'Class:RoutineChange/Stimulus:ev_reopen+' => '', + 'Class:RoutineChange/Stimulus:ev_plan' => 'Планировать', + 'Class:RoutineChange/Stimulus:ev_plan+' => '', + 'Class:RoutineChange/Stimulus:ev_replan' => 'Перепланировать', + 'Class:RoutineChange/Stimulus:ev_replan+' => '', + 'Class:RoutineChange/Stimulus:ev_implement' => 'Реализовать', + 'Class:RoutineChange/Stimulus:ev_implement+' => '', + 'Class:RoutineChange/Stimulus:ev_monitor' => 'Контролировать', + 'Class:RoutineChange/Stimulus:ev_monitor+' => '', + 'Class:RoutineChange/Stimulus:ev_finish' => 'Закончить', + 'Class:RoutineChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: ApprovedChange +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ApprovedChange' => 'Утверждённые изменения', + 'Class:ApprovedChange+' => '', + 'Class:ApprovedChange/Attribute:approval_date' => 'Дата утверждения', + 'Class:ApprovedChange/Attribute:approval_date+' => '', + 'Class:ApprovedChange/Attribute:approval_comment' => 'Коментарий утверждения', + 'Class:ApprovedChange/Attribute:approval_comment+' => '', + 'Class:ApprovedChange/Stimulus:ev_validate' => 'Проверка', + 'Class:ApprovedChange/Stimulus:ev_validate+' => '', + 'Class:ApprovedChange/Stimulus:ev_reject' => 'Отклонение', + 'Class:ApprovedChange/Stimulus:ev_reject+' => '', + 'Class:ApprovedChange/Stimulus:ev_assign' => 'Назначение', + 'Class:ApprovedChange/Stimulus:ev_assign+' => '', + 'Class:ApprovedChange/Stimulus:ev_reopen' => 'Переоткрыть', + 'Class:ApprovedChange/Stimulus:ev_reopen+' => '', + 'Class:ApprovedChange/Stimulus:ev_plan' => 'План', + 'Class:ApprovedChange/Stimulus:ev_plan+' => '', + 'Class:ApprovedChange/Stimulus:ev_approve' => 'Утвердить', + 'Class:ApprovedChange/Stimulus:ev_approve+' => '', + 'Class:ApprovedChange/Stimulus:ev_replan' => 'Перепланировать', + 'Class:ApprovedChange/Stimulus:ev_replan+' => '', + 'Class:ApprovedChange/Stimulus:ev_notapprove' => 'Отклонить утверждение', + 'Class:ApprovedChange/Stimulus:ev_notapprove+' => '', + 'Class:ApprovedChange/Stimulus:ev_implement' => 'Реализовать', + 'Class:ApprovedChange/Stimulus:ev_implement+' => '', + 'Class:ApprovedChange/Stimulus:ev_monitor' => 'Контролировать', + 'Class:ApprovedChange/Stimulus:ev_monitor+' => '', + 'Class:ApprovedChange/Stimulus:ev_finish' => 'Закончить', + 'Class:ApprovedChange/Stimulus:ev_finish+' => '', +)); +// +// Class: NormalChange +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:NormalChange' => 'Обычное изменение', + 'Class:NormalChange+' => '', + 'Class:NormalChange/Attribute:status/Value:new' => 'Новое', + 'Class:NormalChange/Attribute:status/Value:new+' => '', + 'Class:NormalChange/Attribute:status/Value:validated' => 'Проверено', + 'Class:NormalChange/Attribute:status/Value:validated+' => '', + 'Class:NormalChange/Attribute:status/Value:rejected' => 'Отклонено', + 'Class:NormalChange/Attribute:status/Value:rejected+' => '', + 'Class:NormalChange/Attribute:status/Value:assigned' => 'Назначено', + 'Class:NormalChange/Attribute:status/Value:assigned+' => '', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled' => 'Намечено и запланировано', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:NormalChange/Attribute:status/Value:approved' => 'Утверждено', + 'Class:NormalChange/Attribute:status/Value:approved+' => '', + 'Class:NormalChange/Attribute:status/Value:notapproved' => 'Не утверждено', + 'Class:NormalChange/Attribute:status/Value:notapproved+' => '', + 'Class:NormalChange/Attribute:status/Value:implemented' => 'Реализовано', + 'Class:NormalChange/Attribute:status/Value:implemented+' => '', + 'Class:NormalChange/Attribute:status/Value:monitored' => 'Контролируемое', + 'Class:NormalChange/Attribute:status/Value:monitored+' => '', + 'Class:NormalChange/Attribute:status/Value:closed' => 'Закрытое', + 'Class:NormalChange/Attribute:status/Value:closed+' => '', + 'Class:NormalChange/Attribute:acceptance_date' => 'Дата принятия', + 'Class:NormalChange/Attribute:acceptance_date+' => '', + 'Class:NormalChange/Attribute:acceptance_comment' => 'Коментарий принятия', + 'Class:NormalChange/Attribute:acceptance_comment+' => '', + 'Class:NormalChange/Stimulus:ev_validate' => 'Проверить', + 'Class:NormalChange/Stimulus:ev_validate+' => '', + 'Class:NormalChange/Stimulus:ev_reject' => 'Отклонить', + 'Class:NormalChange/Stimulus:ev_reject+' => '', + 'Class:NormalChange/Stimulus:ev_assign' => 'Назначить', + 'Class:NormalChange/Stimulus:ev_assign+' => '', + 'Class:NormalChange/Stimulus:ev_reopen' => 'Переоткрыть', + 'Class:NormalChange/Stimulus:ev_reopen+' => '', + 'Class:NormalChange/Stimulus:ev_plan' => 'Планировать', + 'Class:NormalChange/Stimulus:ev_plan+' => '', + 'Class:NormalChange/Stimulus:ev_approve' => 'Утвердить', + 'Class:NormalChange/Stimulus:ev_approve+' => '', + 'Class:NormalChange/Stimulus:ev_replan' => 'Перепланировать', + 'Class:NormalChange/Stimulus:ev_replan+' => '', + 'Class:NormalChange/Stimulus:ev_notapprove' => 'Отклонить утверждение', + 'Class:NormalChange/Stimulus:ev_notapprove+' => '', + 'Class:NormalChange/Stimulus:ev_implement' => 'Реализовать', + 'Class:NormalChange/Stimulus:ev_implement+' => '', + 'Class:NormalChange/Stimulus:ev_monitor' => 'Контролировать', + 'Class:NormalChange/Stimulus:ev_monitor+' => '', + 'Class:NormalChange/Stimulus:ev_finish' => 'Закончить', + 'Class:NormalChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: EmergencyChange +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:EmergencyChange' => 'Критическое изменение', + 'Class:EmergencyChange+' => '', + 'Class:EmergencyChange/Attribute:status/Value:new' => 'Новое', + 'Class:EmergencyChange/Attribute:status/Value:new+' => '', + 'Class:EmergencyChange/Attribute:status/Value:validated' => 'Проверено', + 'Class:EmergencyChange/Attribute:status/Value:validated+' => '', + 'Class:EmergencyChange/Attribute:status/Value:rejected' => 'Отклонено', + 'Class:EmergencyChange/Attribute:status/Value:rejected+' => '', + 'Class:EmergencyChange/Attribute:status/Value:assigned' => 'Назначено', + 'Class:EmergencyChange/Attribute:status/Value:assigned+' => '', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled' => 'Намечено и запланировано', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:EmergencyChange/Attribute:status/Value:approved' => 'Утверждено', + 'Class:EmergencyChange/Attribute:status/Value:approved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:notapproved' => 'Не утверждено', + 'Class:EmergencyChange/Attribute:status/Value:notapproved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:implemented' => 'Реализовано', + 'Class:EmergencyChange/Attribute:status/Value:implemented+' => '', + 'Class:EmergencyChange/Attribute:status/Value:monitored' => 'Контролируемое', + 'Class:EmergencyChange/Attribute:status/Value:monitored+' => '', + 'Class:EmergencyChange/Attribute:status/Value:closed' => 'Закрыто', + 'Class:EmergencyChange/Attribute:status/Value:closed+' => '', + 'Class:EmergencyChange/Stimulus:ev_validate' => 'Проверить', + 'Class:EmergencyChange/Stimulus:ev_validate+' => '', + 'Class:EmergencyChange/Stimulus:ev_reject' => 'Отклонить', + 'Class:EmergencyChange/Stimulus:ev_reject+' => '', + 'Class:EmergencyChange/Stimulus:ev_assign' => 'Назначить', + 'Class:EmergencyChange/Stimulus:ev_assign+' => '', + 'Class:EmergencyChange/Stimulus:ev_reopen' => 'Переоткрыть', + 'Class:EmergencyChange/Stimulus:ev_reopen+' => '', + 'Class:EmergencyChange/Stimulus:ev_plan' => 'Планировать', + 'Class:EmergencyChange/Stimulus:ev_plan+' => '', + 'Class:EmergencyChange/Stimulus:ev_approve' => 'Утвердить', + 'Class:EmergencyChange/Stimulus:ev_approve+' => '', + 'Class:EmergencyChange/Stimulus:ev_replan' => 'Перепланировать', + 'Class:EmergencyChange/Stimulus:ev_replan+' => '', + 'Class:EmergencyChange/Stimulus:ev_notapprove' => 'Отклонить утверждение', + 'Class:EmergencyChange/Stimulus:ev_notapprove+' => '', + 'Class:EmergencyChange/Stimulus:ev_implement' => 'Реализовать', + 'Class:EmergencyChange/Stimulus:ev_implement+' => '', + 'Class:EmergencyChange/Stimulus:ev_monitor' => 'Контролировать', + 'Class:EmergencyChange/Stimulus:ev_monitor+' => '', + 'Class:EmergencyChange/Stimulus:ev_finish' => 'Закончить', + 'Class:EmergencyChange/Stimulus:ev_finish+' => '', +)); + +?> diff --git a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php index 7a61ebabd2..8cad3f83bb 100644 --- a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php @@ -29,6 +29,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-config-mgmt.php', 'de.dict.itop-config-mgmt.php', 'pt_br.dict.itop-config-mgmt.php', + 'ru.dict.itop-config-mgmt.php', ), 'data.struct' => array( 'data.struct.Audit.xml', diff --git a/modules/itop-config-mgmt-1.0.0/ru.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/ru.dict.itop-config-mgmt.php new file mode 100644 index 0000000000..d8ee082573 --- /dev/null +++ b/modules/itop-config-mgmt-1.0.0/ru.dict.itop-config-mgmt.php @@ -0,0 +1,1052 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +////////////////////////////////////////////////////////////////////// +// Relations +////////////////////////////////////////////////////////////////////// +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Relation:impacts/Description' => 'Elements impacted by', + 'Relation:impacts/VerbUp' => 'Impact...', + 'Relation:impacts/VerbDown' => 'Elements impacted by...', + 'Relation:depends on/Description' => 'Elements this element depends on', + 'Relation:depends on/VerbUp' => 'Depends on...', + 'Relation:depends on/VerbDown' => 'Impacts...', +)); + + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Note: The classes have been grouped by categories: bizmodel +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: Organization +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Organization' => 'Организация', + 'Class:Organization+' => '', + 'Class:Organization/Attribute:name' => 'Название', + 'Class:Organization/Attribute:name+' => 'Общее название', + 'Class:Organization/Attribute:code' => 'Код', + 'Class:Organization/Attribute:code+' => 'Код организации (Siret, DUNS,...)', + 'Class:Organization/Attribute:status' => 'Status', + 'Class:Organization/Attribute:status+' => '', + 'Class:Organization/Attribute:status/Value:active' => 'Активный', + 'Class:Organization/Attribute:status/Value:active+' => 'Активный', + 'Class:Organization/Attribute:status/Value:inactive' => 'Неактивный', + 'Class:Organization/Attribute:status/Value:inactive+' => 'Неактивный', + 'Class:Organization/Attribute:parent_id' => 'Вышестоящая', + 'Class:Organization/Attribute:parent_id+' => 'Вышестоящая организация', + 'Class:Organization/Attribute:parent_name' => 'Название вышестоящей', + 'Class:Organization/Attribute:parent_name+' => 'Название вышестоящей организации', +)); + + +// +// Class: Location +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Location' => 'Расположение', + 'Class:Location+' => 'Любой типа расположения: регион, страна, область, город, площадка, здание, этаж, кoom, стойка,...', + 'Class:Location/Attribute:name' => 'Название', + 'Class:Location/Attribute:name+' => '', + 'Class:Location/Attribute:status' => 'Статус', + 'Class:Location/Attribute:status+' => '', + 'Class:Location/Attribute:status/Value:active' => 'Активный', + 'Class:Location/Attribute:status/Value:active+' => 'Активный', + 'Class:Location/Attribute:status/Value:inactive' => 'Неактивный', + 'Class:Location/Attribute:status/Value:inactive+' => 'Неактивный', + 'Class:Location/Attribute:org_id' => 'Владелец организации', + 'Class:Location/Attribute:org_id+' => '', + 'Class:Location/Attribute:org_name' => 'Название владельца организации', + 'Class:Location/Attribute:org_name+' => '', + 'Class:Location/Attribute:address' => 'Адрес', + 'Class:Location/Attribute:address+' => 'Почтовый адрес', + 'Class:Location/Attribute:postal_code' => 'Индекс', + 'Class:Location/Attribute:postal_code+' => 'Почтовый индекс', + 'Class:Location/Attribute:city' => 'Город', + 'Class:Location/Attribute:city+' => '', + 'Class:Location/Attribute:country' => 'Страна', + 'Class:Location/Attribute:country+' => '', + 'Class:Location/Attribute:parent_id' => 'Вышестоящее расположение', + 'Class:Location/Attribute:parent_id+' => '', + 'Class:Location/Attribute:parent_name' => 'Название вышестоящего', + 'Class:Location/Attribute:parent_name+' => '', + 'Class:Location/Attribute:contact_list' => 'Контакты', + 'Class:Location/Attribute:contact_list+' => 'Контакты расположенные в этом месте', + 'Class:Location/Attribute:infra_list' => 'Инфраструктура', + 'Class:Location/Attribute:infra_list+' => 'КЕ расположенные в этом месте', +)); +// +// Class: Group +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Group' => 'Группа', + 'Class:Group+' => '', + 'Class:Group/Attribute:name' => 'Название', + 'Class:Group/Attribute:name+' => '', + 'Class:Group/Attribute:status' => 'Сатус', + 'Class:Group/Attribute:status+' => '', + 'Class:Group/Attribute:status/Value:implementation' => 'Реализация', + 'Class:Group/Attribute:status/Value:implementation+' => 'Реализация', + 'Class:Group/Attribute:status/Value:obsolete' => 'Устаревший', + 'Class:Group/Attribute:status/Value:obsolete+' => 'Устаревший', + 'Class:Group/Attribute:status/Value:production' => 'Производство', + 'Class:Group/Attribute:status/Value:production+' => 'Производство', + 'Class:Group/Attribute:org_id' => 'Организация', + 'Class:Group/Attribute:org_id+' => '', + 'Class:Group/Attribute:owner_name' => 'Название', + 'Class:Group/Attribute:owner_name+' => 'Общее название', + 'Class:Group/Attribute:description' => 'Описание', + 'Class:Group/Attribute:description+' => '', + 'Class:Group/Attribute:type' => 'Тип', + 'Class:Group/Attribute:type+' => '', + 'Class:Group/Attribute:parent_id' => 'Вышестоящая группа', + 'Class:Group/Attribute:parent_id+' => '', + 'Class:Group/Attribute:parent_name' => 'Название', + 'Class:Group/Attribute:parent_name+' => '', + 'Class:Group/Attribute:ci_list' => 'Связанные КЕ', + 'Class:Group/Attribute:ci_list+' => '', +)); + +// +// Class: lnkGroupToCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkGroupToCI' => 'Группа / КЕ', + 'Class:lnkGroupToCI+' => '', + 'Class:lnkGroupToCI/Attribute:group_id' => 'Группа', + 'Class:lnkGroupToCI/Attribute:group_id+' => '', + 'Class:lnkGroupToCI/Attribute:group_name' => 'Название', + 'Class:lnkGroupToCI/Attribute:group_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_id' => 'КЕ', + 'Class:lnkGroupToCI/Attribute:ci_id+' => '', + 'Class:lnkGroupToCI/Attribute:ci_name' => 'Название', + 'Class:lnkGroupToCI/Attribute:ci_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkGroupToCI/Attribute:ci_status+' => '', + 'Class:lnkGroupToCI/Attribute:reason' => 'Причина', + 'Class:lnkGroupToCI/Attribute:reason+' => '', +)); + + +// +// Class: Contact +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Contact' => 'Контакт', + 'Class:Contact+' => '', + 'Class:Contact/Attribute:name' => 'Название', + 'Class:Contact/Attribute:name+' => '', + 'Class:Contact/Attribute:status' => 'Статус', + 'Class:Contact/Attribute:status+' => '', + 'Class:Contact/Attribute:status/Value:active' => 'Активный', + 'Class:Contact/Attribute:status/Value:active+' => 'Активный', + 'Class:Contact/Attribute:status/Value:inactive' => 'Неактивный', + 'Class:Contact/Attribute:status/Value:inactive+' => 'Неактивный', + 'Class:Contact/Attribute:org_id' => 'Организация', + 'Class:Contact/Attribute:org_id+' => '', + 'Class:Contact/Attribute:org_name' => 'Организация', + 'Class:Contact/Attribute:org_name+' => '', + 'Class:Contact/Attribute:email' => 'Email', + 'Class:Contact/Attribute:email+' => '', + 'Class:Contact/Attribute:phone' => 'Телефон', + 'Class:Contact/Attribute:phone+' => '', + 'Class:Contact/Attribute:location_id' => 'Расположение', + 'Class:Contact/Attribute:location_id+' => '', + 'Class:Contact/Attribute:location_name' => 'Расположение', + 'Class:Contact/Attribute:location_name+' => '', + 'Class:Contact/Attribute:ci_list' => 'КЕ-ы', + 'Class:Contact/Attribute:ci_list+' => 'КЕ связанные с контактом', + 'Class:Contact/Attribute:contract_list' => 'Договора', + 'Class:Contact/Attribute:contract_list+' => 'Договора связанные с контактом', + 'Class:Contact/Attribute:service_list' => 'Сервисы', + 'Class:Contact/Attribute:service_list+' => 'Сервисы связанные с контактом', + 'Class:Contact/Attribute:ticket_list' => 'Ticketы', + 'Class:Contact/Attribute:ticket_list+' => 'Ticketы связанные с контактом', + 'Class:Contact/Attribute:team_list' => 'Команды', + 'Class:Contact/Attribute:team_list+' => 'Команды этого контакта', + 'Class:Contact/Attribute:finalclass' => 'Тип', + 'Class:Contact/Attribute:finalclass+' => '', +)); + +// +// Class: Person +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Person' => 'Человек', + 'Class:Person+' => '', + 'Class:Person/Attribute:first_name' => 'Имя', + 'Class:Person/Attribute:first_name+' => '', + 'Class:Person/Attribute:employee_id' => 'ID Сотрудника', + 'Class:Person/Attribute:employee_id+' => '', +)); + +// +// Class: Team +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Team' => 'Команда', + 'Class:Team+' => '', + 'Class:Team/Attribute:member_list' => 'Члены', + 'Class:Team/Attribute:member_list+' => 'Контакты входящие в команду', +)); + +// +// Class: lnkTeamToContact +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkTeamToContact' => 'Члены команды', + 'Class:lnkTeamToContact+' => 'Члены команды', + 'Class:lnkTeamToContact/Attribute:team_id' => 'Команда', + 'Class:lnkTeamToContact/Attribute:team_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_id' => 'Член', + 'Class:lnkTeamToContact/Attribute:contact_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_location_id' => 'Расположение', + 'Class:lnkTeamToContact/Attribute:contact_location_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_email' => 'Email', + 'Class:lnkTeamToContact/Attribute:contact_email+' => '', + 'Class:lnkTeamToContact/Attribute:contact_phone' => 'Телефон', + 'Class:lnkTeamToContact/Attribute:contact_phone+' => '', + 'Class:lnkTeamToContact/Attribute:role' => 'Роль', + 'Class:lnkTeamToContact/Attribute:role+' => '', +)); + +// +// Class: Document +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Document' => 'Документ', + 'Class:Document+' => '', + 'Class:Document/Attribute:name' => 'Название', + 'Class:Document/Attribute:name+' => '', + 'Class:Document/Attribute:org_id' => 'Организация', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:org_name' => 'Название организации', + 'Class:Document/Attribute:org_name+' => '', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:description' => 'Описание', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:type' => 'Тип', + 'Class:Document/Attribute:type+' => '', + 'Class:Document/Attribute:type/Value:contract' => 'Договор', + 'Class:Document/Attribute:type/Value:contract+' => '', + 'Class:Document/Attribute:type/Value:networkmap' => 'Карта сети', + 'Class:Document/Attribute:type/Value:networkmap+' => '', + 'Class:Document/Attribute:type/Value:presentation' => 'Презентация', + 'Class:Document/Attribute:type/Value:presentation+' => '', + 'Class:Document/Attribute:type/Value:training' => 'Обучение', + 'Class:Document/Attribute:type/Value:training+' => '', + 'Class:Document/Attribute:type/Value:whitePaper' => 'White Paper', + 'Class:Document/Attribute:type/Value:whitePaper+' => '', + 'Class:Document/Attribute:type/Value:workinginstructions' => 'Рабочие инструкции', + 'Class:Document/Attribute:type/Value:workinginstructions+' => '', + 'Class:Document/Attribute:status' => 'Статус', + 'Class:Document/Attribute:status+' => '', + 'Class:Document/Attribute:status/Value:draft' => 'Черновик', + 'Class:Document/Attribute:status/Value:draft+' => '', + 'Class:Document/Attribute:status/Value:obsolete' => 'Устаревший', + 'Class:Document/Attribute:status/Value:obsolete+' => '', + 'Class:Document/Attribute:status/Value:published' => 'Опубликованный', + 'Class:Document/Attribute:status/Value:published+' => '', + 'Class:Document/Attribute:ci_list' => 'КЕ', + 'Class:Document/Attribute:ci_list+' => 'КЕ относящиеся к этому документу', + 'Class:Document/Attribute:contract_list' => 'Договора', + 'Class:Document/Attribute:contract_list+' => 'Договора относящиеся к этому документу', + 'Class:Document/Attribute:service_list' => 'Сервисы', + 'Class:Document/Attribute:service_list+' => 'Сервисы относящиеся к этому документу', + 'Class:Document/Attribute:ticket_list' => 'Ticketы', + 'Class:Document/Attribute:ticket_list+' => 'Ticketы относящиеся к этому документу', + 'Class:Document:PreviewTab' => 'Предпросмотр', +)); + +// +// Class: WebDoc +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:WebDoc' => 'Web документ', + 'Class:WebDoc+' => 'Документ доступный на другом web-сервере', + 'Class:WebDoc/Attribute:url' => 'Url', + 'Class:WebDoc/Attribute:url+' => '', +)); + +// +// Class: Note +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Note' => 'Заметка', + 'Class:Note+' => '', + 'Class:Note/Attribute:note' => 'Текст', + 'Class:Note/Attribute:note+' => '', +)); + +// +// Class: FileDoc +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:FileDoc' => 'Документ (файл)', + 'Class:FileDoc+' => '', + 'Class:FileDoc/Attribute:contents' => 'Содержимое', + 'Class:FileDoc/Attribute:contents+' => '', +)); + +// +// Class: Licence +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Licence' => 'Лицензия', + 'Class:Licence+' => '', + 'Class:Licence/Attribute:provider' => 'Поставщик', + 'Class:Licence/Attribute:provider+' => '', + 'Class:Licence/Attribute:org_id' => 'Владелец', + 'Class:Licence/Attribute:org_id+' => '', + 'Class:Licence/Attribute:org_name' => 'Название', + 'Class:Licence/Attribute:org_name+' => 'Общее название', + 'Class:Licence/Attribute:product' => 'Продукт', + 'Class:Licence/Attribute:product+' => '', + 'Class:Licence/Attribute:name' => 'Название', + 'Class:Licence/Attribute:name+' => '', + 'Class:Licence/Attribute:start' => 'Начальная дата', + 'Class:Licence/Attribute:start+' => '', + 'Class:Licence/Attribute:end' => 'Конечная дата', + 'Class:Licence/Attribute:end+' => '', + 'Class:Licence/Attribute:licence_key' => 'Ключ', + 'Class:Licence/Attribute:licence_key+' => '', + 'Class:Licence/Attribute:scope' => 'Сфера', + 'Class:Licence/Attribute:scope+' => 'Сфера применения', + 'Class:Licence/Attribute:usage_limit' => 'Ограничение использования', + 'Class:Licence/Attribute:usage_limit+' => '', + 'Class:Licence/Attribute:usage_list' => 'Использование', + 'Class:Licence/Attribute:usage_list+' => 'Экземпляры Приложений использующие эту лицензию', +)); + + +// +// Class: Subnet +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Subnet' => 'Подсеть', + 'Class:Subnet+' => '', + //'Class:Subnet/Attribute:name' => 'Name', + //'Class:Subnet/Attribute:name+' => '', + 'Class:Subnet/Attribute:org_id' => 'Организация-владелец', + 'Class:Subnet/Attribute:org_id+' => '', + 'Class:Subnet/Attribute:description' => 'Описание', + 'Class:Subnet/Attribute:description+' => '', + 'Class:Subnet/Attribute:ip' => 'IP', + 'Class:Subnet/Attribute:ip+' => '', + 'Class:Subnet/Attribute:ip_mask' => 'IP маска', + 'Class:Subnet/Attribute:ip_mask+' => '', +)); + +// +// Class: Patch +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Patch' => 'Патч', + 'Class:Patch+' => '', + 'Class:Patch/Attribute:name' => 'Название', + 'Class:Patch/Attribute:name+' => '', + 'Class:Patch/Attribute:description' => 'Описание', + 'Class:Patch/Attribute:description+' => '', + 'Class:Patch/Attribute:target_sw' => 'Область применения', + 'Class:Patch/Attribute:target_sw+' => 'Целевое ПО (ОС или приложеиние)', + 'Class:Patch/Attribute:version' => 'Версия', + 'Class:Patch/Attribute:version+' => '', + 'Class:Patch/Attribute:type' => 'Тир', + 'Class:Patch/Attribute:type+' => '', + 'Class:Patch/Attribute:type/Value:application' => 'Приложение', + 'Class:Patch/Attribute:type/Value:application+' => '', + 'Class:Patch/Attribute:type/Value:os' => 'ОС', + 'Class:Patch/Attribute:type/Value:os+' => '', + 'Class:Patch/Attribute:type/Value:security' => 'Безопастность', + 'Class:Patch/Attribute:type/Value:security+' => '', + 'Class:Patch/Attribute:type/Value:servicepack' => 'Сервис Пак', + 'Class:Patch/Attribute:type/Value:servicepack+' => '', + 'Class:Patch/Attribute:ci_list' => 'Устройства', + 'Class:Patch/Attribute:ci_list+' => 'Устройства на которые установлен патч', +)); + +// +// Class: Software +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Software' => 'Програмное оеспечение', + 'Class:Software+' => '', + 'Class:Software/Attribute:name' => 'Название', + 'Class:Software/Attribute:name+' => '', + 'Class:Software/Attribute:description' => 'Описание', + 'Class:Software/Attribute:description+' => '', + 'Class:Software/Attribute:instance_list' => 'Установки', + 'Class:Software/Attribute:instance_list+' => 'Экземпляры этогоПО', + 'Class:Software/Attribute:finalclass' => 'Тип', + 'Class:Software/Attribute:finalclass+' => '', +)); + +// +// Class: Application +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Application' => 'Приложение', + 'Class:Application+' => '', + 'Class:Application/Attribute:name' => 'Название', + 'Class:Application/Attribute:name+' => '', + 'Class:Application/Attribute:description' => 'Описание', + 'Class:Application/Attribute:description+' => '', + 'Class:Application/Attribute:instance_list' => 'Установки', + 'Class:Application/Attribute:instance_list+' => 'Экземпляры этого приложения', +)); + +// +// Class: DBServer +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:DBServer' => 'База данных', + 'Class:DBServer+' => 'Сервер базы данных SW', + 'Class:DBServer/Attribute:instance_list' => 'Установки', + 'Class:DBServer/Attribute:instance_list+' => 'Экземпляры этой базы данных', +)); + +// +// Class: lnkPatchToCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkPatchToCI' => 'Использование патчей', + 'Class:lnkPatchToCI+' => '', + 'Class:lnkPatchToCI/Attribute:patch_id' => 'Патч', + 'Class:lnkPatchToCI/Attribute:patch_id+' => '', + 'Class:lnkPatchToCI/Attribute:patch_name' => 'Патч', + 'Class:lnkPatchToCI/Attribute:patch_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_id' => 'КЕ', + 'Class:lnkPatchToCI/Attribute:ci_id+' => '', + 'Class:lnkPatchToCI/Attribute:ci_name' => 'КЕ', + 'Class:lnkPatchToCI/Attribute:ci_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkPatchToCI/Attribute:ci_status+' => '', +)); + +// +// Class: FunctionalCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:FunctionalCI' => 'Функционал КЕ', + 'Class:FunctionalCI+' => '', + 'Class:FunctionalCI/Attribute:name' => 'Название', + 'Class:FunctionalCI/Attribute:name+' => '', + 'Class:FunctionalCI/Attribute:status' => 'Статус', + 'Class:FunctionalCI/Attribute:status+' => '', + 'Class:FunctionalCI/Attribute:status/Value:implementation' => 'Внедрение', + 'Class:FunctionalCI/Attribute:status/Value:implementation+' => '', + 'Class:FunctionalCI/Attribute:status/Value:obsolete' => 'Устаревший', + 'Class:FunctionalCI/Attribute:status/Value:obsolete+' => '', + 'Class:FunctionalCI/Attribute:status/Value:production' => 'Производственный', + 'Class:FunctionalCI/Attribute:status/Value:production+' => '', + 'Class:FunctionalCI/Attribute:org_id' => 'Организация-владелец', + 'Class:FunctionalCI/Attribute:org_id+' => '', + 'Class:FunctionalCI/Attribute:owner_name' => 'Организация-владелец', + 'Class:FunctionalCI/Attribute:owner_name+' => '', + 'Class:FunctionalCI/Attribute:importance' => 'Критичность для бизнеса', + 'Class:FunctionalCI/Attribute:importance+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:high' => 'Высокая', + 'Class:FunctionalCI/Attribute:importance/Value:high+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:low' => 'Низкая', + 'Class:FunctionalCI/Attribute:importance/Value:low+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:medium' => 'Средняя', + 'Class:FunctionalCI/Attribute:importance/Value:medium+' => '', + 'Class:FunctionalCI/Attribute:contact_list' => 'Контакты', + 'Class:FunctionalCI/Attribute:contact_list+' => 'Контакты для этой КЕ', + 'Class:FunctionalCI/Attribute:document_list' => 'Документы', + 'Class:FunctionalCI/Attribute:document_list+' => 'Документы для этой КЕ', + 'Class:FunctionalCI/Attribute:solution_list' => 'Программные решения', + 'Class:FunctionalCI/Attribute:solution_list+' => 'Программные решения использующие эту КЕ', + 'Class:FunctionalCI/Attribute:contract_list' => 'Договора', + 'Class:FunctionalCI/Attribute:contract_list+' => 'Договора поддерживающие эту КЕ', + 'Class:FunctionalCI/Attribute:ticket_list' => 'Ticketы', + 'Class:FunctionalCI/Attribute:ticket_list+' => 'Ticketы связанные с этой КЕ', + 'Class:FunctionalCI/Attribute:finalclass' => 'Тип', + 'Class:FunctionalCI/Attribute:finalclass+' => '', +)); + +// +// Class: SoftwareInstance +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:SoftwareInstance' => 'Экземпляры ПО', + 'Class:SoftwareInstance+' => '', + 'Class:SoftwareInstance/Attribute:device_id' => 'Устройство', + 'Class:SoftwareInstance/Attribute:device_id+' => '', + 'Class:SoftwareInstance/Attribute:device_name' => 'Устройство', + 'Class:SoftwareInstance/Attribute:device_name+' => '', + 'Class:SoftwareInstance/Attribute:licence_id' => 'Лицензия', + 'Class:SoftwareInstance/Attribute:licence_id+' => '', + 'Class:SoftwareInstance/Attribute:licence_name' => 'Лицензия', + 'Class:SoftwareInstance/Attribute:licence_name+' => '', + 'Class:SoftwareInstance/Attribute:software_name' => 'ПО', + 'Class:SoftwareInstance/Attribute:software_name+' => '', + 'Class:SoftwareInstance/Attribute:version' => 'Версия', + 'Class:SoftwareInstance/Attribute:version+' => '', + 'Class:SoftwareInstance/Attribute:description' => 'Описание', + 'Class:SoftwareInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationInstance +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ApplicationInstance' => 'Экземпляры приложений', + 'Class:ApplicationInstance+' => '', + 'Class:ApplicationInstance/Attribute:software_id' => 'ПО', + 'Class:ApplicationInstance/Attribute:software_id+' => '', + 'Class:ApplicationInstance/Attribute:software_name' => 'Название', + 'Class:ApplicationInstance/Attribute:software_name+' => '', +)); + + +// +// Class: DBServerInstance +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:DBServerInstance' => 'Экземпляры серверов баз данных', + 'Class:DBServerInstance+' => '', + 'Class:DBServerInstance/Attribute:software_id' => 'ПО', + 'Class:DBServerInstance/Attribute:software_id+' => '', + 'Class:DBServerInstance/Attribute:software_name' => 'Название', + 'Class:DBServerInstance/Attribute:software_name+' => '', + 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Базы данных', + 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Источники баз данных', +)); + + +// +// Class: DatabaseInstance +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:DatabaseInstance' => 'Экземпляры баз данных', + 'Class:DatabaseInstance+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_id' => 'Сервер базы данных', + 'Class:DatabaseInstance/Attribute:db_server_instance_id+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_version' => 'Версия базы данных', + 'Class:DatabaseInstance/Attribute:db_server_instance_version+' => '', + 'Class:DatabaseInstance/Attribute:description' => 'Описание', + 'Class:DatabaseInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationSolution +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ApplicationSolution' => 'Программные решения', + 'Class:ApplicationSolution+' => '', + 'Class:ApplicationSolution/Attribute:description' => 'Описание', + 'Class:ApplicationSolution/Attribute:description+' => '', + 'Class:ApplicationSolution/Attribute:ci_list' => 'КЕ', + 'Class:ApplicationSolution/Attribute:ci_list+' => 'КЕ составляющие решение', + 'Class:ApplicationSolution/Attribute:process_list' => 'Бизнес-процессы', + 'Class:ApplicationSolution/Attribute:process_list+' => 'Бизнес-процессы использующие решение', +)); + +// +// Class: BusinessProcess +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:BusinessProcess' => 'Бизнес-процессы', + 'Class:BusinessProcess+' => '', + 'Class:BusinessProcess/Attribute:description' => 'Описание', + 'Class:BusinessProcess/Attribute:description+' => '', + 'Class:BusinessProcess/Attribute:used_solution_list' => 'Програмные решения', + 'Class:BusinessProcess/Attribute:used_solution_list+' => 'Используемые програмные решения', +)); + +// +// Class: ConnectableCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ConnectableCI' => 'Подключаемые КЕ', + 'Class:ConnectableCI+' => 'Физические КЕ', + 'Class:ConnectableCI/Attribute:brand' => 'Производитель', + 'Class:ConnectableCI/Attribute:brand+' => '', + 'Class:ConnectableCI/Attribute:model' => 'Модель', + 'Class:ConnectableCI/Attribute:model+' => '', + 'Class:ConnectableCI/Attribute:serial_number' => 'Серийный номер', + 'Class:ConnectableCI/Attribute:serial_number+' => '', + 'Class:ConnectableCI/Attribute:asset_ref' => 'Справочник активов', + 'Class:ConnectableCI/Attribute:asset_ref+' => '', +)); + +// +// Class: NetworkInterface +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:NetworkInterface' => 'Сетевой интерфейс', + 'Class:NetworkInterface+' => '', + 'Class:NetworkInterface/Attribute:device_id' => 'Устройство', + 'Class:NetworkInterface/Attribute:device_id+' => '', + 'Class:NetworkInterface/Attribute:device_name' => 'Устройство', + 'Class:NetworkInterface/Attribute:device_name+' => '', + 'Class:NetworkInterface/Attribute:logical_type' => 'Логически тип', + 'Class:NetworkInterface/Attribute:logical_type+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup' => 'Резерв', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical' => 'Логический', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:port' => 'Порт', + 'Class:NetworkInterface/Attribute:logical_type/Value:port+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary' => 'Первичный', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary' => 'Вторичный', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary+' => '', + 'Class:NetworkInterface/Attribute:physical_type' => 'Физический тип', + 'Class:NetworkInterface/Attribute:physical_type+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm' => 'ATM', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet' => 'Ethernet', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay' => 'Frame Relay', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan' => 'VLAN', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan+' => '', + 'Class:NetworkInterface/Attribute:ip_address' => 'Адрес IP', + 'Class:NetworkInterface/Attribute:ip_address+' => '', + 'Class:NetworkInterface/Attribute:ip_mask' => 'Маска IP', + 'Class:NetworkInterface/Attribute:ip_mask+' => '', + 'Class:NetworkInterface/Attribute:mac_address' => 'Адрес MAC', + 'Class:NetworkInterface/Attribute:mac_address+' => '', + 'Class:NetworkInterface/Attribute:speed' => 'Скорость', + 'Class:NetworkInterface/Attribute:speed+' => '', + 'Class:NetworkInterface/Attribute:duplex' => 'Дуплекс', + 'Class:NetworkInterface/Attribute:duplex+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:auto' => 'Auto', + 'Class:NetworkInterface/Attribute:duplex/Value:auto+' => 'Auto', + 'Class:NetworkInterface/Attribute:duplex/Value:full' => 'Full', + 'Class:NetworkInterface/Attribute:duplex/Value:full+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:half' => 'Half', + 'Class:NetworkInterface/Attribute:duplex/Value:half+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown' => 'Неизвестно', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown+' => '', + 'Class:NetworkInterface/Attribute:connected_if' => 'Подключен к', + 'Class:NetworkInterface/Attribute:connected_if+' => 'Подключенный интерфейс', + 'Class:NetworkInterface/Attribute:connected_name' => 'Подключен к', + 'Class:NetworkInterface/Attribute:connected_name+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id' => 'Подключенное устройство', + 'Class:NetworkInterface/Attribute:connected_if_device_id+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name' => 'Устройство', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name+' => '', + 'Class:NetworkInterface/Attribute:link_type' => 'Тип линка', + 'Class:NetworkInterface/Attribute:link_type+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink' => 'Down link', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink' => 'Up link', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink+' => '', +)); + + + +// +// Class: Device +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Device' => 'Устройство', + 'Class:Device+' => '', + 'Class:Device/Attribute:nwinterface_list' => 'Сетевой интерфейс', + 'Class:Device/Attribute:nwinterface_list+' => '', +)); + +// +// Class: PC +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:PC' => 'ПК', + 'Class:PC+' => '', + 'Class:PC/Attribute:cpu' => 'ЦПУ', + 'Class:PC/Attribute:cpu+' => '', + 'Class:PC/Attribute:ram' => 'ОЗУ', + 'Class:PC/Attribute:ram+' => '', + 'Class:PC/Attribute:hdd' => 'Жёсткий диск', + 'Class:PC/Attribute:hdd+' => '', + 'Class:PC/Attribute:os_family' => 'Семейство ОС', + 'Class:PC/Attribute:os_family+' => '', + 'Class:PC/Attribute:os_version' => 'Версия ОС', + 'Class:PC/Attribute:os_version+' => '', + 'Class:PC/Attribute:application_list' => 'Приложения', + 'Class:PC/Attribute:application_list+' => 'Приложения установленные на этом ПК', + 'Class:PC/Attribute:patch_list' => 'Патчи', + 'Class:PC/Attribute:patch_list+' => 'Патчи установленные на этом ПК', +)); + +// +// Class: MobileCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:MobileCI' => 'Мбильные КЕ', + 'Class:MobileCI+' => '', +)); + +// +// Class: MobilePhone +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:MobilePhone' => 'Мобильный телефон', + 'Class:MobilePhone+' => '', + 'Class:MobilePhone/Attribute:number' => 'Номер телефона', + 'Class:MobilePhone/Attribute:number+' => '', + 'Class:MobilePhone/Attribute:imei' => 'IMEI', + 'Class:MobilePhone/Attribute:imei+' => '', + 'Class:MobilePhone/Attribute:hw_pin' => 'Аппаратный PIN', + 'Class:MobilePhone/Attribute:hw_pin+' => '', +)); + +// +// Class: InfrastructureCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:InfrastructureCI' => 'Инфраструктура КЕ', + 'Class:InfrastructureCI+' => '', + 'Class:InfrastructureCI/Attribute:description' => 'Описание', + 'Class:InfrastructureCI/Attribute:description+' => '', + 'Class:InfrastructureCI/Attribute:location_id' => 'Расположение', + 'Class:InfrastructureCI/Attribute:location_id+' => '', + 'Class:InfrastructureCI/Attribute:location_name' => 'Расположение', + 'Class:InfrastructureCI/Attribute:location_name+' => '', + 'Class:InfrastructureCI/Attribute:location_details' => 'Расположение подробно', + 'Class:InfrastructureCI/Attribute:location_details+' => '', + 'Class:InfrastructureCI/Attribute:management_ip' => 'IP управление', + 'Class:InfrastructureCI/Attribute:management_ip+' => '', + 'Class:InfrastructureCI/Attribute:default_gateway' => 'Шлюз по-умолчанию', + 'Class:InfrastructureCI/Attribute:default_gateway+' => '', +)); + +// +// Class: NetworkDevice +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:NetworkDevice' => 'Сетевое устройство', + 'Class:NetworkDevice+' => '', + 'Class:NetworkDevice/Attribute:type' => 'Тип', + 'Class:NetworkDevice/Attribute:type+' => '', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator' => 'WAN Accelerator', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator+' => '', + 'Class:NetworkDevice/Attribute:type/Value:firewall' => 'Firewall', + 'Class:NetworkDevice/Attribute:type/Value:firewall+' => '', + 'Class:NetworkDevice/Attribute:type/Value:hub' => 'Хаб', + 'Class:NetworkDevice/Attribute:type/Value:hub+' => '', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer' => 'Load Balancer', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer+' => '', + 'Class:NetworkDevice/Attribute:type/Value:router' => 'Маршрутизатор', + 'Class:NetworkDevice/Attribute:type/Value:router+' => '', + 'Class:NetworkDevice/Attribute:type/Value:switch' => 'Коммутатор', + 'Class:NetworkDevice/Attribute:type/Value:switch+' => '', + 'Class:NetworkDevice/Attribute:ios_version' => 'Версия IOS', + 'Class:NetworkDevice/Attribute:ios_version+' => '', + 'Class:NetworkDevice/Attribute:ram' => 'ОЗУ', + 'Class:NetworkDevice/Attribute:ram+' => '', + 'Class:NetworkDevice/Attribute:snmp_read' => 'Чтение SNMP', + 'Class:NetworkDevice/Attribute:snmp_read+' => '', + 'Class:NetworkDevice/Attribute:snmp_write' => 'Запись SNMP', + 'Class:NetworkDevice/Attribute:snmp_write+' => '', +)); + +// +// Class: Server +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Server' => 'Сервер', + 'Class:Server+' => '', + 'Class:Server/Attribute:cpu' => 'ЦПК', + 'Class:Server/Attribute:cpu+' => '', + 'Class:Server/Attribute:ram' => 'ОЗУ', + 'Class:Server/Attribute:ram+' => '', + 'Class:Server/Attribute:hdd' => 'Жёсткий диск', + 'Class:Server/Attribute:hdd+' => '', + 'Class:Server/Attribute:os_family' => 'Семейство ОС', + 'Class:Server/Attribute:os_family+' => '', + 'Class:Server/Attribute:os_version' => 'Версия ОС', + 'Class:Server/Attribute:os_version+' => '', + 'Class:Server/Attribute:application_list' => 'Приложения', + 'Class:Server/Attribute:application_list+' => 'Приложения установленные на этом сервере', + 'Class:Server/Attribute:patch_list' => 'Патчи', + 'Class:Server/Attribute:patch_list+' => 'Патчи установленные на этом сервере', +)); + +// +// Class: Printer +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Printer' => 'Принтер', + 'Class:Printer+' => '', + 'Class:Printer/Attribute:type' => 'Тип', + 'Class:Printer/Attribute:type+' => '', + 'Class:Printer/Attribute:type/Value:mopier' => 'Mopier', + 'Class:Printer/Attribute:type/Value:mopier+' => '', + 'Class:Printer/Attribute:type/Value:printer' => 'Принтер', + 'Class:Printer/Attribute:type/Value:printer+' => '', + 'Class:Printer/Attribute:technology' => 'Технология', + 'Class:Printer/Attribute:technology+' => '', + 'Class:Printer/Attribute:technology/Value:inkjet' => 'Чернильный', + 'Class:Printer/Attribute:technology/Value:inkjet+' => '', + 'Class:Printer/Attribute:technology/Value:laser' => 'Лазерный', + 'Class:Printer/Attribute:technology/Value:laser+' => '', + 'Class:Printer/Attribute:technology/Value:tracer' => 'Tracer', + 'Class:Printer/Attribute:technology/Value:tracer+' => '', +)); + +// +// Class: lnkCIToDoc +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkCIToDoc' => 'Документ/КЕ', + 'Class:lnkCIToDoc+' => '', + 'Class:lnkCIToDoc/Attribute:ci_id' => 'КЕ', + 'Class:lnkCIToDoc/Attribute:ci_id+' => '', + 'Class:lnkCIToDoc/Attribute:ci_name' => 'КЕ', + 'Class:lnkCIToDoc/Attribute:ci_name+' => '', + 'Class:lnkCIToDoc/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkCIToDoc/Attribute:ci_status+' => '', + 'Class:lnkCIToDoc/Attribute:document_id' => 'Документ', + 'Class:lnkCIToDoc/Attribute:document_id+' => '', + 'Class:lnkCIToDoc/Attribute:document_name' => 'Документ', + 'Class:lnkCIToDoc/Attribute:document_name+' => '', + 'Class:lnkCIToDoc/Attribute:document_type' => 'Тип документа', + 'Class:lnkCIToDoc/Attribute:document_type+' => '', + 'Class:lnkCIToDoc/Attribute:document_status' => 'Статус документа', + 'Class:lnkCIToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkCIToContact +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkCIToContact' => 'КЕ/Контакт', + 'Class:lnkCIToContact+' => '', + 'Class:lnkCIToContact/Attribute:ci_id' => 'КЕ', + 'Class:lnkCIToContact/Attribute:ci_id+' => '', + 'Class:lnkCIToContact/Attribute:ci_name' => 'КЕ', + 'Class:lnkCIToContact/Attribute:ci_name+' => '', + 'Class:lnkCIToContact/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkCIToContact/Attribute:ci_status+' => '', + 'Class:lnkCIToContact/Attribute:contact_id' => 'Контакт', + 'Class:lnkCIToContact/Attribute:contact_id+' => '', + 'Class:lnkCIToContact/Attribute:contact_name' => 'Контакт', + 'Class:lnkCIToContact/Attribute:contact_name+' => '', + 'Class:lnkCIToContact/Attribute:contact_email' => 'E-mail контакта', + 'Class:lnkCIToContact/Attribute:contact_email+' => '', + 'Class:lnkCIToContact/Attribute:role' => 'Роль', + 'Class:lnkCIToContact/Attribute:role+' => 'Роль контакта в отношении КЕ', +)); + +// +// Class: lnkSolutionToCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkSolutionToCI' => 'КЕ/Решение', + 'Class:lnkSolutionToCI+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_id' => 'Програмное решение', + 'Class:lnkSolutionToCI/Attribute:solution_id+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_name' => 'Програмное решение', + 'Class:lnkSolutionToCI/Attribute:solution_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_id' => 'КЕ', + 'Class:lnkSolutionToCI/Attribute:ci_id+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_name' => 'КЕ', + 'Class:lnkSolutionToCI/Attribute:ci_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkSolutionToCI/Attribute:ci_status+' => '', + 'Class:lnkSolutionToCI/Attribute:utility' => 'Утилита', + 'Class:lnkSolutionToCI/Attribute:utility+' => 'Утилита КЕ в решении', +)); + +// +// Class: lnkProcessToSolution +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkProcessToSolution' => 'Бизнес-процесс/Решение', + 'Class:lnkProcessToSolution+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_id' => 'Програмное решение', + 'Class:lnkProcessToSolution/Attribute:solution_id+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_name' => 'Програмное решение', + 'Class:lnkProcessToSolution/Attribute:solution_name+' => '', + 'Class:lnkProcessToSolution/Attribute:process_id' => 'Процесс', + 'Class:lnkProcessToSolution/Attribute:process_id+' => '', + 'Class:lnkProcessToSolution/Attribute:process_name' => 'Процесс', + 'Class:lnkProcessToSolution/Attribute:process_name+' => '', + 'Class:lnkProcessToSolution/Attribute:reason' => 'Причина', + 'Class:lnkProcessToSolution/Attribute:reason+' => 'Более подробная информация о связи между процессом и решением', +)); + + + +// +// Class extensions +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( +'Class:Subnet/Tab:IPUsage' => 'Использование IP', +'Class:Subnet/Tab:IPUsage-explain' => 'Интерфейсв имеющие IP в диапазоне с: %1$s по %2$s', +'Class:Subnet/Tab:FreeIPs' => 'Свободные IP', +'Class:Subnet/Tab:FreeIPs-count' => 'Свободные IP: %1$s', +'Class:Subnet/Tab:FreeIPs-explain' => 'Перечень 10 свободных IP адресов', +)); + +// +// Application Menu +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( +'Menu:Catalogs' => 'Каталоги', +'Menu:Catalogs+' => 'Типы данных', +'Menu:Audit' => 'Аудит', +'Menu:Audit+' => 'Аудит', +'Menu:Organization' => 'Организации', +'Menu:Organization+' => 'Все организации', +'Menu:Application' => 'Приложения', +'Menu:Application+' => 'Все приложения', +'Menu:DBServer' => 'Серверы баз данных', +'Menu:DBServer+' => 'Серверы баз данных', +'Menu:Audit' => 'Аудит', +'Menu:ConfigManagement' => 'Управление конфигурациями', +'Menu:ConfigManagement+' => 'Управление конфигурациями', +'Menu:ConfigManagementOverview' => 'Обзор', +'Menu:ConfigManagementOverview+' => 'Обзор', +'Menu:Contact' => 'Контакты', +'Menu:Contact+' => 'Контакты', +'Menu:Person' => 'Лица', +'Menu:Person+' => 'Все лица', +'Menu:Team' => 'Команды', +'Menu:Team+' => 'Все команды', +'Menu:Document' => 'Документы', +'Menu:Document+' => 'Все документы', +'Menu:Location' => 'Расположения', +'Menu:Location+' => 'Все расположения', +'Menu:ConfigManagementCI' => 'Конфигурационные единицы', +'Menu:ConfigManagementCI+' => 'Конфигурационные единицы', +'Menu:BusinessProcess' => 'Бизнес-процессы', +'Menu:BusinessProcess+' => 'Все бизнес-процессы', +'Menu:ApplicationSolution' => 'Програмные решения', +'Menu:ApplicationSolution+' => 'Все програмные решения', +'Menu:ConfigManagementSoftware' => 'Управление приложениями', +'Menu:Licence' => 'Лицензии', +'Menu:Licence+' => 'Все лицензии', +'Menu:Patch' => 'Патчи', +'Menu:Patch+' => 'Все патчи', +'Menu:ApplicationInstance' => 'Установленное ПО', +'Menu:ApplicationInstance+' => 'Приложения и сервера БД', +'Menu:ConfigManagementHardware' => 'Управление инфраструктурой', +'Menu:Subnet' => 'Подсети', +'Menu:Subnet+' => 'Все подсети', +'Menu:NetworkDevice' => 'Сетевые устройства', +'Menu:NetworkDevice+' => 'Все сетевые устройства', +'Menu:Server' => 'Серверы', +'Menu:Server+' => 'Все серверы', +'Menu:Printer' => 'Принтеры', +'Menu:Printer+' => 'Все принтеры', +'Menu:MobilePhone' => 'Мобильные телефоны', +'Menu:MobilePhone+' => 'Все мобильные телефоны', +'Menu:PC' => 'Персональные компьютеры', +'Menu:PC+' => 'Все ПК', +'Menu:NewContact' => 'Новый контакт', +'Menu:NewContact+' => 'Новый контакт', +'Menu:SearchContacts' => 'Поиск контактов', +'Menu:SearchContacts+' => 'Поиск контактов', +'Menu:NewCI' => 'Новый КЕ', +'Menu:NewCI+' => 'Новый КЕ', +'Menu:SearchCIs' => 'Поиск КЕ', +'Menu:SearchCIs+' => 'Поиск КЕ', +'Menu:ConfigManagement:Devices' => 'Устройства', +'Menu:ConfigManagement:AllDevices' => 'Количество устройств: %1$d', +'Menu:ConfigManagement:SWAndApps' => 'ПО и приложения', +'Menu:ConfigManagement:Misc' => 'Разное', +'Menu:Group' => 'Группы КЕ', +'Menu:Group+' => 'Группы КЕ', +'Menu:ConfigManagement:Shortcuts' => 'Ярлыки', +'Menu:ConfigManagement:AllContacts' => 'Все контакты: %1$d', + +)); +?> diff --git a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php index 435c6cc2b0..2809a345ca 100644 --- a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-incident-mgmt.php', 'de.dict.itop-incident-mgmt.php', 'pt_br.dict.itop-incident-mgmt.php', + 'ru.dict.itop-incident-mgmt.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', diff --git a/modules/itop-incident-mgmt-1.0.0/ru.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/ru.dict.itop-incident-mgmt.php new file mode 100644 index 0000000000..1db4bb0817 --- /dev/null +++ b/modules/itop-incident-mgmt-1.0.0/ru.dict.itop-incident-mgmt.php @@ -0,0 +1,73 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:IncidentManagement' => 'Управление инцидентами', + 'Menu:IncidentManagement+' => 'Управление инцидентами', + 'Menu:Incident:Overview' => 'Обзор', + 'Menu:Incident:Overview+' => 'Обзор', + 'Menu:NewIncident' => 'Новый инцидент', + 'Menu:NewIncident+' => 'Создать новый инцидент-тикет', + 'Menu:SearchIncidents' => 'Поиск инцидентов', + 'Menu:SearchIncidents+' => 'Поиск инцидент-тикетов', + 'Menu:Incident:Shortcuts' => 'Ярлыки', + 'Menu:Incident:Shortcuts+' => '', + 'Menu:Incident:MyIncidents' => 'Инциденты назначенные на меня', + 'Menu:Incident:MyIncidents+' => 'Управление инцидентами (как Агент)', + 'Menu:Incident:EscalatedIncidents' => 'Эскалированные инциденты', + 'Menu:Incident:EscalatedIncidents+' => 'Эскалированные инциденты', + 'Menu:Incident:OpenIncidents' => 'Все открытые инциденты', + 'Menu:Incident:OpenIncidents+' => 'Все открытые инциденты', + +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: Incident +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Incident' => 'Инцидент', + 'Class:Incident+' => '', + 'Class:Incident/Stimulus:ev_assign' => 'Назначить', + 'Class:Incident/Stimulus:ev_assign+' => '', + 'Class:Incident/Stimulus:ev_reassign' => 'Переназначить', + 'Class:Incident/Stimulus:ev_reassign+' => '', + 'Class:Incident/Stimulus:ev_timeout' => 'ev_timeout', + 'Class:Incident/Stimulus:ev_timeout+' => '', + 'Class:Incident/Stimulus:ev_resolve' => 'Пометить как решённое', + 'Class:Incident/Stimulus:ev_resolve+' => '', + 'Class:Incident/Stimulus:ev_close' => 'Закрыть', + 'Class:Incident/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php index 56db3fe44f..58ae378deb 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'fr.dict.itop-knownerror-mgmt.php', 'de.dict.itop-knownerror-mgmt.php', 'pt_br.dict.itop-knownerror-mgmt.php', + 'ru.dict.itop-knownerror-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-knownerror-mgmt.xml', diff --git a/modules/itop-knownerror-mgmt-1.0.0/ru.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/ru.dict.itop-knownerror-mgmt.php new file mode 100644 index 0000000000..5e567a951e --- /dev/null +++ b/modules/itop-knownerror-mgmt-1.0.0/ru.dict.itop-knownerror-mgmt.php @@ -0,0 +1,147 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: KnownError +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:KnownError' => 'Известные ошибки', + 'Class:KnownError+' => 'Ошибки задокументированные как известные', + 'Class:KnownError/Attribute:name' => 'Название', + 'Class:KnownError/Attribute:name+' => '', + 'Class:KnownError/Attribute:org_id' => 'Клинт', + 'Class:KnownError/Attribute:org_id+' => '', + 'Class:KnownError/Attribute:cust_name' => 'Имя клиента', + 'Class:KnownError/Attribute:cust_name+' => '', + 'Class:KnownError/Attribute:problem_id' => 'Связанная проблема', + 'Class:KnownError/Attribute:problem_id+' => '', + 'Class:KnownError/Attribute:problem_ref' => 'Ссылка', + 'Class:KnownError/Attribute:problem_ref+' => '', + 'Class:KnownError/Attribute:symptom' => 'Проявление', + 'Class:KnownError/Attribute:symptom+' => '', + 'Class:KnownError/Attribute:root_cause' => 'Основная причина', + 'Class:KnownError/Attribute:root_cause+' => '', + 'Class:KnownError/Attribute:workaround' => 'Обходное решение', + 'Class:KnownError/Attribute:workaround+' => '', + 'Class:KnownError/Attribute:solution' => 'Решение', + 'Class:KnownError/Attribute:solution+' => '', + 'Class:KnownError/Attribute:error_code' => 'Код ошибки', + 'Class:KnownError/Attribute:error_code+' => '', + 'Class:KnownError/Attribute:domain' => 'Домен', + 'Class:KnownError/Attribute:domain+' => '', + 'Class:KnownError/Attribute:domain/Value:Application' => 'Приложение', + 'Class:KnownError/Attribute:domain/Value:Application+' => 'Приложение', + 'Class:KnownError/Attribute:domain/Value:Desktop' => 'Рабочее окружение', + 'Class:KnownError/Attribute:domain/Value:Desktop+' => 'Рабочее окружение', + 'Class:KnownError/Attribute:domain/Value:Network' => 'Сеть', + 'Class:KnownError/Attribute:domain/Value:Network+' => 'Сеть', + 'Class:KnownError/Attribute:domain/Value:Server' => 'Сервер', + 'Class:KnownError/Attribute:domain/Value:Server+' => 'Сервер', + 'Class:KnownError/Attribute:vendor' => 'Производитель', + 'Class:KnownError/Attribute:vendor+' => '', + 'Class:KnownError/Attribute:model' => 'Модель', + 'Class:KnownError/Attribute:model+' => '', + 'Class:KnownError/Attribute:version' => 'Версия', + 'Class:KnownError/Attribute:version+' => '', + 'Class:KnownError/Attribute:ci_list' => 'КЕ', + 'Class:KnownError/Attribute:ci_list+' => '', + 'Class:KnownError/Attribute:document_list' => 'Документы', + 'Class:KnownError/Attribute:document_list+' => '', +)); + + +// +// Class: lnkInfraError +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkInfraError' => 'InfraErrorLinks', + 'Class:lnkInfraError+' => 'Infra относящаяся к известной ошибке', + 'Class:lnkInfraError/Attribute:infra_id' => 'КЕ', + 'Class:lnkInfraError/Attribute:infra_id+' => '', + 'Class:lnkInfraError/Attribute:infra_name' => 'Название КЕ', + 'Class:lnkInfraError/Attribute:infra_name+' => '', + 'Class:lnkInfraError/Attribute:infra_status' => 'Статус КЕ', + 'Class:lnkInfraError/Attribute:infra_status+' => '', + 'Class:lnkInfraError/Attribute:error_id' => 'Ошибка', + 'Class:lnkInfraError/Attribute:error_id+' => '', + 'Class:lnkInfraError/Attribute:error_name' => 'Название ошибки', + 'Class:lnkInfraError/Attribute:error_name+' => '', + 'Class:lnkInfraError/Attribute:reason' => 'Причина', + 'Class:lnkInfraError/Attribute:reason+' => '', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkDocumentError' => 'DocumentsErrorLinks', + 'Class:lnkDocumentError+' => 'Связь между документом и известной ошибкой', + 'Class:lnkDocumentError/Attribute:doc_id' => 'Документ', + 'Class:lnkDocumentError/Attribute:doc_id+' => '', + 'Class:lnkDocumentError/Attribute:doc_name' => 'Название документа', + 'Class:lnkDocumentError/Attribute:doc_name+' => '', + 'Class:lnkDocumentError/Attribute:error_id' => 'Ошибка', + 'Class:lnkDocumentError/Attribute:error_id+' => '', + 'Class:lnkDocumentError/Attribute:error_name' => 'Название ошибки', + 'Class:lnkDocumentError/Attribute:error_name+' => '', + 'Class:lnkDocumentError/Attribute:link_type' => 'Информация', + 'Class:lnkDocumentError/Attribute:link_type+' => '', +)); + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:NewError' => 'Новая известная ошибка', + 'Menu:NewError+' => 'Создание новой известной ошибки', + 'Menu:SearchError' => 'Поиск известных ошибок', + 'Menu:SearchError+' => 'Поиск известных ошибок', + 'Menu:Problem:KnownErrors' => 'Все известные ошибки', + 'Menu:Problem:KnownErrors+' => 'Все известные ошибки', +)); +?> diff --git a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php index 0f4fafa920..c077481a51 100644 --- a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'fr.dict.itop-problem-mgmt.php', 'de.dict.itop-problem-mgmt.php', 'pt_br.dict.itop-problem-mgmt.php', + 'ru.dict.itop-problem-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-problem-mgmt.xml', diff --git a/modules/itop-problem-mgmt-1.0.0/ru.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/ru.dict.itop-problem-mgmt.php new file mode 100644 index 0000000000..3f5e385007 --- /dev/null +++ b/modules/itop-problem-mgmt-1.0.0/ru.dict.itop-problem-mgmt.php @@ -0,0 +1,165 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + + + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:ProblemManagement' => 'Управление проблемами', + 'Menu:ProblemManagement+' => 'Управление проблемами', + 'Menu:Problem:Overview' => 'Обзор', + 'Menu:Problem:Overview+' => 'Обзор', + 'Menu:NewProblem' => 'Новая проблема', + 'Menu:NewProblem+' => 'Новая проблема', + 'Menu:SearchProblems' => 'Поиск проблем', + 'Menu:SearchProblems+' => 'Поиск проблем', + 'Menu:Problem:Shortcuts' => 'Ярлыки', + 'Menu:Problem:MyProblems' => 'Мои проблемы', + 'Menu:Problem:MyProblems+' => 'Мои проблемы', + 'Menu:Problem:OpenProblems' => 'Все открытые проблемы', + 'Menu:Problem:OpenProblems+' => 'Все открытые проблемы', + 'UI-ProblemManagementOverview-ProblemByService' => 'Проблемы по сервису', + 'UI-ProblemManagementOverview-ProblemByService+' => 'Проблемы по сервису', + 'UI-ProblemManagementOverview-ProblemByPriority' => 'Проблемы по приоритету', + 'UI-ProblemManagementOverview-ProblemByPriority+' => 'Проблемы по приоритету', + 'UI-ProblemManagementOverview-ProblemUnassigned' => 'Неназначенные проблемы', + 'UI-ProblemManagementOverview-ProblemUnassigned+' => 'Неназначенные проблемы', + 'UI:ProblemMgmtMenuOverview:Title' => 'Панель управление проблемами', + 'UI:ProblemMgmtMenuOverview:Title+' => 'Панель управление проблемами', + +)); +// +// Class: Problem +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Problem' => 'Проблема', + 'Class:Problem+' => '', + 'Class:Problem/Attribute:status' => 'Статус', + 'Class:Problem/Attribute:status+' => '', + 'Class:Problem/Attribute:status/Value:new' => 'Новая', + 'Class:Problem/Attribute:status/Value:new+' => '', + 'Class:Problem/Attribute:status/Value:assigned' => 'Подписана', + 'Class:Problem/Attribute:status/Value:assigned+' => '', + 'Class:Problem/Attribute:status/Value:resolved' => 'Решена', + 'Class:Problem/Attribute:status/Value:resolved+' => '', + 'Class:Problem/Attribute:status/Value:closed' => 'Закрыта', + 'Class:Problem/Attribute:status/Value:closed+' => '', + 'Class:Problem/Attribute:org_id' => 'Клиент', + 'Class:Problem/Attribute:org_id+' => '', + 'Class:Problem/Attribute:org_name' => 'Название', + 'Class:Problem/Attribute:org_name+' => 'Общее название', + 'Class:Problem/Attribute:service_id' => 'Услуга', + 'Class:Problem/Attribute:service_id+' => '', + 'Class:Problem/Attribute:service_name' => 'Название', + 'Class:Problem/Attribute:service_name+' => '', + 'Class:Problem/Attribute:servicesubcategory_id' => 'Категория услуги', + 'Class:Problem/Attribute:servicesubcategory_id+' => '', + 'Class:Problem/Attribute:servicesubcategory_name' => 'Название', + 'Class:Problem/Attribute:servicesubcategory_name+' => '', + 'Class:Problem/Attribute:product' => 'Продукт', + 'Class:Problem/Attribute:product+' => '', + 'Class:Problem/Attribute:impact' => 'Воздействие', + 'Class:Problem/Attribute:impact+' => '', + 'Class:Problem/Attribute:impact/Value:1' => 'Лицо', + 'Class:Problem/Attribute:impact/Value:1+' => '', + 'Class:Problem/Attribute:impact/Value:2' => 'Сервис', + 'Class:Problem/Attribute:impact/Value:2+' => '', + 'Class:Problem/Attribute:impact/Value:3' => 'Департамент', + 'Class:Problem/Attribute:impact/Value:3+' => '', + 'Class:Problem/Attribute:urgency' => 'Срочность', + 'Class:Problem/Attribute:urgency+' => '', + 'Class:Problem/Attribute:urgency/Value:1' => 'Низкая', + 'Class:Problem/Attribute:urgency/Value:1+' => 'Низкая', + 'Class:Problem/Attribute:urgency/Value:2' => 'Средняя', + 'Class:Problem/Attribute:urgency/Value:2+' => 'Средняя', + 'Class:Problem/Attribute:urgency/Value:3' => 'Высокая', + 'Class:Problem/Attribute:urgency/Value:3+' => 'Высокая', + 'Class:Problem/Attribute:priority' => 'Приоритет', + 'Class:Problem/Attribute:priority+' => '', + 'Class:Problem/Attribute:priority/Value:1' => 'Низкий', + 'Class:Problem/Attribute:priority/Value:1+' => '', + 'Class:Problem/Attribute:priority/Value:2' => 'Средний', + 'Class:Problem/Attribute:priority/Value:2+' => '', + 'Class:Problem/Attribute:priority/Value:3' => 'Высокий', + 'Class:Problem/Attribute:priority/Value:3+' => '', + 'Class:Problem/Attribute:workgroup_id' => 'Рабочая группа', + 'Class:Problem/Attribute:workgroup_id+' => '', + 'Class:Problem/Attribute:workgroup_name' => 'Название', + 'Class:Problem/Attribute:workgroup_name+' => '', + 'Class:Problem/Attribute:agent_id' => 'Агент', + 'Class:Problem/Attribute:agent_id+' => '', + 'Class:Problem/Attribute:agent_name' => 'Имя агента', + 'Class:Problem/Attribute:agent_name+' => '', + 'Class:Problem/Attribute:agent_email' => 'e-mail агента', + 'Class:Problem/Attribute:agent_email+' => '', + 'Class:Problem/Attribute:related_change_id' => 'Связанные изменения', + 'Class:Problem/Attribute:related_change_id+' => '', + 'Class:Problem/Attribute:related_change_ref' => 'Ссылка', + 'Class:Problem/Attribute:related_change_ref+' => '', + 'Class:Problem/Attribute:close_date' => 'Дата закрытия', + 'Class:Problem/Attribute:close_date+' => '', + 'Class:Problem/Attribute:last_update' => 'Последнее обновление', + 'Class:Problem/Attribute:last_update+' => '', + 'Class:Problem/Attribute:assignment_date' => 'Дата назначения', + 'Class:Problem/Attribute:assignment_date+' => '', + 'Class:Problem/Attribute:resolution_date' => 'Дата решения', + 'Class:Problem/Attribute:resolution_date+' => '', + 'Class:Problem/Attribute:knownerrors_list' => 'Известные ошибки', + 'Class:Problem/Attribute:knownerrors_list+' => '', + 'Class:Problem/Stimulus:ev_assign' => 'Назначить', + 'Class:Problem/Stimulus:ev_assign+' => '', + 'Class:Problem/Stimulus:ev_reassign' => 'Переназначить', + 'Class:Problem/Stimulus:ev_reassign+' => '', + 'Class:Problem/Stimulus:ev_resolve' => 'Решение', + 'Class:Problem/Stimulus:ev_resolve+' => '', + 'Class:Problem/Stimulus:ev_close' => 'Закрыть', + 'Class:Problem/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php index ea503b4272..9ea24c3af1 100644 --- a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php @@ -30,6 +30,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-request-mgmt.php', 'de.dict.itop-request-mgmt.php', 'pt_br.dict.itop-request-mgmt.php', + 'ru.dict.itop-request-mgmt.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', diff --git a/modules/itop-request-mgmt-1.0.0/ru.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/ru.dict.itop-request-mgmt.php new file mode 100644 index 0000000000..2eaf050ac0 --- /dev/null +++ b/modules/itop-request-mgmt-1.0.0/ru.dict.itop-request-mgmt.php @@ -0,0 +1,84 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Menu:RequestManagement' => 'Helpdesk', + 'Menu:RequestManagement+' => 'Техподдержка', + 'Menu:UserRequest:Overview' => 'Обзор', + 'Menu:UserRequest:Overview+' => 'Обзор', + 'Menu:NewUserRequest' => 'Новый пользовательский запрос', + 'Menu:NewUserRequest+' => 'Создать новый тикет пользовательского запроса', + 'Menu:SearchUserRequests' => 'Поиск пользовательских запросов', + 'Menu:SearchUserRequests+' => 'Поиск тикетов пользовательских запросов', + 'Menu:UserRequest:Shortcuts' => 'Ярлыки', + 'Menu:UserRequest:Shortcuts+' => '', + 'Menu:UserRequest:MyRequests' => 'Запросы назначенные на меня', + 'Menu:UserRequest:MyRequests+' => 'Запросы назначенные на меня (как агент)', + 'Menu:UserRequest:EscalatedRequests' => 'Эскалированные запросы', + 'Menu:UserRequest:EscalatedRequests+' => 'Эскалированные запросы', + 'Menu:UserRequest:OpenRequests' => 'Все открытые запросы', + 'Menu:UserRequest:OpenRequests+' => 'Все открытые запросы', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserRequest +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:UserRequest' => 'Пользовательский запрос', + 'Class:UserRequest+' => '', + 'Class:UserRequest/Attribute:request_type' => 'Тип запроса', + 'Class:UserRequest/Attribute:request_type+' => '', + 'Class:UserRequest/Attribute:request_type/Value:information' => 'Информация', + 'Class:UserRequest/Attribute:request_type/Value:information+' => 'Информация', + 'Class:UserRequest/Attribute:request_type/Value:issue' => 'Номер', + 'Class:UserRequest/Attribute:request_type/Value:issue+' => 'Номер', + 'Class:UserRequest/Attribute:request_type/Value:service request' => 'Запрос сервиса', + 'Class:UserRequest/Attribute:request_type/Value:service request+' => 'Запрос сервиса', + 'Class:UserRequest/Attribute:freeze_reason' => 'Причина заморозки', + 'Class:UserRequest/Attribute:freeze_reason+' => '', + 'Class:UserRequest/Stimulus:ev_assign' => 'Назначить', + 'Class:UserRequest/Stimulus:ev_assign+' => '', + 'Class:UserRequest/Stimulus:ev_reassign' => 'Переназначить', + 'Class:UserRequest/Stimulus:ev_reassign+' => '', + 'Class:UserRequest/Stimulus:ev_timeout' => 'ev_timeout', + 'Class:UserRequest/Stimulus:ev_timeout+' => '', + 'Class:UserRequest/Stimulus:ev_resolve' => 'Пометить как решённое', + 'Class:UserRequest/Stimulus:ev_resolve+' => '', + 'Class:UserRequest/Stimulus:ev_close' => 'Закрыть', + 'Class:UserRequest/Stimulus:ev_close+' => '', + 'Class:UserRequest/Stimulus:ev_freeze' => 'Пометить как замороженное', + 'Class:UserRequest/Stimulus:ev_freeze+' => '', +)); + +?> diff --git a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php index 04a33af7cb..0aa6236e2b 100644 --- a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php @@ -29,6 +29,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-service-mgmt.php', 'de.dict.itop-service-mgmt.php', 'pt_br.dict.itop-service-mgmt.php', + 'ru.dict.itop-service-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-service-mgmt.xml', diff --git a/modules/itop-service-mgmt-1.0.0/ru.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/ru.dict.itop-service-mgmt.php new file mode 100644 index 0000000000..00a7874f7a --- /dev/null +++ b/modules/itop-service-mgmt-1.0.0/ru.dict.itop-service-mgmt.php @@ -0,0 +1,452 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +Dict::Add('RU RU', 'Russian', 'Русский', array( +'Menu:ServiceManagement' => 'Управление сервисами', +'Menu:ServiceManagement+' => 'Обзор управление сервисами', +'Menu:Service:Overview' => 'Обзор', +'Menu:Service:Overview+' => '', +'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Договоры по уровню сервиса', +'UI-ServiceManagementMenu-ContractsByStatus' => 'Договоры по статусу', +'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Договоры заканчивающиеся в течении 30-ти ней', + +'Menu:ServiceType' => 'Типы сервисов', +'Menu:ServiceType+' => 'Типы сервисов', +'Menu:ProviderContract' => 'Договоры с поставщиками', +'Menu:ProviderContract+' => 'Договоры с поставщиками', +'Menu:CustomerContract' => 'Договоры с клиентами', +'Menu:CustomerContract+' => 'Договоры с клиентами', +'Menu:ServiceSubcategory' => 'Подкатегории сервисов', +'Menu:ServiceSubcategory+' => 'Подкатегории сервисов', +'Menu:Service' => 'Сервисы', +'Menu:Service+' => 'Сервисы', +'Menu:SLA' => 'SLAs', +'Menu:SLA+' => 'Соглашения об уровне обслуживания', +'Menu:SLT' => 'SLTs', +'Menu:SLT+' => 'Цели уровня обслуживания', + +)); + + +/* + 'UI:ServiceManagementMenu' => 'Gestion des Services', + 'UI:ServiceManagementMenu+' => 'Gestion des Services', + 'UI:ServiceManagementMenu:Title' => 'Résumé des services & contrats', + 'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contrats par niveau de service', + 'UI-ServiceManagementMenu-ContractsByStatus' => 'Contrats par état', + 'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contrats se terminant dans moins de 30 jours', +*/ + + +// +// Class: Contract +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Contract' => 'Договор', + 'Class:Contract+' => '', + 'Class:Contract/Attribute:name' => 'Название', + 'Class:Contract/Attribute:name+' => '', + 'Class:Contract/Attribute:description' => 'Орисание', + 'Class:Contract/Attribute:description+' => '', + 'Class:Contract/Attribute:start_date' => 'Дата начала', + 'Class:Contract/Attribute:start_date+' => '', + 'Class:Contract/Attribute:end_date' => 'Дата окончания', + 'Class:Contract/Attribute:end_date+' => '', + 'Class:Contract/Attribute:cost' => 'Стоимость', + 'Class:Contract/Attribute:cost+' => '', + 'Class:Contract/Attribute:cost_currency' => 'Валюта стоимости', + 'Class:Contract/Attribute:cost_currency+' => '', + 'Class:Contract/Attribute:cost_currency/Value:dollars' => 'Доллары', + 'Class:Contract/Attribute:cost_currency/Value:dollars+' => '', + 'Class:Contract/Attribute:cost_currency/Value:euros' => 'Евро', + 'Class:Contract/Attribute:cost_currency/Value:euros+' => '', + 'Class:Contract/Attribute:cost_unit' => 'Единица стоимости', + 'Class:Contract/Attribute:cost_unit+' => '', + 'Class:Contract/Attribute:billing_frequency' => 'Частота платежей', + 'Class:Contract/Attribute:billing_frequency+' => '', + 'Class:Contract/Attribute:contact_list' => 'Договора', + 'Class:Contract/Attribute:contact_list+' => 'Договора связанные с этим договром', + 'Class:Contract/Attribute:document_list' => 'Документы', + 'Class:Contract/Attribute:document_list+' => 'Документы связанные с этим договором', + 'Class:Contract/Attribute:ci_list' => 'КЕ', + 'Class:Contract/Attribute:ci_list+' => 'КЕ поддерживаемые договором', + 'Class:Contract/Attribute:finalclass' => 'Тип', + 'Class:Contract/Attribute:finalclass+' => '', +)); + +// +// Class: ProviderContract +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ProviderContract' => 'Договора с поставщиками', + 'Class:ProviderContract+' => '', + 'Class:ProviderContract/Attribute:provider_id' => 'Поставщики', + 'Class:ProviderContract/Attribute:provider_id+' => '', + 'Class:ProviderContract/Attribute:provider_name' => 'Название поставщика', + 'Class:ProviderContract/Attribute:provider_name+' => '', + 'Class:ProviderContract/Attribute:sla' => 'SLA', + 'Class:ProviderContract/Attribute:sla+' => 'Договор об уровне сервиса', + 'Class:ProviderContract/Attribute:coverage' => 'Время работы', + 'Class:ProviderContract/Attribute:coverage+' => '', +)); + +// +// Class: CustomerContract +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:CustomerContract' => 'Договора с клиентами', + 'Class:CustomerContract+' => '', + 'Class:CustomerContract/Attribute:org_id' => 'Клиент', + 'Class:CustomerContract/Attribute:org_id+' => '', + 'Class:CustomerContract/Attribute:org_name' => 'Название клиента', + 'Class:CustomerContract/Attribute:org_name+' => '', + 'Class:CustomerContract/Attribute:provider_id' => 'Поставщик', + 'Class:CustomerContract/Attribute:provider_id+' => '', + 'Class:CustomerContract/Attribute:provider_name' => 'Название поставщика', + 'Class:CustomerContract/Attribute:provider_name+' => '', + 'Class:CustomerContract/Attribute:support_team_id' => 'Команда поддержки', + 'Class:CustomerContract/Attribute:support_team_id+' => '', + 'Class:CustomerContract/Attribute:support_team_name' => 'Команда поддержки', + 'Class:CustomerContract/Attribute:support_team_name+' => '', + 'Class:CustomerContract/Attribute:provider_list' => 'Поставщики', + 'Class:CustomerContract/Attribute:provider_list+' => '', + 'Class:CustomerContract/Attribute:sla_list' => 'SLAs', + 'Class:CustomerContract/Attribute:sla_list+' => 'Список СУО относящихся к договору', + 'Class:CustomerContract/Attribute:provider_list' => 'В основе контрактов', + 'Class:CustomerContract/Attribute:sla_list+' => '', +)); +// +// Class: lnkCustomerContractToProviderContract +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkCustomerContractToProviderContract' => 'Связи между договорами клиентов и поставщиков', + 'Class:lnkCustomerContractToProviderContract+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id' => 'Договор клиента', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name' => 'Название', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id' => 'Договор провайдера', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name' => 'Название', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla' => 'SLA Поставщика', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla+' => 'Соглашение об уровне обслуживания', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage' => 'Время работы', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage+' => '', +)); + + +// +// Class: lnkContractToSLA +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkContractToSLA' => 'Договоры/SLA', + 'Class:lnkContractToSLA+' => '', + 'Class:lnkContractToSLA/Attribute:contract_id' => 'Договор', + 'Class:lnkContractToSLA/Attribute:contract_id+' => '', + 'Class:lnkContractToSLA/Attribute:contract_name' => 'Договор', + 'Class:lnkContractToSLA/Attribute:contract_name+' => '', + 'Class:lnkContractToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_id+' => '', + 'Class:lnkContractToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_name+' => '', + 'Class:lnkContractToSLA/Attribute:coverage' => 'Время работы', + 'Class:lnkContractToSLA/Attribute:coverage+' => '', +)); + +// +// Class: lnkContractToDoc +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkContractToDoc' => 'Договор/Документ', + 'Class:lnkContractToDoc+' => '', + 'Class:lnkContractToDoc/Attribute:contract_id' => 'Договор', + 'Class:lnkContractToDoc/Attribute:contract_id+' => '', + 'Class:lnkContractToDoc/Attribute:contract_name' => 'Договор', + 'Class:lnkContractToDoc/Attribute:contract_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_id' => 'Документ', + 'Class:lnkContractToDoc/Attribute:document_id+' => '', + 'Class:lnkContractToDoc/Attribute:document_name' => 'Документ', + 'Class:lnkContractToDoc/Attribute:document_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_type' => 'Тип документа', + 'Class:lnkContractToDoc/Attribute:document_type+' => '', + 'Class:lnkContractToDoc/Attribute:document_status' => 'Статус документа', + 'Class:lnkContractToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkContractToContact +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkContractToContact' => 'Договор/Договор', + 'Class:lnkContractToContact+' => '', + 'Class:lnkContractToContact/Attribute:contract_id' => 'Договор', + 'Class:lnkContractToContact/Attribute:contract_id+' => '', + 'Class:lnkContractToContact/Attribute:contract_name' => 'Договор', + 'Class:lnkContractToContact/Attribute:contract_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_id' => 'Контакт', + 'Class:lnkContractToContact/Attribute:contact_id+' => '', + 'Class:lnkContractToContact/Attribute:contact_name' => 'Контакт', + 'Class:lnkContractToContact/Attribute:contact_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_email' => 'e-mail Контакта', + 'Class:lnkContractToContact/Attribute:contact_email+' => '', + 'Class:lnkContractToContact/Attribute:role' => 'Роль', + 'Class:lnkContractToContact/Attribute:role+' => '', +)); + +// +// Class: lnkContractToCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkContractToCI' => 'Договор/КЕ', + 'Class:lnkContractToCI+' => '', + 'Class:lnkContractToCI/Attribute:contract_id' => 'Договор', + 'Class:lnkContractToCI/Attribute:contract_id+' => '', + 'Class:lnkContractToCI/Attribute:contract_name' => 'Договор', + 'Class:lnkContractToCI/Attribute:contract_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_id' => 'КЕ', + 'Class:lnkContractToCI/Attribute:ci_id+' => '', + 'Class:lnkContractToCI/Attribute:ci_name' => 'КЕ', + 'Class:lnkContractToCI/Attribute:ci_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkContractToCI/Attribute:ci_status+' => '', +)); + +// +// Class: Service +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Service' => 'Услуга', + 'Class:Service+' => '', + 'Class:Service/Attribute:org_id' => 'Поставщик', + 'Class:Service/Attribute:org_id+' => '', + 'Class:Service/Attribute:provider_name' => 'Поставщик', + 'Class:Service/Attribute:provider_name+' => '', + 'Class:Service/Attribute:name' => 'Название', + 'Class:Service/Attribute:name+' => '', + 'Class:Service/Attribute:description' => 'Описание', + 'Class:Service/Attribute:description+' => '', + 'Class:Service/Attribute:type' => 'Тип', + 'Class:Service/Attribute:type+' => '', + 'Class:Service/Attribute:type/Value:IncidentManagement' => 'Управление инцидентами', + 'Class:Service/Attribute:type/Value:IncidentManagement+' => 'Управление инцидентами', + 'Class:Service/Attribute:type/Value:RequestManagement' => 'Управление запросами', + 'Class:Service/Attribute:type/Value:RequestManagement+' => 'Управление запросами', + 'Class:Service/Attribute:status' => 'Статус', + 'Class:Service/Attribute:status+' => '', + 'Class:Service/Attribute:status/Value:design' => 'Дизайн', + 'Class:Service/Attribute:status/Value:design+' => '', + 'Class:Service/Attribute:status/Value:obsolete' => 'Устаревший', + 'Class:Service/Attribute:status/Value:obsolete+' => '', + 'Class:Service/Attribute:status/Value:production' => 'Производство', + 'Class:Service/Attribute:status/Value:production+' => '', + 'Class:Service/Attribute:subcategory_list' => 'Подкатегория услуги', + 'Class:Service/Attribute:subcategory_list+' => '', + 'Class:Service/Attribute:sla_list' => 'SLAs', + 'Class:Service/Attribute:sla_list+' => '', + 'Class:Service/Attribute:document_list' => 'Документы', + 'Class:Service/Attribute:document_list+' => 'Документа прикреплённые к услуге', + 'Class:Service/Attribute:contact_list' => 'Контакты', + 'Class:Service/Attribute:contact_list+' => 'Контакты имющие роль для услуги', + 'Class:Service/Tab:Related_Contracts' => 'Связанные договора', + 'Class:Service/Tab:Related_Contracts+' => 'Договора связанные с услугой', +)); + +// +// Class: ServiceSubcategory +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ServiceSubcategory' => 'Подкатегории услуг', + 'Class:ServiceSubcategory+' => '', + 'Class:ServiceSubcategory/Attribute:name' => 'Название', + 'Class:ServiceSubcategory/Attribute:name+' => '', + 'Class:ServiceSubcategory/Attribute:description' => 'Описание', + 'Class:ServiceSubcategory/Attribute:description+' => '', + 'Class:ServiceSubcategory/Attribute:service_id' => 'Услуга', + 'Class:ServiceSubcategory/Attribute:service_id+' => '', + 'Class:ServiceSubcategory/Attribute:service_name' => 'Услуга', + 'Class:ServiceSubcategory/Attribute:service_name+' => '', +)); + +// +// Class: SLA +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:SLA' => 'SLA', + 'Class:SLA+' => '', + 'Class:SLA/Attribute:name' => 'Название', + 'Class:SLA/Attribute:name+' => '', + 'Class:SLA/Attribute:service_id' => 'Услуга', + 'Class:SLA/Attribute:service_id+' => '', + 'Class:SLA/Attribute:service_name' => 'Услуга', + 'Class:SLA/Attribute:service_name+' => '', + 'Class:SLA/Attribute:slt_list' => 'SLTs', + 'Class:SLA/Attribute:slt_list+' => 'Список порогов уровней услуг', +)); + +// +// Class: SLT +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:SLT' => 'SLT', + 'Class:SLT+' => 'Порог уровня услуги', + 'Class:SLT/Attribute:name' => 'Название', + 'Class:SLT/Attribute:name+' => '', + 'Class:SLT/Attribute:metric' => 'Метрика', + 'Class:SLT/Attribute:metric+' => '', + 'Class:SLT/Attribute:metric/Value:TTO' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTO+' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTR' => 'TTR', + 'Class:SLT/Attribute:metric/Value:TTR+' => 'TTR', + 'Class:SLT/Attribute:ticket_priority' => 'Приоритет тикета', + 'Class:SLT/Attribute:ticket_priority+' => '', + 'Class:SLT/Attribute:ticket_priority/Value:1' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:1+' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:2' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:2+' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:3' => '3', + 'Class:SLT/Attribute:ticket_priority/Value:3+' => '3', + 'Class:SLT/Attribute:value' => 'Значение', + 'Class:SLT/Attribute:value+' => '', + 'Class:SLT/Attribute:value_unit' => 'Единица', + 'Class:SLT/Attribute:value_unit+' => '', + 'Class:SLT/Attribute:value_unit/Value:days' => 'дней', + 'Class:SLT/Attribute:value_unit/Value:days+' => 'дней', + 'Class:SLT/Attribute:value_unit/Value:hours' => 'часов', + 'Class:SLT/Attribute:value_unit/Value:hours+' => 'часов', + 'Class:SLT/Attribute:value_unit/Value:minutes' => 'минут', + 'Class:SLT/Attribute:value_unit/Value:minutes+' => 'минут', + 'Class:SLT/Attribute:sla_list' => 'SLAs', + 'Class:SLT/Attribute:sla_list+' => 'СУО использующие ПУС', +)); + +// +// Class: lnkSLTToSLA +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkSLTToSLA' => 'SLT/SLA', + 'Class:lnkSLTToSLA+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_id+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_id' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_id+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_name' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_metric' => 'Метрика', + 'Class:lnkSLTToSLA/Attribute:slt_metric+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority' => 'Приоритет тикета', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value' => 'Значение', + 'Class:lnkSLTToSLA/Attribute:slt_value+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit' => 'Единица', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit+' => '', +)); + +// +// Class: lnkServiceToDoc +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkServiceToDoc' => 'Услуга/Документ', + 'Class:lnkServiceToDoc+' => '', + 'Class:lnkServiceToDoc/Attribute:service_id' => 'Услуга', + 'Class:lnkServiceToDoc/Attribute:service_id+' => '', + 'Class:lnkServiceToDoc/Attribute:service_name' => 'Услуга', + 'Class:lnkServiceToDoc/Attribute:service_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_id' => 'Документ', + 'Class:lnkServiceToDoc/Attribute:document_id+' => '', + 'Class:lnkServiceToDoc/Attribute:document_name' => 'Документ', + 'Class:lnkServiceToDoc/Attribute:document_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_type' => 'Тип документа', + 'Class:lnkServiceToDoc/Attribute:document_type+' => '', + 'Class:lnkServiceToDoc/Attribute:document_status' => 'Статус документа', + 'Class:lnkServiceToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkServiceToContact +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkServiceToContact' => 'Услуга/Договор', + 'Class:lnkServiceToContact+' => '', + 'Class:lnkServiceToContact/Attribute:service_id' => 'Услуга', + 'Class:lnkServiceToContact/Attribute:service_id+' => '', + 'Class:lnkServiceToContact/Attribute:service_name' => 'Услуга', + 'Class:lnkServiceToContact/Attribute:service_name+' => '', + 'Class:lnkServiceToContact/Attribute:contact_id' => 'Договор', + 'Class:lnkServiceToContact/Attribute:contact_id+' => '', + 'Class:lnkServiceToContact/Attribute:contact_name' => 'Договор', + 'Class:lnkServiceToContact/Attribute:contact_name+' => '', + 'Class:lnkServiceToContact/Attribute:contact_email' => 'Контактный email', + 'Class:lnkServiceToContact/Attribute:contact_email+' => '', + 'Class:lnkServiceToContact/Attribute:role' => 'Роль', + 'Class:lnkServiceToContact/Attribute:role+' => '', +)); + +// +// Class: lnkServiceToCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkServiceToCI' => 'Услуга/КЕ', + 'Class:lnkServiceToCI+' => '', + 'Class:lnkServiceToCI/Attribute:service_id' => 'Услуга', + 'Class:lnkServiceToCI/Attribute:service_id+' => '', + 'Class:lnkServiceToCI/Attribute:service_name' => 'Услуга', + 'Class:lnkServiceToCI/Attribute:service_name+' => '', + 'Class:lnkServiceToCI/Attribute:ci_id' => 'КЕ', + 'Class:lnkServiceToCI/Attribute:ci_id+' => '', + 'Class:lnkServiceToCI/Attribute:ci_name' => 'КЕ', + 'Class:lnkServiceToCI/Attribute:ci_name+' => '', + 'Class:lnkServiceToCI/Attribute:ci_status' => 'Статус КЕ', + 'Class:lnkServiceToCI/Attribute:ci_status+' => '', +)); + +?> diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index 000723d5c4..bd9095932b 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -29,6 +29,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-tickets.php', 'de.dict.itop-tickets.php', 'pt_br.dict.itop-tickets.php', + 'ru.dict.itop-tickets.php', ), 'data.struct' => array( 'data.struct.ta-actions.xml', diff --git a/modules/itop-tickets-1.0.0/ru.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/ru.dict.itop-tickets.php new file mode 100644 index 0000000000..03c1a5fb38 --- /dev/null +++ b/modules/itop-tickets-1.0.0/ru.dict.itop-tickets.php @@ -0,0 +1,262 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Ticket +// + +// +// Class: Ticket +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:Ticket' => 'Тикеи', + 'Class:Ticket+' => '', + 'Class:Ticket/Attribute:ref' => 'Ссылка', + 'Class:Ticket/Attribute:ref+' => '', + 'Class:Ticket/Attribute:title' => 'Название', + 'Class:Ticket/Attribute:title+' => '', + 'Class:Ticket/Attribute:description' => 'Описание', + 'Class:Ticket/Attribute:description+' => '', + 'Class:Ticket/Attribute:ticket_log' => 'Лог', + 'Class:Ticket/Attribute:ticket_log+' => '', + 'Class:Ticket/Attribute:start_date' => 'Начат', + 'Class:Ticket/Attribute:start_date+' => '', + 'Class:Ticket/Attribute:document_list' => 'Документы', + 'Class:Ticket/Attribute:document_list+' => 'Документы относящиеся к тикету', + 'Class:Ticket/Attribute:ci_list' => 'КЕ', + 'Class:Ticket/Attribute:ci_list+' => 'КЕ затронутые инцидентом', + 'Class:Ticket/Attribute:contact_list' => 'Контакты', + 'Class:Ticket/Attribute:contact_list+' => 'Привлечённые команды и лица', + 'Class:Ticket/Attribute:incident_list' => 'Связанные инциденты', + 'Class:Ticket/Attribute:incident_list+' => '', + 'Class:Ticket/Attribute:finalclass' => 'Тип', + 'Class:Ticket/Attribute:finalclass+' => '', +)); + + +// +// Class: lnkTicketToDoc +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkTicketToDoc' => 'Тикет/Документ', + 'Class:lnkTicketToDoc+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_id' => 'Тикет', + 'Class:lnkTicketToDoc/Attribute:ticket_id+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_ref' => '№ тикета', + 'Class:lnkTicketToDoc/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToDoc/Attribute:document_id' => 'Документ', + 'Class:lnkTicketToDoc/Attribute:document_id+' => '', + 'Class:lnkTicketToDoc/Attribute:document_name' => 'Документ', + 'Class:lnkTicketToDoc/Attribute:document_name+' => '', +)); + +// +// Class: lnkTicketToContact +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkTicketToContact' => 'Тикет/Контакт', + 'Class:lnkTicketToContact+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_id' => 'Тикет', + 'Class:lnkTicketToContact/Attribute:ticket_id+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_ref' => '№ тикета', + 'Class:lnkTicketToContact/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToContact/Attribute:contact_id' => 'Контакт', + 'Class:lnkTicketToContact/Attribute:contact_id+' => '', + 'Class:lnkTicketToContact/Attribute:contact_name' => 'Контакт', + 'Class:lnkTicketToContact/Attribute:contact_name+' => '', + 'Class:lnkTicketToContact/Attribute:contact_email' => 'Email', + 'Class:lnkTicketToContact/Attribute:contact_email+' => '', + 'Class:lnkTicketToContact/Attribute:role' => 'Роль', + 'Class:lnkTicketToContact/Attribute:role+' => '', +)); + +// +// Class: lnkTicketToCI +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:lnkTicketToCI' => 'Тикет/КЕ', + 'Class:lnkTicketToCI+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_id' => 'Тикет', + 'Class:lnkTicketToCI/Attribute:ticket_id+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_ref' => '№ тикета', + 'Class:lnkTicketToCI/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToCI/Attribute:ci_id' => 'КЕ', + 'Class:lnkTicketToCI/Attribute:ci_id+' => '', + 'Class:lnkTicketToCI/Attribute:ci_name' => 'КЕ', + 'Class:lnkTicketToCI/Attribute:ci_name+' => '', + 'Class:lnkTicketToCI/Attribute:ci_status' => 'КЕ Статус', + 'Class:lnkTicketToCI/Attribute:ci_status+' => '', + 'Class:lnkTicketToCI/Attribute:impact' => 'Воздействие', + 'Class:lnkTicketToCI/Attribute:impact+' => '', +)); + + +// +// Class: ResponseTicket +// + +Dict::Add('RU RU', 'Russian', 'Русский', array( + 'Class:ResponseTicket' => 'Ответный тикет', + 'Class:ResponseTicket+' => '', + 'Class:ResponseTicket/Attribute:status' => 'Статус', + 'Class:ResponseTicket/Attribute:status+' => '', + 'Class:ResponseTicket/Attribute:status/Value:new' => 'Новый', + 'Class:ResponseTicket/Attribute:status/Value:new+' => 'недавно открытый', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto' => 'Эскалация/TTO', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto+' => '', + 'Class:ResponseTicket/Attribute:status/Value:assigned' => 'Назначен', + 'Class:ResponseTicket/Attribute:status/Value:assigned+' => '', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr' => 'Эскалация/TTR', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr+' => '', + 'Class:ResponseTicket/Attribute:status/Value:frozen' => 'Заморожен', + 'Class:ResponseTicket/Attribute:status/Value:frozen+' => '', + 'Class:ResponseTicket/Attribute:status/Value:resolved' => 'Решён', + 'Class:ResponseTicket/Attribute:status/Value:resolved+' => '', + 'Class:ResponseTicket/Attribute:status/Value:closed' => 'Закріт', + 'Class:ResponseTicket/Attribute:status/Value:closed+' => '', + 'Class:ResponseTicket/Attribute:caller_id' => 'Вызывающий', + 'Class:ResponseTicket/Attribute:caller_id+' => '', + 'Class:ResponseTicket/Attribute:caller_email' => 'Email', + 'Class:ResponseTicket/Attribute:caller_email+' => '', + 'Class:ResponseTicket/Attribute:org_id' => 'Клиент', + 'Class:ResponseTicket/Attribute:org_id+' => '', + 'Class:ResponseTicket/Attribute:org_name' => 'Клиент', + 'Class:ResponseTicket/Attribute:org_name+' => '', + 'Class:ResponseTicket/Attribute:service_id' => 'Услуга', + 'Class:ResponseTicket/Attribute:service_id+' => '', + 'Class:ResponseTicket/Attribute:service_name' => 'Клиент', + 'Class:ResponseTicket/Attribute:service_name+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_id' => 'Элемент услуги', + 'Class:ResponseTicket/Attribute:servicesubcategory_id+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_name' => 'Название', + 'Class:ResponseTicket/Attribute:servicesubcategory_name+' => '', + 'Class:ResponseTicket/Attribute:product' => 'Продукт', + 'Class:ResponseTicket/Attribute:product+' => '', + 'Class:ResponseTicket/Attribute:impact' => 'Воздействие', + 'Class:ResponseTicket/Attribute:impact+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Департамент', + 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:2' => 'Услуга', + 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Персона', + 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', + 'Class:ResponseTicket/Attribute:urgency' => 'Срочность', + 'Class:ResponseTicket/Attribute:urgency+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Высокая', + 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Средняя', + 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Низкая', + 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', + 'Class:ResponseTicket/Attribute:priority' => 'Приоритет', + 'Class:ResponseTicket/Attribute:priority+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Высокий', + 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Средний', + 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Низкий', + 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', + 'Class:ResponseTicket/Attribute:workgroup_id' => 'Рабочая группа', + 'Class:ResponseTicket/Attribute:workgroup_id+' => '', + 'Class:ResponseTicket/Attribute:workgroup_name' => 'Рабочая группа', + 'Class:ResponseTicket/Attribute:workgroup_name+' => '', + 'Class:ResponseTicket/Attribute:agent_id' => 'Агент', + 'Class:ResponseTicket/Attribute:agent_id+' => '', + 'Class:ResponseTicket/Attribute:agent_name' => 'Агент', + 'Class:ResponseTicket/Attribute:agent_name+' => '', + 'Class:ResponseTicket/Attribute:agent_email' => 'email агента', + 'Class:ResponseTicket/Attribute:agent_email+' => '', + 'Class:ResponseTicket/Attribute:related_problem_id' => 'Связанная проблема', + 'Class:ResponseTicket/Attribute:related_problem_id+' => '', + 'Class:ResponseTicket/Attribute:related_problem_ref' => 'Ссылка', + 'Class:ResponseTicket/Attribute:related_problem_ref+' => '', + 'Class:ResponseTicket/Attribute:related_change_id' => 'Относящееся изменения', + 'Class:ResponseTicket/Attribute:related_change_id+' => '', + 'Class:ResponseTicket/Attribute:related_change_ref' => 'Относящееся изменения', + 'Class:ResponseTicket/Attribute:related_change_ref+' => '', + 'Class:ResponseTicket/Attribute:close_date' => 'Закрыто', + 'Class:ResponseTicket/Attribute:close_date+' => '', + 'Class:ResponseTicket/Attribute:last_update' => 'Последнее изменение', + 'Class:ResponseTicket/Attribute:last_update+' => '', + 'Class:ResponseTicket/Attribute:assignment_date' => 'Дата назначения', + 'Class:ResponseTicket/Attribute:assignment_date+' => '', + 'Class:ResponseTicket/Attribute:resolution_date' => 'Дата решения', + 'Class:ResponseTicket/Attribute:resolution_date+' => '', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline' => 'Срок эскалации TTO', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline' => 'Срок эскалации TTR', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:closure_deadline' => 'Срок закрытия', + 'Class:ResponseTicket/Attribute:closure_deadline+' => '', + 'Class:ResponseTicket/Attribute:resolution_code' => 'Код решения', + 'Class:ResponseTicket/Attribute:resolution_code+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce' => 'Не воспроизводится', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate' => 'Дубликатный тикет', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed' => 'Исправлен', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant' => 'Нерелавнтный', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant+' => '', + 'Class:ResponseTicket/Attribute:solution' => 'Решение', + 'Class:ResponseTicket/Attribute:solution+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction' => 'Удовлетворённость пользователя', + 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Польностью доволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => 'Польностью доволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Вполне доволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => 'Вполне доволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Недоволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => 'Недоволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Очень недоволен', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => 'Очень недоволен', + 'Class:ResponseTicket/Attribute:user_commment' => 'Коментарии пользователя', + 'Class:ResponseTicket/Attribute:user_commment+' => '', + 'Class:ResponseTicket/Stimulus:ev_assign' => 'Назначить', + 'Class:ResponseTicket/Stimulus:ev_assign+' => '', + 'Class:ResponseTicket/Stimulus:ev_reassign' => 'Переназначить', + 'Class:ResponseTicket/Stimulus:ev_reassign+' => '', + 'Class:ResponseTicket/Stimulus:ev_timeout' => 'Эскалировать', + 'Class:ResponseTicket/Stimulus:ev_timeout+' => '', + 'Class:ResponseTicket/Stimulus:ev_resolve' => 'Пометить как решённый', + 'Class:ResponseTicket/Stimulus:ev_resolve+' => '', + 'Class:ResponseTicket/Stimulus:ev_close' => 'Закрыт', + 'Class:ResponseTicket/Stimulus:ev_close+' => '', +)); + +?> From de4945bca6e07ed7aeb9734874bfd6bf63781665 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 20 Dec 2010 11:17:05 +0000 Subject: [PATCH 944/970] - Added the russian localization (thanks to Vladimir Shilov) SVN:trunk[1029] --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index 4f4cb6cebe..a21983afed 100644 --- a/readme.txt +++ b/readme.txt @@ -47,6 +47,7 @@ Everaldo Coelho and the Oxygen Team for their wonderful icons. The JQuery team and the all the jQuery plugins authors for developing such a powerful library. Phil Eddies for the numerous feedbacks provided, and the first implementation of CKEdit Marco Tulio for the Portuguese (Brazilian) translation +Vladimir Shilov for the Russian translation 2. INSTALLATION ============ From 6bc3a80c8c742aa6e09301385acd8d9fec436019 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 20 Dec 2010 11:28:50 +0000 Subject: [PATCH 945/970] - Small updates to the German localization SVN:trunk[1030] --- .../de.dict.itop-incident-mgmt.php | 4 ++-- .../itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/itop-incident-mgmt-1.0.0/de.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/de.dict.itop-incident-mgmt.php index 3db02a93ad..81e263373e 100644 --- a/modules/itop-incident-mgmt-1.0.0/de.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/de.dict.itop-incident-mgmt.php @@ -60,9 +60,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:Incident' => 'Incident', 'Class:Incident+' => '', - 'Class:Incident/Stimulus:ev_assign' => 'Zuteilen', + 'Class:Incident/Stimulus:ev_assign' => '"Zuweisen"', 'Class:Incident/Stimulus:ev_assign+' => '', - 'Class:Incident/Stimulus:ev_reassign' => 'Umverteilen', + 'Class:Incident/Stimulus:ev_reassign' => 'Neu zuweisen', 'Class:Incident/Stimulus:ev_reassign+' => '', 'Class:Incident/Stimulus:ev_timeout' => 'ev-Timeout', 'Class:Incident/Stimulus:ev_timeout+' => '', diff --git a/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php index d87cf63176..2ac966dd9a 100644 --- a/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php @@ -148,6 +148,8 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:CustomerContract/Attribute:provider_list+' => '', 'Class:CustomerContract/Attribute:sla_list' => 'SLAs', 'Class:CustomerContract/Attribute:sla_list+' => 'Liste der SLAs zu dem Vertrag', + 'Class:CustomerContract/Attribute:provider_list' => 'Zugrunde liegende Verträge', + 'Class:CustomerContract/Attribute:sla_list+' => '', )); // Class: lnkCustomerContractToProviderContract @@ -287,9 +289,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:Service/Attribute:document_list' => 'Dokumente', 'Class:Service/Attribute:document_list+' => 'Dokumente beigefügt zu dem Service', 'Class:Service/Attribute:contact_list' => 'Kontakte', - 'Class:Service/Attribute:contact_list+' => 'Kontakte haben eine Rolle für diesen Service', - 'Class:Service/Tab:Related_Contracts' => 'Dazugehörige Kontakte', - 'Class:Service/Tab:Related_Contracts+' => 'Kontakte, die diesen Vertrag unterschrieben haben', + 'Class:Service/Attribute:contact_list+' => 'Kontakte, die für diesen Service eine Rolle wahrnehmen', + 'Class:Service/Tab:Related_Contracts' => 'Dazugehörige Verträge', + 'Class:Service/Tab:Related_Contracts+' => 'Verträge, die für diesen Vertrag unterschrieben wurden', )); // From 19463b370c9947f27d6dd170362ae183b45c69f8 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 20 Dec 2010 11:30:16 +0000 Subject: [PATCH 946/970] - Updates to the Spanish localization SVN:trunk[1031] --- dictionaries/es_cr.dictionary.itop.ui.php | 861 +++++++++++----------- 1 file changed, 427 insertions(+), 434 deletions(-) diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 9c04ad7098..a873a82efb 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -304,505 +304,498 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:WelcomeMenuPage+' => 'Bienvenido a iTop', 'UI:WelcomeMenu:Title' => 'Bienvenido a iTop', - 'UI:WelcomeMenu:LeftBlock' => '

    iTop es un completo; portal IT funcioanl basado en código abierto (OpenSource).

    + 'UI:WelcomeMenu:LeftBlock' => '

    iTop es un completo; portal IT funcional basado en código abierto (OpenSource).

      Incluye:
    • Una CMDB competa (Configuration management database) para documentar y manejar el inverntario de TI..
    • Un módul de gestión de incidentes, para llevar la trazabilidad y comunicar los eventos que estan afectando IT.
    • -
    • Un módulo de gestion de cambio para planear y llevar la trazabilidad hechos al ambiente de TI.
    • +
    • Un módulo de gestion de cambio para planear y llevar la trazabilidad de cambios hechos al ambiente de TI.
    • Una base de conocimiento para acelerar la correción de incidentes.
    • Un moódulo de Cortes/Caídas para documentar todas las caídas planeadas o no y notificar a los contactods del caso.
    • Tableros de controles para rapidamente tener visión general del ambiente TI..
    -

    All the modules can be setup, step by step, indepently of each other.

    ', +

    Todos los modulos pueden ser configurados, paso a paso, individual e independientemente de los otros.

    ', - 'UI:WelcomeMenu:RightBlock' => '

    iTop is service provider oriented, it allows IT engineers to manage easily multiple customers or organizations. -

      iTop, delivers a feature-rich set of business processes that: -
    • Enhances IT management effectiveness
    • -
    • Drives IT operations performance
    • -
    • Improves customer satisfaction and provides executives with insights into business performance.
    • + 'UI:WelcomeMenu:RightBlock' => '

      iTop esta orientada a los proveedores de servicios, le permite a los Ingenieros de TI administrar facilmente multiples clientes y organizaciones. +

        iTop, provee un conjunto de funciones de procesos de negocio que: +
      • Mejora Enhances IT management effectiveness
      • +
      • Dirige el desempeño de la operaciones de TI
      • +
      • Incrementa la satisfaccion del cliente y provee a los ejecutivos con detalles del desempeño del negocio.

      -

      iTop is completely opened to be integrated within your current IT Management infrastructure.

      +

      iTop es completamente abierto para ser integrado con su actual infraestructura de Gestion de TI.

      -

        Adopting this new generation of IT Operational portal will help you to: -
      • Better manage a more and more complex IT environment.
      • -
      • Implement ITIL processes at your own pace.
      • -
      • Manage the most important asset of your IT: Documentation.
      • +
          Adoptar esta nueva generacion de portales de operaciones de TI le ayudara a: +
        • Mejorar gestion de entornos de TI mas y mas complejos.
        • +
        • Implementar los procesos de ITIL a su propio ritmo.
        • +
        • Administrar el bien mas importante de su TI: Documentacion.

        ', - 'UI:WelcomeMenu:MyCalls' => 'My requests', - 'UI:WelcomeMenu:MyIncidents' => 'Incidents assigned to me', - 'UI:AllOrganizations' => ' All Organizations ', - 'UI:YourSearch' => 'Your Search', - 'UI:LoggedAsMessage' => 'Logged in as %1$s', - 'UI:LoggedAsMessage+Admin' => 'Logged in as %1$s (Administrator)', - 'UI:Button:Logoff' => 'Log off', - 'UI:Button:GlobalSearch' => 'Search', - 'UI:Button:Search' => ' Search ', - 'UI:Button:Query' => ' Query ', + 'UI:WelcomeMenu:MyCalls' => 'Mis solicitudes', + 'UI:WelcomeMenu:MyIncidents' => 'Incidentes asignados a mi', + 'UI:AllOrganizations' => ' Todas las Organizaciones', + 'UI:YourSearch' => 'Su busqueda', + 'UI:LoggedAsMessage' => 'Conectado como %1$s', + 'UI:LoggedAsMessage+Admin' => 'Conectado como %1$s (Administrator)', + 'UI:Button:Logoff' => 'Cerrar sesion', + 'UI:Button:GlobalSearch' => 'Buscar', + 'UI:Button:Search' => ' Buscar ', + 'UI:Button:Query' => ' Consulta ', 'UI:Button:Ok' => 'Ok', - 'UI:Button:Cancel' => 'Cancel', - 'UI:Button:Apply' => 'Apply', - 'UI:Button:Back' => ' << Back ', - 'UI:Button:Next' => ' Next >> ', - 'UI:Button:Finish' => ' Finish ', - 'UI:Button:DoImport' => ' Run the Import ! ', - 'UI:Button:Done' => ' Done ', - 'UI:Button:SimulateImport' => ' Simulate the Import ', - 'UI:Button:Test' => 'Test!', - 'UI:Button:Evaluate' => ' Evaluate ', - 'UI:Button:AddObject' => ' Add... ', - 'UI:Button:BrowseObjects' => ' Browse... ', - 'UI:Button:Add' => ' Add ', - 'UI:Button:AddToList' => ' << Add ', - 'UI:Button:RemoveFromList' => ' Remove >> ', - 'UI:Button:FilterList' => ' Filter... ', - 'UI:Button:Create' => ' Create ', - 'UI:Button:Delete' => ' Delete ! ', - 'UI:Button:ChangePassword' => ' Change Password ', - 'UI:Button:ResetPassword' => ' Reset Password ', + 'UI:Button:Cancel' => 'Cancelar', + 'UI:Button:Apply' => 'Aplicar', + 'UI:Button:Back' => ' << Anterior ', + 'UI:Button:Next' => ' Siguiente >> ', + 'UI:Button:Finish' => ' Finalizar ', + 'UI:Button:DoImport' => ' Importar los datos ! ', + 'UI:Button:Done' => ' Listo ', + 'UI:Button:SimulateImport' => ' Simular la Importacion ', + 'UI:Button:Test' => 'Probar!', + 'UI:Button:Evaluate' => ' Evaluar ', + 'UI:Button:AddObject' => ' Agregar... ', + 'UI:Button:BrowseObjects' => ' Examinar... ', + 'UI:Button:Add' => ' agregar ', + 'UI:Button:AddToList' => ' << Agregar ', + 'UI:Button:RemoveFromList' => ' Remover >> ', + 'UI:Button:FilterList' => ' Filtrar... ', + 'UI:Button:Create' => ' Crear ', + 'UI:Button:Delete' => ' Borrar! ', + 'UI:Button:ChangePassword' => ' Cambiar Contraseña', + 'UI:Button:ResetPassword' => ' Restablecer Contraseña', - 'UI:SearchToggle' => 'Search', + 'UI:SearchToggle' => 'Buscar', 'UI:ClickToCreateNew' => 'Crear un nuevo %1$s', 'UI:SearchFor_Class' => 'Buscar %1$s objetos', 'UI:NoObjectToDisplay' => 'Ningún objeto para visualizar.', - 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template.', - 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template.', - 'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by is mandatory. Check the definition of the display template.', - 'UI:Error:InvalidGroupByFields' => 'Invalid list of fields to group by: "%1$s".', - 'UI:Error:UnsupportedStyleOfBlock' => 'Error: unsupported style of block: "%1$s".', - 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Incorrect link definition: the class of objects to manage: %1$s was not found as an external key in the class %2$s', - 'UI:Error:Object_Class_Id_NotFound' => 'Object: %1$s:%2$d not found.', - 'UI:Error:WizardCircularReferenceInDependencies' => 'Error: Circular reference in the dependencies between the fields, check the data model.', - 'UI:Error:UploadedFileTooBig' => 'Uploaded file is too big. (Max allowed size is %1$s. Check you PHP configuration for upload_max_filesize and post_max_size.', - 'UI:Error:UploadedFileTruncated.' => 'Uploaded file has been truncated !', - 'UI:Error:NoTmpDir' => 'The temporary directory is not defined.', - 'UI:Error:CannotWriteToTmp_Dir' => 'Unable to write the temporary file to the disk. upload_tmp_dir = "%1$s".', - 'UI:Error:UploadStoppedByExtension_FileName' => 'Upload stopped by extension. (Original file name = "%1$s").', - 'UI:Error:UploadFailedUnknownCause_Code' => 'File upload failed, unknown cause. (Error code = "%1$s").', + 'UI:Error:MandatoryTemplateParameter_object_id' => 'El parametro object_id es obligatorio cuando link_attr es especificado. Verifique la definicion de la plantilla de visualizacion.', + 'UI:Error:MandatoryTemplateParameter_target_attr' => 'El parametro target_attr es obligatorio cuando link_attr es especificado. Verifique la definicion de la plantilla de visualizacion.', + 'UI:Error:MandatoryTemplateParameter_group_by' => 'El parametro group_by es obligatorio. Verifique la definicion de la plantilla de visualizacion.', + 'UI:Error:InvalidGroupByFields' => 'La lista de campos para agrupar por: "%1$s" es invalida.', + 'UI:Error:UnsupportedStyleOfBlock' => 'Error: Estilo de bloque no soportado: "%1$s".', + 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Definicio de vinculo incorrecto: la clase de objeto a administrar : %1$s no fue encontrada como clave externa en la clase %2$s', + 'UI:Error:Object_Class_Id_NotFound' => 'No se encontro el objeto: %1$s:%2$d.', + 'UI:Error:WizardCircularReferenceInDependencies' => 'Error: Verifique el modelo de datos, Existen referencias cisculares en la dependencias entre los campos.', + 'UI:Error:UploadedFileTooBig' => 'archivo cargado es muy grande. (tamaño maximo permitido es de %1$s. Verifique su configuracion de PHP para upload_max_filesize.', + 'UI:Error:UploadedFileTruncated.' => 'El archivo cargado ha sido truncado!', + 'UI:Error:NoTmpDir' => 'El directorio temporal no ha sido definido.', + 'UI:Error:CannotWriteToTmp_Dir' => 'No fue posible escribir el archivo temporal al disco. upload_tmp_dir = "%1$s".', + 'UI:Error:UploadStoppedByExtension_FileName' => 'Carga de archivo interrumpida por la extension. (Nombre de archivo original = "%1$s").', + 'UI:Error:UploadFailedUnknownCause_Code' => 'Carga de archivo fallida, causa desconocida. (Codigo de error = "%1$s").', - 'UI:Error:1ParametersMissing' => 'Error: the following parameter must be specified for this operation: %1$s.', - 'UI:Error:2ParametersMissing' => 'Error: the following parameters must be specified for this operation: %1$s and %2$s.', - 'UI:Error:3ParametersMissing' => 'Error: the following parameters must be specified for this operation: %1$s, %2$s and %3$s.', - 'UI:Error:4ParametersMissing' => 'Error: the following parameters must be specified for this operation: %1$s, %2$s, %3$s and %4$s.', - 'UI:Error:IncorrectOQLQuery_Message' => 'Error: incorrect OQL query: %1$s', - 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'An error occured while running the query: %1$s', - 'UI:Error:ObjectAlreadyUpdated' => 'Error: the object has already been updated.', - 'UI:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated.', - 'UI:Error:ObjectsAlreadyDeleted' => 'Error: objects have already been deleted!', - 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'You are not allowed to perform a bulk delete of objects of class %1$s', - 'UI:Error:DeleteNotAllowedOn_Class' => 'You are not allowed to delete objects of class %1$s', - 'UI:Error:BulkModifyNotAllowedOn_Class' => 'You are not allowed to perform a bulk update of objects of class %1$s', - 'UI:Error:ObjectAlreadyCloned' => 'Error: the object has already been cloned!', - 'UI:Error:ObjectAlreadyCreated' => 'Error: the object has already been created!', - 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Error: invalid stimulus "%1$s" on object %2$s in state "%3$s".', + 'UI:Error:1ParametersMissing' => 'Error: El siguiente parametro debe ser especificado para esta operacion: %1$s.', + 'UI:Error:2ParametersMissing' => 'Error: Los siguientes parametros deben ser especificados para esta operacion: %1$s and %2$s.', + 'UI:Error:3ParametersMissing' => 'Error: Los siguientes parametros deben ser especificados para esta operacion: %1$s, %2$s and %3$s.', + 'UI:Error:4ParametersMissing' => 'Error: Los siguientes parametros deben ser especificados para esta operacion: %1$s, %2$s, %3$s and %4$s.', + 'UI:Error:IncorrectOQLQuery_Message' => 'Error: Consulta OQL incorrecta: %1$s', + 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Se ha producido un error al ejecutar la consulta: %1$s', + 'UI:Error:ObjectAlreadyUpdated' => 'Error: el objeta ha sido previamente actualizado.', + 'UI:Error:ObjectCannotBeUpdated' => 'Error: el objeto no puede ser actualizado.', + 'UI:Error:ObjectsAlreadyDeleted' => 'Error: los objetos ya han sido borrados!', + 'UI:Error:BulkDeleteNotAllowedOn_Class' => 'No esta autorizado a borrar un lote de de objetos de la clase %1$s', + 'UI:Error:DeleteNotAllowedOn_Class' => 'No esta autorizado a borrar objetos del la clase %1$s', + 'UI:Error:BulkModifyNotAllowedOn_Class' => 'No esta autorizado a actualizar un lote de de objetos de la clase %1$s', + 'UI:Error:ObjectAlreadyCloned' => 'Error: el objeto ha sido previamente duplicado!', + 'UI:Error:ObjectAlreadyCreated' => 'Error: el objeto ha sido previamente creado!', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Error: estimulo invalido "%1$s" en objeto %2$s en estado "%3$s".', - 'UI:GroupBy:Count' => 'Count', - 'UI:GroupBy:Count+' => 'Number of elements', - 'UI:CountOfObjects' => '%1$d objects matching the criteria.', - 'UI_CountOfObjectsShort' => '%1$d objects.', - 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', - 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', - 'UI:HistoryTab' => 'History', - 'UI:History:BulkImports' => 'History', - 'UI:History:BulkImports+' => 'List of CSV imports (last first)', - 'UI:History:BulkImportDetails' => 'Changes resulting from the CSV import performed on %1$s (by %2$s)', - 'UI:History:Date' => 'Date', - 'UI:History:Date+' => 'Date of the change', - 'UI:History:User' => 'User', - 'UI:History:User+' => 'User who made the change', - 'UI:History:Changes' => 'Changes', - 'UI:History:Changes+' => 'Changes made to the object', + 'UI:GroupBy:Count' => 'Cuenta', + 'UI:GroupBy:Count+' => 'Numero de elementoso', + 'UI:CountOfObjects' => '%1$d objetos cumplen criterio.', + 'UI_CountOfObjectsShort' => '%1$d objetos.', + 'UI:NoObject_Class_ToDisplay' => 'No hay %1$s para mostrar', + 'UI:History:LastModified_On_By' => 'Ultima modificacion el %1$s por %2$s.', + 'UI:HistoryTab' => 'Historia', + 'UI:History:Date' => 'Fecha', + 'UI:History:Date+' => 'Fecha del Cambio', + 'UI:History:User' => 'Usuario', + 'UI:History:User+' => 'Usuario que hizo el cambio', + 'UI:History:Changes' => 'Cambios', + 'UI:History:Changes+' => 'Chambios hechos al objeto', + 'UI:Loading' => 'Cargando...', + 'UI:Menu:Actions' => 'Acciones', + 'UI:Menu:New' => 'Nuevo...', 'UI:History:StatsCreations' => 'Created', 'UI:History:StatsCreations+' => 'Count of objects created', 'UI:History:StatsModifs' => 'Modified', 'UI:History:StatsModifs+' => 'Count of objects modified', 'UI:History:StatsDeletes' => 'Deleted', 'UI:History:StatsDeletes+' => 'Count of objects deleted', - 'UI:Loading' => 'Loading...', - 'UI:Menu:Actions' => 'Actions', - 'UI:Menu:New' => 'New...', - 'UI:Menu:Add' => 'Add...', - 'UI:Menu:Manage' => 'Manage...', 'UI:Menu:EMail' => 'eMail', - 'UI:Menu:CSVExport' => 'CSV Export', - 'UI:Menu:Modify' => 'Modify...', - 'UI:Menu:Delete' => 'Delete...', - 'UI:Menu:Manage' => 'Manage...', - 'UI:Menu:BulkDelete' => 'Delete...', - 'UI:UndefinedObject' => 'undefined', - 'UI:Document:OpenInNewWindow:Download' => 'Open in new window: %1$s, Download: %2$s', - 'UI:SelectAllToggle+' => 'Select / Deselect All', - 'UI:TruncatedResults' => '%1$d objects displayed out of %2$d', - 'UI:DisplayAll' => 'Display All', - 'UI:CountOfResults' => '%1$d object(s)', - 'UI:ChangesLogTitle' => 'Changes log (%1$d):', - 'UI:EmptyChangesLogTitle' => 'Changes log is empty', + 'UI:Menu:CSVExport' => 'exportar a CSV', + 'UI:Menu:Modify' => 'Modificar...', + 'UI:Menu:Delete' => 'Borrar...', + 'UI:Menu:Manage' => 'Administrar...', + 'UI:Menu:BulkDelete' => 'Borrar...', + 'UI:UndefinedObject' => 'indefinido', + 'UI:Document:OpenInNewWindow:Download' => 'abrir en nueva ventana: %1$s, Descargar: %2$s', + 'UI:SelectAllToggle+' => 'Seleccionar / Deseleccionar Todo', + 'UI:TruncatedResults' => 'Mostrando %1$d objetos de %2$d', + 'UI:DisplayAll' => 'Mostrar todo', + 'UI:CountOfResults' => '%1$d objeto(s)', + 'UI:ChangesLogTitle' => 'Registro de cambios (%1$d):', + 'UI:EmptyChangesLogTitle' => 'Registro de cambios esta vacio', 'UI:SearchFor_Class_Objects' => 'Buscar %1$s objetos', - 'UI:OQLQueryBuilderTitle' => 'OQL Query Builder', - 'UI:OQLQueryTab' => 'OQL Query', - 'UI:SimpleSearchTab' => 'Simple Search', - 'UI:Details+' => 'Details', - 'UI:SearchValue:Any' => '* Any *', - 'UI:SearchValue:Mixed' => '* mixed *', - 'UI:SelectOne' => '-- select one --', - 'UI:Login:Welcome' => 'Welcome to iTop!', - 'UI:Login:IncorrectLoginPassword' => 'Incorrect login/password, please try again.', - 'UI:Login:IdentifyYourself' => 'Identify yourself before continuing', - 'UI:Login:UserNamePrompt' => 'User Name', - 'UI:Login:PasswordPrompt' => 'Password', - 'UI:Login:ChangeYourPassword' => 'Change Your Password', - 'UI:Login:OldPasswordPrompt' => 'Old password', - 'UI:Login:NewPasswordPrompt' => 'New password', - 'UI:Login:RetypeNewPasswordPrompt' => 'Retype new password', - 'UI:Login:IncorrectOldPassword' => 'Error: the old password is incorrect', - 'UI:LogOffMenu' => 'Log off', - 'UI:ChangePwdMenu' => 'Change Password...', + 'UI:OQLQueryBuilderTitle' => 'Constructor de consultas OQL', + 'UI:OQLQueryTab' => 'Consulta OQL', + 'UI:SimpleSearchTab' => 'Busqueda simple', + 'UI:Details+' => 'Detalles', + 'UI:SearchValue:Any' => '* Cualquiera *', + 'UI:SearchValue:Mixed' => '* mezclado *', + 'UI:SelectOne' => '-- Seleccione uno --', + 'UI:Login:Welcome' => 'Bienvenido a iTop!', + 'UI:Login:IncorrectLoginPassword' => 'Usuario/Contraseña incorrecto, por favor intente otra vez.', + 'UI:Login:IdentifyYourself' => 'Identifiquese antes de continuar', + 'UI:Login:UserNamePrompt' => 'Nombre de Usuario', + 'UI:Login:PasswordPrompt' => 'Contraseña', + 'UI:Login:ChangeYourPassword' => 'Cambien su Contraseña', + 'UI:Login:OldPasswordPrompt' => 'Contraseña Anterior', + 'UI:Login:NewPasswordPrompt' => 'Contraseña Nueva', + 'UI:Login:RetypeNewPasswordPrompt' => 'Reintroduzca Nueva contraseña', + 'UI:Login:IncorrectOldPassword' => 'Error: la contraseña anterior es incorrecta', + 'UI:LogOffMenu' => 'Cerrar sesion', + 'UI:ChangePwdMenu' => 'Cambiar Contraseña...', 'UI:AccessRO-All' => 'iTop is read-only', 'UI:AccessRO-Users' => 'iTop is read-only for end-users', - 'UI:Login:RetypePwdDoesNotMatch' => 'New password and retyped new password do not match !', - 'UI:Button:Login' => 'Enter iTop', - 'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.', - 'UI:Login:Error:AccessAdmin' => 'Access restricted to people having administrator privileges. Please, contact an iTop administrator.', - 'UI:CSVImport:MappingSelectOne' => '-- select one --', - 'UI:CSVImport:MappingNotApplicable' => '-- ignore this field --', - 'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!', - 'UI:Title:DataPreview' => 'Data Preview', - 'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: The data contains only one column. Did you select the appropriate separator character?', - 'UI:CSVImport:FieldName' => 'Field %1$d', - 'UI:CSVImport:DataLine1' => 'Data Line 1', - 'UI:CSVImport:DataLine2' => 'Data Line 2', - 'UI:CSVImport:idField' => 'id (Primary Key)', - 'UI:Title:BulkImport' => 'iTop - Bulk import', - 'UI:Title:BulkImport+' => 'CSV Import Wizard', - 'UI:CSVImport:ClassesSelectOne' => '-- select one --', - 'UI:CSVImport:ErrorExtendedAttCode' => 'Internal error: "%1$s" is an incorrect code because "%2$s" is NOT an external key of the class "%3$s"', - 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objects(s) will stay unchanged.', - 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objects(s) will be modified.', - 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objects(s) will be added.', - 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objects(s) will have errors.', + 'UI:Login:Error:AccessRestricted' => 'El acceso a iTop esta restringido. Por favor contacte al administrador de iTop.', + 'UI:Login:Error:AccessAdmin' => 'Acceso restringido a usuarios con privilegio de administrador. Por favor contacte al administrador de iTop.', + 'UI:CSVImport:MappingSelectOne' => '-- seleccione uno --', + 'UI:CSVImport:MappingNotApplicable' => '-- ignore este campo --', + 'UI:CSVImport:NoData' => 'Conjunto de datos vacio..., pro favor provea alguna data!', + 'UI:Title:DataPreview' => 'Vista previa de datos', + 'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: La data solo contiene una columna. Selecciono el separador de campos adecuado?', + 'UI:CSVImport:FieldName' => 'Campo %1$d', + 'UI:CSVImport:DataLine1' => 'Linea de datos 1', + 'UI:CSVImport:DataLine2' => 'Linea de datos 2', + 'UI:CSVImport:idField' => 'id (Clave primaria)', + 'UI:Title:BulkImport' => 'iTop - Importacion por lotes', + 'UI:Title:BulkImport+' => 'Asistente de importar CSV', + 'UI:CSVImport:ClassesSelectOne' => '-- seleccione uno --', + 'UI:CSVImport:ErrorExtendedAttCode' => 'error interno: "%1$s" es un codigo incorrecto debido a que "%2$s" NO es una clave externa de la clase "%3$s"', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d objeto(s) permanecera sin cambio.', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d objeto(s) sera modificado.', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d objeto(s) sera agregado.', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d objeto(s) tendra error.', 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d objects(s) remained unchanged.', - 'UI:CSVImport:ObjectsWereModified' => '%1$d objects(s) were modified.', - 'UI:CSVImport:ObjectsWereAdded' => '%1$d objects(s) were added.', - 'UI:CSVImport:ObjectsHadErrors' => '%1$d objects(s) had errors.', - 'UI:Title:CSVImportStep2' => 'Step 2 of 5: CSV data options', - 'UI:Title:CSVImportStep3' => 'Step 3 of 5: Data mapping', - 'UI:Title:CSVImportStep4' => 'Step 4 of 5: Import simulation', - 'UI:Title:CSVImportStep5' => 'Step 5 of 5: Import completed', - 'UI:CSVImport:LinesNotImported' => 'Lines that could not be loaded:', - 'UI:CSVImport:LinesNotImported+' => 'The following lines have not been imported because they contain errors', - 'UI:CSVImport:SeparatorComma+' => ', (comma)', - 'UI:CSVImport:SeparatorSemicolon+' => '; (semicolon)', - 'UI:CSVImport:SeparatorTab+' => 'tab', - 'UI:CSVImport:SeparatorOther' => 'other:', - 'UI:CSVImport:QualifierDoubleQuote+' => '" (double quote)', - 'UI:CSVImport:QualifierSimpleQuote+' => '\' (simple quote)', - 'UI:CSVImport:QualifierOther' => 'other:', - 'UI:CSVImport:TreatFirstLineAsHeader' => 'Treat the first line as a header (column names)', - 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'Skip %1$s line(s) at the beginning of the file', - 'UI:CSVImport:CSVDataPreview' => 'CSV Data Preview', - 'UI:CSVImport:SelectFile' => 'Select the file to import:', - 'UI:CSVImport:Tab:LoadFromFile' => 'Load from a file', - 'UI:CSVImport:Tab:CopyPaste' => 'Copy and paste data', - 'UI:CSVImport:Tab:Templates' => 'Templates', - 'UI:CSVImport:PasteData' => 'Paste the data to import:', - 'UI:CSVImport:PickClassForTemplate' => 'Pick the template to download: ', - 'UI:CSVImport:SeparatorCharacter' => 'Separator character:', - 'UI:CSVImport:TextQualifierCharacter' => 'Text qualifier character', - 'UI:CSVImport:CommentsAndHeader' => 'Comments and header', - 'UI:CSVImport:SelectClass' => 'Select the class to import:', - 'UI:CSVImport:AdvancedMode' => 'Advanced mode', - 'UI:CSVImport:AdvancedMode+' => 'In advanced mode the "id" (primary key) of the objects can be used to update and rename objects.' . - 'However the column "id" (if present) can only be used as a search criteria and can not be combined with any other search criteria.', - 'UI:CSVImport:SelectAClassFirst' => 'To configure the mapping, select a class first.', - 'UI:CSVImport:HeaderFields' => 'Fields', - 'UI:CSVImport:HeaderMappings' => 'Mappings', - 'UI:CSVImport:HeaderSearch' => 'Search?', - 'UI:CSVImport:AlertIncompleteMapping' => 'Please select a mapping for every field.', - 'UI:CSVImport:AlertNoSearchCriteria' => 'Please select at least one search criteria', + 'UI:CSVImport:ObjectsWereModified' => '%1$d objeto(s) sera modificado.', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d objeto(s) fue agregado.', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d objeto(s) tuvo errores.', + 'UI:Title:CSVImportStep2' => 'Paso 2 de 5: opciones de datos CSV', + 'UI:Title:CSVImportStep3' => 'Paso 3 de 5: mapeo de datos', + 'UI:Title:CSVImportStep4' => 'Paso 4 de 5: simular la importacion', + 'UI:Title:CSVImportStep5' => 'Paso 5 de 5: importacion completada', + 'UI:CSVImport:LinesNotImported' => 'Lineas que no pudieron ser cargadas:', + 'UI:CSVImport:LinesNotImported+' => 'Las siguientes lineas no pudieron ser importadas porque contienen errores', + 'UI:CSVImport:SeparatorComma+' => ', (coma)', + 'UI:CSVImport:SeparatorSemicolon+' => '; (punto y coma)', + 'UI:CSVImport:SeparatorTab+' => 'tabulador', + 'UI:CSVImport:SeparatorOther' => 'otro:', + 'UI:CSVImport:QualifierDoubleQuote+' => '" (comilla doble)', + 'UI:CSVImport:QualifierSimpleQuote+' => '\' (comilla simple)', + 'UI:CSVImport:QualifierOther' => 'otro:', + 'UI:CSVImport:TreatFirstLineAsHeader' => 'Use la primera linea como encabezado de columna(nombre de columnas))', + 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'Omitir %1$s linea(s) al inicio de el archivo', + 'UI:CSVImport:CSVDataPreview' => 'Vista previa de los datos CSV', + 'UI:CSVImport:SelectFile' => 'Seleccione el archivo a importar:', + 'UI:CSVImport:Tab:LoadFromFile' => 'Cargar desde archivo', + 'UI:CSVImport:Tab:CopyPaste' => 'Copiar y pegar data', + 'UI:CSVImport:Tab:Templates' => 'Plantillas', + 'UI:CSVImport:PasteData' => 'Pegue la data a importar:', + 'UI:CSVImport:PickClassForTemplate' => 'seleccione la plantilla a descargar: ', + 'UI:CSVImport:SeparatorCharacter' => 'Caracter separador:', + 'UI:CSVImport:TextQualifierCharacter' => 'Caracter para calificar como texto', + 'UI:CSVImport:CommentsAndHeader' => 'Comentarios y encabezado', + 'UI:CSVImport:SelectClass' => 'Seleccione la clase a importar:', + 'UI:CSVImport:AdvancedMode' => 'Modo avanzado', + 'UI:CSVImport:AdvancedMode+' => 'En modo avanzado el "id" (clave primaria) de los objetos puede ser usado para actualizar y renombrar objetos.' . + 'Sin embargo, la columna "id" (si esta presente) solo puede ser usado como criterio de busqueda y no puede ser combinado con ningun otro criterio de busqueda.', + 'UI:CSVImport:SelectAClassFirst' => 'Para configurar el mapeo, primero seleccione un clase.', + 'UI:CSVImport:HeaderFields' => 'Campos', + 'UI:CSVImport:HeaderMappings' => 'Mapeo', + 'UI:CSVImport:HeaderSearch' => 'Buscar?', + 'UI:CSVImport:AlertIncompleteMapping' => 'Por favor seleccione un mapeo para cada categoria.', + 'UI:CSVImport:AlertNoSearchCriteria' => 'Por favor seleccione al menos un criterio de busqueda', - 'UI:UniversalSearchTitle' => 'iTop - Universal Search', + 'UI:UniversalSearchTitle' => 'iTop - Busqueda Universal', 'UI:UniversalSearch:Error' => 'Error: %1$s', - 'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ', + 'UI:UniversalSearch:LabelSelectTheClass' => 'Seleccione la clase a buscar: ', - 'UI:Audit:Title' => 'iTop - CMDB Audit', - 'UI:Audit:InteractiveAudit' => 'Interactive Audit', - 'UI:Audit:HeaderAuditRule' => 'Audit Rule', - 'UI:Audit:HeaderNbObjects' => '# Objects', - 'UI:Audit:HeaderNbErrors' => '# Errors', + 'UI:Audit:Title' => 'iTop - Auditoria a CMDB', + 'UI:Audit:InteractiveAudit' => 'Auditoria Interactiva', + 'UI:Audit:HeaderAuditRule' => 'Reglas de Auditoria', + 'UI:Audit:HeaderNbObjects' => '# Objetos', + 'UI:Audit:HeaderNbErrors' => '# Errores', 'UI:Audit:PercentageOk' => '% Ok', - 'UI:RunQuery:Title' => 'iTop - OQL Query Evaluation', - 'UI:RunQuery:QueryExamples' => 'Query Examples', - 'UI:RunQuery:HeaderPurpose' => 'Purpose', - 'UI:RunQuery:HeaderPurpose+' => 'Explanation about the query', - 'UI:RunQuery:HeaderOQLExpression' => 'OQL Expression', - 'UI:RunQuery:HeaderOQLExpression+' => 'The query in OQL syntax', - 'UI:RunQuery:ExpressionToEvaluate' => 'Expression to evaluate: ', - 'UI:RunQuery:MoreInfo' => 'More information about the query: ', - 'UI:RunQuery:DevelopedQuery' => 'Redevelopped query expression: ', - 'UI:RunQuery:SerializedFilter' => 'Serialized filter: ', - 'UI:RunQuery:Error' => 'An error occured while running the query: %1$s', + 'UI:RunQuery:Title' => 'iTop - Evaluacion de consultas OQL', + 'UI:RunQuery:QueryExamples' => 'Explorador de Consultas', + 'UI:RunQuery:HeaderPurpose' => 'Proposito', + 'UI:RunQuery:HeaderPurpose+' => 'Explicacion acerca de la consulta', + 'UI:RunQuery:HeaderOQLExpression' => 'Expresion OQL', + 'UI:RunQuery:HeaderOQLExpression+' => 'La consulta en syntaxis OQL', + 'UI:RunQuery:ExpressionToEvaluate' => 'Expresion a evaluar: ', + 'UI:RunQuery:MoreInfo' => 'Mas informacion acerca de la consulta: ', + 'UI:RunQuery:DevelopedQuery' => 'Expresion de consulta rediseñada: ', + 'UI:RunQuery:SerializedFilter' => 'Filtro de serializacion: ', + 'UI:RunQuery:Error' => 'Ha ocurrido un error al ejecutar la consulta: %1$s', - 'UI:Schema:Title' => 'iTop objects schema', - 'UI:Schema:CategoryMenuItem' => 'Category %1$s', - 'UI:Schema:Relationships' => 'Relationships', - 'UI:Schema:AbstractClass' => 'Abstract class: no object from this class can be instantiated.', - 'UI:Schema:NonAbstractClass' => 'Non abstract class: objects from this class can be instantiated.', - 'UI:Schema:ClassHierarchyTitle' => 'Class hierarchy', - 'UI:Schema:AllClasses' => 'All classes', - 'UI:Schema:ExternalKey_To' => 'External key to %1$s', - 'UI:Schema:Columns_Description' => 'Columns: %1$s', - 'UI:Schema:Default_Description' => 'Default: "%1$s"', - 'UI:Schema:NullAllowed' => 'Null Allowed', - 'UI:Schema:NullNotAllowed' => 'Null NOT Allowed', - 'UI:Schema:Attributes' => 'Attributes', - 'UI:Schema:AttributeCode' => 'Attribute Code', - 'UI:Schema:AttributeCode+' => 'Internal code of the attribute', - 'UI:Schema:Label' => 'Label', - 'UI:Schema:Label+' => 'Label of the attribute', - 'UI:Schema:Type' => 'Type', + 'UI:Schema:Title' => 'Esquema de objetos iTop', + 'UI:Schema:CategoryMenuItem' => 'Categoria %1$s', + 'UI:Schema:Relationships' => 'Relaciones', + 'UI:Schema:AbstractClass' => 'Clase abstracta: ningun objeto de esta clase puede ser representado.', + 'UI:Schema:NonAbstractClass' => 'Clase no abstracta: objetos de esta clase pueden ser representados.', + 'UI:Schema:ClassHierarchyTitle' => 'Jerarquia de clases', + 'UI:Schema:AllClasses' => 'Todas las clases', + 'UI:Schema:ExternalKey_To' => 'clave externa a %1$s', + 'UI:Schema:Columns_Description' => 'Columnas: %1$s', + 'UI:Schema:Default_Description' => 'Predeterminar: "%1$s"', + 'UI:Schema:NullAllowed' => 'Permite Null', + 'UI:Schema:NullNotAllowed' => 'NO permite Null', + 'UI:Schema:Attributes' => 'Atributos', + 'UI:Schema:AttributeCode' => 'Codigo de Atributo', + 'UI:Schema:AttributeCode+' => 'Codigo interno del atributo', + 'UI:Schema:Label' => 'Etiqueta', + 'UI:Schema:Label+' => 'Etiqueta del atributo', + 'UI:Schema:Type' => 'Tipo', - 'UI:Schema:Type+' => 'Data type of the attribute', - 'UI:Schema:Origin' => 'Origin', - 'UI:Schema:Origin+' => 'The base class in which this attribute is defined', - 'UI:Schema:Description' => 'Description', - 'UI:Schema:Description+' => 'Description of the attribute', - 'UI:Schema:AllowedValues' => 'Allowed values', - 'UI:Schema:AllowedValues+' => 'Restrictions on the possible values for this attribute', - 'UI:Schema:MoreInfo' => 'More info', - 'UI:Schema:MoreInfo+' => 'More information about the field defined in the database', - 'UI:Schema:SearchCriteria' => 'Search criteria', - 'UI:Schema:FilterCode' => 'Filter code', - 'UI:Schema:FilterCode+' => 'Code of this search criteria', - 'UI:Schema:FilterDescription' => 'Description', - 'UI:Schema:FilterDescription+' => 'Description of this search criteria', - 'UI:Schema:AvailOperators' => 'Available operators', - 'UI:Schema:AvailOperators+' => 'Possible operators for this search criteria', - 'UI:Schema:ChildClasses' => 'Child classes', - 'UI:Schema:ReferencingClasses' => 'Referencing classes', - 'UI:Schema:RelatedClasses' => 'Related classes', - 'UI:Schema:LifeCycle' => 'Life cycle', - 'UI:Schema:Triggers' => 'Triggers', - 'UI:Schema:Relation_Code_Description' => 'Relation %1$s (%2$s)', - 'UI:Schema:RelationDown_Description' => 'Down: %1$s', - 'UI:Schema:RelationUp_Description' => 'Up: %1$s', - 'UI:Schema:RelationPropagates' => '%1$s: propagate to %2$d levels, query: %3$s', - 'UI:Schema:RelationDoesNotPropagate' => '%1$s: does not propagates (%2$d levels), query: %3$s', - 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s is referenced by the class %2$s via the field %3$s', - 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s is linked to %2$s via %3$s::%4$s', - 'UI:Schema:Links:1-n' => 'Classes pointing to %1$s (1:n links):', - 'UI:Schema:Links:n-n' => 'Classes linked to %1$s (n:n links):', - 'UI:Schema:Links:All' => 'Graph of all related classes', - 'UI:Schema:NoLifeCyle' => 'There is no life cycle defined for this class.', - 'UI:Schema:LifeCycleTransitions' => 'Transitions', - 'UI:Schema:LifeCyleAttributeOptions' => 'Attribute options', - 'UI:Schema:LifeCycleHiddenAttribute' => 'Hidden', - 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Read-only', - 'UI:Schema:LifeCycleMandatoryAttribute' => 'Mandatory', - 'UI:Schema:LifeCycleAttributeMustChange' => 'Must change', - 'UI:Schema:LifeCycleAttributeMustPrompt' => 'User will be prompted to change the value', - 'UI:Schema:LifeCycleEmptyList' => 'empty list', + 'UI:Schema:Type+' => 'Tipo de dato del atributo', + 'UI:Schema:Origin' => 'Origen', + 'UI:Schema:Origin+' => 'La clase base en donde esta definido este atributo', + 'UI:Schema:Description' => 'Descripcion', + 'UI:Schema:Description+' => 'Descripcion del atributo', + 'UI:Schema:AllowedValues' => 'Valores permitidos', + 'UI:Schema:AllowedValues+' => 'Restricciones en los posibles valores para este atributo', + 'UI:Schema:MoreInfo' => 'Mas informacion', + 'UI:Schema:MoreInfo+' => 'Mas informacion acerca del campo definido en la base de datos', + 'UI:Schema:SearchCriteria' => 'Criterio de busqueda', + 'UI:Schema:FilterCode' => 'Codigo de filtro', + 'UI:Schema:FilterCode+' => 'Codigo de este criterio de busqueda', + 'UI:Schema:FilterDescription' => 'Descripcion', + 'UI:Schema:FilterDescription+' => 'Descripcion de este criterio de busqueda', + 'UI:Schema:AvailOperators' => 'Operadores disponibles', + 'UI:Schema:AvailOperators+' => 'Operadores posibles para este criterio de busqueda', + 'UI:Schema:ChildClasses' => 'Clases menores', + 'UI:Schema:ReferencingClasses' => 'Clases de referencia', + 'UI:Schema:RelatedClasses' => 'Clases relacionadas', + 'UI:Schema:LifeCycle' => 'Ciclo de vida', + 'UI:Schema:Triggers' => 'Gatillos', + 'UI:Schema:Relation_Code_Description' => 'Relacion %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Abajo: %1$s', + 'UI:Schema:RelationUp_Description' => 'Arriba: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: propagar a %2$d niveles, consulta: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: no se propaga(%2$d nivel), consulta: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s esta referenciado por la clase %2$s a travez de el campo %3$s', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s esta vinculado a %2$s a travez de %3$s::%4$s', + 'UI:Schema:Links:1-n' => 'Clases apuntando a %1$s (1:n enlaces):', + 'UI:Schema:Links:n-n' => 'Clases apuntando a %1$s (n:n enlaces):', + 'UI:Schema:Links:All' => 'Grafico de todos los casos relacionados', + 'UI:Schema:NoLifeCyle' => 'No hay ciclo de vida definido para esta clase.', + 'UI:Schema:LifeCycleTransitions' => 'Transiciones', + 'UI:Schema:LifeCyleAttributeOptions' => 'Opciones del atributo', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Oculto', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Solo-lectrura', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Mandatorio', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Debe cambiar', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Se le pedira al usuario que cambie el valor', + 'UI:Schema:LifeCycleEmptyList' => 'lista vacia', - 'UI:LinksWidget:Autocomplete+' => 'Type the first 3 characters...', - 'UI:Combo:SelectValue' => '--- select a value ---', - 'UI:Label:SelectedObjects' => 'Selected objects: ', - 'UI:Label:AvailableObjects' => 'Available objects: ', - 'UI:Link_Class_Attributes' => '%1$s attributes', - 'UI:SelectAllToggle+' => 'Select All / Deselect All', - 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Add %1$s objects linked with %2$s: %3$s', - 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Manage %1$s objects linked with %2$s: %3$s', - 'UI:AddLinkedObjectsOf_Class' => 'Add %1$ss...', - 'UI:RemoveLinkedObjectsOf_Class' => 'Remove selected objects', - 'UI:Message:EmptyList:UseAdd' => 'The list is empty, use the "Add..." button to add elements.', - 'UI:Message:EmptyList:UseSearchForm' => 'Use the search form above to search for objects to be added.', + 'UI:LinksWidget:Autocomplete+' => 'Escriba los primeros 3 caracteres...', + 'UI:Combo:SelectValue' => '--- seleccione un valor ---', + 'UI:Label:SelectedObjects' => 'Objetos seleccionados: ', + 'UI:Label:AvailableObjects' => 'Objetos disponibles: ', + 'UI:Link_Class_Attributes' => '%1$s atributos', + 'UI:SelectAllToggle+' => 'Seleccionar todo / Deseleccionar todo', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => 'Agregar %1$s objetos vinculados con %2$s: %3$s', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => 'Administrar %1$s objetos vinculados con %2$s: %3$s', + 'UI:AddLinkedObjectsOf_Class' => 'Agregar %1$ss...', + 'UI:RemoveLinkedObjectsOf_Class' => 'Eliminar los objetos seleccionados', + 'UI:Message:EmptyList:UseAdd' => 'La lista esta vaica, use el boton Agregar... para agregar elementos.', + 'UI:Message:EmptyList:UseSearchForm' => 'Use la forma arriba para buscar objetos a ser agregados.', - 'UI:Wizard:FinalStepTitle' => 'Final step: confirmation', - 'UI:Title:DeletionOf_Object' => 'Deletion of %1$s', - 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Bulk deletion of %1$d objects of class %2$s', - 'UI:Delete:NotAllowedToDelete' => 'You are not allowed to delete this object', - 'UI:Delete:NotAllowedToUpdate_Fields' => 'You are not allowed to update the following field(s): %1$s', - 'UI:Error:NotEnoughRightsToDelete' => 'This object could not be deleted because the current user do not have sufficient rights', - 'UI:Error:CannotDeleteBecauseOfDepencies' => 'This object could not be deleted because some manual operations must be performed prior to that', - 'UI:Archive_User_OnBehalfOf_User' => '%1$s on behalf of %2$s', - 'UI:Delete:AutomaticallyDeleted' => 'automatically deleted', - 'UI:Delete:AutomaticResetOf_Fields' => 'automatic reset of field(s): %1$s', - 'UI:Delete:CleaningUpRefencesTo_Object' => 'Cleaning up all references to %1$s...', - 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Cleaning up all references to %1$d objects of class %2$s...', - 'UI:Delete:Done+' => 'What was done...', - 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s deleted.', - 'UI:Delete:ConfirmDeletionOf_Name' => 'Deletion of %1$s', - 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Deletion of %1$d objects of class %2$s', - 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => 'Should be automaticaly deleted, but you are not allowed to do so', - 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => 'Must be deleted manually - but you are not allowed to delete this object, please contact your application admin', - 'UI:Delete:WillBeDeletedAutomatically' => 'Will be automaticaly deleted', - 'UI:Delete:MustBeDeletedManually' => 'Must be deleted manually', - 'UI:Delete:CannotUpdateBecause_Issue' => 'Should be automatically updated, but: %1$s', - 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'will be automaticaly updated (reset: %1$s)', - 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objects/links are referencing %2$s', - 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objects/links are referencing some of the objects to be deleted', - 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'To ensure Database integrity, any reference should be further eliminated', - 'UI:Delete:Consequence+' => 'What will be done', - 'UI:Delete:SorryDeletionNotAllowed' => 'Sorry, you are not allowed to delete this object, see the detailed explanations above', - 'UI:Delete:PleaseDoTheManualOperations' => 'Please perform the manual operations listed above prior to requesting the deletion of this object', - 'UI:Delect:Confirm_Object' => 'Please confirm that you want to delete %1$s.', - 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Please confirm that you want to delete the following %1$d objects of class %2$s.', - 'UI:WelcomeToITop' => 'Welcome to iTop', - 'UI:DetailsPageTitle' => 'iTop - %1$s - %2$s details', + 'UI:Wizard:FinalStepTitle' => 'Paso final: Confirmacion', + 'UI:Title:DeletionOf_Object' => 'Borrado de %1$s', + 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => 'Borrado por lote de %1$d objetos de la clase %2$s', + 'UI:Delete:NotAllowedToDelete' => 'No esta autorizado para borrar este objeto', + 'UI:Delete:NotAllowedToUpdate_Fields' => 'No esta autorizado para actualizar el siguiente campo(s): %1$s', + 'UI:Error:NotEnoughRightsToDelete' => 'Este objeto no pudo ser borrado porque el usuario actual no posee suficientes permisos', + 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Este objeto no pudo ser borrado porque algunas operaciones manuales deben ser ejecutadas antes de eso', + 'UI:Archive_User_OnBehalfOf_User' => '%1$s en nombre de %2$s', + 'UI:Delete:AutomaticallyDeleted' => 'Borrado automaticamente', + 'UI:Delete:AutomaticResetOf_Fields' => 'reinicio automatico de campo(s): %1$s', + 'UI:Delete:CleaningUpRefencesTo_Object' => 'Limpiando todas las referencias a %1$s...', + 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => 'Limpiando todas las referencias a %1$d objetos de la clase %2$s...', + 'UI:Delete:Done+' => 'Lo que se hizo...', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s borrado.', + 'UI:Delete:ConfirmDeletionOf_Name' => 'Borrado de %1$s', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => 'Borrado de %1$d objetos de al clase %2$s', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => 'Beberia ser eliminado automaticamente, pero usted no esta autorizado para hacerlo', + 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => 'Debe ser borrado manualmente - pero usted no esta autorizado para borrar este objeto, por favor contacte al administrador de la aplicacion', + 'UI:Delete:WillBeDeletedAutomatically' => 'Sera borrado automaticamente', + 'UI:Delete:MustBeDeletedManually' => 'Debe ser borrado manualmente', + 'UI:Delete:CannotUpdateBecause_Issue' => 'Debe ser actualizado automaticamente, pero: %1$s', + 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'sera actualizado automaticamente (reset: %1$s)', + 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objetos/vinculos estan referenciando %2$s', + 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objetos/vinculos estan referenciando algunos de los objetos a ser borrados', + 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Para asegurar la integridad de la Base de Datos, cualquier referencia debera ser completamente eliminada', + 'UI:Delete:Consequence+' => 'Lo que se hara', + 'UI:Delete:SorryDeletionNotAllowed' => 'Disculpe, usted no esta autorizado a eliminar este objeto, vea la explciacion detallada abajo', + 'UI:Delete:PleaseDoTheManualOperations' => 'Por favor ejecute las operaciones manuales antes de eliminar este objeto', + 'UI:Delect:Confirm_Object' => 'Por favor confirme que quiere borrar %1$s.', + 'UI:Delect:Confirm_Count_ObjectsOf_Class' => 'Port favor confirme que quiere eliminar los siguientes %1$d objeto de la clase %2$s.', + 'UI:WelcomeToITop' => 'Bienvenido a iTop', + 'UI:DetailsPageTitle' => 'iTop - %1$s - %2$s detalles', 'UI:ErrorPageTitle' => 'iTop - Error', - 'UI:ObjectDoesNotExist' => 'Sorry, this object does not exist (or you are not allowed to view it).', - 'UI:SearchResultsPageTitle' => 'iTop - Search Results', - 'UI:Search:NoSearch' => 'Nothing to search for', - 'UI:FullTextSearchTitle_Text' => 'Results for "%1$s":', - 'UI:Search:Count_ObjectsOf_Class_Found' => '%1$d object(s) of class %2$s found.', - 'UI:Search:NoObjectFound' => 'No object found.', - 'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s modification', - 'UI:ModificationTitle_Class_Object' => 'Modification of %1$s: %2$s', - 'UI:ClonePageTitle_Object_Class' => 'iTop - Clone %1$s - %2$s modification', - 'UI:CloneTitle_Class_Object' => 'Clone of %1$s: %2$s', - 'UI:CreationPageTitle_Class' => 'iTop - Creation of a new %1$s ', - 'UI:CreationTitle_Class' => 'Creation of a new %1$s', - 'UI:SelectTheTypeOf_Class_ToCreate' => 'Select the type of %1$s to create:', - 'UI:Class_Object_NotUpdated' => 'No change detected, %1$s (%2$s) has not been modified.', - 'UI:Class_Object_Updated' => '%1$s (%2$s) updated.', - 'UI:BulkDeletePageTitle' => 'iTop - Bulk Delete', - 'UI:BulkDeleteTitle' => 'Select the objects you want to delete:', - 'UI:PageTitle:ObjectCreated' => 'iTop Object Created.', - 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s created.', - 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Applying %1$s on object: %2$s in state %3$s to target state: %4$s.', - 'UI:ObjectCouldNotBeWritten' => 'The object could not be written: %1$s', - 'UI:PageTitle:FatalError' => 'iTop - Fatal Error', - 'UI:FatalErrorMessage' => 'Fatal error, iTop cannot continue.', - 'UI:SystemIntrusion' => 'Access denied. You have trying to perform an operation that is not allowed for you.', + 'UI:ObjectDoesNotExist' => 'Disculpe, este objeto no existe (o no esta autorizado para verlo).', + 'UI:SearchResultsPageTitle' => 'iTop - Resultados de la Busqueda', + 'UI:Search:NoSearch' => 'Nada para buscar', + 'UI:FullTextSearchTitle_Text' => 'Resultados para "%1$s":', + 'UI:Search:Count_ObjectsOf_Class_Found' => '%1$d objeto(s) de la clase %2$s encontrado(s).', + 'UI:Search:NoObjectFound' => 'No se encontraron objetos.', + 'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s modificacion', + 'UI:ModificationTitle_Class_Object' => 'Modificacion de %1$s: %2$s', + 'UI:ClonePageTitle_Object_Class' => 'iTop - Duplicar %1$s - %2$s modificacion', + 'UI:CloneTitle_Class_Object' => 'Duplicado de %1$s: %2$s', + 'UI:CreationPageTitle_Class' => 'iTop - Creacion de un nuevo %1$s ', + 'UI:CreationTitle_Class' => 'Creacion de un nuevo %1$s', + 'UI:SelectTheTypeOf_Class_ToCreate' => 'Seleccione el tipo de %1$s a crear:', + 'UI:Class_Object_NotUpdated' => 'No se detectaron cambios, %1$s (%2$s) no fue modificado.', + 'UI:Class_Object_Updated' => '%1$s (%2$s) actualizado.', + 'UI:BulkDeletePageTitle' => 'iTop - Eliminar por lote', + 'UI:BulkDeleteTitle' => 'Seleccione los objetos que desea eliminar:', + 'UI:PageTitle:ObjectCreated' => 'Objeto de iTop creado.', + 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s creado.', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Aplicando %1$s en el objeto: %2$s en estado %3$s al estado deseado: %4$s.', + 'UI:ObjectCouldNotBeWritten' => 'el objeto no pudo ser escrito: %1$s', + 'UI:PageTitle:FatalError' => 'iTop - Error Fatal', + 'UI:FatalErrorMessage' => 'Error fatal, iTop no puede continuar.', + 'UI:SystemIntrusion' => 'Acceso denegado. Esta tratando de ejecutar una operacion no permitida para usted.', 'UI:Error_Details' => 'Error: %1$s.', - 'UI:PageTitle:ClassProjections' => 'iTop user management - class projections', - 'UI:PageTitle:ProfileProjections' => 'iTop user management - profile projections', - 'UI:UserManagement:Class' => 'Class', - 'UI:UserManagement:Class+' => 'Class of objects', - 'UI:UserManagement:ProjectedObject' => 'Object', - 'UI:UserManagement:ProjectedObject+' => 'Projected object', - 'UI:UserManagement:AnyObject' => '* any *', - 'UI:UserManagement:User' => 'User', - 'UI:UserManagement:User+' => 'User involved in the projection', - 'UI:UserManagement:Profile' => 'Profile', - 'UI:UserManagement:Profile+' => 'Profile in which the projection is specified', - 'UI:UserManagement:Action:Read' => 'Read', - 'UI:UserManagement:Action:Read+' => 'Read/display objects', - 'UI:UserManagement:Action:Modify' => 'Modify', - 'UI:UserManagement:Action:Modify+' => 'Create and edit (modify) objects', - 'UI:UserManagement:Action:Delete' => 'Delete', - 'UI:UserManagement:Action:Delete+' => 'Delete objects', - 'UI:UserManagement:Action:BulkRead' => 'Bulk Read (Export)', - 'UI:UserManagement:Action:BulkRead+' => 'List objects or export massively', - 'UI:UserManagement:Action:BulkModify' => 'Bulk Modify', - 'UI:UserManagement:Action:BulkModify+' => 'Massively create/edit (CSV import)', - 'UI:UserManagement:Action:BulkDelete' => 'Bulk Delete', - 'UI:UserManagement:Action:BulkDelete+' => 'Massively delete objects', + 'UI:PageTitle:ClassProjections' => 'Administracion de usuarios iTop - proyecciones de clases', + 'UI:PageTitle:ProfileProjections' => 'Administracion de usuarios iTop - proyecciones de perfil', + 'UI:UserManagement:Class' => 'Clase', + 'UI:UserManagement:Class+' => 'Clase de objetos', + 'UI:UserManagement:ProjectedObject' => 'Objeto', + 'UI:UserManagement:ProjectedObject+' => 'Objeto proyectado', + 'UI:UserManagement:AnyObject' => '* cualquiera *', + 'UI:UserManagement:User' => 'Usuario', + 'UI:UserManagement:User+' => 'Usuario implicado en la proyeccion', + 'UI:UserManagement:Profile' => 'Perfil', + 'UI:UserManagement:Profile+' => 'Perfil en el cual se especifico la proyeccion', + 'UI:UserManagement:Action:Read' => 'Leer', + 'UI:UserManagement:Action:Read+' => 'Leer/Mostrar objetos', + 'UI:UserManagement:Action:Modify' => 'Modificar', + 'UI:UserManagement:Action:Modify+' => 'Crear y editar (modificar) objetos', + 'UI:UserManagement:Action:Delete' => 'Eliminar', + 'UI:UserManagement:Action:Delete+' => 'Eliminar objetos', + 'UI:UserManagement:Action:BulkRead' => 'Lectura por lote (Exportar)', + 'UI:UserManagement:Action:BulkRead+' => 'Listar objetos o exportar masivamente', + 'UI:UserManagement:Action:BulkModify' => 'Modificacion masiva', + 'UI:UserManagement:Action:BulkModify+' => 'Crear/Editar masivamente (importar CSV)', + 'UI:UserManagement:Action:BulkDelete' => 'eliminacion masiva', + 'UI:UserManagement:Action:BulkDelete+' => 'eliminacion masiva de objetos', 'UI:UserManagement:Action:Stimuli' => 'Stimuli', - 'UI:UserManagement:Action:Stimuli+' => 'Allowed (compound) actions', - 'UI:UserManagement:Action' => 'Action', - 'UI:UserManagement:Action+' => 'Action performed by the user', - 'UI:UserManagement:TitleActions' => 'Actions', - 'UI:UserManagement:Permission' => 'Permission', - 'UI:UserManagement:Permission+' => 'User\'s permissions', - 'UI:UserManagement:Attributes' => 'Attributes', - 'UI:UserManagement:ActionAllowed:Yes' => 'Yes', + 'UI:UserManagement:Action:Stimuli+' => 'Acciones (compound) permitidas', + 'UI:UserManagement:Action' => 'Accion', + 'UI:UserManagement:Action+' => 'Accion ejecutada por el usuario', + 'UI:UserManagement:TitleActions' => 'Acciones', + 'UI:UserManagement:Permission' => 'Permisos', + 'UI:UserManagement:Permission+' => 'Permisos de usuario', + 'UI:UserManagement:Attributes' => 'Atributos', + 'UI:UserManagement:ActionAllowed:Yes' => 'Si', 'UI:UserManagement:ActionAllowed:No' => 'No', - 'UI:UserManagement:AdminProfile+' => 'Administrators have full read/write access to all objects in the database.', + 'UI:UserManagement:AdminProfile+' => 'Los administradores tienen acceso total de lectura/escritura para todos los objetos en la base de datos.', 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', - 'UI:UserManagement:NoLifeCycleApplicable+' => 'No lifecycle has been defined for this class', - 'UI:UserManagement:GrantMatrix' => 'Grant Matrix', - 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Link between %1$s and %2$s', + 'UI:UserManagement:NoLifeCycleApplicable+' => 'No se ha definido ciclo de vida para esta clase', + 'UI:UserManagement:GrantMatrix' => 'Matriz de acceso', + 'UI:UserManagement:LinkBetween_User_And_Profile' => 'Vinculo entre %1$s y %2$s', - 'Menu:AdminTools' => 'Admin tools', - 'Menu:AdminTools+' => 'Administration tools', - 'Menu:AdminTools?' => 'Tools accessible only to users having the administrator profile', + 'Menu:AdminTools' => 'Herramientas Administrativas', + 'Menu:AdminTools+' => 'Herramientas de administracion', + 'Menu:AdminTools?' => 'Herramientas accesibles soloa usuariso con perfil de administrador', - 'UI:ChangeManagementMenu' => 'Change Management', - 'UI:ChangeManagementMenu+' => 'Change Management', - 'UI:ChangeManagementMenu:Title' => 'Changes Overview', - 'UI-ChangeManagementMenu-ChangesByType' => 'Changes by type', - 'UI-ChangeManagementMenu-ChangesByStatus' => 'Changes by status', - 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Changes by workgroup', - 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Changes not yet assigned', + 'UI:ChangeManagementMenu' => 'Control de Cambios', + 'UI:ChangeManagementMenu+' => 'Control de Cambios', + 'UI:ChangeManagementMenu:Title' => 'Sumario de cambios', + 'UI-ChangeManagementMenu-ChangesByType' => 'Cambios por tipo', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Cambios por estado', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'Cambios por grupo de trabajo', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Cambios no asignados aun', - 'UI:ConfigurationItemsMenu'=> 'Configuration Items', - 'UI:ConfigurationItemsMenu+'=> 'All Devices', - 'UI:ConfigurationItemsMenu:Title' => 'Configuration Items Overview', - 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Servers by criticity', - 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PCs by criticity', - 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Network devices by criticity', - 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Applications by criticity', + 'UI:ConfigurationItemsMenu'=> 'Elementos de configuracion', + 'UI:ConfigurationItemsMenu+'=> 'Todos los dispositivos', + 'UI:ConfigurationItemsMenu:Title' => 'Sumario de Elementos de Configuracion', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Servidores por criticidad', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PCs por criticidad', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Dispositivos de red por criticidad', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Aplicaciones por criticidad', - 'UI:ConfigurationManagementMenu' => 'Configuration Management', - 'UI:ConfigurationManagementMenu+' => 'Configuration Management', - 'UI:ConfigurationManagementMenu:Title' => 'Infrastructure Overview', - 'UI-ConfigurationManagementMenu-InfraByType' => 'Infrastructure objects by type', - 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Infrastructure objects by status', + 'UI:ConfigurationManagementMenu' => 'Gestion de la Configuracion', + 'UI:ConfigurationManagementMenu+' => 'Gestion de la Configuracion', + 'UI:ConfigurationManagementMenu:Title' => 'Sumario de Infrastructura', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Objetos de infrastructura por tipo', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Objetos de infraestructura por estatus', -'UI:ConfigMgmtMenuOverview:Title' => 'Dashboard for Configuration Management', -'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => 'Configuration Items by status', -'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => 'Configuration Items by type', +'UI:ConfigMgmtMenuOverview:Title' => 'Panel de control for Gestion de la Configuracion', +'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => 'Elementos de la configuracion por estado', +'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => 'elementos de configuracion por tipo', -'UI:RequestMgmtMenuOverview:Title' => 'Dashboard for Request Management', -'UI-RequestManagementOverview-RequestByService' => 'User Requests by service', -'UI-RequestManagementOverview-RequestByPriority' => 'User Requests by priority', -'UI-RequestManagementOverview-RequestUnassigned' => 'User Requests not yet assigned to an agent', +'UI:RequestMgmtMenuOverview:Title' => 'Panel de control for Gestion de Solicitudes', +'UI-RequestManagementOverview-RequestByService' => 'Solicitudes de usuario por servicio', +'UI-RequestManagementOverview-RequestByPriority' => 'Solicitudes de usuario por prioridad', +'UI-RequestManagementOverview-RequestUnassigned' => 'Solicitudes de usuario sin asignar a un agente', -'UI:IncidentMgmtMenuOverview:Title' => 'Dashboard for Incident Management', -'UI-IncidentManagementOverview-IncidentByService' => 'Incidents by service', -'UI-IncidentManagementOverview-IncidentByPriority' => 'Incident by priority', -'UI-IncidentManagementOverview-IncidentUnassigned' => 'Incidents not yet assigned to an agent', +'UI:IncidentMgmtMenuOverview:Title' => 'Panel de control for Gestion de Incidentes', +'UI-IncidentManagementOverview-IncidentByService' => 'Incidentes por servicio', +'UI-IncidentManagementOverview-IncidentByPriority' => 'Incidentes por prioridad', +'UI-IncidentManagementOverview-IncidentUnassigned' => 'Incidentes no asignados a un agente', -'UI:ChangeMgmtMenuOverview:Title' => 'Dashboard for Change Management', -'UI-ChangeManagementOverview-ChangeByType' => 'Changes by type', -'UI-ChangeManagementOverview-ChangeUnassigned' => 'Changes not yet assigned to an agent', -'UI-ChangeManagementOverview-ChangeWithOutage' => 'Outages due to changes', +'UI:ChangeMgmtMenuOverview:Title' => 'Panel de control for Control de Cambios', +'UI-ChangeManagementOverview-ChangeByType' => 'Cambios por tipo', +'UI-ChangeManagementOverview-ChangeUnassigned' => 'Cambios no asignados a un agente', +'UI-ChangeManagementOverview-ChangeWithOutage' => 'Interrupciones de servicios debida a cambios', -'UI:ServiceMgmtMenuOverview:Title' => 'Dashboard for Service Management', -'UI-ServiceManagementOverview-CustomerContractToRenew' => 'Customer contracts to be renewed in 30 days', -'UI-ServiceManagementOverview-ProviderContractToRenew' => 'Provider contracts to be renewed in 30 days', +'UI:ServiceMgmtMenuOverview:Title' => 'Panel de control for Gestion de Servicios', +'UI-ServiceManagementOverview-CustomerContractToRenew' => 'Contratos de usuario a ser renovados en 30 dias', +'UI-ServiceManagementOverview-ProviderContractToRenew' => 'contratos de proveedores a ser renovados en 30 dias', - 'UI:ContactsMenu' => 'Contacts', - 'UI:ContactsMenu+' => 'Contacts', - 'UI:ContactsMenu:Title' => 'Contacts Overview', - 'UI-ContactsMenu-ContactsByLocation' => 'Contacts by location', - 'UI-ContactsMenu-ContactsByType' => 'Contacts by type', - 'UI-ContactsMenu-ContactsByStatus' => 'Contacts by status', + 'UI:ContactsMenu' => 'Contactos', + 'UI:ContactsMenu+' => 'Contactos', + 'UI:ContactsMenu:Title' => 'Sumario de Contactos', + 'UI-ContactsMenu-ContactsByLocation' => 'Contactos por ubicacion', + 'UI-ContactsMenu-ContactsByType' => 'Contactos por tipo', + 'UI-ContactsMenu-ContactsByStatus' => 'Contactos por estado', - 'Menu:CSVImportMenu' => 'CSV import', - 'Menu:CSVImportMenu+' => 'Bulk creation or update', + 'Menu:CSVImportMenu' => 'Importar CSV', + 'Menu:CSVImportMenu+' => 'Creacion o actualizacion masiva', - 'Menu:DataModelMenu' => 'Data Model', - 'Menu:DataModelMenu+' => 'Overview of the Data Model', + 'Menu:DataModelMenu' => 'Modelo de Datos', + 'Menu:DataModelMenu+' => 'Sumario del Modelo de Datos', - 'Menu:ExportMenu' => 'Export', - 'Menu:ExportMenu+' => 'Export the results of any query in HTML, CSV or XML', + 'Menu:ExportMenu' => 'Exportar', + 'Menu:ExportMenu+' => 'Exportar los resultados de cualquier consulta eb HTML, CSV o XML', - 'Menu:NotificationsMenu' => 'Notifications', - 'Menu:NotificationsMenu+' => 'Configuration of the Notifications', - 'UI:NotificationsMenu:Title' => 'Configuration of the Notifications', - 'UI:NotificationsMenu:Help' => 'Help', - 'UI:NotificationsMenu:HelpContent' => '

        In iTop the notifications are fully customizable. They are based on two sets of objects: triggers and actions.

        -

        Triggers define when a notification will be executed. There are 3 types of triggers for covering 3 differents phases of an object life cycle: + 'Menu:NotificationsMenu' => 'Notificaciones', + 'Menu:NotificationsMenu+' => 'Configuracion de las Notificaciones', + 'UI:NotificationsMenu:Title' => 'Configuracion de las Notificaciones', + 'UI:NotificationsMenu:Help' => 'Ayuda', + 'UI:NotificationsMenu:HelpContent' => '

        En iTop las notificaciones son completamente personalizables. Estan basadas en dos conjuntos de objetos: Gatuillos y acciones.

        +

        Gatillos definen cuando una notificacion debe ser ejecutada. existen 3 tipos de gatillos para cubrir las 3 diferentes fases del ciclo de vida de un objeto:

          -
        1. the "OnCreate" triggers get executed when an object of the specified class is created
        2. -
        3. the "OnStateEnter" triggers get executed before an object of the given class enters a specified state (coming from another state)
        4. -
        5. the "OnStateLeave" triggers get executed when an object of the given class is leaving a specified state
        6. +
        7. los gatillos "OnCreate" son ejecutados cuando un objeto de la clase especificada es creado
        8. +
        9. los gatillos "OnStateEnter" son ejecutados antes de que un determinado objeto entre un estado especificado (viniendo de otro estado)
        10. +
        11. los gatillos "OnStateLeave" son ejecutados cuando un objeto de clase determinada deja un estado especificado

        -Actions define the actions to be performed when the triggers execute. For now there is only one kind of action consisting in sending an email message. -Such actions also define the template to be used for sending the email as well as the other parameters of the message like the recipients, importance, etc. +Acciones definen las acciones a ser ejecutadas cuando los gatillos se disparan, por ahora el unico tipo de accion consiste en enviar un mensaje de correo. +Tales acciones tambien definen la plantilla a ser usada para enviar el correo asi como otros parametros del mensaje como receptor, importancia, etc.

        -

        A special page: email.test.php is available for testing and troubleshooting your PHP mail configuration.

        -

        To be executed, actions must be associated to triggers. -When associated with a trigger, each action is given an "order" number, specifying in which order the actions are to be executed.

        ', +

        Una pagina especial: email.test.php esta disponible para pruebar y diagnosticar su configuracion de correo de PHP.

        +

        Para ser ejecutadas, las acciones deben estar asociadas con los gatillos. +Cuando se asocien con un gatillo, cada accion recibe un numero de "orden", esto especifica en que orden se ejecutaran las acciones.

        ', 'UI:NotificationsMenu:Triggers' => 'Disparadores', - 'UI:NotificationsMenu:AvailableTriggers' => 'Disparadores disponiblesAvailable triggers', + 'UI:NotificationsMenu:AvailableTriggers' => 'Disparadores disponibles', 'UI:NotificationsMenu:OnCreate' => 'cuando un objeto es creado', 'UI:NotificationsMenu:OnStateEnter' => 'Cuando un objeto entra a un estado específico', 'UI:NotificationsMenu:OnStateLeave' => 'Cuando un objeto sale de un estado específico', @@ -834,7 +827,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'Menu:UserAccountsMenu:Title' => 'Cuentas de usuario', 'UI:iTopVersion:Short' => 'iTop versión %1$s', - 'UI:iTopVersion:Long' => 'iTop versión %1$s-%2$s built on %3$s', + 'UI:iTopVersion:Long' => 'iTop versión %1$s-%2$s compilada en %3$s', 'UI:PropertiesTab' => 'Propiedades', 'UI:OpenDocumentInNewWindow_' => 'Abra este documento en una ventana nueva: %1$s', @@ -847,9 +840,9 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Ayuda', - 'UI:PasswordConfirm' => '(Confirm)', + 'UI:PasswordConfirm' => '(Confirmar)', - 'Enum:Undefined' => 'Undefined', + 'Enum:Undefined' => 'Indefinido', )); ?> From c6a6af0aa2122fd6600368ab39440c6285d88207 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 20 Dec 2010 11:45:38 +0000 Subject: [PATCH 947/970] Including approot is not needed here ! SVN:trunk[1032] --- application/ajaxwebpage.class.inc.php | 1 - 1 file changed, 1 deletion(-) diff --git a/application/ajaxwebpage.class.inc.php b/application/ajaxwebpage.class.inc.php index 250eac496f..0f81c70ac8 100644 --- a/application/ajaxwebpage.class.inc.php +++ b/application/ajaxwebpage.class.inc.php @@ -24,7 +24,6 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ -require_once('../approot.inc.php'); require_once(APPROOT."/application/webpage.class.inc.php"); class ajax_page extends WebPage From adfed90733a04f04dac5fa022ba4bb2cb4bc5d42 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 20 Dec 2010 11:48:34 +0000 Subject: [PATCH 948/970] Localized in turkish SVN:trunk[1033] --- core/config.class.inc.php | 2 + dictionaries/de.dictionary.itop.core.php | 2 +- dictionaries/es_cr.dictionary.itop.core.php | 2 +- dictionaries/pt_br.dictionary.itop.core.php | 2 +- dictionaries/tr.dictionary.itop.core.php | 386 ++++++ dictionaries/tr.dictionary.itop.ui.php | 869 ++++++++++++++ .../module.authent-external.php | 1 + .../tr.dict.authent-external.php | 46 + modules/authent-ldap/module.authent-ldap.php | 1 + modules/authent-ldap/tr.dict.authent-ldap.php | 47 + .../authent-local/module.authent-local.php | 1 + .../authent-local/tr.dict.authent-local.php | 47 + .../disabled.module.itop-basic.php | 1 + .../module.itop-change-mgmt.php | 1 + .../tr.dict.itop-change-mgmt.php | 346 ++++++ .../module.itop-config-mgmt.php | 1 + .../tr.dict.itop-config-mgmt.php | 1052 +++++++++++++++++ .../module.itop-incident-mgmt.php | 1 + .../tr.dict.itop-incident-mgmt.php | 73 ++ .../module.itop-knownerror-mgmt.php | 1 + .../tr.dict.itop-knownerror-mgmt.php | 147 +++ .../module.itop-problem-mgmt.php | 1 + .../tr.dict.itop-problem-mgmt.php | 165 +++ .../module.itop-request-mgmt.php | 1 + .../tr.dict.itop-request-mgmt.php | 84 ++ .../module.itop-service-mgmt.php | 1 + .../tr.dict.itop-service-mgmt.php | 442 +++++++ .../module.itop-tickets.php | 1 + .../tr.dict.itop-tickets.php | 265 +++++ readme.txt | 1 + 30 files changed, 3987 insertions(+), 3 deletions(-) create mode 100644 dictionaries/tr.dictionary.itop.core.php create mode 100644 dictionaries/tr.dictionary.itop.ui.php create mode 100644 modules/authent-external/tr.dict.authent-external.php create mode 100644 modules/authent-ldap/tr.dict.authent-ldap.php create mode 100644 modules/authent-local/tr.dict.authent-local.php create mode 100644 modules/itop-change-mgmt-1.0.0/tr.dict.itop-change-mgmt.php create mode 100644 modules/itop-config-mgmt-1.0.0/tr.dict.itop-config-mgmt.php create mode 100644 modules/itop-incident-mgmt-1.0.0/tr.dict.itop-incident-mgmt.php create mode 100644 modules/itop-knownerror-mgmt-1.0.0/tr.dict.itop-knownerror-mgmt.php create mode 100644 modules/itop-problem-mgmt-1.0.0/tr.dict.itop-problem-mgmt.php create mode 100644 modules/itop-request-mgmt-1.0.0/tr.dict.itop-request-mgmt.php create mode 100644 modules/itop-service-mgmt-1.0.0/tr.dict.itop-service-mgmt.php create mode 100644 modules/itop-tickets-1.0.0/tr.dict.itop-tickets.php diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 0ca0b0b448..04af9f3742 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -364,6 +364,8 @@ class Config 'dictionaries/pt_br.dictionary.itop.core.php', // Support for Brazilian Portuguese 'dictionaries/ru.dictionary.itop.ui.php', // Support for Russian 'dictionaries/ru.dictionary.itop.core.php', // Support for Russian + 'dictionaries/tr.dictionary.itop.ui.php', // Support for Turkish + 'dictionaries/tr.dictionary.itop.core.php', // Support for Turkish ); foreach($this->m_aSettings as $sPropCode => $aSettingInfo) { diff --git a/dictionaries/de.dictionary.itop.core.php b/dictionaries/de.dictionary.itop.core.php index b55bf3bf56..56ab2f9d60 100644 --- a/dictionaries/de.dictionary.itop.core.php +++ b/dictionaries/de.dictionary.itop.core.php @@ -33,7 +33,7 @@ // Class: CMDBChange // -Dict::Add('EN US', 'English', 'English', array( +Dict::Add('DE DE', 'English', 'English', array( 'Core:AttributeLinkedSet' => 'Array of objects', 'Core:AttributeLinkedSet+' => 'Any kind of objects [subclass] of the same class', diff --git a/dictionaries/es_cr.dictionary.itop.core.php b/dictionaries/es_cr.dictionary.itop.core.php index 8dc9a42811..3ebaa7fb55 100644 --- a/dictionaries/es_cr.dictionary.itop.core.php +++ b/dictionaries/es_cr.dictionary.itop.core.php @@ -29,7 +29,7 @@ ////////////////////////////////////////////////////////////////////// // -Dict::Add('EN US', 'English', 'English', array( +Dict::Add('ES CR', 'English', 'English', array( 'Core:AttributeLinkedSet' => 'Array of objects', 'Core:AttributeLinkedSet+' => 'Any kind of objects [subclass] of the same class', diff --git a/dictionaries/pt_br.dictionary.itop.core.php b/dictionaries/pt_br.dictionary.itop.core.php index d2f420ae4c..95903267c6 100644 --- a/dictionaries/pt_br.dictionary.itop.core.php +++ b/dictionaries/pt_br.dictionary.itop.core.php @@ -24,7 +24,7 @@ */ -Dict::Add('EN US', 'English', 'English', array( +Dict::Add('PT BR', 'English', 'English', array( 'Core:AttributeLinkedSet' => 'Array of objects', 'Core:AttributeLinkedSet+' => 'Any kind of objects [subclass] of the same class', diff --git a/dictionaries/tr.dictionary.itop.core.php b/dictionaries/tr.dictionary.itop.core.php new file mode 100644 index 0000000000..e974d33dbb --- /dev/null +++ b/dictionaries/tr.dictionary.itop.core.php @@ -0,0 +1,386 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + + +////////////////////////////////////////////////////////////////////// +// Classes in 'core/cmdb' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: CMDBChange +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChange' => 'Değişiklik', + 'Class:CMDBChange+' => 'Değişiklik izleme', + 'Class:CMDBChange/Attribute:date' => 'tarih', + 'Class:CMDBChange/Attribute:date+' => 'değişikliğin yapıldığı tarih', + 'Class:CMDBChange/Attribute:userinfo' => 'diğer bilgiler', + 'Class:CMDBChange/Attribute:userinfo+' => 'ilave bilgiler', +)); + +// +// Class: CMDBChangeOp +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOp' => 'Değişiklik işlemi', + 'Class:CMDBChangeOp+' => 'Değişiklik izleme', + 'Class:CMDBChangeOp/Attribute:change' => 'değişiklik', + 'Class:CMDBChangeOp/Attribute:change+' => 'değişiklik', + 'Class:CMDBChangeOp/Attribute:date' => 'tarih', + 'Class:CMDBChangeOp/Attribute:date+' => 'değişikliğin yapıldığı zaman', + 'Class:CMDBChangeOp/Attribute:userinfo' => 'kullanıcı', + 'Class:CMDBChangeOp/Attribute:userinfo+' => 'değişikliğ yapan', + 'Class:CMDBChangeOp/Attribute:objclass' => 'nesne sınıfı', + 'Class:CMDBChangeOp/Attribute:objclass+' => 'nesne sınıfı', + 'Class:CMDBChangeOp/Attribute:objkey' => 'nesne no', + 'Class:CMDBChangeOp/Attribute:objkey+' => 'nesne no', + 'Class:CMDBChangeOp/Attribute:finalclass' => 'tip', + 'Class:CMDBChangeOp/Attribute:finalclass+' => '', +)); + +// +// Class: CMDBChangeOpCreate +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOpCreate' => 'nesne yaratımı', + 'Class:CMDBChangeOpCreate+' => 'Nesne Yaratım izleme', +)); + +// +// Class: CMDBChangeOpDelete +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOpDelete' => 'nesne silimi', + 'Class:CMDBChangeOpDelete+' => 'Nesne silme izleme', +)); + +// +// Class: CMDBChangeOpSetAttribute +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOpSetAttribute' => 'nesne değişikliği', + 'Class:CMDBChangeOpSetAttribute+' => 'Nesne değişiminin izlemesi', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Özellik', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => 'Değişen özelliğin kodu', +)); + +// +// Class: CMDBChangeOpSetAttributeScalar +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOpSetAttributeScalar' => 'özellik değişimi', + 'Class:CMDBChangeOpSetAttributeScalar+' => 'Nesne özellik değişimi izleme', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Önceki değer', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => 'önceki değer', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Yeni değer', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'yeni değer', +)); +// Used by CMDBChangeOp... & derived classes +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Change:ObjectCreated' => 'Nesne yaratıldı', + 'Change:ObjectDeleted' => 'Nesne silindi', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s\'nin değeri %2$s olarak atandı (önceki değer: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%2$s\'ye %1$s eklendi', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$\'nin değeri deiştirildi, önceki değer: %2$s', + 'Change:AttName_Changed' => '%1$s değiştirildi', +)); + +// +// Class: CMDBChangeOpSetAttributeBlob +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOpSetAttributeBlob' => 'tarih değişimi', + 'Class:CMDBChangeOpSetAttributeBlob+' => 'tarih değişim izleme', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Önceki veri', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => 'önceki değer', +)); + +// +// Class: CMDBChangeOpSetAttributeText +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CMDBChangeOpSetAttributeText' => 'metin değişikliği', + 'Class:CMDBChangeOpSetAttributeText+' => 'metin değişikliği izleme', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Önceki veri', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => 'önceki değer', +)); + +// +// Class: Event +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Event' => 'Olay kaydı', + 'Class:Event+' => 'Uygulama olayı', + 'Class:Event/Attribute:message' => 'mesaj', + 'Class:Event/Attribute:message+' => 'Olay tanımlama', + 'Class:Event/Attribute:date' => 'tarih', + 'Class:Event/Attribute:date+' => 'değişiklik tarihi', + 'Class:Event/Attribute:userinfo' => 'kullanıcı bigileri', + 'Class:Event/Attribute:userinfo+' => 'olay anındaki kullanıcı', + 'Class:Event/Attribute:finalclass' => 'tip', + 'Class:Event/Attribute:finalclass+' => '', +)); + +// +// Class: EventNotification +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:EventNotification' => 'Olay uyarımı', + 'Class:EventNotification+' => 'Uyarının tarihçesi', + 'Class:EventNotification/Attribute:trigger_id' => 'Uyarı tetikçisi', + 'Class:EventNotification/Attribute:trigger_id+' => 'kullanıcı hesabı', + 'Class:EventNotification/Attribute:action_id' => 'kullanıcı', + 'Class:EventNotification/Attribute:action_id+' => 'kullanıcı hesabı', + 'Class:EventNotification/Attribute:object_id' => 'Nesne belirleyicisi', + 'Class:EventNotification/Attribute:object_id+' => 'nesne belirleyicisi (olayı tetikleyen nesne ?)', +)); + +// +// Class: EventNotificationEmail +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:EventNotificationEmail' => 'E-posta gönderim işlemi', + 'Class:EventNotificationEmail+' => 'Gönderilen E-posta tarihçesi', + 'Class:EventNotificationEmail/Attribute:to' => 'Kime', + 'Class:EventNotificationEmail/Attribute:to+' => 'Kime', + 'Class:EventNotificationEmail/Attribute:cc' => 'Kopya', + 'Class:EventNotificationEmail/Attribute:cc+' => 'Kopya', + 'Class:EventNotificationEmail/Attribute:bcc' => 'Gizli Kopya', + 'Class:EventNotificationEmail/Attribute:bcc+' => 'Gizli Kopya', + 'Class:EventNotificationEmail/Attribute:from' => 'Kimden', + 'Class:EventNotificationEmail/Attribute:from+' => 'Mesajı gönderen', + 'Class:EventNotificationEmail/Attribute:subject' => 'Konu', + 'Class:EventNotificationEmail/Attribute:subject+' => 'Konu', + 'Class:EventNotificationEmail/Attribute:body' => 'Mesaj', + 'Class:EventNotificationEmail/Attribute:body+' => 'Mesaj', +)); + +// +// Class: EventIssue +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:EventIssue' => 'Olay ekle', + 'Class:EventIssue+' => 'Olay tipi (uyarı, hata, vb.)', + 'Class:EventIssue/Attribute:issue' => 'Konu', + 'Class:EventIssue/Attribute:issue+' => 'Olan', + 'Class:EventIssue/Attribute:impact' => 'Etkisi', + 'Class:EventIssue/Attribute:impact+' => 'Sonuçları', + 'Class:EventIssue/Attribute:page' => 'Sayfa', + 'Class:EventIssue/Attribute:page+' => 'HTTP giriş noktası', + 'Class:EventIssue/Attribute:arguments_post' => 'Verilen değişkenlerin değerleri', + 'Class:EventIssue/Attribute:arguments_post+' => 'HTTP değişken değerleri', + 'Class:EventIssue/Attribute:arguments_get' => 'URL POST değişken değerleri', + 'Class:EventIssue/Attribute:arguments_get+' => 'HTTP GET değişken değerleri', + 'Class:EventIssue/Attribute:callstack' => 'Çağrım sırası', + 'Class:EventIssue/Attribute:callstack+' => 'Çağrım sırası', + 'Class:EventIssue/Attribute:data' => 'Veri', + 'Class:EventIssue/Attribute:data+' => 'Diğer bilgiler', +)); + +// +// Class: EventWebService +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:EventWebService' => 'Web service olayı', + 'Class:EventWebService+' => 'web service çağrım sırası', + 'Class:EventWebService/Attribute:verb' => 'Fiil', + 'Class:EventWebService/Attribute:verb+' => 'Operasyonun adı', + 'Class:EventWebService/Attribute:result' => 'Sonuç', + 'Class:EventWebService/Attribute:result+' => 'Genel başarı/başarısızlık', + 'Class:EventWebService/Attribute:log_info' => 'Bilgi kaydı', + 'Class:EventWebService/Attribute:log_info+' => 'Sonuç bilgi kaydı', + 'Class:EventWebService/Attribute:log_warning' => 'Uyarı kaydı', + 'Class:EventWebService/Attribute:log_warning+' => 'Sonuç uyarı kaydı', + 'Class:EventWebService/Attribute:log_error' => 'Hata kaydı', + 'Class:EventWebService/Attribute:log_error+' => 'Sonuç hata kaydı', + 'Class:EventWebService/Attribute:data' => 'Veri', + 'Class:EventWebService/Attribute:data+' => 'Sonuç veri', +)); + +// +// Class: Action +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Action' => 'Özel işlem', + 'Class:Action+' => 'Kullanıcının tanımladığı işlemler', + 'Class:Action/Attribute:name' => 'Adı', + 'Class:Action/Attribute:name+' => '', + 'Class:Action/Attribute:description' => 'Tanımlama', + 'Class:Action/Attribute:description+' => '', + 'Class:Action/Attribute:status' => 'Durum', + 'Class:Action/Attribute:status+' => 'Kullanımda mı?', + 'Class:Action/Attribute:status/Value:test' => 'Test aşamasında', + 'Class:Action/Attribute:status/Value:test+' => 'Test aşamasında', + 'Class:Action/Attribute:status/Value:enabled' => 'Kullanımda', + 'Class:Action/Attribute:status/Value:enabled+' => 'Kullanımda', + 'Class:Action/Attribute:status/Value:disabled' => 'Etkin değil', + 'Class:Action/Attribute:status/Value:disabled+' => 'Etkin değil', + 'Class:Action/Attribute:trigger_list' => 'İlgili tetikleyiciler', + 'Class:Action/Attribute:trigger_list+' => 'İşleme bağlı tetikleyici', + 'Class:Action/Attribute:finalclass' => 'Tip', + 'Class:Action/Attribute:finalclass+' => '', +)); + +// +// Class: ActionNotification +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ActionNotification' => 'Bildirim', + 'Class:ActionNotification+' => 'Bildirim (soyut)', +)); + +// +// Class: ActionEmail +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ActionEmail' => 'E-posta bildirimi', + 'Class:ActionEmail+' => '', + 'Class:ActionEmail/Attribute:test_recipient' => 'Test alıcısı', + 'Class:ActionEmail/Attribute:test_recipient+' => 'Durumu "Test" olması durumundaki alıcı', + 'Class:ActionEmail/Attribute:from' => 'Kimden', + 'Class:ActionEmail/Attribute:from+' => 'e-posta başlığında gönderilecek', + 'Class:ActionEmail/Attribute:reply_to' => 'Yanıtla', + 'Class:ActionEmail/Attribute:reply_to+' => 'e-posta başlığında gönderilecek', + 'Class:ActionEmail/Attribute:to' => 'Kime', + 'Class:ActionEmail/Attribute:to+' => 'E-posta alıcısı', + 'Class:ActionEmail/Attribute:cc' => 'Kopya', + 'Class:ActionEmail/Attribute:cc+' => 'Kopya', + 'Class:ActionEmail/Attribute:bcc' => 'gizli kopya', + 'Class:ActionEmail/Attribute:bcc+' => 'Gizli alıcı', + 'Class:ActionEmail/Attribute:subject' => 'konu', + 'Class:ActionEmail/Attribute:subject+' => 'E-posta konusu', + 'Class:ActionEmail/Attribute:body' => 'E-posta içeriği', + 'Class:ActionEmail/Attribute:body+' => 'E-posta içeriği', + 'Class:ActionEmail/Attribute:importance' => 'önem derecesi', + 'Class:ActionEmail/Attribute:importance+' => 'önem derecesi', + 'Class:ActionEmail/Attribute:importance/Value:low' => 'düşük', + 'Class:ActionEmail/Attribute:importance/Value:low+' => 'düşük', + 'Class:ActionEmail/Attribute:importance/Value:normal' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:normal+' => 'normal', + 'Class:ActionEmail/Attribute:importance/Value:high' => 'yüksek', + 'Class:ActionEmail/Attribute:importance/Value:high+' => 'yüksek', +)); + +// +// Class: Trigger +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Trigger' => 'Tetikleyici', + 'Class:Trigger+' => 'Özel olay yürütücü', + 'Class:Trigger/Attribute:description' => 'Tanımlama', + 'Class:Trigger/Attribute:description+' => 'tek satır tanımlama', + 'Class:Trigger/Attribute:action_list' => 'Tetiklenen işlemler', + 'Class:Trigger/Attribute:action_list+' => 'Tetiklenen işlemler', + 'Class:Trigger/Attribute:finalclass' => 'Tip', + 'Class:Trigger/Attribute:finalclass+' => '', +)); + +// +// Class: TriggerOnObject +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:TriggerOnObject' => 'Tetiklenen (sınıf bağımlılığı)', + 'Class:TriggerOnObject+' => 'Verilen sınıflar üzerinde işlemleri gerçekleştir', + 'Class:TriggerOnObject/Attribute:target_class' => 'Hedef sınıf', + 'Class:TriggerOnObject/Attribute:target_class+' => '', +)); + +// +// Class: TriggerOnStateChange +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:TriggerOnStateChange' => 'Tetiklenen (durum değişikliğinde)', + 'Class:TriggerOnStateChange+' => 'Durum değişikliğinde tetiklenen işlemler', + 'Class:TriggerOnStateChange/Attribute:state' => 'Durum', + 'Class:TriggerOnStateChange/Attribute:state+' => '', +)); + +// +// Class: TriggerOnStateEnter +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:TriggerOnStateEnter' => 'Tetiklenen (duruma girişte)', + 'Class:TriggerOnStateEnter+' => 'Durum değişikliğinde tetiklenen işlemler (duruma giriş)', +)); + +// +// Class: TriggerOnStateLeave +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:TriggerOnStateLeave' => 'Tetiklenen (durum çıkışında)', + 'Class:TriggerOnStateLeave+' => 'Durum değişikliğinde tetiklenen işlemler (duruma çıkış)', +)); + +// +// Class: TriggerOnObjectCreate +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:TriggerOnObjectCreate' => 'Tetiklenen (nesne yaratımında)', + 'Class:TriggerOnObjectCreate+' => 'Verilen sınıf tipi nesne yaratımında tetiklenen işlemler', +)); + +// +// Class: lnkTriggerAction +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkTriggerAction' => 'İşlem/Tetikleme', + 'Class:lnkTriggerAction+' => 'Tetikleme ve işlem arasındaki ilişki', + 'Class:lnkTriggerAction/Attribute:action_id' => 'İşlem', + 'Class:lnkTriggerAction/Attribute:action_id+' => 'Yapılacak işlem', + 'Class:lnkTriggerAction/Attribute:action_name' => 'İşlem', + 'Class:lnkTriggerAction/Attribute:action_name+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_id' => 'Tetikleme', + 'Class:lnkTriggerAction/Attribute:trigger_id+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_name' => 'Tetikleme', + 'Class:lnkTriggerAction/Attribute:trigger_name+' => '', + 'Class:lnkTriggerAction/Attribute:order' => 'Order', + 'Class:lnkTriggerAction/Attribute:order+' => 'İşlem uygulama sırası', +)); + + +?> diff --git a/dictionaries/tr.dictionary.itop.ui.php b/dictionaries/tr.dictionary.itop.ui.php new file mode 100644 index 0000000000..ca7eaef84c --- /dev/null +++ b/dictionaries/tr.dictionary.itop.ui.php @@ -0,0 +1,869 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +////////////////////////////////////////////////////////////////////// +// Classes in 'gui' +////////////////////////////////////////////////////////////////////// +// + +////////////////////////////////////////////////////////////////////// +// Classes in 'application' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: AuditCategory +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:AuditCategory' => 'Denetleme Kategorisi', + 'Class:AuditCategory+' => 'Denetlemedeki kategori', + 'Class:AuditCategory/Attribute:name' => 'Kategori Adı', + 'Class:AuditCategory/Attribute:name+' => 'Kategornin kısa adı', + 'Class:AuditCategory/Attribute:description' => 'Kategori tanımlaması', + 'Class:AuditCategory/Attribute:description+' => 'Kategori tanımlaması', + 'Class:AuditCategory/Attribute:definition_set' => 'Tanımlama seti', + 'Class:AuditCategory/Attribute:definition_set+' => 'Denetlenecek nesneler için OQL ifadesi', + 'Class:AuditCategory/Attribute:rules_list' => 'Denetlem kuralları', + 'Class:AuditCategory/Attribute:rules_list+' => 'Kategori için denetleme kuralları', +)); + +// +// Class: AuditRule +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:AuditRule' => 'Denetleme Kuralı', + 'Class:AuditRule+' => 'Denetleme Kategorisi kuralı', + 'Class:AuditRule/Attribute:name' => 'Kural Adı', + 'Class:AuditRule/Attribute:name+' => 'Kural Adı', + 'Class:AuditRule/Attribute:description' => 'Kural tanımlaması', + 'Class:AuditRule/Attribute:description+' => 'Kural tanımlaması', + 'Class:AuditRule/Attribute:query' => 'Çalıştırılacak Sorgu', + 'Class:AuditRule/Attribute:query+' => 'Çalıştırılcak OQL ifadesi', + 'Class:AuditRule/Attribute:valid_flag' => 'Geçerli nesneler?', + 'Class:AuditRule/Attribute:valid_flag+' => 'Kural geçerli nesne döndürüse doğru, diğer durumda yanlış', + 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'doğru', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => 'doğru', + 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'yanlış', + 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'yanlış', + 'Class:AuditRule/Attribute:category_id' => 'Kategori', + 'Class:AuditRule/Attribute:category_id+' => 'Kuralın kategorisi', + 'Class:AuditRule/Attribute:category_name' => 'Kategori', + 'Class:AuditRule/Attribute:category_name+' => 'Kural için kategori adı', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'addon/userrights' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: User +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:User' => 'Kullanıcı', + 'Class:User+' => 'Kullanıcı', + 'Class:User/Attribute:finalclass' => 'Hesap tipi', + 'Class:User/Attribute:finalclass+' => '', + 'Class:User/Attribute:contactid' => 'İrtibat (kişi)', + 'Class:User/Attribute:contactid+' => 'İrtibat detayları', + 'Class:User/Attribute:last_name' => 'Soyadı', + 'Class:User/Attribute:last_name+' => 'İrtibatın soyadı', + 'Class:User/Attribute:first_name' => 'Adı', + 'Class:User/Attribute:first_name+' => 'İrtibatın adı', + 'Class:User/Attribute:email' => 'E-posta', + 'Class:User/Attribute:email+' => 'Kişinin e-posta adresi', + 'Class:User/Attribute:login' => 'Kullanıcı adı', + 'Class:User/Attribute:login+' => 'Kullanıcı adı', + 'Class:User/Attribute:language' => 'Dil', + 'Class:User/Attribute:language+' => 'Dil', + 'Class:User/Attribute:language/Value:EN US' => 'English', + 'Class:User/Attribute:language/Value:EN US+' => 'English (U.S.)', + 'Class:User/Attribute:language/Value:FR FR' => 'French', + 'Class:User/Attribute:language/Value:FR FR+' => 'French (France)', + 'Class:User/Attribute:language/Value:TR TR' => 'Turkish', + 'Class:User/Attribute:language/Value:TR TR+' => 'Turkish (Turkey)', + 'Class:User/Attribute:profile_list' => 'Profiller', + 'Class:User/Attribute:profile_list+' => 'Kullanıcı rolü', + 'Class:User/Attribute:allowed_org_list' => 'Erişim yetkisi verilen kurumlar', + 'Class:User/Attribute:allowed_org_list+' => 'Kullanıcın erişime yetkili olduğu kurumlar. Kurum tanımlanmaz ise sınırlama olmaz.', + + 'Class:User/Error:LoginMustBeUnique' => 'Kullanıcı adı tekil olmalı - "%1s" mevcut bir kullanıcıya ait.', + 'Class:User/Error:AtLeastOneProfileIsNeeded' => 'En az bir profil kullanıcıya atanmalı', +)); + +// +// Class: URP_Profiles +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_Profiles' => 'Profil', + 'Class:URP_Profiles+' => 'Kullanıcı profili', + 'Class:URP_Profiles/Attribute:name' => 'Adı', + 'Class:URP_Profiles/Attribute:name+' => 'Profil adı', + 'Class:URP_Profiles/Attribute:description' => 'Tanımlama', + 'Class:URP_Profiles/Attribute:description+' => 'Profil tanımlama', + 'Class:URP_Profiles/Attribute:user_list' => 'Kullanıcılar', + 'Class:URP_Profiles/Attribute:user_list+' => 'bu rolü kullanan kullanıcılar', +)); + +// +// Class: URP_Dimensions +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_Dimensions' => 'boyut', + 'Class:URP_Dimensions+' => 'uygulama boyutları (silo kullanımları)', + 'Class:URP_Dimensions/Attribute:name' => 'Adı', + 'Class:URP_Dimensions/Attribute:name+' => 'Boyut adı', + 'Class:URP_Dimensions/Attribute:description' => 'Tanımlama', + 'Class:URP_Dimensions/Attribute:description+' => 'Tanımlama', + 'Class:URP_Dimensions/Attribute:type' => 'Tip', + 'Class:URP_Dimensions/Attribute:type+' => 'sınıf adı veya veri tipi (projection unit)', +)); + +// +// Class: URP_UserProfile +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_UserProfile' => 'Kullanıcı Profili', + 'Class:URP_UserProfile+' => 'Kullanıcı Profili', + 'Class:URP_UserProfile/Attribute:userid' => 'Kullanıcı', + 'Class:URP_UserProfile/Attribute:userid+' => 'Kullanıcı hesabı', + 'Class:URP_UserProfile/Attribute:userlogin' => 'Kullanıcı adı', + 'Class:URP_UserProfile/Attribute:userlogin+' => 'Kullanıcı hesabı', + 'Class:URP_UserProfile/Attribute:profileid' => 'Profil', + 'Class:URP_UserProfile/Attribute:profileid+' => 'Kullanıcı profili', + 'Class:URP_UserProfile/Attribute:profile' => 'Profil', + 'Class:URP_UserProfile/Attribute:profile+' => 'Profil adı', + 'Class:URP_UserProfile/Attribute:reason' => 'Sebep', + 'Class:URP_UserProfile/Attribute:reason+' => 'Kullanıcının bu rolü alma sebebini açıklayınız', +)); + +// +// Class: URP_UserOrg +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_UserOrg' => 'Kullanıcı Kurumu', + 'Class:URP_UserOrg+' => 'İzin verilen kurumlar', + 'Class:URP_UserOrg/Attribute:userid' => 'Kullanıcı', + 'Class:URP_UserOrg/Attribute:userid+' => 'Kullanıcı hesabı', + 'Class:URP_UserOrg/Attribute:userlogin' => 'Kullanıcı', + 'Class:URP_UserOrg/Attribute:userlogin+' => 'Kullanıcı hesabı', + 'Class:URP_UserOrg/Attribute:allowed_org_id' => 'Kurum', + 'Class:URP_UserOrg/Attribute:allowed_org_id+' => 'Erişim yetkisi kurumlar', + 'Class:URP_UserOrg/Attribute:allowed_org_name' => 'Kurumu', + 'Class:URP_UserOrg/Attribute:allowed_org_name+' => 'Erişim yetkisi verilen kurumlar', + 'Class:URP_UserOrg/Attribute:reason' => 'Sebep', + 'Class:URP_UserOrg/Attribute:reason+' => 'Kullanıcının bu rolü alma sebebini açıklayınız', +)); + +// +// Class: URP_ProfileProjection +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_ProfileProjection' => 'profile_projection', + 'Class:URP_ProfileProjection+' => 'profile projections', + 'Class:URP_ProfileProjection/Attribute:dimensionid' => 'Boyut', + 'Class:URP_ProfileProjection/Attribute:dimensionid+' => 'uygulama boyutu', + 'Class:URP_ProfileProjection/Attribute:dimension' => 'Boyut', + 'Class:URP_ProfileProjection/Attribute:dimension+' => 'uygulama boyutu', + 'Class:URP_ProfileProjection/Attribute:profileid' => 'Profil', + 'Class:URP_ProfileProjection/Attribute:profileid+' => 'profil kullanımı', + 'Class:URP_ProfileProjection/Attribute:profile' => 'Profil', + 'Class:URP_ProfileProjection/Attribute:profile+' => 'Profil adı', + 'Class:URP_ProfileProjection/Attribute:value' => 'Değer ifadesi', + 'Class:URP_ProfileProjection/Attribute:value+' => 'OQL ifadesi (kullanıcı $user) | sabit | | +özellik kodu', + 'Class:URP_ProfileProjection/Attribute:attribute' => 'Attribute', + 'Class:URP_ProfileProjection/Attribute:attribute+' => 'Hedef özellik kodu (opsiyonel)', +)); + +// +// Class: URP_ClassProjection +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_ClassProjection' => 'sınıf projeksiyonu', + 'Class:URP_ClassProjection+' => 'sınıf projeksiyonu', + 'Class:URP_ClassProjection/Attribute:dimensionid' => 'Boyut', + 'Class:URP_ClassProjection/Attribute:dimensionid+' => 'uygulama boyutu', + 'Class:URP_ClassProjection/Attribute:dimension' => 'Boyut', + 'Class:URP_ClassProjection/Attribute:dimension+' => 'uygulama boyutu', + 'Class:URP_ClassProjection/Attribute:class' => 'Sınıf', + 'Class:URP_ClassProjection/Attribute:class+' => 'Hedef sınıf', + 'Class:URP_ClassProjection/Attribute:value' => 'Değer ifadesi', + 'Class:URP_ClassProjection/Attribute:value+' => 'OQL ifadesi (kullanıcı $user) | sabit | | +özellik kodu', + 'Class:URP_ClassProjection/Attribute:attribute' => 'Özellik', + 'Class:URP_ClassProjection/Attribute:attribute+' => 'Hedef özellik kodu (opsiyonel)', +)); + +// +// Class: URP_ActionGrant +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_ActionGrant' => 'işlem yetkileri', + 'Class:URP_ActionGrant+' => 'sınıf üzerindeki yetkiler', + 'Class:URP_ActionGrant/Attribute:profileid' => 'Profil', + 'Class:URP_ActionGrant/Attribute:profileid+' => 'Kullanım profili', + 'Class:URP_ActionGrant/Attribute:profile' => 'Profil', + 'Class:URP_ActionGrant/Attribute:profile+' => 'Kullanım profili', + 'Class:URP_ActionGrant/Attribute:class' => 'Sınıf', + 'Class:URP_ActionGrant/Attribute:class+' => 'Hedef sınıf', + 'Class:URP_ActionGrant/Attribute:permission' => 'Erişim yetkisi', + 'Class:URP_ActionGrant/Attribute:permission+' => 'yetkili veya yetkisiz?', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes' => 'evet', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => 'evet', + 'Class:URP_ActionGrant/Attribute:permission/Value:no' => 'hayır', + 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => 'hayır', + 'Class:URP_ActionGrant/Attribute:action' => 'İşlem', + 'Class:URP_ActionGrant/Attribute:action+' => 'verilen sınıf üzerinde uygulanacak işlemler', +)); + +// +// Class: URP_StimulusGrant +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_StimulusGrant' => 'uyarı yetkileri', + 'Class:URP_StimulusGrant+' => 'nesnenin yaşam döngüsündeki uyarı yetkileri', + 'Class:URP_StimulusGrant/Attribute:profileid' => 'Profil', + 'Class:URP_StimulusGrant/Attribute:profileid+' => 'Kullanım profili', + 'Class:URP_StimulusGrant/Attribute:profile' => 'Profil', + 'Class:URP_StimulusGrant/Attribute:profile+' => 'Kullanım profili', + 'Class:URP_StimulusGrant/Attribute:class' => 'Sınıf', + 'Class:URP_StimulusGrant/Attribute:class+' => 'Hedef sınıf', + 'Class:URP_StimulusGrant/Attribute:permission' => 'Yetki', + 'Class:URP_StimulusGrant/Attribute:permission+' => 'yetkili veya yetkisiz?', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => 'evet', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => 'evet', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => 'hayır', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => 'hayır', + 'Class:URP_StimulusGrant/Attribute:stimulus' => 'Uyarı', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => 'uyarı kodu', +)); + +// +// Class: URP_AttributeGrant +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:URP_AttributeGrant' => 'özellik yetkisi', + 'Class:URP_AttributeGrant+' => 'özellik seviyesinde yetki', + 'Class:URP_AttributeGrant/Attribute:actiongrantid' => 'İzin verilen işlem', + 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => 'İşlem izni', + 'Class:URP_AttributeGrant/Attribute:attcode' => 'Özellik', + 'Class:URP_AttributeGrant/Attribute:attcode+' => 'Özellik kodu', +)); + +// +// String from the User Interface: menu, messages, buttons, etc... +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:WelcomeMenu' => 'Hoşgeldiniz', + 'Menu:WelcomeMenu+' => 'iTop\'a Hoşgeldiniz', + 'Menu:WelcomeMenuPage' => 'Hoşgeldiniz', + 'Menu:WelcomeMenuPage+' => 'iTop\'a Hoşgeldiniz', + 'UI:WelcomeMenu:Title' => 'iTop\'a Hoşgeldiniz', + + 'UI:WelcomeMenu:LeftBlock' => '

        iTop açık kaynak Bilişim İşlem Potalıdır.

        +
          Kapsamı: +
        • Bilişim altyapısının tanımlandığı ve dokümante edildiği Konfigürasyon Yönetimi CMDB (Configuration management database)modülü.
        • +
        • Bilişim altyapısı ile ilgili tüm olayların takibi.
        • +
        • Bilişim altyapısının değişim yönetimi.
        • +
        • Bilinen hatalar bilgi kütüphanesi.
        • +
        • Planlı kesintilerin kayıt altına alınması ve ilgililerin uyarılması.
        • +
        • Özet gösterge ekranları
        • +
        +

        Tüm modüller bağımsız olarak, adım adım kurulabilir.

        ', + + 'UI:WelcomeMenu:RightBlock' => '

        iTop servis sağlayıcı maktığı ile hazırlanmış olup, birden fazla müşteri ve kuruma kolaylıkla hizmet vermeye imkan sağlar. +

          iTop, zengin iş süreçleri tanımlama imkanıyla: +
        • Bilişim yönetim etkinliğini
        • +
        • Operasyon performansını
        • +
        • Müşteri memnuniyetini ve yönetimin iş performansı hakkında bilgi sahibi olmasını sağlar.
        • +
        +

        +

        iTop mevcut Bilşim altyapınızla entegre edilmeye açıktır.

        +

        +

          Yeni nesil operasyonel Bilişim portalı : +
        • Bilişim ortamının daha iyi yönetilmesini.
        • +
        • ITIL süreçlerinin kendi başınıza uygulanmaya.
        • +
        • İşletmenin en önemli kaynağı olan dokümantasyonu yönetmesine imkan sağlar.
        • +
        +

        ', + 'UI:WelcomeMenu:AllOpenRequests' => 'Açık istekler: %1$d', + 'UI:WelcomeMenu:MyCalls' => 'İsteklerim', + 'UI:WelcomeMenu:OpenIncidents' => 'Açık Arızalar: %1$d', + 'UI:WelcomeMenu:AllConfigItems' => 'Konfigürasyon Kalemleri: %1$d', + 'UI:WelcomeMenu:MyIncidents' => 'Bana atanan hatalar', + 'UI:AllOrganizations' => ' Tüm Kurumlar ', + 'UI:YourSearch' => 'Arama', + 'UI:LoggedAsMessage' => '%1$s kullanıcısı ile bağlanıldı', + 'UI:LoggedAsMessage+Admin' => '%1$s (Administrator) kullanıcısı ile bağlanıldı', + 'UI:Button:Logoff' => 'Çıkış', + 'UI:Button:GlobalSearch' => 'Arama', + 'UI:Button:Search' => ' Arama ', + 'UI:Button:Query' => ' Sorgu ', + 'UI:Button:Ok' => 'Tamam', + 'UI:Button:Cancel' => 'İptal', + 'UI:Button:Apply' => 'Uygula', + 'UI:Button:Back' => ' << Geri ', + 'UI:Button:Next' => ' İleri >> ', + 'UI:Button:Finish' => ' Bitir ', + 'UI:Button:DoImport' => ' Dışardan Veri alı çalıştır ! ', + 'UI:Button:Done' => ' Biiti ', + 'UI:Button:SimulateImport' => ' Veri alışını simule et ', + 'UI:Button:Test' => 'Test!', + 'UI:Button:Evaluate' => ' Değerlendir ', + 'UI:Button:AddObject' => ' Ekle... ', + 'UI:Button:BrowseObjects' => ' Listele... ', + 'UI:Button:Add' => ' Ekle ', + 'UI:Button:AddToList' => ' << Ekle ', + 'UI:Button:RemoveFromList' => ' Sil >> ', + 'UI:Button:FilterList' => ' Filtreleme... ', + 'UI:Button:Create' => ' Yarat ', + 'UI:Button:Delete' => ' Sil ! ', + 'UI:Button:ChangePassword' => ' Şifre değiştir ', + 'UI:Button:ResetPassword' => ' Şifreyi sıfırla ', + + 'UI:SearchToggle' => 'Ara', + 'UI:ClickToCreateNew' => 'Yeni %1$s yarat', + 'UI:SearchFor_Class' => '%1$s Arama', + 'UI:NoObjectToDisplay' => 'Görüntülenecek nesne bulunamadı.', + 'UI:Error:MandatoryTemplateParameter_object_id' => 'link_attr tanımlandığında object_id alanı zorunludur. Görüntülme (Display) şablonun tanımlamasını kontrol ediniz.', + 'UI:Error:MandatoryTemplateParameter_target_attr' => 'link_attr tanımlandığında target_attr alanı zorunludur. Görüntülme (Display) şablonun tanımlamasını kontrol ediniz.', + 'UI:Error:MandatoryTemplateParameter_group_by' => 'group_by alanı zorunludur. Görüntülme (Display) şablonun tanımlamasını kontrol ediniz.', + 'UI:Error:InvalidGroupByFields' => 'group by geçersiz alan listesi: "%1$s".', + 'UI:Error:UnsupportedStyleOfBlock' => 'Hata: blok için desteklenmeyen stil: "%1$s".', + 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Hatalı ilişki tanımı: yönetilecek sınıfa: %1$s ait ilişki anahtarı (an external key) sınıfında %2$s bulunamadı', + 'UI:Error:Object_Class_Id_NotFound' => 'Nesne: %1$s:%2$d bulunamadı.', + 'UI:Error:WizardCircularReferenceInDependencies' => 'Hata: Alanlar arasında döngüsel bağımlılık (Circular reference in the dependencies) tespit edildi. Veri modelinizi kontrol ediniz.', + 'UI:Error:UploadedFileTooBig' => 'Yüklenmek istenen dosya çok büyük. (üst sınır %1$s). PHP configürasyonunu kontrol ediniz (upload_max_filesize ve post_max_size parametrelerini düzenleyiniz).', + 'UI:Error:UploadedFileTruncated.' => 'Yüklenen dosyanın tamamı yüklenemedi !', + 'UI:Error:NoTmpDir' => 'Gecici dizi (temporary directory) tanımlı değil.', + 'UI:Error:CannotWriteToTmp_Dir' => 'Geçici dosya diske yazılamadı. upload_tmp_dir = "%1$s".', + 'UI:Error:UploadStoppedByExtension_FileName' => 'Dosya yükleme dosya uzantısı nedeniyle duruduruldu. (Dosya adı = "%1$s").', + 'UI:Error:UploadFailedUnknownCause_Code' => 'Dosya yükleme bilinmeyen bir sebeple başarısız oldu. (Hata kodu = "%1$s").', + + 'UI:Error:1ParametersMissing' => 'Hata: Bu operasyon için %1$s parametresi tanımlanmalı.', + 'UI:Error:2ParametersMissing' => 'Hata: Bu operasyon için %1$s ve %2$s parametreleri tanımlanmalı.', + 'UI:Error:3ParametersMissing' => 'Hata: Bu operasyon için %1$s, %2$s ve %3$s parametreleri tanımlanmalı.', + 'UI:Error:4ParametersMissing' => 'Hata: Bu operasyon için %1$s, %2$s, %3$s ve %4$s parametreleri tanımlanmalı.', + 'UI:Error:IncorrectOQLQuery_Message' => 'Hata: hatalı OQL sorgusu: %1$s', + 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Sorgu sırasında hata oluştu: %1$s', + 'UI:Error:ObjectAlreadyUpdated' => 'Hata: nesne hali hazırda güncellendi.', + 'UI:Error:ObjectCannotBeUpdated' => 'Hata: nesne güncellenemedi.', + 'UI:Error:ObjectsAlreadyDeleted' => 'Hata: nesne hali hazırda silinmiş!', + 'UI:Error:BulkDeleteNotAllowedOn_Class' => '%1$s sınıfına ait nesnelerin toplu silimine yetkiniz yok.', + 'UI:Error:DeleteNotAllowedOn_Class' => '%1$s sınıfına ait nesnelerin silimine yetkiniz yok.', + 'UI:Error:BulkModifyNotAllowedOn_Class' => '%1$s sınıfına ait nesnelerin toplu güncellenmesine yetkiniz yok.', + 'UI:Error:ObjectAlreadyCloned' => 'Hata: nesne hali hazırda klonlanmış!', + 'UI:Error:ObjectAlreadyCreated' => 'Hata: nesne hali hazırda yaratılmış!', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => 'Hata: "%3$s" durumundaki %2$s nesnesi için "%1$s" uyarısı geçersizdir.', + + + 'UI:GroupBy:Count' => 'Say', + 'UI:GroupBy:Count+' => 'Eleman sayısı', + 'UI:CountOfObjects' => 'Kritere uyan %1$d nesne bulundu.', + 'UI_CountOfObjectsShort' => '%1$d nesne.', + 'UI:NoObject_Class_ToDisplay' => '%1$s nesne listelenecek', + 'UI:History:LastModified_On_By' => '%1$s tarihinde %2$s tarafından değiştirilmiş.', + 'UI:HistoryTab' => 'Tarihçe', + 'UI:NotificationsTab' => 'Uyarılar', + 'UI:History:Date' => 'Tarih', + 'UI:History:Date+' => 'Değişiklik tarihi', + 'UI:History:User' => 'Kullanıcı', + 'UI:History:User+' => 'Değişikliğ yapan kullanıcı', + 'UI:History:Changes' => 'Değişiklikler', + 'UI:History:Changes+' => 'Nesneye yapılan değişiklikler', + 'UI:Loading' => 'Yükleniyor...', + 'UI:Menu:Actions' => 'İşlemler', + 'UI:Menu:New' => 'Yeni...', + 'UI:Menu:Add' => 'Ekle...', + 'UI:Menu:Manage' => 'Yönet...', + 'UI:Menu:EMail' => 'e-posta', + 'UI:Menu:CSVExport' => 'CSV olarak dışarı ver', + 'UI:Menu:Modify' => 'Düzenle...', + 'UI:Menu:Delete' => 'Sil...', + 'UI:Menu:Manage' => 'Yönet...', + 'UI:Menu:BulkDelete' => 'Sil...', + 'UI:UndefinedObject' => 'tanımsız', + 'UI:Document:OpenInNewWindow:Download' => 'Yeni pencerede aç: %1$s, Karşıdan yükle: %2$s', + 'UI:SelectAllToggle+' => 'Tümünü Seç / Tümünü seçme', + 'UI:TruncatedResults' => '%1$d / %2$d', + 'UI:DisplayAll' => 'Hepsini göster', + 'UI:CollapseList' => 'Gizle', + 'UI:CountOfResults' => '%1$d nesne', + 'UI:ChangesLogTitle' => 'değişiklik kaydı (%1$d):', + 'UI:EmptyChangesLogTitle' => 'deiğişiklik kaydı boş', + 'UI:SearchFor_Class_Objects' => '%1$s nesnelerini ara', + 'UI:OQLQueryBuilderTitle' => 'OQL Sorgu hazırlama', + 'UI:OQLQueryTab' => 'OQL Sorgu', + 'UI:SimpleSearchTab' => 'Basit arama', + 'UI:Details+' => 'Detaylar', + 'UI:SearchValue:Any' => '* Herhangi *', + 'UI:SearchValue:Mixed' => '* karışık *', + 'UI:SelectOne' => '-- Birini seçiniz --', + 'UI:Login:Welcome' => 'iTop\'a Hoşgeldiniz!', + 'UI:Login:IncorrectLoginPassword' => 'Hatalı kullanıcı/şifre tekrar deneyiniz.', + 'UI:Login:IdentifyYourself' => 'Devam etmeden önce kendinizi tanıtınız', + 'UI:Login:UserNamePrompt' => 'Kullanıcı Adı', + 'UI:Login:PasswordPrompt' => 'Şifre', + 'UI:Login:ChangeYourPassword' => 'Şifre Değiştir', + 'UI:Login:OldPasswordPrompt' => 'Mevcut şifre', + 'UI:Login:NewPasswordPrompt' => 'Yeni şifre', + 'UI:Login:RetypeNewPasswordPrompt' => 'Yeni şifre tekrar', + 'UI:Login:IncorrectOldPassword' => 'Hata: mevcut şifre hatalı', + 'UI:LogOffMenu' => 'Çıkış', + 'UI:LogOff:ThankYou' => 'iTop Kullanıdığınız için teşekkürler', + 'UI:LogOff:ClickHereToLoginAgain' => 'Tekrar bağlanmak için tıklayınız...', + 'UI:ChangePwdMenu' => 'Şifre değiştir...', + 'UI:Login:RetypePwdDoesNotMatch' => 'Yeni şifre eşlenmedi !', + 'UI:Button:Login' => 'iTop\'a Giriş', + 'UI:Login:Error:AccessRestricted' => 'iTop erişim sınırlandırıldı. Sistem yöneticisi ile irtibata geçiniz', + 'UI:Login:Error:AccessAdmin' => 'Erişim sistem yönetci hesaplaları ile mümkün. Sistem yöneticisi ile irtibata geçiniz.', + 'UI:CSVImport:MappingSelectOne' => '-- Birini seçiniz --', + 'UI:CSVImport:MappingNotApplicable' => '-- alanı ihmal et --', + 'UI:CSVImport:NoData' => 'Boş veri seti..., veri giriniz!', + 'UI:Title:DataPreview' => 'Veri öngörüntüleme', + 'UI:CSVImport:ErrorOnlyOneColumn' => 'Hata: Veri sadece bir kolon içeriyor. Doğru ayıraç karakteri seçtiniz mi ?', + 'UI:CSVImport:FieldName' => 'Alan %1$d', + 'UI:CSVImport:DataLine1' => 'Veri Satırı 1', + 'UI:CSVImport:DataLine2' => 'Veri Satırı 2', + 'UI:CSVImport:idField' => 'id (Tekil anahtar)', + 'UI:Title:BulkImport' => 'iTop - Toplu giriş', + 'UI:Title:BulkImport+' => 'CSV içeri aktarma aracı', + 'UI:CSVImport:ClassesSelectOne' => '-- Birini seçiniz --', + 'UI:CSVImport:ErrorExtendedAttCode' => 'Hata: "%1$s" hatalı kod, çünkü "%2$s" ile "%3$s" tekil ilişkide değil', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d adet nesne değişmeyecek.', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d adet nesne değiştirilecek.', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d adet nesne eklenecek.', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d adet nesnede hata oluştu.', + 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d adet nesne değişmedi.', + 'UI:CSVImport:ObjectsWereModified' => '%1$d adet nesne güncellendi.', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d adet nesne eklendi.', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d adet nesnede hata tespit edildi.', + 'UI:Title:CSVImportStep2' => 'Step 2 of 5: CSV veri seçenekleri', + 'UI:Title:CSVImportStep3' => 'Step 3 of 5: Veri eşleme', + 'UI:Title:CSVImportStep4' => 'Step 4 of 5: Verinin içeri aktarım simülasyonu', + 'UI:Title:CSVImportStep5' => 'Step 5 of 5: İçeri aktarım tamamlandı', + 'UI:CSVImport:LinesNotImported' => 'Satırlar yüklenemedi:', + 'UI:CSVImport:LinesNotImported+' => 'Aşağıdaki satırlar hata nedeniyle yüklenemedi', + 'UI:CSVImport:SeparatorComma+' => ', (virgül)', + 'UI:CSVImport:SeparatorSemicolon+' => '; (noktalı virgül)', + 'UI:CSVImport:SeparatorTab+' => 'tab', + 'UI:CSVImport:SeparatorOther' => 'diğer:', + 'UI:CSVImport:QualifierDoubleQuote+' => '" (çift tırnak)', + 'UI:CSVImport:QualifierSimpleQuote+' => '\' (tırnak)', + 'UI:CSVImport:QualifierOther' => 'diğer:', + 'UI:CSVImport:TreatFirstLineAsHeader' => 'İlk satırı başlık olarak değerlendir(kolon isimleri)', + 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => 'Skip %1$s line(s) at the beginning of the file', + 'UI:CSVImport:CSVDataPreview' => 'CSV Veri Görüntüleme', + 'UI:CSVImport:SelectFile' => 'İçeri aktarılacak dosyayı seçiniz:', + 'UI:CSVImport:Tab:LoadFromFile' => 'Dosyadan oku', + 'UI:CSVImport:Tab:CopyPaste' => 'Veriyi kopyala yapıştır', + 'UI:CSVImport:Tab:Templates' => 'Şablonlar', + 'UI:CSVImport:PasteData' => 'İçeri aktarılacak veriyi yapıştır:', + 'UI:CSVImport:PickClassForTemplate' => 'İndirilecek şablonu seçiniz: ', + 'UI:CSVImport:SeparatorCharacter' => 'Ayıraç karakteri:', + 'UI:CSVImport:TextQualifierCharacter' => 'Metin belirteç karakteri', + 'UI:CSVImport:CommentsAndHeader' => 'Yorum ve başlık', + 'UI:CSVImport:SelectClass' => 'İçeri aktarılacak sınıfı seçiniz:', + 'UI:CSVImport:AdvancedMode' => 'Uzman modu', + 'UI:CSVImport:AdvancedMode+' => 'Uzman modunda (In advanced mode) "id" (primary key) alanı nesnenin güncellenmesi ve adının değiştirilmesi için kullanılabilir.' . + '"id" (mevcut ise) alanı tek sorgu kriteri olarak kullnılabilri ve diğer sorgu kriterleri ile birleştirilmez.', + 'UI:CSVImport:SelectAClassFirst' => 'Eşlemeyi yapmak için önce sınıfı seçiniz.', + 'UI:CSVImport:HeaderFields' => 'Alanlar', + 'UI:CSVImport:HeaderMappings' => 'Eşlemeler', + 'UI:CSVImport:HeaderSearch' => 'Arama?', + 'UI:CSVImport:AlertIncompleteMapping' => 'Lütfen tüm alanlar için alan eşlemesini yapınız.', + 'UI:CSVImport:AlertNoSearchCriteria' => 'Lütfen en az bir sorgu kriteri seçiniz.', + 'UI:CSVImport:Encoding' => 'Karakter kodlaması', + 'UI:UniversalSearchTitle' => 'iTop - Genel arama', + 'UI:UniversalSearch:Error' => 'Hata: %1$s', + 'UI:UniversalSearch:LabelSelectTheClass' => 'Aranacak sınıfı seçiniz: ', + + 'UI:Audit:Title' => 'iTop - CMDB Denetleme', + 'UI:Audit:InteractiveAudit' => 'Etkileşimli Denetleme', + 'UI:Audit:HeaderAuditRule' => 'Denetleme Kuralı', + 'UI:Audit:HeaderNbObjects' => 'Nesne Sayısı', + 'UI:Audit:HeaderNbErrors' => 'Hata sayısı', + 'UI:Audit:PercentageOk' => '% Tamam', + + 'UI:RunQuery:Title' => 'iTop - OQL Sorgu değerlendirme', + 'UI:RunQuery:QueryExamples' => 'Sorgu örnekleri', + 'UI:RunQuery:HeaderPurpose' => 'Amaç', + 'UI:RunQuery:HeaderPurpose+' => 'Sorgu açıklaması', + 'UI:RunQuery:HeaderOQLExpression' => 'OQL ifadesi', + 'UI:RunQuery:HeaderOQLExpression+' => 'OQL yapısında sorgu', + 'UI:RunQuery:ExpressionToEvaluate' => 'Değerlendirilecek ifade: ', + 'UI:RunQuery:MoreInfo' => 'Sorgu hakkında detaylı bilgi: ', + 'UI:RunQuery:DevelopedQuery' => 'Yeniden düzenlenen sorgu: ', + 'UI:RunQuery:SerializedFilter' => 'Özel filtre: ', + 'UI:RunQuery:Error' => 'Sorgu sırasında hata oluştu: %1$s', + + 'UI:Schema:Title' => 'iTop objects schema', + 'UI:Schema:CategoryMenuItem' => 'Kategori %1$s', + 'UI:Schema:Relationships' => 'İlişkiler', + 'UI:Schema:AbstractClass' => 'Soyut sınıf: bu sınıftan nesne türetilemez.', + 'UI:Schema:NonAbstractClass' => 'Soyut olmayan sınıf: bu sınıftan nesne türetilebilir.', + 'UI:Schema:ClassHierarchyTitle' => 'Sınıf ilişkisi', + 'UI:Schema:AllClasses' => 'Tüm sınıflar', + 'UI:Schema:ExternalKey_To' => 'Harici anahtar %1$s', + 'UI:Schema:Columns_Description' => 'Kolonlar: %1$s', + 'UI:Schema:Default_Description' => 'Öndeğer: "%1$s"', + 'UI:Schema:NullAllowed' => 'Boş olamaz', + 'UI:Schema:NullNotAllowed' => 'Boş olabilir', + 'UI:Schema:Attributes' => 'Özellikler', + 'UI:Schema:AttributeCode' => 'Özellik kodu', + 'UI:Schema:AttributeCode+' => 'Özellik için dahili kod', + 'UI:Schema:Label' => 'Etiket', + 'UI:Schema:Label+' => 'Özellik etiketi', + 'UI:Schema:Type' => 'Tip', + + 'UI:Schema:Type+' => 'Özellik veri tipi', + 'UI:Schema:Origin' => 'Kaynak', + 'UI:Schema:Origin+' => 'Özelliğin tanımlandığı ana sınıf', + 'UI:Schema:Description' => 'Tanımlama', + 'UI:Schema:Description+' => 'Özellik tanımı', + 'UI:Schema:AllowedValues' => 'Alabileceği değerler', + 'UI:Schema:AllowedValues+' => 'Özelliğin alabileceği değer kısıtları', + 'UI:Schema:MoreInfo' => 'Daha fazla bilgi', + 'UI:Schema:MoreInfo+' => 'Veritabanında tanımlı alan için daha fazla bilgi', + 'UI:Schema:SearchCriteria' => 'Arama kriteri', + 'UI:Schema:FilterCode' => 'Filtreleme kodu', + 'UI:Schema:FilterCode+' => 'Arama kriter kodu', + 'UI:Schema:FilterDescription' => 'Tanımlama', + 'UI:Schema:FilterDescription+' => 'Arama kiter kodu tanılaması', + 'UI:Schema:AvailOperators' => 'Kullanılabilir işlemler', + 'UI:Schema:AvailOperators+' => 'Arama kriteri için kullanılabilir işlemler', + 'UI:Schema:ChildClasses' => 'Alt sınıflar', + 'UI:Schema:ReferencingClasses' => 'Refrans sınıflar', + 'UI:Schema:RelatedClasses' => 'İlgili sınıflar', + 'UI:Schema:LifeCycle' => 'yaşam döngüsü', + 'UI:Schema:Triggers' => 'Tetikleyiciler', + 'UI:Schema:Relation_Code_Description' => 'İlişki %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Aşağı: %1$s', + 'UI:Schema:RelationUp_Description' => 'Yukarı: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: %2$d seviye öteler, sorgu: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: (%2$d seviye) ötelenmez, sorgu: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s %2$s\'nın %3$s alanı ile ilişkilendirilmiştir', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s alanı %3$s::%4$s aracılığı %2$s ile ilişkilendirilmiştir', + 'UI:Schema:Links:1-n' => 'Sınıf bağlantısı %1$s (1:n links):', + 'UI:Schema:Links:n-n' => 'Sınıf bağlantısı %1$s (n:n links):', + 'UI:Schema:Links:All' => 'İlişkili sınıfların grafiği', + 'UI:Schema:NoLifeCyle' => 'Bu sınıf için yaşam döngüsü tanımlanmamış.', + 'UI:Schema:LifeCycleTransitions' => 'Geçişler', + 'UI:Schema:LifeCyleAttributeOptions' => 'Özellik seçenekleri', + 'UI:Schema:LifeCycleHiddenAttribute' => 'Gizli', + 'UI:Schema:LifeCycleReadOnlyAttribute' => 'Salt okunur', + 'UI:Schema:LifeCycleMandatoryAttribute' => 'Zorunlu Alan', + 'UI:Schema:LifeCycleAttributeMustChange' => 'Değiştirilmesi gereken', + 'UI:Schema:LifeCycleAttributeMustPrompt' => 'Kullanıcıdan değeri değüiştirmesi istenir', + 'UI:Schema:LifeCycleEmptyList' => 'boş liste', + + 'UI:LinksWidget:Autocomplete+' => 'İlk 3 karakteri giriniz...', + 'UI:Combo:SelectValue' => '--- değer seçiniz ---', + 'UI:Label:SelectedObjects' => 'Seçilen nesneler: ', + 'UI:Label:AvailableObjects' => 'Seçilebilir nesneler: ', + 'UI:Link_Class_Attributes' => '%1$s özellikler', + 'UI:SelectAllToggle+' => 'Tümünü seç / Tümünü seçme', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => '%2$s: %3$s ile ilişkideki %1$s nesnelerini Ekle ', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => '%2$s ile %1$s arasında yeni bağlantı oluştur ', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => '%2$s: %3$s ile bağlantılı %1$s nesnelerini yönet ', + 'UI:AddLinkedObjectsOf_Class' => '%1$s nesnelerini ekle...', + 'UI:RemoveLinkedObjectsOf_Class' => 'Seçili nesnleri sil', + 'UI:Message:EmptyList:UseAdd' => 'Liste boş, Yeni nesne ekleme için "Yeni..." seçiniz.', + 'UI:Message:EmptyList:UseSearchForm' => 'Eklemek istediğiniz nesneleri bulmak için yukarıdaki arama formunu kullanınız.', + + 'UI:Wizard:FinalStepTitle' => 'Final step: confirmation', + 'UI:Title:DeletionOf_Object' => '%1$s silimi', + 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => '%2$s sınıfına ait çoklu %1$d nesne silimi', + 'UI:Delete:NotAllowedToDelete' => 'Bu nesneyi silmek için yetkiniz yok', + 'UI:Delete:NotAllowedToUpdate_Fields' => '%1$s alanlarını güncellemek için yetkiniz yok', + 'UI:Error:NotEnoughRightsToDelete' => 'Nesne yetersiz yetki nedeniyle silinemedi', + 'UI:Error:CannotDeleteBecauseOfDepencies' => 'Bu nesneyi silmek için öncelikli dışarıdan yapılması gereken işlemler var', + 'UI:Archive_User_OnBehalfOf_User' => '%1$s on behalf of %2$s', + 'UI:Delete:AutomaticallyDeleted' => 'otomatik olarak silindi', + 'UI:Delete:AutomaticResetOf_Fields' => '%1$s alanlarını otomatik sıfırla', + 'UI:Delete:CleaningUpRefencesTo_Object' => '%1$s nesnesine verilen tüm referansları temizle...', + 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => '%2$s sınıfına ait %1$d nesnesinin tüm referanslarını temizle ...', + 'UI:Delete:Done+' => 'Ne yapıldı...', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s silindi.', + 'UI:Delete:ConfirmDeletionOf_Name' => '%1$s\'in silimi', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$s sınıfına ait %1$d nesnelerinin silimi ', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => 'Otomatik silimesini mi istiyorsunuz, ancak buna yetkiniz yok', + 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => 'Manuel silinmeli - ancak bu nesneyi silmeye yetkiniz yok, lütfen sistem yöneticisiyle irtibata geçiniz.', + 'UI:Delete:WillBeDeletedAutomatically' => 'Otomatik olarak silinecek', + 'UI:Delete:MustBeDeletedManually' => 'Manuel silinmeli', + 'UI:Delete:CannotUpdateBecause_Issue' => 'Otomatik güncellenmeli, ancak: %1$s', + 'UI:Delete:WillAutomaticallyUpdate_Fields' => 'otomatik güncellenecek (reset: %1$s)', + 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d nesne/ilişki %2$s\'yi referans ediyor', + 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d silinmek istenen nesne/bağlantıları referans veriyor', + 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Veri tabanı doğruluğu(Database integrity) için yeni referans verilmesi engellenmelidir', + 'UI:Delete:Consequence+' => 'Ne yapılacak', + 'UI:Delete:SorryDeletionNotAllowed' => 'Bu nesneyi silmeye yetkiniz yok, yukarıdaki açıklamayı bakınız', + 'UI:Delete:PleaseDoTheManualOperations' => 'Bu nesneyi silmeden önce yukarıdaki işlemleri manuel olarak yapınız', + 'UI:Delect:Confirm_Object' => '%1$s\'i silmek istediğnizden emin misiniz?', + 'UI:Delect:Confirm_Count_ObjectsOf_Class' => '%1$d nesnesini (sınıfı %2$s) silmek istediğinizden emin misiniz?', + 'UI:WelcomeToITop' => 'iTop\'a Hoşgeldiniz', + 'UI:DetailsPageTitle' => 'iTop - %1$s - %2$s detayları', + 'UI:ErrorPageTitle' => 'iTop - Hata', + 'UI:ObjectDoesNotExist' => 'Nesne mevcut değil veya yetkiniz yok.', + 'UI:SearchResultsPageTitle' => 'iTop - Arama Sonuçları', + 'UI:Search:NoSearch' => 'Nothing to search for', + 'UI:FullTextSearchTitle_Text' => '"%1$s" için arama sonuçları:', + 'UI:Search:Count_ObjectsOf_Class_Found' => '%2$s sınıfına ait %1$d nesne bulundu.', + 'UI:Search:NoObjectFound' => 'Kayıt bulunamadı.', + 'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s modifikasyon', + 'UI:ModificationTitle_Class_Object' => '%1$s: %2$s modifikasyonu', + 'UI:ClonePageTitle_Object_Class' => 'iTop - %1$s - %2$s modifikasyonunu klonlayınız', + 'UI:CloneTitle_Class_Object' => '%1$s klonu: %2$s', + 'UI:CreationPageTitle_Class' => 'iTop - Yeni %1$s yaratımı', + 'UI:CreationTitle_Class' => 'Yeni %1$s yarat', + 'UI:SelectTheTypeOf_Class_ToCreate' => 'Yaratılacak %1$s nesne tipini seçiniz', + 'UI:Class_Object_NotUpdated' => 'Değişiklik tespit edilemedi, %1$s (%2$s) güncellenmedi.', + 'UI:Class_Object_Updated' => '%1$s (%2$s) güncellendi.', + 'UI:BulkDeletePageTitle' => 'iTop - Toplu silme işlemi', + 'UI:BulkDeleteTitle' => 'Silmek istediğiniz nesneleri seçiniz:', + 'UI:PageTitle:ObjectCreated' => 'iTop Nesne yaratıldı.', + 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s yaratıldı.', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '%1$s işlemi %2$s durumunda %3$s nesnesine uygulanır. Bir sonraki durum: %4$s.', + 'UI:ObjectCouldNotBeWritten' => 'Nesne kaydedilemedi: %1$s', + 'UI:PageTitle:FatalError' => 'iTop - Kritik Hata', + 'UI:SystemIntrusion' => 'Bu işlem için yetkiniz yok', + 'UI:FatalErrorMessage' => 'Kritik Hata, iTop devam edemiyor.', + 'UI:Error_Details' => 'Hata: %1$s.', + + 'UI:PageTitle:ClassProjections' => 'iTop Kullanıcı Yönetimi - sınıf koruması', + 'UI:PageTitle:ProfileProjections' => 'iTop Kullanıcı Yönetimi - profil koruması', + 'UI:UserManagement:Class' => 'Sınıf', + 'UI:UserManagement:Class+' => 'Nesnin sınıfı', + 'UI:UserManagement:ProjectedObject' => 'Nesne', + 'UI:UserManagement:ProjectedObject+' => 'Projected object', + 'UI:UserManagement:AnyObject' => '* herhangi *', + 'UI:UserManagement:User' => 'Kullanıcı', + 'UI:UserManagement:User+' => 'User involved in the projection', + 'UI:UserManagement:Profile' => 'Profil', + 'UI:UserManagement:Profile+' => 'Profile in which the projection is specified', + 'UI:UserManagement:Action:Read' => 'Oku', + 'UI:UserManagement:Action:Read+' => 'Nesneyi görüntüle', + 'UI:UserManagement:Action:Modify' => 'Güncelle', + 'UI:UserManagement:Action:Modify+' => 'Nesneyi yarat/güncelle', + 'UI:UserManagement:Action:Delete' => 'Sil', + 'UI:UserManagement:Action:Delete+' => 'Nesneleri sil', + 'UI:UserManagement:Action:BulkRead' => 'Toplu oku (dışarı aktar)', + 'UI:UserManagement:Action:BulkRead+' => 'Nesneleri listele veya toplu dışarı aktar', + 'UI:UserManagement:Action:BulkModify' => 'Toplu güncelleme', + 'UI:UserManagement:Action:BulkModify+' => 'Toplu yaratma/güncelleme(CSV içeri aktar)', + 'UI:UserManagement:Action:BulkDelete' => 'Toplu Silim', + 'UI:UserManagement:Action:BulkDelete+' => 'Nesneleri toplu olarak sil', + 'UI:UserManagement:Action:Stimuli' => 'Uyarı', + 'UI:UserManagement:Action:Stimuli+' => 'İzin verilen çoklu işlemler', + 'UI:UserManagement:Action' => 'İşlem', + 'UI:UserManagement:Action+' => 'İşlem kullanıcı tarafından yapıldı', + 'UI:UserManagement:TitleActions' => 'İşlemler', + 'UI:UserManagement:Permission' => 'Yetki', + 'UI:UserManagement:Permission+' => 'Kullanıcı yetkileri', + 'UI:UserManagement:Attributes' => 'Özellikler', + 'UI:UserManagement:ActionAllowed:Yes' => 'Evet', + 'UI:UserManagement:ActionAllowed:No' => 'Hayır', + 'UI:UserManagement:AdminProfile+' => 'Sistem Yöneticisi tüm okuma/yazma işlemleri için yetkilidir.', + 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', + 'UI:UserManagement:NoLifeCycleApplicable+' => 'Bu nesne için yaşam döngüsü tanımsız', + 'UI:UserManagement:GrantMatrix' => 'Yetkiler', + 'UI:UserManagement:LinkBetween_User_And_Profile' => '%1$s ve %2$s arasındaki ilişki', + 'UI:UserManagement:LinkBetween_User_And_Org' => '%1$s ve %2$s arasındaki ilişki', + + 'Menu:AdminTools' => 'Yönetim Araçları', + 'Menu:AdminTools+' => 'Yönetim Araçları', + 'Menu:AdminTools?' => 'Yönetici profiline izin verilen araçlar', + + 'UI:ChangeManagementMenu' => 'Değişiklik Yönetimi', + 'UI:ChangeManagementMenu+' => 'Değişiklik Yönetimi', + 'UI:ChangeManagementMenu:Title' => 'Değişiklik Özeti', + 'UI-ChangeManagementMenu-ChangesByType' => 'Değişiklik tipine göre', + 'UI-ChangeManagementMenu-ChangesByStatus' => 'Değişiklik durumuna göre', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => 'İş grubuna değişiklikler', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => 'Atanmamış Değişiklikler', + + 'UI:ConfigurationItemsMenu'=> 'Konfigürasyon kalemleri', + 'UI:ConfigurationItemsMenu+'=> 'Tüm cihazlar', + 'UI:ConfigurationItemsMenu:Title' => 'Konfigürasyon Kalemlerinin Özeti', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => 'Servers by criticity', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => 'PCs by criticity', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => 'Network devices by criticity', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => 'Applications by criticity', + + 'UI:ConfigurationManagementMenu' => 'Konfigürasyon Yönetimi', + 'UI:ConfigurationManagementMenu+' => 'Konfigürasyon Yönetimi', + 'UI:ConfigurationManagementMenu:Title' => 'Altyapı Özeti', + 'UI-ConfigurationManagementMenu-InfraByType' => 'Infrastructure objects by type', + 'UI-ConfigurationManagementMenu-InfraByStatus' => 'Infrastructure objects by status', + +'UI:ConfigMgmtMenuOverview:Title' => 'Konfigürasyon Yönetimi Gösterge Tablosu', +'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => 'Durumlarına göre Konfigürasyon Kalemleri(KK)', +'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => 'Tiplerine göre Konfigürasyon Kalemleri(KK)', + +'UI:RequestMgmtMenuOverview:Title' => 'Çağrı Yönetimi Gösterge Tablosu', +'UI-RequestManagementOverview-RequestByService' => 'Hizmetlere göre çağrılar', +'UI-RequestManagementOverview-RequestByPriority' => 'Önceliklere göre çağrılar', +'UI-RequestManagementOverview-RequestUnassigned' => 'Henüz atanmamış çağrılar', + +'UI:IncidentMgmtMenuOverview:Title' => 'Arıza Gösterge Tablosu', +'UI-IncidentManagementOverview-IncidentByService' => 'Servislere göre arızalar', +'UI-IncidentManagementOverview-IncidentByPriority' => 'Önceliklere göre arızalar', +'UI-IncidentManagementOverview-IncidentUnassigned' => 'Henüz atanmamış arızalar', + +'UI:ChangeMgmtMenuOverview:Title' => 'Değişiklik Yönetimi Gösterge Tablosu', +'UI-ChangeManagementOverview-ChangeByType' => 'Tiplerine göre değişiklikler', +'UI-ChangeManagementOverview-ChangeUnassigned' => 'Henüz atanmamış değişiklikler', +'UI-ChangeManagementOverview-ChangeWithOutage' => 'Değişiklik nedeniyle devre dışı', + +'UI:ServiceMgmtMenuOverview:Title' => 'Hizmet Yönetimi Gösterge Tablosu', +'UI-ServiceManagementOverview-CustomerContractToRenew' => '30 gün içinde biten Müşteri Sözleşmeleri', +'UI-ServiceManagementOverview-ProviderContractToRenew' => '30 gün içinde biten Tedarikçi Sözleşmeleri', + + 'UI:ContactsMenu' => 'İrtibatlar', + 'UI:ContactsMenu+' => 'İrtibatlar', + 'UI:ContactsMenu:Title' => 'İrtibatlar Özetleri', + 'UI-ContactsMenu-ContactsByLocation' => 'Yerleşkeye göre irtibatlar', + 'UI-ContactsMenu-ContactsByType' => 'Tipine göre irtibatlar', + 'UI-ContactsMenu-ContactsByStatus' => 'Durumuna göre irtibatlar', + + 'Menu:CSVImportMenu' => 'CSV dışardan al', + 'Menu:CSVImportMenu+' => 'Çoklu yaratım veya güncelleme', + + 'Menu:DataModelMenu' => 'Veri Modeli', + 'Menu:DataModelMenu+' => 'Veri Modeli Özeti', + + 'Menu:ExportMenu' => 'Dışarı ver', + 'Menu:ExportMenu+' => 'Sorgu sonucunu HTML, CSV veya XML olarak dışarı aktar', + + 'Menu:NotificationsMenu' => 'Uyarılar', + 'Menu:NotificationsMenu+' => 'Uyarıların yapılandırılması', + 'UI:NotificationsMenu:Title' => 'Uyarıların yapılandırılması', + 'UI:NotificationsMenu:Help' => 'Yardım', + 'UI:NotificationsMenu:HelpContent' => '

        In iTop uyarı mekanizması ihtiyaca göre uyarlanabilir. Uyarılar iki tip nesne üzerine kurulmuştur: tetikleme (triggers) ve işlemler (actions).

        +

        Triggers uyarının ne zaman yapılacağını belirler. 3 tip tetikleme vardır ve nesnenin 3 durumuna ilişkilendirilmiştir: +

          +
        1. the "OnCreate" tetikleme nesne yaratıldığı zaman çalışır
        2. +
        3. the "OnStateEnter" tetikleme nesne belli bir duruma girişinde çalışır
        4. +
        5. the "OnStateLeave" tetikleme nesne belli bir durumdan çıkışında çalışır
        6. +
        +

        +

        +Actions tetikleme olduğunda yapılacak işlemleri belirler. Şimdilik sadece e-posta gönderme işlemi yapılabilmektedir. +E-posta için şablon tanımlanabilmektedir. Şablona parametreler girilebilmektedir (recipients, importance, etc.). +

        +

        Özel sayfa: email.test.php PHP e-posta konfigürnunu test ediniz.

        +

        İşlemin gerçekleşmesi için bir tetikleme ile ilişkilendirilmesi gerekir. +Tetikleme gerçekleştiriğinde işlemler tanımlanan sıra numarası ile gerçekleştirilir.

        ', + 'UI:NotificationsMenu:Triggers' => 'Tetikleyiciler', + 'UI:NotificationsMenu:AvailableTriggers' => 'Kullanılabilir tetikleyiciler', + 'UI:NotificationsMenu:OnCreate' => 'Nesne yaratıldığında', + 'UI:NotificationsMenu:OnStateEnter' => 'Nesnenin durumuna girişinde', + 'UI:NotificationsMenu:OnStateLeave' => 'Nesnenin durumdan çıkışında', + 'UI:NotificationsMenu:Actions' => 'İşlemler', + 'UI:NotificationsMenu:AvailableActions' => 'Kullanılabilir işlemler', + + 'Menu:AuditCategories' => 'Denetleme Kategorileri', + 'Menu:AuditCategories+' => 'Denetleme Kategorileri', + 'Menu:Notifications:Title' => 'Denetleme Kategorileri', + + 'Menu:RunQueriesMenu' => 'Sorgu çalıştır', + 'Menu:RunQueriesMenu+' => 'Sorgu çalıştır', + + 'Menu:DataAdministration' => 'Veri Yönetimi', + 'Menu:DataAdministration+' => 'Veri Yönetimi', + + 'Menu:UniversalSearchMenu' => 'Genel sorgu', + 'Menu:UniversalSearchMenu+' => 'Herhangi bir arama...', + + 'Menu:ApplicationLogMenu' => 'Uygulama olay kayıtları', + 'Menu:ApplicationLogMenu+' => 'Uygulama olay kayıtları', + 'Menu:ApplicationLogMenu:Title' => 'Uygulama olay kayıtları', + + 'Menu:UserManagementMenu' => 'Kullanıcı Yönetimi', + 'Menu:UserManagementMenu+' => 'Kullanıcı Yönetimi', + + 'Menu:ProfilesMenu' => 'Profiller', + 'Menu:ProfilesMenu+' => 'Profiller', + 'Menu:ProfilesMenu:Title' => 'Profiller', + + 'Menu:UserAccountsMenu' => 'Kullanıcı Hesapları', + 'Menu:UserAccountsMenu+' => 'Kullanıcı Hesapları', + 'Menu:UserAccountsMenu:Title' => 'Kullanıcı Hesapları', + + 'UI:iTopVersion:Short' => 'iTop versiyonu %1$s', + 'UI:iTopVersion:Long' => 'iTop %3$s tarihli versiyonu %1$s-%2$s', + 'UI:PropertiesTab' => 'Özellikler', + + 'UI:OpenDocumentInNewWindow_' => 'Dokümanı yeni pencerede aç: %1$s', + 'UI:DownloadDocument_' => 'Dokümanı indir: %1$s', + 'UI:Document:NoPreview' => 'Bu tip doküman için öngösterim mevcut değil', + + 'UI:DeadlineMissedBy_duration' => '%1$s ile kaçırıldı', + 'UI:Deadline_LessThan1Min' => '< 1 dk.', + 'UI:Deadline_Minutes' => '%1$d dk.', + 'UI:Deadline_Hours_Minutes' => '%1$dh %2$ddk', + 'UI:Deadline_Days_Hours_Minutes' => '%1$d gün %2$d saat %3$d dk', + 'UI:Help' => 'Yardım', + 'UI:PasswordConfirm' => '(Onay)', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => 'Yeni %1$s nesneleri eklemeden önce bu nesneyi kaydediniz.', + 'UI:DisplayThisMessageAtStartup' => 'Bu mesajı başlangıçta göster', + 'UI:RelationshipGraph' => 'Grafiksel gösterim', + 'UI:RelationshipList' => 'List', + + 'Portal:Title' => 'iTop Kullanıcı Portalı', + 'Portal:Refresh' => 'Yenile', + 'Portal:Back' => 'Geri', + 'Portal:CreateNewRequest' => 'Yeni istek yarat', + 'Portal:ChangeMyPassword' => 'Şifre değiştir', + 'Portal:Disconnect' => 'Çıkış', + 'Portal:OpenRequests' => 'Açık isteklerim', + 'Portal:ResolvedRequests' => 'Çözdüğüm istekler', + 'Portal:SelectService' => 'Kataloğdan servis seçiniz:', + 'Portal:PleaseSelectOneService' => 'Sevis seçiniz', + 'Portal:SelectSubcategoryFrom_Service' => '%1$s servis için alt kategorsi seçiniz:', + 'Portal:PleaseSelectAServiceSubCategory' => 'Alt kategori seçiniz', + 'Portal:DescriptionOfTheRequest' => 'İstek tanımlaması:', + 'Portal:TitleRequestDetailsFor_Request' => 'İsteğin detayı %1$s:', + 'Portal:NoOpenRequest' => 'Bu kategoride istek yok.', + 'Portal:Button:CloseTicket' => 'Çağrıyı kapat', + 'Portal:EnterYourCommentsOnTicket' => 'İsteğin çözümüne yönelik açıklamalar:', + 'Portal:ErrorNoContactForThisUser' => 'Hata: mevcut kullanıcının irtibat bilgisi yok. Sistem yöneticisi ile irtibata geçiniz.', + + 'Enum:Undefined' => 'Tanımsız', +)); + + + +?> diff --git a/modules/authent-external/module.authent-external.php b/modules/authent-external/module.authent-external.php index 2e15fb6d51..b299d72c90 100644 --- a/modules/authent-external/module.authent-external.php +++ b/modules/authent-external/module.authent-external.php @@ -50,6 +50,7 @@ SetupWebPage::AddModule( 'fr.dict.authent-external.php', 'de.dict.authent-external.php', 'ru.dict.authent-external.php', + 'tr.dict.authent-external.php', ), 'data.struct' => array( //'data.struct.authent-ldap.xml', diff --git a/modules/authent-external/tr.dict.authent-external.php b/modules/authent-external/tr.dict.authent-external.php new file mode 100644 index 0000000000..d6a3540d9c --- /dev/null +++ b/modules/authent-external/tr.dict.authent-external.php @@ -0,0 +1,46 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserExternal +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:UserExternal' => 'Harici kullanıcı', + 'Class:UserExternal+' => 'iTop dışında yetki kontrolü yapılan kullanıcı', +)); + + + +?> diff --git a/modules/authent-ldap/module.authent-ldap.php b/modules/authent-ldap/module.authent-ldap.php index edaa38003b..ceece994dd 100644 --- a/modules/authent-ldap/module.authent-ldap.php +++ b/modules/authent-ldap/module.authent-ldap.php @@ -33,6 +33,7 @@ SetupWebPage::AddModule( 'fr.dict.authent-ldap.php', 'de.dict.authent-ldap.php', 'ru.dict.authent-ldap.php', + 'tr.dict.authent-ldap.php', ), 'data.struct' => array( //'data.struct.authent-ldap.xml', diff --git a/modules/authent-ldap/tr.dict.authent-ldap.php b/modules/authent-ldap/tr.dict.authent-ldap.php new file mode 100644 index 0000000000..8984abbbeb --- /dev/null +++ b/modules/authent-ldap/tr.dict.authent-ldap.php @@ -0,0 +1,47 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserLDAP +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:UserLDAP' => 'LDAP kullanıcısı', + 'Class:UserLDAP+' => 'Yetki kontrolü LDAP tarafından yapılan', + 'Class:UserLDAP/Attribute:password' => 'Şifre', + 'Class:UserLDAP/Attribute:password+' => 'şifre', +)); + + + +?> diff --git a/modules/authent-local/module.authent-local.php b/modules/authent-local/module.authent-local.php index f0ee27eb3e..7359c25cb6 100644 --- a/modules/authent-local/module.authent-local.php +++ b/modules/authent-local/module.authent-local.php @@ -27,6 +27,7 @@ SetupWebPage::AddModule( 'fr.dict.authent-local.php', 'de.dict.authent-local.php', 'ru.dict.authent-local.php', + 'tr.dict.authent-local.php', ), 'data.struct' => array( //'data.struct.authent-local.xml', diff --git a/modules/authent-local/tr.dict.authent-local.php b/modules/authent-local/tr.dict.authent-local.php new file mode 100644 index 0000000000..bf3e994051 --- /dev/null +++ b/modules/authent-local/tr.dict.authent-local.php @@ -0,0 +1,47 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserLocal +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:UserLocal' => 'iTop kullanıcısı', + 'Class:UserLocal+' => 'Yetki kontorlünü iTop tarafından yapılan kullanıcı', + 'Class:UserLocal/Attribute:password' => 'Şifre', + 'Class:UserLocal/Attribute:password+' => 'şifre', +)); + + + +?> diff --git a/modules/itop-basic-1.0.0/disabled.module.itop-basic.php b/modules/itop-basic-1.0.0/disabled.module.itop-basic.php index a67add14ac..a163e9f0f9 100644 --- a/modules/itop-basic-1.0.0/disabled.module.itop-basic.php +++ b/modules/itop-basic-1.0.0/disabled.module.itop-basic.php @@ -27,6 +27,7 @@ SetupWebPage::AddModule( 'es_cr.dict.itop-basic.php', 'fr.dict.itop-basic.php', 'pt_br.dict.itop-basic.php', + 'tr.dict.itop-basic.php', ), 'data.struct' => array( //'data.struct.itop-basic.xml', diff --git a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php index db9a87f92d..544053f8e8 100644 --- a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'de.dict.itop-change-mgmt.php', 'pt_br.dict.itop-change-mgmt.php', 'ru.dict.itop-change-mgmt.php', + 'tr.dict.itop-change-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-change-mgmt.xml', diff --git a/modules/itop-change-mgmt-1.0.0/tr.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/tr.dict.itop-change-mgmt.php new file mode 100644 index 0000000000..cf5cfddc86 --- /dev/null +++ b/modules/itop-change-mgmt-1.0.0/tr.dict.itop-change-mgmt.php @@ -0,0 +1,346 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:ChangeManagement' => 'Değişiklik Yönetimi', + 'Menu:Change:Overview' => 'Özet', + 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'Yeni değişiklik', + 'Menu:NewChange+' => 'Yeni değişiklik isteği yarat', + 'Menu:SearchChanges' => 'Değişiklik ara', + 'Menu:SearchChanges+' => 'Değişiklik isteği ara', + 'Menu:Change:Shortcuts' => 'Kısayollar', + 'Menu:Change:Shortcuts+' => '', + 'Menu:WaitingAcceptance' => 'Kabul bekleyen değişiklik talepleri', + 'Menu:WaitingAcceptance+' => '', + 'Menu:WaitingApproval' => 'Onay bekleyen değişiklik talepleri', + 'Menu:WaitingApproval+' => '', + 'Menu:Changes' => 'Açık değişiklikler', + 'Menu:Changes+' => '', + 'Menu:MyChanges' => 'Bana atanan değişiklik istekleri', + 'Menu:MyChanges+' => 'Bana atanan değişiklik istekleri', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Change +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Change' => 'Değişiklik', + 'Class:Change+' => '', + 'Class:Change/Attribute:start_date' => 'Planlanan başlama tarihi', + 'Class:Change/Attribute:start_date+' => '', + 'Class:Change/Attribute:status' => 'Durumu', + 'Class:Change/Attribute:status+' => '', + 'Class:Change/Attribute:status/Value:new' => 'Yeni', + 'Class:Change/Attribute:status/Value:new+' => '', + 'Class:Change/Attribute:status/Value:validated' => 'Kontrol edilen', + 'Class:Change/Attribute:status/Value:validated+' => '', + 'Class:Change/Attribute:status/Value:rejected' => 'Reddedilen', + 'Class:Change/Attribute:status/Value:rejected+' => '', + 'Class:Change/Attribute:status/Value:assigned' => 'Atanmış', + 'Class:Change/Attribute:status/Value:assigned+' => '', + 'Class:Change/Attribute:status/Value:plannedscheduled' => 'Planlanan', + 'Class:Change/Attribute:status/Value:plannedscheduled+' => '', + 'Class:Change/Attribute:status/Value:approved' => 'Onaylanan', + 'Class:Change/Attribute:status/Value:approved+' => '', + 'Class:Change/Attribute:status/Value:notapproved' => 'Onaylanmayan', + 'Class:Change/Attribute:status/Value:notapproved+' => '', + 'Class:Change/Attribute:status/Value:implemented' => 'Uygulanan', + 'Class:Change/Attribute:status/Value:implemented+' => '', + 'Class:Change/Attribute:status/Value:monitored' => 'İzlenen', + 'Class:Change/Attribute:status/Value:monitored+' => '', + 'Class:Change/Attribute:status/Value:closed' => 'Kapanan', + 'Class:Change/Attribute:status/Value:closed+' => '', + 'Class:Change/Attribute:reason' => 'Sebep', + 'Class:Change/Attribute:reason+' => '', + 'Class:Change/Attribute:requestor_id' => 'İstek sahibi', + 'Class:Change/Attribute:requestor_id+' => '', + 'Class:Change/Attribute:requestor_email' => 'İstek sahibi', + 'Class:Change/Attribute:requestor_email+' => '', + 'Class:Change/Attribute:org_id' => 'Müşteri', + 'Class:Change/Attribute:org_id+' => '', + 'Class:Change/Attribute:org_name' => 'Müşteri', + 'Class:Change/Attribute:org_name+' => '', + 'Class:Change/Attribute:workgroup_id' => 'İş grubu', + 'Class:Change/Attribute:workgroup_id+' => '', + 'Class:Change/Attribute:workgroup_name' => 'İş grubu', + 'Class:Change/Attribute:workgroup_name+' => '', + 'Class:Change/Attribute:creation_date' => 'Yaratıldı', + 'Class:Change/Attribute:creation_date+' => '', + 'Class:Change/Attribute:last_update' => 'Son güncelleme', + 'Class:Change/Attribute:last_update+' => '', + 'Class:Change/Attribute:end_date' => 'Bitiş tarihi', + 'Class:Change/Attribute:end_date+' => '', + 'Class:Change/Attribute:close_date' => 'Bitirilme tarihi', + 'Class:Change/Attribute:close_date+' => '', + 'Class:Change/Attribute:impact' => 'Etkisi', + 'Class:Change/Attribute:impact+' => '', + 'Class:Change/Attribute:agent_id' => 'Yetkili', + 'Class:Change/Attribute:agent_id+' => '', + 'Class:Change/Attribute:agent_name' => 'Yetkili', + 'Class:Change/Attribute:agent_name+' => '', + 'Class:Change/Attribute:agent_email' => 'Yetkili', + 'Class:Change/Attribute:agent_email+' => '', + 'Class:Change/Attribute:supervisor_group_id' => 'Supervizör ekip', + 'Class:Change/Attribute:supervisor_group_id+' => '', + 'Class:Change/Attribute:supervisor_group_name' => 'Supervizör ekip', + 'Class:Change/Attribute:supervisor_group_name+' => '', + 'Class:Change/Attribute:supervisor_id' => 'Supervizör', + 'Class:Change/Attribute:supervisor_id+' => '', + 'Class:Change/Attribute:supervisor_email' => 'Supervizör', + 'Class:Change/Attribute:supervisor_email+' => '', + 'Class:Change/Attribute:manager_group_id' => 'Yönetici ekibi', + 'Class:Change/Attribute:manager_group_id+' => '', + 'Class:Change/Attribute:manager_group_name' => 'Yönetici ekibi', + 'Class:Change/Attribute:manager_group_name+' => '', + 'Class:Change/Attribute:manager_id' => 'Yönetici', + 'Class:Change/Attribute:manager_id+' => '', + 'Class:Change/Attribute:manager_email' => 'Yönetici', + 'Class:Change/Attribute:manager_email+' => '', + 'Class:Change/Attribute:outage' => 'Servis kesilmesi', + 'Class:Change/Attribute:outage+' => '', + 'Class:Change/Attribute:outage/Value:yes' => 'Evet', + 'Class:Change/Attribute:outage/Value:yes+' => '', + 'Class:Change/Attribute:outage/Value:no' => 'Hayır', + 'Class:Change/Attribute:outage/Value:no+' => '', + 'Class:Change/Attribute:change_request' => 'İstek', + 'Class:Change/Attribute:change_request+' => '', + 'Class:Change/Attribute:fallback' => 'Geridönüş planı', + 'Class:Change/Attribute:fallback+' => '', + 'Class:Change/Stimulus:ev_validate' => 'Doğrula', + 'Class:Change/Stimulus:ev_validate+' => '', + 'Class:Change/Stimulus:ev_reject' => 'Ret', + 'Class:Change/Stimulus:ev_reject+' => '', + 'Class:Change/Stimulus:ev_assign' => 'Ata', + 'Class:Change/Stimulus:ev_assign+' => '', + 'Class:Change/Stimulus:ev_reopen' => 'Tekrar aç', + 'Class:Change/Stimulus:ev_reopen+' => '', + 'Class:Change/Stimulus:ev_plan' => 'Planla', + 'Class:Change/Stimulus:ev_plan+' => '', + 'Class:Change/Stimulus:ev_approve' => 'Onayla', + 'Class:Change/Stimulus:ev_approve+' => '', + 'Class:Change/Stimulus:ev_replan' => 'Tekrar planla', + 'Class:Change/Stimulus:ev_replan+' => '', + 'Class:Change/Stimulus:ev_notapprove' => 'Ret', + 'Class:Change/Stimulus:ev_notapprove+' => '', + 'Class:Change/Stimulus:ev_implement' => 'Uygula', + 'Class:Change/Stimulus:ev_implement+' => '', + 'Class:Change/Stimulus:ev_monitor' => 'İzle', + 'Class:Change/Stimulus:ev_monitor+' => '', + 'Class:Change/Stimulus:ev_finish' => 'Bitir', + 'Class:Change/Stimulus:ev_finish+' => '', +)); + +// +// Class: RoutineChange +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:RoutineChange' => 'Sıradan değişiklik', + 'Class:RoutineChange+' => '', + 'Class:RoutineChange/Attribute:status/Value:new' => 'Yeni', + 'Class:RoutineChange/Attribute:status/Value:new+' => '', + 'Class:RoutineChange/Attribute:status/Value:assigned' => 'Atanmış', + 'Class:RoutineChange/Attribute:status/Value:assigned+' => '', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled' => 'Planlanan', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:RoutineChange/Attribute:status/Value:approved' => 'Onaylanan', + 'Class:RoutineChange/Attribute:status/Value:approved+' => '', + 'Class:RoutineChange/Attribute:status/Value:implemented' => 'Uygulanan', + 'Class:RoutineChange/Attribute:status/Value:implemented+' => '', + 'Class:RoutineChange/Attribute:status/Value:monitored' => 'İzlenen', + 'Class:RoutineChange/Attribute:status/Value:monitored+' => '', + 'Class:RoutineChange/Attribute:status/Value:closed' => 'Kapatılan', + 'Class:RoutineChange/Attribute:status/Value:closed+' => '', + 'Class:RoutineChange/Stimulus:ev_validate' => 'Doğrulanan', + 'Class:RoutineChange/Stimulus:ev_validate+' => '', + 'Class:RoutineChange/Stimulus:ev_assign' => 'Atanan', + 'Class:RoutineChange/Stimulus:ev_assign+' => '', + 'Class:RoutineChange/Stimulus:ev_reopen' => 'Tekrar açılan', + 'Class:RoutineChange/Stimulus:ev_reopen+' => '', + 'Class:RoutineChange/Stimulus:ev_plan' => 'Planlanan', + 'Class:RoutineChange/Stimulus:ev_plan+' => '', + 'Class:RoutineChange/Stimulus:ev_replan' => 'Tekrar planlanan', + 'Class:RoutineChange/Stimulus:ev_replan+' => '', + 'Class:RoutineChange/Stimulus:ev_implement' => 'Uygula', + 'Class:RoutineChange/Stimulus:ev_implement+' => '', + 'Class:RoutineChange/Stimulus:ev_monitor' => 'İzle', + 'Class:RoutineChange/Stimulus:ev_monitor+' => '', + 'Class:RoutineChange/Stimulus:ev_finish' => 'Bitir', + 'Class:RoutineChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: ApprovedChange +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ApprovedChange' => 'Onaylanan değişiklik', + 'Class:ApprovedChange+' => '', + 'Class:ApprovedChange/Attribute:approval_date' => 'Onay tarihi', + 'Class:ApprovedChange/Attribute:approval_date+' => '', + 'Class:ApprovedChange/Attribute:approval_comment' => 'Onay yorumu', + 'Class:ApprovedChange/Attribute:approval_comment+' => '', + 'Class:ApprovedChange/Stimulus:ev_validate' => 'Onaylı', + 'Class:ApprovedChange/Stimulus:ev_validate+' => '', + 'Class:ApprovedChange/Stimulus:ev_reject' => 'Reddedilen', + 'Class:ApprovedChange/Stimulus:ev_reject+' => '', + 'Class:ApprovedChange/Stimulus:ev_assign' => 'Ata', + 'Class:ApprovedChange/Stimulus:ev_assign+' => '', + 'Class:ApprovedChange/Stimulus:ev_reopen' => 'Tekrar aç', + 'Class:ApprovedChange/Stimulus:ev_reopen+' => '', + 'Class:ApprovedChange/Stimulus:ev_plan' => 'Planla', + 'Class:ApprovedChange/Stimulus:ev_plan+' => '', + 'Class:ApprovedChange/Stimulus:ev_approve' => 'Onayla', + 'Class:ApprovedChange/Stimulus:ev_approve+' => '', + 'Class:ApprovedChange/Stimulus:ev_replan' => 'Tekrar planla', + 'Class:ApprovedChange/Stimulus:ev_replan+' => '', + 'Class:ApprovedChange/Stimulus:ev_notapprove' => 'Onayı reddet', + 'Class:ApprovedChange/Stimulus:ev_notapprove+' => '', + 'Class:ApprovedChange/Stimulus:ev_implement' => 'Uygula', + 'Class:ApprovedChange/Stimulus:ev_implement+' => '', + 'Class:ApprovedChange/Stimulus:ev_monitor' => 'İzle', + 'Class:ApprovedChange/Stimulus:ev_monitor+' => '', + 'Class:ApprovedChange/Stimulus:ev_finish' => 'Bitir', + 'Class:ApprovedChange/Stimulus:ev_finish+' => '', +)); +// +// Class: NormalChange +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:NormalChange' => 'Normal değişiklik', + 'Class:NormalChange+' => '', + 'Class:NormalChange/Attribute:status/Value:new' => 'Yeni', + 'Class:NormalChange/Attribute:status/Value:new+' => '', + 'Class:NormalChange/Attribute:status/Value:validated' => 'Doğrulanan', + 'Class:NormalChange/Attribute:status/Value:validated+' => '', + 'Class:NormalChange/Attribute:status/Value:rejected' => 'Reddedilen', + 'Class:NormalChange/Attribute:status/Value:rejected+' => '', + 'Class:NormalChange/Attribute:status/Value:assigned' => 'Atanan', + 'Class:NormalChange/Attribute:status/Value:assigned+' => '', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled' => 'Planlanan', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:NormalChange/Attribute:status/Value:approved' => 'Onaylanan', + 'Class:NormalChange/Attribute:status/Value:approved+' => '', + 'Class:NormalChange/Attribute:status/Value:notapproved' => 'Onaylanmayan', + 'Class:NormalChange/Attribute:status/Value:notapproved+' => '', + 'Class:NormalChange/Attribute:status/Value:implemented' => 'Uygulanan', + 'Class:NormalChange/Attribute:status/Value:implemented+' => '', + 'Class:NormalChange/Attribute:status/Value:monitored' => 'İzlenen', + 'Class:NormalChange/Attribute:status/Value:monitored+' => '', + 'Class:NormalChange/Attribute:status/Value:closed' => 'Kapatılan', + 'Class:NormalChange/Attribute:status/Value:closed+' => '', + 'Class:NormalChange/Attribute:acceptance_date' => 'Kabul tarihi', + 'Class:NormalChange/Attribute:acceptance_date+' => '', + 'Class:NormalChange/Attribute:acceptance_comment' => 'Kabul yorumu', + 'Class:NormalChange/Attribute:acceptance_comment+' => '', + 'Class:NormalChange/Stimulus:ev_validate' => 'Doğrula', + 'Class:NormalChange/Stimulus:ev_validate+' => '', + 'Class:NormalChange/Stimulus:ev_reject' => 'Reddet', + 'Class:NormalChange/Stimulus:ev_reject+' => '', + 'Class:NormalChange/Stimulus:ev_assign' => 'Ata', + 'Class:NormalChange/Stimulus:ev_assign+' => '', + 'Class:NormalChange/Stimulus:ev_reopen' => 'Tekrar aç', + 'Class:NormalChange/Stimulus:ev_reopen+' => '', + 'Class:NormalChange/Stimulus:ev_plan' => 'Planla', + 'Class:NormalChange/Stimulus:ev_plan+' => '', + 'Class:NormalChange/Stimulus:ev_approve' => 'Onayla', + 'Class:NormalChange/Stimulus:ev_approve+' => '', + 'Class:NormalChange/Stimulus:ev_replan' => 'Tekrar planla', + 'Class:NormalChange/Stimulus:ev_replan+' => '', + 'Class:NormalChange/Stimulus:ev_notapprove' => 'Onayı reddet', + 'Class:NormalChange/Stimulus:ev_notapprove+' => '', + 'Class:NormalChange/Stimulus:ev_implement' => 'Uygula', + 'Class:NormalChange/Stimulus:ev_implement+' => '', + 'Class:NormalChange/Stimulus:ev_monitor' => 'İzle', + 'Class:NormalChange/Stimulus:ev_monitor+' => '', + 'Class:NormalChange/Stimulus:ev_finish' => 'Bitir', + 'Class:NormalChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: EmergencyChange +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:EmergencyChange' => 'Acil değişiklik', + 'Class:EmergencyChange+' => '', + 'Class:EmergencyChange/Attribute:status/Value:new' => 'Yeni', + 'Class:EmergencyChange/Attribute:status/Value:new+' => '', + 'Class:EmergencyChange/Attribute:status/Value:validated' => 'Doğrulanan', + 'Class:EmergencyChange/Attribute:status/Value:validated+' => '', + 'Class:EmergencyChange/Attribute:status/Value:rejected' => 'Reddedilen', + 'Class:EmergencyChange/Attribute:status/Value:rejected+' => '', + 'Class:EmergencyChange/Attribute:status/Value:assigned' => 'Atanan', + 'Class:EmergencyChange/Attribute:status/Value:assigned+' => '', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled' => 'Planan', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:EmergencyChange/Attribute:status/Value:approved' => 'Onaylanan', + 'Class:EmergencyChange/Attribute:status/Value:approved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:notapproved' => 'Onaylanmayan', + 'Class:EmergencyChange/Attribute:status/Value:notapproved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:implemented' => 'Uygulanan', + 'Class:EmergencyChange/Attribute:status/Value:implemented+' => '', + 'Class:EmergencyChange/Attribute:status/Value:monitored' => 'İzlenen', + 'Class:EmergencyChange/Attribute:status/Value:monitored+' => '', + 'Class:EmergencyChange/Attribute:status/Value:closed' => 'Kapatılan', + 'Class:EmergencyChange/Attribute:status/Value:closed+' => '', + 'Class:EmergencyChange/Stimulus:ev_validate' => 'Doğrula', + 'Class:EmergencyChange/Stimulus:ev_validate+' => '', + 'Class:EmergencyChange/Stimulus:ev_reject' => 'Reddet', + 'Class:EmergencyChange/Stimulus:ev_reject+' => '', + 'Class:EmergencyChange/Stimulus:ev_assign' => 'Ata', + 'Class:EmergencyChange/Stimulus:ev_assign+' => '', + 'Class:EmergencyChange/Stimulus:ev_reopen' => 'Tekrar aç', + 'Class:EmergencyChange/Stimulus:ev_reopen+' => '', + 'Class:EmergencyChange/Stimulus:ev_plan' => 'Planla', + 'Class:EmergencyChange/Stimulus:ev_plan+' => '', + 'Class:EmergencyChange/Stimulus:ev_approve' => 'Onayla', + 'Class:EmergencyChange/Stimulus:ev_approve+' => '', + 'Class:EmergencyChange/Stimulus:ev_replan' => 'Tekrar planla', + 'Class:EmergencyChange/Stimulus:ev_replan+' => '', + 'Class:EmergencyChange/Stimulus:ev_notapprove' => 'Onayı reddet', + 'Class:EmergencyChange/Stimulus:ev_notapprove+' => '', + 'Class:EmergencyChange/Stimulus:ev_implement' => 'Uygula', + 'Class:EmergencyChange/Stimulus:ev_implement+' => '', + 'Class:EmergencyChange/Stimulus:ev_monitor' => 'İzle', + 'Class:EmergencyChange/Stimulus:ev_monitor+' => '', + 'Class:EmergencyChange/Stimulus:ev_finish' => 'Bitir', + 'Class:EmergencyChange/Stimulus:ev_finish+' => '', +)); + +?> diff --git a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php index 8cad3f83bb..5bcb491acb 100644 --- a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php @@ -30,6 +30,7 @@ SetupWebPage::AddModule( 'de.dict.itop-config-mgmt.php', 'pt_br.dict.itop-config-mgmt.php', 'ru.dict.itop-config-mgmt.php', + 'tr.dict.itop-config-mgmt.php', ), 'data.struct' => array( 'data.struct.Audit.xml', diff --git a/modules/itop-config-mgmt-1.0.0/tr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/tr.dict.itop-config-mgmt.php new file mode 100644 index 0000000000..07082bbae6 --- /dev/null +++ b/modules/itop-config-mgmt-1.0.0/tr.dict.itop-config-mgmt.php @@ -0,0 +1,1052 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +////////////////////////////////////////////////////////////////////// +// Relations +////////////////////////////////////////////////////////////////////// +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Relation:impacts/Description' => 'Etkilenen kalemler', + 'Relation:impacts/VerbUp' => 'Etkiler...', + 'Relation:impacts/VerbDown' => 'Etkilenenler...', + 'Relation:depends on/Description' => 'Bu kaleme bağımlı olan kalemler', + 'Relation:depends on/VerbUp' => 'Bağımlı olanlar...', + 'Relation:depends on/VerbDown' => 'Etkiledikleri...', +)); + + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Note: The classes have been grouped by categories: bizmodel +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: Organization +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Organization' => 'Kurum', + 'Class:Organization+' => '', + 'Class:Organization/Attribute:name' => 'Adı', + 'Class:Organization/Attribute:name+' => 'Kullanılan adı', + 'Class:Organization/Attribute:code' => 'Kodu', + 'Class:Organization/Attribute:code+' => 'Kurumu kodu (Siret, DUNS,...)', + 'Class:Organization/Attribute:status' => 'Durumu', + 'Class:Organization/Attribute:status+' => '', + 'Class:Organization/Attribute:status/Value:active' => 'Etkin', + 'Class:Organization/Attribute:status/Value:active+' => 'Etkin', + 'Class:Organization/Attribute:status/Value:inactive' => 'Etkin değil', + 'Class:Organization/Attribute:status/Value:inactive+' => 'Etkin değil', + 'Class:Organization/Attribute:parent_id' => 'Bağlı olduğu kurum', + 'Class:Organization/Attribute:parent_id+' => 'Bağlı olduğu kurum', + 'Class:Organization/Attribute:parent_name' => 'Bağlı olduğu kurumun adı', + 'Class:Organization/Attribute:parent_name+' => 'Bağlı olduğu kurumun adı', +)); + + +// +// Class: Location +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Location' => 'Yerleşke', + 'Class:Location+' => 'Yerleşke : Bölge, Ülke, Şehir, Yerleşke, Bina, Kat, Oda, kabin,...', + 'Class:Location/Attribute:name' => 'Adı', + 'Class:Location/Attribute:name+' => '', + 'Class:Location/Attribute:status' => 'Durumu', + 'Class:Location/Attribute:status+' => '', + 'Class:Location/Attribute:status/Value:active' => 'Etkin', + 'Class:Location/Attribute:status/Value:active+' => 'Etkin', + 'Class:Location/Attribute:status/Value:inactive' => 'Etkin değil', + 'Class:Location/Attribute:status/Value:inactive+' => 'Etkin değil', + 'Class:Location/Attribute:org_id' => 'Kurumun sahibi', + 'Class:Location/Attribute:org_id+' => '', + 'Class:Location/Attribute:org_name' => 'Kurumun sahibinin adı', + 'Class:Location/Attribute:org_name+' => '', + 'Class:Location/Attribute:address' => 'Adres', + 'Class:Location/Attribute:address+' => 'Posta adresi', + 'Class:Location/Attribute:postal_code' => 'Posta kodu', + 'Class:Location/Attribute:postal_code+' => 'Posta kodu', + 'Class:Location/Attribute:city' => 'Şehir', + 'Class:Location/Attribute:city+' => '', + 'Class:Location/Attribute:country' => 'Ülke', + 'Class:Location/Attribute:country+' => '', + 'Class:Location/Attribute:parent_id' => 'Bağlı olduğu yerleşke', + 'Class:Location/Attribute:parent_id+' => '', + 'Class:Location/Attribute:parent_name' => 'Ebebeyn adı', + 'Class:Location/Attribute:parent_name+' => '', + 'Class:Location/Attribute:contact_list' => 'İrtibatlar', + 'Class:Location/Attribute:contact_list+' => 'Yerleşkedeki irtibatlar', + 'Class:Location/Attribute:infra_list' => 'Altyapı', + 'Class:Location/Attribute:infra_list+' => 'Yerleşkedeki Konfigürasyon Kalemleri (KK)', +)); +// +// Class: Group +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Group' => 'Grup', + 'Class:Group+' => '', + 'Class:Group/Attribute:name' => 'Adı', + 'Class:Group/Attribute:name+' => '', + 'Class:Group/Attribute:status' => 'Surumu', + 'Class:Group/Attribute:status+' => '', + 'Class:Group/Attribute:status/Value:implementation' => 'Uygulama', + 'Class:Group/Attribute:status/Value:implementation+' => 'Uygulama', + 'Class:Group/Attribute:status/Value:obsolete' => 'Üretimden kalkan', + 'Class:Group/Attribute:status/Value:obsolete+' => 'Üretimden kalkan', + 'Class:Group/Attribute:status/Value:production' => 'Kullanımda', + 'Class:Group/Attribute:status/Value:production+' => 'Kullanımda', + 'Class:Group/Attribute:org_id' => 'Kurum', + 'Class:Group/Attribute:org_id+' => '', + 'Class:Group/Attribute:owner_name' => 'Adı', + 'Class:Group/Attribute:owner_name+' => 'Kullanılan Adı', + 'Class:Group/Attribute:description' => 'Tanımlama', + 'Class:Group/Attribute:description+' => '', + 'Class:Group/Attribute:type' => 'Tip', + 'Class:Group/Attribute:type+' => '', + 'Class:Group/Attribute:parent_id' => 'Bağlı olduğu grup', + 'Class:Group/Attribute:parent_id+' => '', + 'Class:Group/Attribute:parent_name' => 'Adı', + 'Class:Group/Attribute:parent_name+' => '', + 'Class:Group/Attribute:ci_list' => 'Bağlantılı Konfigürasyon Kalemleri (KK)', + 'Class:Group/Attribute:ci_list+' => '', +)); + +// +// Class: lnkGroupToCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkGroupToCI' => 'Grup / KK', + 'Class:lnkGroupToCI+' => '', + 'Class:lnkGroupToCI/Attribute:group_id' => 'Grup', + 'Class:lnkGroupToCI/Attribute:group_id+' => '', + 'Class:lnkGroupToCI/Attribute:group_name' => 'Adı', + 'Class:lnkGroupToCI/Attribute:group_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_id' => 'KK', + 'Class:lnkGroupToCI/Attribute:ci_id+' => '', + 'Class:lnkGroupToCI/Attribute:ci_name' => 'Adı', + 'Class:lnkGroupToCI/Attribute:ci_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_status' => 'KK durumu', + 'Class:lnkGroupToCI/Attribute:ci_status+' => '', + 'Class:lnkGroupToCI/Attribute:reason' => 'Sebep', + 'Class:lnkGroupToCI/Attribute:reason+' => '', +)); + + +// +// Class: Contact +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Contact' => 'İrtibat', + 'Class:Contact+' => '', + 'Class:Contact/Attribute:name' => 'Adı', + 'Class:Contact/Attribute:name+' => '', + 'Class:Contact/Attribute:status' => 'Durumu', + 'Class:Contact/Attribute:status+' => '', + 'Class:Contact/Attribute:status/Value:active' => 'Etkin', + 'Class:Contact/Attribute:status/Value:active+' => 'Etkin', + 'Class:Contact/Attribute:status/Value:inactive' => 'Etkin değil', + 'Class:Contact/Attribute:status/Value:inactive+' => 'Etkin değil', + 'Class:Contact/Attribute:org_id' => 'Kurum', + 'Class:Contact/Attribute:org_id+' => '', + 'Class:Contact/Attribute:org_name' => 'Kurum', + 'Class:Contact/Attribute:org_name+' => '', + 'Class:Contact/Attribute:email' => 'E-posta', + 'Class:Contact/Attribute:email+' => '', + 'Class:Contact/Attribute:phone' => 'Telefon', + 'Class:Contact/Attribute:phone+' => '', + 'Class:Contact/Attribute:location_id' => 'Yerleşke', + 'Class:Contact/Attribute:location_id+' => '', + 'Class:Contact/Attribute:location_name' => 'Yerleşke', + 'Class:Contact/Attribute:location_name+' => '', + 'Class:Contact/Attribute:ci_list' => 'Konfigürasyon kalemleri', + 'Class:Contact/Attribute:ci_list+' => 'İrtibatla ilgili KK', + 'Class:Contact/Attribute:contract_list' => 'İrtibatlar', + 'Class:Contact/Attribute:contract_list+' => 'İrtibatla ilgili sözleşmeler', + 'Class:Contact/Attribute:service_list' => 'Hizmetler', + 'Class:Contact/Attribute:service_list+' => 'İrtibatla ilgili hizmetler', + 'Class:Contact/Attribute:ticket_list' => 'Çağrılar', + 'Class:Contact/Attribute:ticket_list+' => 'İrtibatla ilgili çağrılar', + 'Class:Contact/Attribute:team_list' => 'Ekipler', + 'Class:Contact/Attribute:team_list+' => 'İrtibatın dahil olduğu ekip', + 'Class:Contact/Attribute:finalclass' => 'Tip', + 'Class:Contact/Attribute:finalclass+' => '', +)); + +// +// Class: Person +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Person' => 'Kişi', + 'Class:Person+' => '', + 'Class:Person/Attribute:first_name' => 'Adı', + 'Class:Person/Attribute:first_name+' => '', + 'Class:Person/Attribute:employee_id' => 'Çalışan No', + 'Class:Person/Attribute:employee_id+' => '', +)); + +// +// Class: Team +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Team' => 'Ekip', + 'Class:Team+' => '', + 'Class:Team/Attribute:member_list' => 'Üyeleri', + 'Class:Team/Attribute:member_list+' => 'Ekip üyeleri', +)); + +// +// Class: lnkTeamToContact +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkTeamToContact' => 'Ekip üyeleri', + 'Class:lnkTeamToContact+' => 'Ekip üyeleri', + 'Class:lnkTeamToContact/Attribute:team_id' => 'Ekip', + 'Class:lnkTeamToContact/Attribute:team_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_id' => 'Üye', + 'Class:lnkTeamToContact/Attribute:contact_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_location_id' => 'Yerleşke', + 'Class:lnkTeamToContact/Attribute:contact_location_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_email' => 'E-posta', + 'Class:lnkTeamToContact/Attribute:contact_email+' => '', + 'Class:lnkTeamToContact/Attribute:contact_phone' => 'Telefon', + 'Class:lnkTeamToContact/Attribute:contact_phone+' => '', + 'Class:lnkTeamToContact/Attribute:role' => 'Rolü', + 'Class:lnkTeamToContact/Attribute:role+' => '', +)); + +// +// Class: Document +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Document' => 'Doküman', + 'Class:Document+' => '', + 'Class:Document/Attribute:name' => 'Adı', + 'Class:Document/Attribute:name+' => '', + 'Class:Document/Attribute:org_id' => 'Kurum', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:org_name' => 'Kurum Adı', + 'Class:Document/Attribute:org_name+' => '', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:description' => 'Tanımlama', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:type' => 'Tip', + 'Class:Document/Attribute:type+' => '', + 'Class:Document/Attribute:type/Value:contract' => 'Sözleşme', + 'Class:Document/Attribute:type/Value:contract+' => '', + 'Class:Document/Attribute:type/Value:networkmap' => 'Ağ haritası', + 'Class:Document/Attribute:type/Value:networkmap+' => '', + 'Class:Document/Attribute:type/Value:presentation' => 'Sunum', + 'Class:Document/Attribute:type/Value:presentation+' => '', + 'Class:Document/Attribute:type/Value:training' => 'Eğitim', + 'Class:Document/Attribute:type/Value:training+' => '', + 'Class:Document/Attribute:type/Value:whitePaper' => 'Tanıtım', + 'Class:Document/Attribute:type/Value:whitePaper+' => '', + 'Class:Document/Attribute:type/Value:workinginstructions' => 'İş talimatı', + 'Class:Document/Attribute:type/Value:workinginstructions+' => '', + 'Class:Document/Attribute:status' => 'Durumu', + 'Class:Document/Attribute:status+' => '', + 'Class:Document/Attribute:status/Value:draft' => 'Taslak', + 'Class:Document/Attribute:status/Value:draft+' => '', + 'Class:Document/Attribute:status/Value:obsolete' => 'Geçersiz', + 'Class:Document/Attribute:status/Value:obsolete+' => '', + 'Class:Document/Attribute:status/Value:published' => 'Yayınlanan', + 'Class:Document/Attribute:status/Value:published+' => '', + 'Class:Document/Attribute:ci_list' => 'Konfigürasyon kalemleri', + 'Class:Document/Attribute:ci_list+' => 'Dokümana referans veren KKler', + 'Class:Document/Attribute:contract_list' => 'İrtibatlar', + 'Class:Document/Attribute:contract_list+' => 'Dokümanla ilgili sözleşmeler', + 'Class:Document/Attribute:service_list' => 'Servisler', + 'Class:Document/Attribute:service_list+' => 'Dokümanla ilgili servisler', + 'Class:Document/Attribute:ticket_list' => 'Çağrılar', + 'Class:Document/Attribute:ticket_list+' => 'Dokümanla ilgili çağrılar', + 'Class:Document:PreviewTab' => 'Ön görünüm', +)); + +// +// Class: WebDoc +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:WebDoc' => 'Web Dokümanı', + 'Class:WebDoc+' => 'Farklı web sunucunda olan dokümanlar', + 'Class:WebDoc/Attribute:url' => 'Url', + 'Class:WebDoc/Attribute:url+' => '', +)); + +// +// Class: Note +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Note' => 'Not', + 'Class:Note+' => '', + 'Class:Note/Attribute:note' => 'Metin', + 'Class:Note/Attribute:note+' => '', +)); + +// +// Class: FileDoc +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:FileDoc' => 'Doküman (dosya)', + 'Class:FileDoc+' => '', + 'Class:FileDoc/Attribute:contents' => 'İçerik', + 'Class:FileDoc/Attribute:contents+' => '', +)); + +// +// Class: Licence +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Licence' => 'Lisans', + 'Class:Licence+' => '', + 'Class:Licence/Attribute:provider' => 'Lisansı veren', + 'Class:Licence/Attribute:provider+' => '', + 'Class:Licence/Attribute:org_id' => 'Sahibi', + 'Class:Licence/Attribute:org_id+' => '', + 'Class:Licence/Attribute:org_name' => 'Adı', + 'Class:Licence/Attribute:org_name+' => 'Adı', + 'Class:Licence/Attribute:product' => 'Ürün', + 'Class:Licence/Attribute:product+' => '', + 'Class:Licence/Attribute:name' => 'Adı', + 'Class:Licence/Attribute:name+' => '', + 'Class:Licence/Attribute:start' => 'Başlangıç tarihi', + 'Class:Licence/Attribute:start+' => '', + 'Class:Licence/Attribute:end' => 'Bitiş tarihi', + 'Class:Licence/Attribute:end+' => '', + 'Class:Licence/Attribute:licence_key' => 'Lisans', + 'Class:Licence/Attribute:licence_key+' => '', + 'Class:Licence/Attribute:scope' => 'Kapsam', + 'Class:Licence/Attribute:scope+' => '', + 'Class:Licence/Attribute:usage_limit' => 'Kullanım limit', + 'Class:Licence/Attribute:usage_limit+' => '', + 'Class:Licence/Attribute:usage_list' => 'Kullanım', + 'Class:Licence/Attribute:usage_list+' => 'Lisansı kullanan uygulamalar', +)); + + +// +// Class: Subnet +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Subnet' => 'Subnet', + 'Class:Subnet+' => '', + //'Class:Subnet/Attribute:name' => 'Adı', + //'Class:Subnet/Attribute:name+' => '', + 'Class:Subnet/Attribute:org_id' => 'Kurum', + 'Class:Subnet/Attribute:org_id+' => '', + 'Class:Subnet/Attribute:description' => 'Tanımlama', + 'Class:Subnet/Attribute:description+' => '', + 'Class:Subnet/Attribute:ip' => 'IP', + 'Class:Subnet/Attribute:ip+' => '', + 'Class:Subnet/Attribute:ip_mask' => 'IP Mask', + 'Class:Subnet/Attribute:ip_mask+' => '', +)); + +// +// Class: Patch +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Patch' => 'Yama', + 'Class:Patch+' => '', + 'Class:Patch/Attribute:name' => 'Adı', + 'Class:Patch/Attribute:name+' => '', + 'Class:Patch/Attribute:description' => 'Tanımlama', + 'Class:Patch/Attribute:description+' => '', + 'Class:Patch/Attribute:target_sw' => 'Hedef uygulama', + 'Class:Patch/Attribute:target_sw+' => 'Hedef yazılım', + 'Class:Patch/Attribute:version' => 'Versiyon', + 'Class:Patch/Attribute:version+' => '', + 'Class:Patch/Attribute:type' => 'Tip', + 'Class:Patch/Attribute:type+' => '', + 'Class:Patch/Attribute:type/Value:application' => 'Uygulama', + 'Class:Patch/Attribute:type/Value:application+' => '', + 'Class:Patch/Attribute:type/Value:os' => 'İşletim sistemi', + 'Class:Patch/Attribute:type/Value:os+' => '', + 'Class:Patch/Attribute:type/Value:security' => 'Güvenlik', + 'Class:Patch/Attribute:type/Value:security+' => '', + 'Class:Patch/Attribute:type/Value:servicepack' => 'Servis paketi', + 'Class:Patch/Attribute:type/Value:servicepack+' => '', + 'Class:Patch/Attribute:ci_list' => 'Cihazlar', + 'Class:Patch/Attribute:ci_list+' => 'Yamanın yüklendiği cihazlar', +)); + +// +// Class: Software +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Software' => 'Yazılım', + 'Class:Software+' => '', + 'Class:Software/Attribute:name' => 'Adı', + 'Class:Software/Attribute:name+' => '', + 'Class:Software/Attribute:description' => 'Tanımlama', + 'Class:Software/Attribute:description+' => '', + 'Class:Software/Attribute:instance_list' => 'Yüklemeler', + 'Class:Software/Attribute:instance_list+' => 'Yazılımın kurulumları', + 'Class:Software/Attribute:finalclass' => 'Tip', + 'Class:Software/Attribute:finalclass+' => '', +)); + +// +// Class: Application +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Application' => 'Uygulama', + 'Class:Application+' => '', + 'Class:Application/Attribute:name' => 'Adı', + 'Class:Application/Attribute:name+' => '', + 'Class:Application/Attribute:description' => 'Tanımlama', + 'Class:Application/Attribute:description+' => '', + 'Class:Application/Attribute:instance_list' => 'Yüklemeler', + 'Class:Application/Attribute:instance_list+' => 'Uygulamanın kurulumları', +)); + +// +// Class: DBServer +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:DBServer' => 'Veritabanı', + 'Class:DBServer+' => 'Veritabanı yazılımı', + 'Class:DBServer/Attribute:instance_list' => 'Yüklemeler', + 'Class:DBServer/Attribute:instance_list+' => 'Veritabanı kurulumları', +)); + +// +// Class: lnkPatchToCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkPatchToCI' => 'Yama kullanımı', + 'Class:lnkPatchToCI+' => '', + 'Class:lnkPatchToCI/Attribute:patch_id' => 'Yama', + 'Class:lnkPatchToCI/Attribute:patch_id+' => '', + 'Class:lnkPatchToCI/Attribute:patch_name' => 'Yama', + 'Class:lnkPatchToCI/Attribute:patch_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_id' => 'KK', + 'Class:lnkPatchToCI/Attribute:ci_id+' => '', + 'Class:lnkPatchToCI/Attribute:ci_name' => 'KK', + 'Class:lnkPatchToCI/Attribute:ci_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_status' => 'KK Durumu', + 'Class:lnkPatchToCI/Attribute:ci_status+' => '', +)); + +// +// Class: FunctionalCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:FunctionalCI' => 'Fonksiyonel KK', + 'Class:FunctionalCI+' => '', + 'Class:FunctionalCI/Attribute:name' => 'Adı', + 'Class:FunctionalCI/Attribute:name+' => '', + 'Class:FunctionalCI/Attribute:status' => 'Durumu', + 'Class:FunctionalCI/Attribute:status+' => '', + 'Class:FunctionalCI/Attribute:status/Value:implementation' => 'Uygulama', + 'Class:FunctionalCI/Attribute:status/Value:implementation+' => '', + 'Class:FunctionalCI/Attribute:status/Value:obsolete' => 'Üretimden kalkan', + 'Class:FunctionalCI/Attribute:status/Value:obsolete+' => '', + 'Class:FunctionalCI/Attribute:status/Value:production' => 'Kullanımda', + 'Class:FunctionalCI/Attribute:status/Value:production+' => '', + 'Class:FunctionalCI/Attribute:org_id' => 'Sahip kurum', + 'Class:FunctionalCI/Attribute:org_id+' => '', + 'Class:FunctionalCI/Attribute:owner_name' => 'Sahip kurum', + 'Class:FunctionalCI/Attribute:owner_name+' => '', + 'Class:FunctionalCI/Attribute:importance' => 'İş açısından kritikliği', + 'Class:FunctionalCI/Attribute:importance+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:high' => 'Yüksek', + 'Class:FunctionalCI/Attribute:importance/Value:high+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:low' => 'Düşük', + 'Class:FunctionalCI/Attribute:importance/Value:low+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:medium' => 'Orta', + 'Class:FunctionalCI/Attribute:importance/Value:medium+' => '', + 'Class:FunctionalCI/Attribute:contact_list' => 'İrtibatlar', + 'Class:FunctionalCI/Attribute:contact_list+' => 'KK için İrtibatlar', + 'Class:FunctionalCI/Attribute:document_list' => 'Dokümanlar', + 'Class:FunctionalCI/Attribute:document_list+' => 'KK için dokümanlar', + 'Class:FunctionalCI/Attribute:solution_list' => 'Uygulama çözümleri', + 'Class:FunctionalCI/Attribute:solution_list+' => 'KKyi kullanan uygulama çözümleri', + 'Class:FunctionalCI/Attribute:contract_list' => 'İrtibatlar', + 'Class:FunctionalCI/Attribute:contract_list+' => 'KKyi destekleyen sözleşmeler', + 'Class:FunctionalCI/Attribute:ticket_list' => 'Çağrılar', + 'Class:FunctionalCI/Attribute:ticket_list+' => 'KK ile ilgili çağrılar', + 'Class:FunctionalCI/Attribute:finalclass' => 'Tip', + 'Class:FunctionalCI/Attribute:finalclass+' => '', +)); + +// +// Class: SoftwareInstance +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:SoftwareInstance' => 'Yazılım Kurulumu', + 'Class:SoftwareInstance+' => '', + 'Class:SoftwareInstance/Attribute:device_id' => 'Cihaz', + 'Class:SoftwareInstance/Attribute:device_id+' => '', + 'Class:SoftwareInstance/Attribute:device_name' => 'Cihaz', + 'Class:SoftwareInstance/Attribute:device_name+' => '', + 'Class:SoftwareInstance/Attribute:licence_id' => 'Lisans', + 'Class:SoftwareInstance/Attribute:licence_id+' => '', + 'Class:SoftwareInstance/Attribute:licence_name' => 'Lisans', + 'Class:SoftwareInstance/Attribute:licence_name+' => '', + 'Class:SoftwareInstance/Attribute:software_name' => 'Yazılım', + 'Class:SoftwareInstance/Attribute:software_name+' => '', + 'Class:SoftwareInstance/Attribute:version' => 'Versiyon', + 'Class:SoftwareInstance/Attribute:version+' => '', + 'Class:SoftwareInstance/Attribute:description' => 'Tanımlama', + 'Class:SoftwareInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationInstance +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ApplicationInstance' => 'Uygulama Kurulumu', + 'Class:ApplicationInstance+' => '', + 'Class:ApplicationInstance/Attribute:software_id' => 'Yazılım', + 'Class:ApplicationInstance/Attribute:software_id+' => '', + 'Class:ApplicationInstance/Attribute:software_name' => 'Adı', + 'Class:ApplicationInstance/Attribute:software_name+' => '', +)); + + +// +// Class: DBServerInstance +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:DBServerInstance' => 'Veritabanı Sunucusu', + 'Class:DBServerInstance+' => '', + 'Class:DBServerInstance/Attribute:software_id' => 'Yazılım', + 'Class:DBServerInstance/Attribute:software_id+' => '', + 'Class:DBServerInstance/Attribute:software_name' => 'Adı', + 'Class:DBServerInstance/Attribute:software_name+' => '', + 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Veritabanları', + 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Veritabanları', +)); + + +// +// Class: DatabaseInstance +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:DatabaseInstance' => 'Veritabanı', + 'Class:DatabaseInstance+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_id' => 'Veritabanı sunucusu', + 'Class:DatabaseInstance/Attribute:db_server_instance_id+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_version' => 'Veritabanı versiyonu', + 'Class:DatabaseInstance/Attribute:db_server_instance_version+' => '', + 'Class:DatabaseInstance/Attribute:description' => 'Tanımlama', + 'Class:DatabaseInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationSolution +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ApplicationSolution' => 'Uygulama çözümleri', + 'Class:ApplicationSolution+' => '', + 'Class:ApplicationSolution/Attribute:description' => 'Tanımlama', + 'Class:ApplicationSolution/Attribute:description+' => '', + 'Class:ApplicationSolution/Attribute:ci_list' => 'Konfigürasyon kalemleri', + 'Class:ApplicationSolution/Attribute:ci_list+' => 'Çözümü oluşturan KKler', + 'Class:ApplicationSolution/Attribute:process_list' => 'İş süreçleri', + 'Class:ApplicationSolution/Attribute:process_list+' => 'Uygulama çözümüne bağımlı iş süreci', +)); + +// +// Class: BusinessProcess +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:BusinessProcess' => 'İş süreci', + 'Class:BusinessProcess+' => '', + 'Class:BusinessProcess/Attribute:description' => 'Tanımlama', + 'Class:BusinessProcess/Attribute:description+' => '', + 'Class:BusinessProcess/Attribute:used_solution_list' => 'Uygulama çözümleri', + 'Class:BusinessProcess/Attribute:used_solution_list+' => 'Sürecin bağımlı olduğu iş süreci', +)); + +// +// Class: ConnectableCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ConnectableCI' => 'Bağlanabilir KK', + 'Class:ConnectableCI+' => 'Fiziksel KK', + 'Class:ConnectableCI/Attribute:brand' => 'Marka', + 'Class:ConnectableCI/Attribute:brand+' => '', + 'Class:ConnectableCI/Attribute:model' => 'Model', + 'Class:ConnectableCI/Attribute:model+' => '', + 'Class:ConnectableCI/Attribute:serial_number' => 'Seri No', + 'Class:ConnectableCI/Attribute:serial_number+' => '', + 'Class:ConnectableCI/Attribute:asset_ref' => 'Kaynak referansı', + 'Class:ConnectableCI/Attribute:asset_ref+' => '', +)); + +// +// Class: NetworkInterface +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:NetworkInterface' => 'Network arayüzü', + 'Class:NetworkInterface+' => '', + 'Class:NetworkInterface/Attribute:device_id' => 'Cihaz', + 'Class:NetworkInterface/Attribute:device_id+' => '', + 'Class:NetworkInterface/Attribute:device_name' => 'Cihaz', + 'Class:NetworkInterface/Attribute:device_name+' => '', + 'Class:NetworkInterface/Attribute:logical_type' => 'Mantıksal tipi', + 'Class:NetworkInterface/Attribute:logical_type+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup' => 'Yedek', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical' => 'Mantıksal', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:port' => 'Port', + 'Class:NetworkInterface/Attribute:logical_type/Value:port+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary' => 'Birincil', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary' => 'İkincil', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary+' => '', + 'Class:NetworkInterface/Attribute:physical_type' => 'Fiziksel tip', + 'Class:NetworkInterface/Attribute:physical_type+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm' => 'ATM', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet' => 'Ethernet', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay' => 'Frame Relay', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan' => 'VLAN', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan+' => '', + 'Class:NetworkInterface/Attribute:ip_address' => 'IP Adresi', + 'Class:NetworkInterface/Attribute:ip_address+' => '', + 'Class:NetworkInterface/Attribute:ip_mask' => 'IP maskesi', + 'Class:NetworkInterface/Attribute:ip_mask+' => '', + 'Class:NetworkInterface/Attribute:mac_address' => 'MAC Adresi', + 'Class:NetworkInterface/Attribute:mac_address+' => '', + 'Class:NetworkInterface/Attribute:speed' => 'Hızı', + 'Class:NetworkInterface/Attribute:speed+' => '', + 'Class:NetworkInterface/Attribute:duplex' => 'Çift yönlü', + 'Class:NetworkInterface/Attribute:duplex+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:auto' => 'Otomatik', + 'Class:NetworkInterface/Attribute:duplex/Value:auto+' => 'Otomatik', + 'Class:NetworkInterface/Attribute:duplex/Value:full' => 'Full', + 'Class:NetworkInterface/Attribute:duplex/Value:full+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:half' => 'Half', + 'Class:NetworkInterface/Attribute:duplex/Value:half+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown' => 'Unknown', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown+' => '', + 'Class:NetworkInterface/Attribute:connected_if' => 'Bağlantıda', + 'Class:NetworkInterface/Attribute:connected_if+' => 'Bağlantıdaki arayüz', + 'Class:NetworkInterface/Attribute:connected_name' => 'Bağlantıda', + 'Class:NetworkInterface/Attribute:connected_name+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id' => 'Bağlı cihazlar', + 'Class:NetworkInterface/Attribute:connected_if_device_id+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name' => 'Cihaz', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name+' => '', + 'Class:NetworkInterface/Attribute:link_type' => 'Hat tipi', + 'Class:NetworkInterface/Attribute:link_type+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink' => 'Hat kapalı', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink' => 'Hat açık', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink+' => '', +)); + + + +// +// Class: Device +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Device' => 'Cihaz', + 'Class:Device+' => '', + 'Class:Device/Attribute:nwinterface_list' => 'Network arayüzleri', + 'Class:Device/Attribute:nwinterface_list+' => '', +)); + +// +// Class: PC +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:PC' => 'PC', + 'Class:PC+' => '', + 'Class:PC/Attribute:cpu' => 'CPU', + 'Class:PC/Attribute:cpu+' => '', + 'Class:PC/Attribute:ram' => 'RAM', + 'Class:PC/Attribute:ram+' => '', + 'Class:PC/Attribute:hdd' => 'Sabit disk', + 'Class:PC/Attribute:hdd+' => '', + 'Class:PC/Attribute:os_family' => 'OS Family', + 'Class:PC/Attribute:os_family+' => '', + 'Class:PC/Attribute:os_version' => 'OS Versiyonu', + 'Class:PC/Attribute:os_version+' => '', + 'Class:PC/Attribute:application_list' => 'Uygulamalar', + 'Class:PC/Attribute:application_list+' => 'PCye yüklü programlar', + 'Class:PC/Attribute:patch_list' => 'Yamalar', + 'Class:PC/Attribute:patch_list+' => 'PCye yüklü yamalar', +)); + +// +// Class: MobileCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:MobileCI' => 'Mobil KK', + 'Class:MobileCI+' => '', +)); + +// +// Class: MobilePhone +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:MobilePhone' => 'Cep telefonu', + 'Class:MobilePhone+' => '', + 'Class:MobilePhone/Attribute:number' => 'Tel No', + 'Class:MobilePhone/Attribute:number+' => '', + 'Class:MobilePhone/Attribute:imei' => 'IMEI', + 'Class:MobilePhone/Attribute:imei+' => '', + 'Class:MobilePhone/Attribute:hw_pin' => 'Hardware PIN', + 'Class:MobilePhone/Attribute:hw_pin+' => '', +)); + +// +// Class: InfrastructureCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:InfrastructureCI' => 'Altyapı KK', + 'Class:InfrastructureCI+' => '', + 'Class:InfrastructureCI/Attribute:description' => 'Tanımlama', + 'Class:InfrastructureCI/Attribute:description+' => '', + 'Class:InfrastructureCI/Attribute:location_id' => 'Yerleşke', + 'Class:InfrastructureCI/Attribute:location_id+' => '', + 'Class:InfrastructureCI/Attribute:location_name' => 'Yerleşke', + 'Class:InfrastructureCI/Attribute:location_name+' => '', + 'Class:InfrastructureCI/Attribute:location_details' => 'Yerleşke detayları', + 'Class:InfrastructureCI/Attribute:location_details+' => '', + 'Class:InfrastructureCI/Attribute:management_ip' => 'Yönetim IPsi', + 'Class:InfrastructureCI/Attribute:management_ip+' => '', + 'Class:InfrastructureCI/Attribute:default_gateway' => 'Default Gateway', + 'Class:InfrastructureCI/Attribute:default_gateway+' => '', +)); + +// +// Class: NetworkDevice +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:NetworkDevice' => 'Ağ Cihazı', + 'Class:NetworkDevice+' => '', + 'Class:NetworkDevice/Attribute:type' => 'Tip', + 'Class:NetworkDevice/Attribute:type+' => '', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator' => 'WAN Accelerator', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator+' => '', + 'Class:NetworkDevice/Attribute:type/Value:firewall' => 'Firewall', + 'Class:NetworkDevice/Attribute:type/Value:firewall+' => '', + 'Class:NetworkDevice/Attribute:type/Value:hub' => 'Hub', + 'Class:NetworkDevice/Attribute:type/Value:hub+' => '', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer' => 'Load Balancer', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer+' => '', + 'Class:NetworkDevice/Attribute:type/Value:router' => 'Router', + 'Class:NetworkDevice/Attribute:type/Value:router+' => '', + 'Class:NetworkDevice/Attribute:type/Value:switch' => 'Switch', + 'Class:NetworkDevice/Attribute:type/Value:switch+' => '', + 'Class:NetworkDevice/Attribute:ios_version' => 'IOS Version', + 'Class:NetworkDevice/Attribute:ios_version+' => '', + 'Class:NetworkDevice/Attribute:ram' => 'RAM', + 'Class:NetworkDevice/Attribute:ram+' => '', + 'Class:NetworkDevice/Attribute:snmp_read' => 'SNMP Read', + 'Class:NetworkDevice/Attribute:snmp_read+' => '', + 'Class:NetworkDevice/Attribute:snmp_write' => 'SNMP Write', + 'Class:NetworkDevice/Attribute:snmp_write+' => '', +)); + +// +// Class: Server +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Server' => 'Sunucu', + 'Class:Server+' => '', + 'Class:Server/Attribute:cpu' => 'CPU', + 'Class:Server/Attribute:cpu+' => '', + 'Class:Server/Attribute:ram' => 'RAM', + 'Class:Server/Attribute:ram+' => '', + 'Class:Server/Attribute:hdd' => 'Sabit Disk', + 'Class:Server/Attribute:hdd+' => '', + 'Class:Server/Attribute:os_family' => 'OS Family', + 'Class:Server/Attribute:os_family+' => '', + 'Class:Server/Attribute:os_version' => 'OS Versiyonu', + 'Class:Server/Attribute:os_version+' => '', + 'Class:Server/Attribute:application_list' => 'Uygulamalar', + 'Class:Server/Attribute:application_list+' => 'Sunucuya yüklü uygulamalar', + 'Class:Server/Attribute:patch_list' => 'Yamalar', + 'Class:Server/Attribute:patch_list+' => 'Sunucuya yüklü yamalar', +)); + +// +// Class: Printer +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Printer' => 'Yazıcı', + 'Class:Printer+' => '', + 'Class:Printer/Attribute:type' => 'Tip', + 'Class:Printer/Attribute:type+' => '', + 'Class:Printer/Attribute:type/Value:mopier' => 'Mopier', + 'Class:Printer/Attribute:type/Value:mopier+' => '', + 'Class:Printer/Attribute:type/Value:printer' => 'Printer', + 'Class:Printer/Attribute:type/Value:printer+' => '', + 'Class:Printer/Attribute:technology' => 'Technology', + 'Class:Printer/Attribute:technology+' => '', + 'Class:Printer/Attribute:technology/Value:inkjet' => 'Inkjet', + 'Class:Printer/Attribute:technology/Value:inkjet+' => '', + 'Class:Printer/Attribute:technology/Value:laser' => 'Laser', + 'Class:Printer/Attribute:technology/Value:laser+' => '', + 'Class:Printer/Attribute:technology/Value:tracer' => 'Tracer', + 'Class:Printer/Attribute:technology/Value:tracer+' => '', +)); + +// +// Class: lnkCIToDoc +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkCIToDoc' => 'Doküman/KK', + 'Class:lnkCIToDoc+' => '', + 'Class:lnkCIToDoc/Attribute:ci_id' => 'KK', + 'Class:lnkCIToDoc/Attribute:ci_id+' => '', + 'Class:lnkCIToDoc/Attribute:ci_name' => 'KK', + 'Class:lnkCIToDoc/Attribute:ci_name+' => '', + 'Class:lnkCIToDoc/Attribute:ci_status' => 'KK Durumu', + 'Class:lnkCIToDoc/Attribute:ci_status+' => '', + 'Class:lnkCIToDoc/Attribute:document_id' => 'Doküman', + 'Class:lnkCIToDoc/Attribute:document_id+' => '', + 'Class:lnkCIToDoc/Attribute:document_name' => 'Doküman', + 'Class:lnkCIToDoc/Attribute:document_name+' => '', + 'Class:lnkCIToDoc/Attribute:document_type' => 'Doküman tipi', + 'Class:lnkCIToDoc/Attribute:document_type+' => '', + 'Class:lnkCIToDoc/Attribute:document_status' => 'Doküman durumu', + 'Class:lnkCIToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkCIToContact +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkCIToContact' => 'KK/İrtibat', + 'Class:lnkCIToContact+' => '', + 'Class:lnkCIToContact/Attribute:ci_id' => 'KK', + 'Class:lnkCIToContact/Attribute:ci_id+' => '', + 'Class:lnkCIToContact/Attribute:ci_name' => 'KK', + 'Class:lnkCIToContact/Attribute:ci_name+' => '', + 'Class:lnkCIToContact/Attribute:ci_status' => 'KK Durumu', + 'Class:lnkCIToContact/Attribute:ci_status+' => '', + 'Class:lnkCIToContact/Attribute:contact_id' => 'İrtibat', + 'Class:lnkCIToContact/Attribute:contact_id+' => '', + 'Class:lnkCIToContact/Attribute:contact_name' => 'İrtibat', + 'Class:lnkCIToContact/Attribute:contact_name+' => '', + 'Class:lnkCIToContact/Attribute:contact_email' => 'İrtibat E-posta', + 'Class:lnkCIToContact/Attribute:contact_email+' => '', + 'Class:lnkCIToContact/Attribute:role' => 'Rolü', + 'Class:lnkCIToContact/Attribute:role+' => 'KK ile ilgili irtibatın rolü', +)); + +// +// Class: lnkSolutionToCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkSolutionToCI' => 'KK/Çözüm', + 'Class:lnkSolutionToCI+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_id' => 'Uygulama Çözümü', + 'Class:lnkSolutionToCI/Attribute:solution_id+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_name' => 'Uygulama Çözümü', + 'Class:lnkSolutionToCI/Attribute:solution_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_id' => 'KK', + 'Class:lnkSolutionToCI/Attribute:ci_id+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_name' => 'KK', + 'Class:lnkSolutionToCI/Attribute:ci_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_status' => 'KK Durumu', + 'Class:lnkSolutionToCI/Attribute:ci_status+' => '', + 'Class:lnkSolutionToCI/Attribute:utility' => 'Yardımcı araç', + 'Class:lnkSolutionToCI/Attribute:utility+' => 'Çözümdeki KK için yardımcı araç', +)); + +// +// Class: lnkProcessToSolution +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkProcessToSolution' => 'İş Süreci/Çözüm', + 'Class:lnkProcessToSolution+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_id' => 'Uygulama Çözümü', + 'Class:lnkProcessToSolution/Attribute:solution_id+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_name' => 'Uygulama Çözümü', + 'Class:lnkProcessToSolution/Attribute:solution_name+' => '', + 'Class:lnkProcessToSolution/Attribute:process_id' => 'Süreç', + 'Class:lnkProcessToSolution/Attribute:process_id+' => '', + 'Class:lnkProcessToSolution/Attribute:process_name' => 'Süreç', + 'Class:lnkProcessToSolution/Attribute:process_name+' => '', + 'Class:lnkProcessToSolution/Attribute:reason' => 'Sebep', + 'Class:lnkProcessToSolution/Attribute:reason+' => 'Çözüm ve süreç arasındaki ilişkinin detayı', +)); + + + +// +// Class extensions +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( +'Class:Subnet/Tab:IPUsage' => 'IP Kullanımı', +'Class:Subnet/Tab:IPUsage-explain' => '%1$s - %2$s aralığındaki IPye sahip arayüzler', +'Class:Subnet/Tab:FreeIPs' => 'Boş IPler', +'Class:Subnet/Tab:FreeIPs-count' => 'Boş IPler: %1$s', +'Class:Subnet/Tab:FreeIPs-explain' => 'Boş IP adresleri', +)); + +// +// Application Menu +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( +'Menu:Catalogs' => 'Kataloglar', +'Menu:Catalogs+' => 'Veri tipleri', +'Menu:Audit' => 'Denetleme', +'Menu:Audit+' => 'Denetleme', +'Menu:Organization' => 'Kurumlar', +'Menu:Organization+' => 'Tüm Kurumlar', +'Menu:Application' => 'Uygulamalar', +'Menu:Application+' => 'Tüm Uygulamalar', +'Menu:DBServer' => 'Veritabanı sunucuları', +'Menu:DBServer+' => 'Veritabanı sunucuları', +'Menu:Audit' => 'Denetleme', +'Menu:ConfigManagement' => 'Konfigürasyon Yönetimi', +'Menu:ConfigManagement+' => 'Konfigürasyon Yönetimi', +'Menu:ConfigManagementOverview' => 'Özet', +'Menu:ConfigManagementOverview+' => 'Özet', +'Menu:Contact' => 'İrtibatlar', +'Menu:Contact+' => 'İrtibatlar', +'Menu:Person' => 'Kişiler', +'Menu:Person+' => 'Tüm Kişiler', +'Menu:Team' => 'Ekipler', +'Menu:Team+' => 'Tüm ekipler', +'Menu:Document' => 'Dokümanlar', +'Menu:Document+' => 'Tüm dokümanlar', +'Menu:Location' => 'Yerleşkeler', +'Menu:Location+' => 'Tüm Yerleşkeler', +'Menu:ConfigManagementCI' => 'Konfigürasyon Kalemleri', +'Menu:ConfigManagementCI+' => 'Konfigürasyon Kalemleri', +'Menu:BusinessProcess' => 'İş süreçleri', +'Menu:BusinessProcess+' => 'Tüm İş süreçleri', +'Menu:ApplicationSolution' => 'Uygulama çözümleri', +'Menu:ApplicationSolution+' => 'Tüm Uygulama çözümleri', +'Menu:ConfigManagementSoftware' => 'Uygulama Yönetimi', +'Menu:Licence' => 'Lisanslar', +'Menu:Licence+' => 'Tüm Lisanslar', +'Menu:Patch' => 'Yamalar', +'Menu:Patch+' => 'Tüm Yamalar', +'Menu:ApplicationInstance' => 'Yüklenen yazılımlar', +'Menu:ApplicationInstance+' => 'Uygulama ve Veritabanı sunucuları', +'Menu:ConfigManagementHardware' => 'Altyapı Yönetimi', +'Menu:Subnet' => 'Subnets', +'Menu:Subnet+' => 'All Subnets', +'Menu:NetworkDevice' => 'Network cihazları', +'Menu:NetworkDevice+' => 'Tüm Network cihazları', +'Menu:Server' => 'Sunucular', +'Menu:Server+' => 'Tüm Sunucular', +'Menu:Printer' => 'Yazıcılar', +'Menu:Printer+' => 'Tüm Yazıcılar', +'Menu:MobilePhone' => 'Cep Telefonları', +'Menu:MobilePhone+' => 'Tüm Cep Telefonları', +'Menu:PC' => 'Kişisel Bilgisayarlar', +'Menu:PC+' => 'Tüm Kişisel Bilgisayarlar', +'Menu:NewContact' => 'Yeni İrtibat', +'Menu:NewContact+' => 'Yeni İrtibat', +'Menu:SearchContacts' => 'İrtibat ara', +'Menu:SearchContacts+' => 'İrtibat ara', +'Menu:NewCI' => 'Yeni KK', +'Menu:NewCI+' => 'Yeni KK', +'Menu:SearchCIs' => 'KK ara', +'Menu:SearchCIs+' => 'KK ara', +'Menu:ConfigManagement:Devices' => 'Cihazlar', +'Menu:ConfigManagement:AllDevices' => 'Cihaz sayısı: %1$d', +'Menu:ConfigManagement:SWAndApps' => 'Yazılım ve uygulamalar', +'Menu:ConfigManagement:Misc' => 'Diğer', +'Menu:Group' => 'KK Grupları', +'Menu:Group+' => 'KK Grupları', +'Menu:ConfigManagement:Shortcuts' => 'Kısalyollar', +'Menu:ConfigManagement:AllContacts' => 'Tüm irtibatlar: %1$d', + +)); +?> diff --git a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php index 2809a345ca..c75ab27b72 100644 --- a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php @@ -32,6 +32,7 @@ SetupWebPage::AddModule( 'de.dict.itop-incident-mgmt.php', 'pt_br.dict.itop-incident-mgmt.php', 'ru.dict.itop-incident-mgmt.php', + 'tr.dict.itop-incident-mgmt.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', diff --git a/modules/itop-incident-mgmt-1.0.0/tr.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/tr.dict.itop-incident-mgmt.php new file mode 100644 index 0000000000..e144e12e40 --- /dev/null +++ b/modules/itop-incident-mgmt-1.0.0/tr.dict.itop-incident-mgmt.php @@ -0,0 +1,73 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:IncidentManagement' => 'Arıza Yönetimi', + 'Menu:IncidentManagement+' => 'Arıza Yönetimi', + 'Menu:Incident:Overview' => 'Özet', + 'Menu:Incident:Overview+' => 'Özet', + 'Menu:NewIncident' => 'Yeni arıza', + 'Menu:NewIncident+' => 'Yeni arıza kaydı yarat', + 'Menu:SearchIncidents' => 'Arıza kayıt arama', + 'Menu:SearchIncidents+' => 'Arıza kayıt arama', + 'Menu:Incident:Shortcuts' => 'Kısayollar', + 'Menu:Incident:Shortcuts+' => '', + 'Menu:Incident:MyIncidents' => 'Bana atanan arıza kayıtları', + 'Menu:Incident:MyIncidents+' => 'Bana atanan arıza kayıtları', + 'Menu:Incident:EscalatedIncidents' => 'Yönetime aktarılan arıza kayıtları', + 'Menu:Incident:EscalatedIncidents+' => 'Yönetime aktarılan arıza kayıtları', + 'Menu:Incident:OpenIncidents' => 'Tüm açık arıza kayıtları', + 'Menu:Incident:OpenIncidents+' => 'Tüm açık arıza kayıtları', + +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: Incident +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Incident' => 'Arıza', + 'Class:Incident+' => '', + 'Class:Incident/Stimulus:ev_assign' => 'Ata', + 'Class:Incident/Stimulus:ev_assign+' => '', + 'Class:Incident/Stimulus:ev_reassign' => 'Tekrar ata', + 'Class:Incident/Stimulus:ev_reassign+' => '', + 'Class:Incident/Stimulus:ev_timeout' => 'Değerlendirme süre aşımı', + 'Class:Incident/Stimulus:ev_timeout+' => '', + 'Class:Incident/Stimulus:ev_resolve' => 'Çözümlendi', + 'Class:Incident/Stimulus:ev_resolve+' => '', + 'Class:Incident/Stimulus:ev_close' => 'Kapatıldı', + 'Class:Incident/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php index 58ae378deb..82af122ad0 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php @@ -32,6 +32,7 @@ SetupWebPage::AddModule( 'de.dict.itop-knownerror-mgmt.php', 'pt_br.dict.itop-knownerror-mgmt.php', 'ru.dict.itop-knownerror-mgmt.php', + 'tr.dict.itop-knownerror-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-knownerror-mgmt.xml', diff --git a/modules/itop-knownerror-mgmt-1.0.0/tr.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/tr.dict.itop-knownerror-mgmt.php new file mode 100644 index 0000000000..9fa37dea39 --- /dev/null +++ b/modules/itop-knownerror-mgmt-1.0.0/tr.dict.itop-knownerror-mgmt.php @@ -0,0 +1,147 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: KnownError +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:KnownError' => 'Bilinen hata', + 'Class:KnownError+' => 'Hata bilinen hatalara kaydedildi', + 'Class:KnownError/Attribute:name' => 'Adı', + 'Class:KnownError/Attribute:name+' => '', + 'Class:KnownError/Attribute:org_id' => 'Müşteri', + 'Class:KnownError/Attribute:org_id+' => '', + 'Class:KnownError/Attribute:cust_name' => 'Müşteri Adı', + 'Class:KnownError/Attribute:cust_name+' => '', + 'Class:KnownError/Attribute:problem_id' => 'İlgili problem', + 'Class:KnownError/Attribute:problem_id+' => '', + 'Class:KnownError/Attribute:problem_ref' => 'Referans', + 'Class:KnownError/Attribute:problem_ref+' => '', + 'Class:KnownError/Attribute:symptom' => 'Belirtisi', + 'Class:KnownError/Attribute:symptom+' => '', + 'Class:KnownError/Attribute:root_cause' => 'Ana sebep', + 'Class:KnownError/Attribute:root_cause+' => '', + 'Class:KnownError/Attribute:workaround' => 'Ara çözüm', + 'Class:KnownError/Attribute:workaround+' => '', + 'Class:KnownError/Attribute:solution' => 'Çözüm', + 'Class:KnownError/Attribute:solution+' => '', + 'Class:KnownError/Attribute:error_code' => 'Hata kodu', + 'Class:KnownError/Attribute:error_code+' => '', + 'Class:KnownError/Attribute:domain' => 'Etki alanı', + 'Class:KnownError/Attribute:domain+' => '', + 'Class:KnownError/Attribute:domain/Value:Application' => 'Uygulama', + 'Class:KnownError/Attribute:domain/Value:Application+' => 'Uygulama', + 'Class:KnownError/Attribute:domain/Value:Desktop' => 'Masaüstü', + 'Class:KnownError/Attribute:domain/Value:Desktop+' => 'Masaüstü', + 'Class:KnownError/Attribute:domain/Value:Network' => 'Ağ', + 'Class:KnownError/Attribute:domain/Value:Network+' => 'Ağ', + 'Class:KnownError/Attribute:domain/Value:Server' => 'Sunucu', + 'Class:KnownError/Attribute:domain/Value:Server+' => 'Sunucu', + 'Class:KnownError/Attribute:vendor' => 'Üretici', + 'Class:KnownError/Attribute:vendor+' => '', + 'Class:KnownError/Attribute:model' => 'Model', + 'Class:KnownError/Attribute:model+' => '', + 'Class:KnownError/Attribute:version' => 'Versiyon', + 'Class:KnownError/Attribute:version+' => '', + 'Class:KnownError/Attribute:ci_list' => 'KKler', + 'Class:KnownError/Attribute:ci_list+' => '', + 'Class:KnownError/Attribute:document_list' => 'Dokümanlar', + 'Class:KnownError/Attribute:document_list+' => '', +)); + + +// +// Class: lnkInfraError +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkInfraError' => 'Altyapı hata bağlantısı', + 'Class:lnkInfraError+' => 'Alyapı ile ilgili bilinen hata', + 'Class:lnkInfraError/Attribute:infra_id' => 'KK', + 'Class:lnkInfraError/Attribute:infra_id+' => '', + 'Class:lnkInfraError/Attribute:infra_name' => 'KK Adı', + 'Class:lnkInfraError/Attribute:infra_name+' => '', + 'Class:lnkInfraError/Attribute:infra_status' => 'KK durumu', + 'Class:lnkInfraError/Attribute:infra_status+' => '', + 'Class:lnkInfraError/Attribute:error_id' => 'Hata', + 'Class:lnkInfraError/Attribute:error_id+' => '', + 'Class:lnkInfraError/Attribute:error_name' => 'Hata adı', + 'Class:lnkInfraError/Attribute:error_name+' => '', + 'Class:lnkInfraError/Attribute:reason' => 'Sebep', + 'Class:lnkInfraError/Attribute:reason+' => '', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkDocumentError' => 'Bağlı Doküman hatası', + 'Class:lnkDocumentError+' => 'Doküman ve bilinen hata bağlantısı', + 'Class:lnkDocumentError/Attribute:doc_id' => 'Doküman', + 'Class:lnkDocumentError/Attribute:doc_id+' => '', + 'Class:lnkDocumentError/Attribute:doc_name' => 'Doküman Adı', + 'Class:lnkDocumentError/Attribute:doc_name+' => '', + 'Class:lnkDocumentError/Attribute:error_id' => 'Hata', + 'Class:lnkDocumentError/Attribute:error_id+' => '', + 'Class:lnkDocumentError/Attribute:error_name' => 'Hata Adı', + 'Class:lnkDocumentError/Attribute:error_name+' => '', + 'Class:lnkDocumentError/Attribute:link_type' => 'Bilgi', + 'Class:lnkDocumentError/Attribute:link_type+' => '', +)); + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:NewError' => 'Yeni bilinen hata', + 'Menu:NewError+' => 'Yeni bilinen hata yatarımı', + 'Menu:SearchError' => 'Bilinen hataları ara', + 'Menu:SearchError+' => 'Bilinen hataları ara', + 'Menu:Problem:KnownErrors' => 'Tüm bilinen hatalar', + 'Menu:Problem:KnownErrors+' => 'Tüm bilinen hatalar', +)); +?> diff --git a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php index c077481a51..feb36a8b74 100644 --- a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php @@ -32,6 +32,7 @@ SetupWebPage::AddModule( 'de.dict.itop-problem-mgmt.php', 'pt_br.dict.itop-problem-mgmt.php', 'ru.dict.itop-problem-mgmt.php', + 'tr.dict.itop-problem-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-problem-mgmt.xml', diff --git a/modules/itop-problem-mgmt-1.0.0/tr.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/tr.dict.itop-problem-mgmt.php new file mode 100644 index 0000000000..a7e50b6272 --- /dev/null +++ b/modules/itop-problem-mgmt-1.0.0/tr.dict.itop-problem-mgmt.php @@ -0,0 +1,165 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + + + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:ProblemManagement' => 'Problem Yönetimi', + 'Menu:ProblemManagement+' => 'Problem Yönetimi', + 'Menu:Problem:Overview' => 'Özet', + 'Menu:Problem:Overview+' => 'Özet', + 'Menu:NewProblem' => 'Yeni Problem', + 'Menu:NewProblem+' => 'Yeni Problem', + 'Menu:SearchProblems' => 'Problem Ara', + 'Menu:SearchProblems+' => 'Problem Ara', + 'Menu:Problem:Shortcuts' => 'Kısayollar', + 'Menu:Problem:MyProblems' => 'Problemlerim', + 'Menu:Problem:MyProblems+' => 'Problemlerim', + 'Menu:Problem:OpenProblems' => 'Tüm açık problemler', + 'Menu:Problem:OpenProblems+' => 'Tüm açık problemler', + 'UI-ProblemManagementOverview-ProblemByService' => 'Servis problemleri', + 'UI-ProblemManagementOverview-ProblemByService+' => 'Servis problemleri', + 'UI-ProblemManagementOverview-ProblemByPriority' => 'Önceliklere göre problemler', + 'UI-ProblemManagementOverview-ProblemByPriority+' => 'Önceliklere göre problemler', + 'UI-ProblemManagementOverview-ProblemUnassigned' => 'Atanmamış Problemler', + 'UI-ProblemManagementOverview-ProblemUnassigned+' => 'Atanmamış Problemler', + 'UI:ProblemMgmtMenuOverview:Title' => 'Problem Yönetimi Gösterge Tablosu', + 'UI:ProblemMgmtMenuOverview:Title+' => 'Problem Yönetimi Gösterge Tablosu', + +)); +// +// Class: Problem +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Problem' => 'Problem', + 'Class:Problem+' => '', + 'Class:Problem/Attribute:status' => 'Durum', + 'Class:Problem/Attribute:status+' => '', + 'Class:Problem/Attribute:status/Value:new' => 'Yeni', + 'Class:Problem/Attribute:status/Value:new+' => '', + 'Class:Problem/Attribute:status/Value:assigned' => 'Atanmış', + 'Class:Problem/Attribute:status/Value:assigned+' => '', + 'Class:Problem/Attribute:status/Value:resolved' => 'Çözülmüş', + 'Class:Problem/Attribute:status/Value:resolved+' => '', + 'Class:Problem/Attribute:status/Value:closed' => 'Kapanmış', + 'Class:Problem/Attribute:status/Value:closed+' => '', + 'Class:Problem/Attribute:org_id' => 'Müşteri', + 'Class:Problem/Attribute:org_id+' => '', + 'Class:Problem/Attribute:org_name' => 'Adı', + 'Class:Problem/Attribute:org_name+' => 'Yaygın adı', + 'Class:Problem/Attribute:service_id' => 'Servis', + 'Class:Problem/Attribute:service_id+' => '', + 'Class:Problem/Attribute:service_name' => 'Adı', + 'Class:Problem/Attribute:service_name+' => '', + 'Class:Problem/Attribute:servicesubcategory_id' => 'Servis Kategorisi', + 'Class:Problem/Attribute:servicesubcategory_id+' => '', + 'Class:Problem/Attribute:servicesubcategory_name' => 'Adı', + 'Class:Problem/Attribute:servicesubcategory_name+' => '', + 'Class:Problem/Attribute:product' => 'Ürün', + 'Class:Problem/Attribute:product+' => '', + 'Class:Problem/Attribute:impact' => 'Etkisi', + 'Class:Problem/Attribute:impact+' => '', + 'Class:Problem/Attribute:impact/Value:1' => 'Kişi', + 'Class:Problem/Attribute:impact/Value:1+' => '', + 'Class:Problem/Attribute:impact/Value:2' => 'Servis', + 'Class:Problem/Attribute:impact/Value:2+' => '', + 'Class:Problem/Attribute:impact/Value:3' => 'Bölüm', + 'Class:Problem/Attribute:impact/Value:3+' => '', + 'Class:Problem/Attribute:urgency' => 'Aciliyeti', + 'Class:Problem/Attribute:urgency+' => '', + 'Class:Problem/Attribute:urgency/Value:1' => 'Düşük', + 'Class:Problem/Attribute:urgency/Value:1+' => 'Düşük', + 'Class:Problem/Attribute:urgency/Value:2' => 'Orta', + 'Class:Problem/Attribute:urgency/Value:2+' => 'Orta', + 'Class:Problem/Attribute:urgency/Value:3' => 'Yüksek', + 'Class:Problem/Attribute:urgency/Value:3+' => 'Yüksek', + 'Class:Problem/Attribute:priority' => 'Öncelik', + 'Class:Problem/Attribute:priority+' => '', + 'Class:Problem/Attribute:priority/Value:1' => 'Düşük', + 'Class:Problem/Attribute:priority/Value:1+' => '', + 'Class:Problem/Attribute:priority/Value:2' => 'Orta', + 'Class:Problem/Attribute:priority/Value:2+' => '', + 'Class:Problem/Attribute:priority/Value:3' => 'Yüksek', + 'Class:Problem/Attribute:priority/Value:3+' => '', + 'Class:Problem/Attribute:workgroup_id' => 'Çalışma Grubu', + 'Class:Problem/Attribute:workgroup_id+' => '', + 'Class:Problem/Attribute:workgroup_name' => 'Adı', + 'Class:Problem/Attribute:workgroup_name+' => '', + 'Class:Problem/Attribute:agent_id' => 'Sorumlu', + 'Class:Problem/Attribute:agent_id+' => '', + 'Class:Problem/Attribute:agent_name' => 'Sorumlu Adı', + 'Class:Problem/Attribute:agent_name+' => '', + 'Class:Problem/Attribute:agent_email' => 'Sorumlu e-posta', + 'Class:Problem/Attribute:agent_email+' => '', + 'Class:Problem/Attribute:related_change_id' => 'İlgili değişiklik', + 'Class:Problem/Attribute:related_change_id+' => '', + 'Class:Problem/Attribute:related_change_ref' => 'Referans', + 'Class:Problem/Attribute:related_change_ref+' => '', + 'Class:Problem/Attribute:close_date' => 'Kapanış Tarihi', + 'Class:Problem/Attribute:close_date+' => '', + 'Class:Problem/Attribute:last_update' => 'Son güncelleme tarihi', + 'Class:Problem/Attribute:last_update+' => '', + 'Class:Problem/Attribute:assignment_date' => 'Atanma tarihi', + 'Class:Problem/Attribute:assignment_date+' => '', + 'Class:Problem/Attribute:resolution_date' => 'Çözülme tarihi', + 'Class:Problem/Attribute:resolution_date+' => '', + 'Class:Problem/Attribute:knownerrors_list' => 'Bilinen Hatalar', + 'Class:Problem/Attribute:knownerrors_list+' => '', + 'Class:Problem/Stimulus:ev_assign' => 'Ata', + 'Class:Problem/Stimulus:ev_assign+' => '', + 'Class:Problem/Stimulus:ev_reassign' => 'Yeniden ata', + 'Class:Problem/Stimulus:ev_reassign+' => '', + 'Class:Problem/Stimulus:ev_resolve' => 'Çöz', + 'Class:Problem/Stimulus:ev_resolve+' => '', + 'Class:Problem/Stimulus:ev_close' => 'Kapat', + 'Class:Problem/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php index 9ea24c3af1..d4ec3f4d09 100644 --- a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'de.dict.itop-request-mgmt.php', 'pt_br.dict.itop-request-mgmt.php', 'ru.dict.itop-request-mgmt.php', + 'tr.dict.itop-request-mgmt.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', diff --git a/modules/itop-request-mgmt-1.0.0/tr.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/tr.dict.itop-request-mgmt.php new file mode 100644 index 0000000000..184bec7f98 --- /dev/null +++ b/modules/itop-request-mgmt-1.0.0/tr.dict.itop-request-mgmt.php @@ -0,0 +1,84 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Menu:RequestManagement' => 'Yardım masası', + 'Menu:RequestManagement+' => 'Yardım masası', + 'Menu:UserRequest:Overview' => 'Özet', + 'Menu:UserRequest:Overview+' => 'Özet', + 'Menu:NewUserRequest' => 'Yeni çağrı', + 'Menu:NewUserRequest+' => 'Yeni çağrı yarat', + 'Menu:SearchUserRequests' => 'Kullanıcı çağrılarını ara', + 'Menu:SearchUserRequests+' => 'Kullanıcı çağrılarını ara', + 'Menu:UserRequest:Shortcuts' => 'Kısayollar', + 'Menu:UserRequest:Shortcuts+' => '', + 'Menu:UserRequest:MyRequests' => 'Bana atanan çağrılar', + 'Menu:UserRequest:MyRequests+' => 'Bana atanan çağrılar', + 'Menu:UserRequest:EscalatedRequests' => 'Yönetime aktarılan çağrılar', + 'Menu:UserRequest:EscalatedRequests+' => 'Yönetime aktarılan çağrılar', + 'Menu:UserRequest:OpenRequests' => 'Tüm açık çağrılar', + 'Menu:UserRequest:OpenRequests+' => 'Tüm açık çağrılar', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserRequest +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:UserRequest' => 'Kullanıcı isteği', + 'Class:UserRequest+' => '', + 'Class:UserRequest/Attribute:request_type' => 'İstek Tipi', + 'Class:UserRequest/Attribute:request_type+' => '', + 'Class:UserRequest/Attribute:request_type/Value:information' => 'Bilgi', + 'Class:UserRequest/Attribute:request_type/Value:information+' => 'Bilgi', + 'Class:UserRequest/Attribute:request_type/Value:issue' => 'Konu', + 'Class:UserRequest/Attribute:request_type/Value:issue+' => 'Konu', + 'Class:UserRequest/Attribute:request_type/Value:service request' => 'Servis isteği', + 'Class:UserRequest/Attribute:request_type/Value:service request+' => 'Servis isteği', + 'Class:UserRequest/Attribute:freeze_reason' => 'Tanımlanmamış istek', + 'Class:UserRequest/Attribute:freeze_reason+' => '', + 'Class:UserRequest/Stimulus:ev_assign' => 'Ata', + 'Class:UserRequest/Stimulus:ev_assign+' => '', + 'Class:UserRequest/Stimulus:ev_reassign' => 'Tekrar ata', + 'Class:UserRequest/Stimulus:ev_reassign+' => '', + 'Class:UserRequest/Stimulus:ev_timeout' => 'zaman aşımı', + 'Class:UserRequest/Stimulus:ev_timeout+' => '', + 'Class:UserRequest/Stimulus:ev_resolve' => 'Çözümlendi', + 'Class:UserRequest/Stimulus:ev_resolve+' => '', + 'Class:UserRequest/Stimulus:ev_close' => 'Kapatıldı', + 'Class:UserRequest/Stimulus:ev_close+' => '', + 'Class:UserRequest/Stimulus:ev_freeze' => 'Beklemede', + 'Class:UserRequest/Stimulus:ev_freeze+' => '', +)); + +?> \ No newline at end of file diff --git a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php index 0aa6236e2b..9becde3a0e 100644 --- a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php @@ -30,6 +30,7 @@ SetupWebPage::AddModule( 'de.dict.itop-service-mgmt.php', 'pt_br.dict.itop-service-mgmt.php', 'ru.dict.itop-service-mgmt.php', + 'tr.dict.itop-service-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-service-mgmt.xml', diff --git a/modules/itop-service-mgmt-1.0.0/tr.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/tr.dict.itop-service-mgmt.php new file mode 100644 index 0000000000..a22d2d3f99 --- /dev/null +++ b/modules/itop-service-mgmt-1.0.0/tr.dict.itop-service-mgmt.php @@ -0,0 +1,442 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( +'Menu:ServiceManagement' => 'Hizmet Yönetimi', +'Menu:ServiceManagement+' => 'Hizmet Yönetimi', +'Menu:Service:Overview' => 'Özet', +'Menu:Service:Overview+' => '', +'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Hizmet seviyesine göre sözleşmeler', +'UI-ServiceManagementMenu-ContractsByStatus' => 'Durumuna göre sözleşmeler', +'UI-ServiceManagementMenu-ContractsEndingIn30Days' => '30 gün çinde biten sözleşmeler', + +'Menu:ServiceType' => 'Hizmet Tipleri', +'Menu:ServiceType+' => 'Hizmet Tipleri', +'Menu:ProviderContract' => 'Tedarikçi Sözleşmeleri', +'Menu:ProviderContract+' => 'Tedarikçi Sözleşmeleri', +'Menu:CustomerContract' => 'Müşteri Sözleşmeleri', +'Menu:CustomerContract+' => 'Müşteri Sözleşmeleri', +'Menu:ServiceSubcategory' => 'Hizmet alt kategorileri', +'Menu:ServiceSubcategory+' => 'Hizmet alt kategorileri', +'Menu:Service' => 'Hizmetler', +'Menu:Service+' => 'Hizmetler', +'Menu:SLA' => 'SLAs', +'Menu:SLA+' => 'Hizmet Seviyesi Anlaşmaları', +'Menu:SLT' => 'SLTs', +'Menu:SLT+' => 'Hizmet Seviyesi Taahütleri', + +)); + +// +// Class: Contract +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Contract' => 'Sözleşme', + 'Class:Contract+' => '', + 'Class:Contract/Attribute:name' => 'Adı', + 'Class:Contract/Attribute:name+' => '', + 'Class:Contract/Attribute:description' => 'Tanımlama', + 'Class:Contract/Attribute:description+' => '', + 'Class:Contract/Attribute:start_date' => 'Başlangıç Tarihi', + 'Class:Contract/Attribute:start_date+' => '', + 'Class:Contract/Attribute:end_date' => 'Bitiş Tarihi', + 'Class:Contract/Attribute:end_date+' => '', + 'Class:Contract/Attribute:cost' => 'Maliyet', + 'Class:Contract/Attribute:cost+' => '', + 'Class:Contract/Attribute:cost_currency' => 'Para Birimi', + 'Class:Contract/Attribute:cost_currency+' => '', + 'Class:Contract/Attribute:cost_currency/Value:dollars' => 'ABD Doları', + 'Class:Contract/Attribute:cost_currency/Value:dollars+' => '', + 'Class:Contract/Attribute:cost_currency/Value:euros' => 'Avro', + 'Class:Contract/Attribute:cost_currency/Value:euros+' => '', + 'Class:Contract/Attribute:cost_unit' => 'Cost unit', + 'Class:Contract/Attribute:cost_unit+' => '', + 'Class:Contract/Attribute:billing_frequency' => 'Faturlandırma dönemleri', + 'Class:Contract/Attribute:billing_frequency+' => '', + 'Class:Contract/Attribute:contact_list' => 'İrtibatlar', + 'Class:Contract/Attribute:contact_list+' => 'Sözleşme için İrtibatlar', + 'Class:Contract/Attribute:document_list' => 'Dokümanlar', + 'Class:Contract/Attribute:document_list+' => 'Sözleşmeye eklenen dokümanlar', + 'Class:Contract/Attribute:ci_list' => 'KK', + 'Class:Contract/Attribute:ci_list+' => 'Sözleşme kapsamındaki KK', + 'Class:Contract/Attribute:finalclass' => 'Tip', + 'Class:Contract/Attribute:finalclass+' => '', +)); + +// +// Class: ProviderContract +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ProviderContract' => 'Tedarikçi Sözleşmesi', + 'Class:ProviderContract+' => '', + 'Class:ProviderContract/Attribute:provider_id' => 'Tedarikçi', + 'Class:ProviderContract/Attribute:provider_id+' => '', + 'Class:ProviderContract/Attribute:provider_name' => 'Tedarikçi Adı', + 'Class:ProviderContract/Attribute:provider_name+' => '', + 'Class:ProviderContract/Attribute:sla' => 'SLA', + 'Class:ProviderContract/Attribute:sla+' => 'Service Level Agreement', + 'Class:ProviderContract/Attribute:coverage' => 'Service hours', + 'Class:ProviderContract/Attribute:coverage+' => '', +)); + +// +// Class: CustomerContract +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:CustomerContract' => 'Müşteri Sözleşmesi', + 'Class:CustomerContract+' => '', + 'Class:CustomerContract/Attribute:org_id' => 'Müşteri', + 'Class:CustomerContract/Attribute:org_id+' => '', + 'Class:CustomerContract/Attribute:org_name' => 'Müşteri Adı', + 'Class:CustomerContract/Attribute:org_name+' => '', + 'Class:CustomerContract/Attribute:provider_id' => 'Tedarikçi', + 'Class:CustomerContract/Attribute:provider_id+' => '', + 'Class:CustomerContract/Attribute:provider_name' => 'Tedarikçi Adı', + 'Class:CustomerContract/Attribute:provider_name+' => '', + 'Class:CustomerContract/Attribute:support_team_id' => 'Destek Ekibi', + 'Class:CustomerContract/Attribute:support_team_id+' => '', + 'Class:CustomerContract/Attribute:support_team_name' => 'Destek Ekibi', + 'Class:CustomerContract/Attribute:support_team_name+' => '', + 'Class:CustomerContract/Attribute:provider_list' => 'Tedarikçiler', + 'Class:CustomerContract/Attribute:provider_list+' => '', + 'Class:CustomerContract/Attribute:sla_list' => 'SLAs', + 'Class:CustomerContract/Attribute:sla_list+' => 'List of SLA related to the contract', + 'Class:CustomerContract/Attribute:provider_list' => 'Altyüklenici Sözleşmeleri', + 'Class:CustomerContract/Attribute:sla_list+' => '', +)); +// +// Class: lnkCustomerContractToProviderContract +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkCustomerContractToProviderContract' => 'Müşteri ve Tedarikçi Sözleşmesi ilişkilendirmesi', + 'Class:lnkCustomerContractToProviderContract+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id' => 'Müşteri Sözleşmesi', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name' => 'Adı', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id' => 'Tedarikçi Sözleşmesi', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name' => 'Adı', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla' => 'Tedarikçi SLA', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla+' => 'Hizmet Seviyesi Anlaşması', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage' => 'Hizmet süresi (saat)', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage+' => '', +)); + + +// +// Class: lnkContractToSLA +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkContractToSLA' => 'Sözleşme/SLA', + 'Class:lnkContractToSLA+' => '', + 'Class:lnkContractToSLA/Attribute:contract_id' => 'Sözleşme', + 'Class:lnkContractToSLA/Attribute:contract_id+' => '', + 'Class:lnkContractToSLA/Attribute:contract_name' => 'Sözleşme', + 'Class:lnkContractToSLA/Attribute:contract_name+' => '', + 'Class:lnkContractToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_id+' => '', + 'Class:lnkContractToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_name+' => '', + 'Class:lnkContractToSLA/Attribute:coverage' => 'Hizmet süresi (saat)', + 'Class:lnkContractToSLA/Attribute:coverage+' => '', +)); + +// +// Class: lnkContractToDoc +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkContractToDoc' => 'Sözleşme/Doküman', + 'Class:lnkContractToDoc+' => '', + 'Class:lnkContractToDoc/Attribute:contract_id' => 'Sözleşme', + 'Class:lnkContractToDoc/Attribute:contract_id+' => '', + 'Class:lnkContractToDoc/Attribute:contract_name' => 'Sözleşme', + 'Class:lnkContractToDoc/Attribute:contract_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_id' => 'Doküman', + 'Class:lnkContractToDoc/Attribute:document_id+' => '', + 'Class:lnkContractToDoc/Attribute:document_name' => 'Doküman', + 'Class:lnkContractToDoc/Attribute:document_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_type' => 'Doküman tipi', + 'Class:lnkContractToDoc/Attribute:document_type+' => '', + 'Class:lnkContractToDoc/Attribute:document_status' => 'Doküman durumu', + 'Class:lnkContractToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkContractToContact +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkContractToContact' => 'Sözleşme/Sözleşme', + 'Class:lnkContractToContact+' => '', + 'Class:lnkContractToContact/Attribute:contract_id' => 'Sözleşme', + 'Class:lnkContractToContact/Attribute:contract_id+' => '', + 'Class:lnkContractToContact/Attribute:contract_name' => 'Sözleşme', + 'Class:lnkContractToContact/Attribute:contract_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_id' => 'Sözleşme', + 'Class:lnkContractToContact/Attribute:contact_id+' => '', + 'Class:lnkContractToContact/Attribute:contact_name' => 'Sözleşme', + 'Class:lnkContractToContact/Attribute:contact_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_email' => 'Sözleşme e-posta', + 'Class:lnkContractToContact/Attribute:contact_email+' => '', + 'Class:lnkContractToContact/Attribute:role' => 'Rol', + 'Class:lnkContractToContact/Attribute:role+' => '', +)); + +// +// Class: lnkContractToCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkContractToCI' => 'Sözleşme/KK', + 'Class:lnkContractToCI+' => '', + 'Class:lnkContractToCI/Attribute:contract_id' => 'Sözleşme', + 'Class:lnkContractToCI/Attribute:contract_id+' => '', + 'Class:lnkContractToCI/Attribute:contract_name' => 'Sözleşme', + 'Class:lnkContractToCI/Attribute:contract_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_id' => 'KK', + 'Class:lnkContractToCI/Attribute:ci_id+' => '', + 'Class:lnkContractToCI/Attribute:ci_name' => 'KK', + 'Class:lnkContractToCI/Attribute:ci_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_status' => 'KK Durumu', + 'Class:lnkContractToCI/Attribute:ci_status+' => '', +)); + +// +// Class: Service +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Service' => 'Hizmet', + 'Class:Service+' => '', + 'Class:Service/Attribute:org_id' => 'Hizmet Sağlayıcı', + 'Class:Service/Attribute:org_id+' => '', + 'Class:Service/Attribute:provider_name' => 'Hizmet Sağlayıcı', + 'Class:Service/Attribute:provider_name+' => '', + 'Class:Service/Attribute:name' => 'Adı', + 'Class:Service/Attribute:name+' => '', + 'Class:Service/Attribute:description' => 'Tanımlama', + 'Class:Service/Attribute:description+' => '', + 'Class:Service/Attribute:type' => 'Tip', + 'Class:Service/Attribute:type+' => '', + 'Class:Service/Attribute:type/Value:IncidentManagement' => 'Arıza Yönetimi', + 'Class:Service/Attribute:type/Value:IncidentManagement+' => 'Arıza Yönetimi', + 'Class:Service/Attribute:type/Value:RequestManagement' => 'Çağrı Yönetimi', + 'Class:Service/Attribute:type/Value:RequestManagement+' => 'Çağrı Yönetimi', + 'Class:Service/Attribute:status' => 'Durum', + 'Class:Service/Attribute:status+' => '', + 'Class:Service/Attribute:status/Value:design' => 'Tasarım', + 'Class:Service/Attribute:status/Value:design+' => '', + 'Class:Service/Attribute:status/Value:obsolete' => 'Üretimden Kalkan', + 'Class:Service/Attribute:status/Value:obsolete+' => '', + 'Class:Service/Attribute:status/Value:production' => 'Kullanımda', + 'Class:Service/Attribute:status/Value:production+' => '', + 'Class:Service/Attribute:subcategory_list' => 'Hizmet alt kategorisi', + 'Class:Service/Attribute:subcategory_list+' => '', + 'Class:Service/Attribute:sla_list' => 'SLAs', + 'Class:Service/Attribute:sla_list+' => '', + 'Class:Service/Attribute:document_list' => 'Dokümanlar', + 'Class:Service/Attribute:document_list+' => 'Hizmet bağlı dokümanlar', + 'Class:Service/Attribute:contact_list' => 'Sözleşmeler', + 'Class:Service/Attribute:contact_list+' => 'Hizmet ile ilintili Sözleşmeler', + 'Class:Service/Tab:Related_Contracts' => 'Hizmet ile ilintili Sözleşmeler', + 'Class:Service/Tab:Related_Contracts+' => 'Hizmet ile ilintili Sözleşmeler', +)); + +// +// Class: ServiceSubcategory +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ServiceSubcategory' => 'Hizmet alt kategorisi', + 'Class:ServiceSubcategory+' => '', + 'Class:ServiceSubcategory/Attribute:name' => 'Adı', + 'Class:ServiceSubcategory/Attribute:name+' => '', + 'Class:ServiceSubcategory/Attribute:description' => 'Tanımlama', + 'Class:ServiceSubcategory/Attribute:description+' => '', + 'Class:ServiceSubcategory/Attribute:service_id' => 'Hizmet', + 'Class:ServiceSubcategory/Attribute:service_id+' => '', + 'Class:ServiceSubcategory/Attribute:service_name' => 'Hizmet', + 'Class:ServiceSubcategory/Attribute:service_name+' => '', +)); + +// +// Class: SLA +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:SLA' => 'SLA', + 'Class:SLA+' => 'Hizmet Seviyesi Anlaşması', + 'Class:SLA/Attribute:name' => 'Adı', + 'Class:SLA/Attribute:name+' => '', + 'Class:SLA/Attribute:service_id' => 'Hizmet', + 'Class:SLA/Attribute:service_id+' => '', + 'Class:SLA/Attribute:service_name' => 'Hizmet', + 'Class:SLA/Attribute:service_name+' => '', + 'Class:SLA/Attribute:slt_list' => 'SLTs', + 'Class:SLA/Attribute:slt_list+' => 'Hizmet Seviyesi Taahütler', +)); + +// +// Class: SLT +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:SLT' => 'SLT', + 'Class:SLT+' => 'Hizmet Seviyesi Taahütler', + 'Class:SLT/Attribute:name' => 'Adı', + 'Class:SLT/Attribute:name+' => '', + 'Class:SLT/Attribute:metric' => 'Metrik', + 'Class:SLT/Attribute:metric+' => '', + 'Class:SLT/Attribute:metric/Value:TTO' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTO+' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTR' => 'TTR', + 'Class:SLT/Attribute:metric/Value:TTR+' => 'TTR', + 'Class:SLT/Attribute:ticket_priority' => 'Çağrı önceliği', + 'Class:SLT/Attribute:ticket_priority+' => '', + 'Class:SLT/Attribute:ticket_priority/Value:1' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:1+' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:2' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:2+' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:3' => '3', + 'Class:SLT/Attribute:ticket_priority/Value:3+' => '3', + 'Class:SLT/Attribute:value' => 'Değer', + 'Class:SLT/Attribute:value+' => '', + 'Class:SLT/Attribute:value_unit' => 'Birim', + 'Class:SLT/Attribute:value_unit+' => '', + 'Class:SLT/Attribute:value_unit/Value:days' => 'gün', + 'Class:SLT/Attribute:value_unit/Value:days+' => 'gün', + 'Class:SLT/Attribute:value_unit/Value:hours' => 'saat', + 'Class:SLT/Attribute:value_unit/Value:hours+' => 'saat', + 'Class:SLT/Attribute:value_unit/Value:minutes' => 'dakika', + 'Class:SLT/Attribute:value_unit/Value:minutes+' => 'dakika', + 'Class:SLT/Attribute:sla_list' => 'SLAs', + 'Class:SLT/Attribute:sla_list+' => 'SLAs using the SLT', +)); + +// +// Class: lnkSLTToSLA +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkSLTToSLA' => 'SLT/SLA', + 'Class:lnkSLTToSLA+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_id+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_id' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_id+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_name' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_metric' => 'Metrik', + 'Class:lnkSLTToSLA/Attribute:slt_metric+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority' => 'Çağrı önceliği', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value' => 'Değer', + 'Class:lnkSLTToSLA/Attribute:slt_value+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit' => 'Birim', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit+' => '', +)); + +// +// Class: lnkServiceToDoc +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkServiceToDoc' => 'Hizmet/Doküman', + 'Class:lnkServiceToDoc+' => '', + 'Class:lnkServiceToDoc/Attribute:service_id' => 'Hizmet', + 'Class:lnkServiceToDoc/Attribute:service_id+' => '', + 'Class:lnkServiceToDoc/Attribute:service_name' => 'Hizmet', + 'Class:lnkServiceToDoc/Attribute:service_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_id' => 'Doküman', + 'Class:lnkServiceToDoc/Attribute:document_id+' => '', + 'Class:lnkServiceToDoc/Attribute:document_name' => 'Doküman', + 'Class:lnkServiceToDoc/Attribute:document_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_type' => 'Doküman tipi', + 'Class:lnkServiceToDoc/Attribute:document_type+' => '', + 'Class:lnkServiceToDoc/Attribute:document_status' => 'Doküman durumu', + 'Class:lnkServiceToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkServiceToContact +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkServiceToContact' => 'Hizmet/Sözleşme', + 'Class:lnkServiceToContact+' => '', + 'Class:lnkServiceToContact/Attribute:service_id' => 'Hizmet', + 'Class:lnkServiceToContact/Attribute:service_id+' => '', + 'Class:lnkServiceToContact/Attribute:service_name' => 'Hizmet', + 'Class:lnkServiceToContact/Attribute:service_name+' => '', + 'Class:lnkServiceToContact/Attribute:contact_id' => 'Sözleşme', + 'Class:lnkServiceToContact/Attribute:contact_id+' => '', + 'Class:lnkServiceToContact/Attribute:contact_name' => 'Sözleşme', + 'Class:lnkServiceToContact/Attribute:contact_name+' => '', + 'Class:lnkServiceToContact/Attribute:contact_email' => 'Sözleşme e-posta', + 'Class:lnkServiceToContact/Attribute:contact_email+' => '', + 'Class:lnkServiceToContact/Attribute:role' => 'Rol', + 'Class:lnkServiceToContact/Attribute:role+' => '', +)); + +// +// Class: lnkServiceToCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkServiceToCI' => 'Hizmet/KK', + 'Class:lnkServiceToCI+' => '', + 'Class:lnkServiceToCI/Attribute:service_id' => 'Hizmet', + 'Class:lnkServiceToCI/Attribute:service_id+' => '', + 'Class:lnkServiceToCI/Attribute:service_name' => 'Hizmet', + 'Class:lnkServiceToCI/Attribute:service_name+' => '', + 'Class:lnkServiceToCI/Attribute:ci_id' => 'KK', + 'Class:lnkServiceToCI/Attribute:ci_id+' => '', + 'Class:lnkServiceToCI/Attribute:ci_name' => 'KK', + 'Class:lnkServiceToCI/Attribute:ci_name+' => '', + 'Class:lnkServiceToCI/Attribute:ci_status' => 'KK durumu', + 'Class:lnkServiceToCI/Attribute:ci_status+' => '', +)); + + +?> diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index bd9095932b..47b7fa7e86 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -30,6 +30,7 @@ SetupWebPage::AddModule( 'de.dict.itop-tickets.php', 'pt_br.dict.itop-tickets.php', 'ru.dict.itop-tickets.php', + 'tr.dict.itop-tickets.php', ), 'data.struct' => array( 'data.struct.ta-actions.xml', diff --git a/modules/itop-tickets-1.0.0/tr.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/tr.dict.itop-tickets.php new file mode 100644 index 0000000000..e85126a6c3 --- /dev/null +++ b/modules/itop-tickets-1.0.0/tr.dict.itop-tickets.php @@ -0,0 +1,265 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Ticket +// + +// +// Class: Ticket +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:Ticket' => 'Kayıt', + 'Class:Ticket+' => '', + 'Class:Ticket/Attribute:ref' => 'Referans', + 'Class:Ticket/Attribute:ref+' => '', + 'Class:Ticket/Attribute:title' => 'Başlık', + 'Class:Ticket/Attribute:title+' => '', + 'Class:Ticket/Attribute:description' => 'Tanımlama', + 'Class:Ticket/Attribute:description+' => '', + 'Class:Ticket/Attribute:ticket_log' => 'Log', + 'Class:Ticket/Attribute:ticket_log+' => '', + 'Class:Ticket/Attribute:start_date' => 'Açılış tarihi', + 'Class:Ticket/Attribute:start_date+' => '', + 'Class:Ticket/Attribute:document_list' => 'Dokümanlar', + 'Class:Ticket/Attribute:document_list+' => 'Kayıtla ilgili dokümanlar', + 'Class:Ticket/Attribute:ci_list' => 'KKleri', + 'Class:Ticket/Attribute:ci_list+' => 'Kayıtla ilgili Konfigüreasyon Kalemleri', + 'Class:Ticket/Attribute:contact_list' => 'İrtibatlar', + 'Class:Ticket/Attribute:contact_list+' => 'Dahil olan kişi ve ekipler', + 'Class:Ticket/Attribute:incident_list' => 'İlgili Arıza kayıtları', + 'Class:Ticket/Attribute:incident_list+' => '', + 'Class:Ticket/Attribute:finalclass' => 'Tip', + 'Class:Ticket/Attribute:finalclass+' => '', +)); + + +// +// Class: lnkTicketToDoc +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkTicketToDoc' => 'Kayıt/Doküman', + 'Class:lnkTicketToDoc+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_id' => 'Kayıt', + 'Class:lnkTicketToDoc/Attribute:ticket_id+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_ref' => 'Kayıt #', + 'Class:lnkTicketToDoc/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToDoc/Attribute:document_id' => 'Doküman', + 'Class:lnkTicketToDoc/Attribute:document_id+' => '', + 'Class:lnkTicketToDoc/Attribute:document_name' => 'Doküman', + 'Class:lnkTicketToDoc/Attribute:document_name+' => '', +)); + +// +// Class: lnkTicketToContact +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkTicketToContact' => 'Kayıt/İrtibat', + 'Class:lnkTicketToContact+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_id' => 'Kayıt', + 'Class:lnkTicketToContact/Attribute:ticket_id+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_ref' => 'Kayıt #', + 'Class:lnkTicketToContact/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToContact/Attribute:contact_id' => 'İrtibat', + 'Class:lnkTicketToContact/Attribute:contact_id+' => '', + 'Class:lnkTicketToContact/Attribute:contact_name' => 'İrtibat', + 'Class:lnkTicketToContact/Attribute:contact_name+' => '', + 'Class:lnkTicketToContact/Attribute:contact_email' => 'E-posta', + 'Class:lnkTicketToContact/Attribute:contact_email+' => '', + 'Class:lnkTicketToContact/Attribute:role' => 'Rolü', + 'Class:lnkTicketToContact/Attribute:role+' => '', +)); + +// +// Class: lnkTicketToCI +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:lnkTicketToCI' => 'Kayıt/KK', + 'Class:lnkTicketToCI+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_id' => 'Kayıt', + 'Class:lnkTicketToCI/Attribute:ticket_id+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_ref' => 'Kayıt #', + 'Class:lnkTicketToCI/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToCI/Attribute:ci_id' => 'KK', + 'Class:lnkTicketToCI/Attribute:ci_id+' => '', + 'Class:lnkTicketToCI/Attribute:ci_name' => 'KK', + 'Class:lnkTicketToCI/Attribute:ci_name+' => '', + 'Class:lnkTicketToCI/Attribute:ci_status' => 'KK durumu', + 'Class:lnkTicketToCI/Attribute:ci_status+' => '', + 'Class:lnkTicketToCI/Attribute:impact' => 'Etkisi', + 'Class:lnkTicketToCI/Attribute:impact+' => '', +)); + + +// +// Class: ResponseTicket +// + +Dict::Add('TR TR', 'Turkish', 'Türkçe', array( + 'Class:ResponseTicket' => 'KarşıKayıt', + 'Class:ResponseTicket+' => '', + 'Class:ResponseTicket/Attribute:status' => 'Durumu', + 'Class:ResponseTicket/Attribute:status+' => '', + 'Class:ResponseTicket/Attribute:status/Value:new' => 'Yeni', + 'Class:ResponseTicket/Attribute:status/Value:new+' => 'Yeni açılan', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto' => 'Yönetime aktarılan/TTO', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto+' => '', + 'Class:ResponseTicket/Attribute:status/Value:assigned' => 'Atanmış', + 'Class:ResponseTicket/Attribute:status/Value:assigned+' => '', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr' => 'Yönetime aktarılan/TTR', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr+' => '', + 'Class:ResponseTicket/Attribute:status/Value:frozen' => 'Beklemede', + 'Class:ResponseTicket/Attribute:status/Value:frozen+' => '', + 'Class:ResponseTicket/Attribute:status/Value:resolved' => 'Çözüldü', + 'Class:ResponseTicket/Attribute:status/Value:resolved+' => '', + 'Class:ResponseTicket/Attribute:status/Value:closed' => 'Kapatıldı', + 'Class:ResponseTicket/Attribute:status/Value:closed+' => '', + 'Class:ResponseTicket/Attribute:caller_id' => 'Arayan', + 'Class:ResponseTicket/Attribute:caller_id+' => '', + 'Class:ResponseTicket/Attribute:caller_email' => 'E-posta', + 'Class:ResponseTicket/Attribute:caller_email+' => '', + 'Class:ResponseTicket/Attribute:org_id' => 'Müşteri', + 'Class:ResponseTicket/Attribute:org_id+' => '', + 'Class:ResponseTicket/Attribute:org_name' => 'Müşteri', + 'Class:ResponseTicket/Attribute:org_name+' => '', + 'Class:ResponseTicket/Attribute:service_id' => 'Servis', + 'Class:ResponseTicket/Attribute:service_id+' => '', + 'Class:ResponseTicket/Attribute:service_name' => 'Adı', + 'Class:ResponseTicket/Attribute:service_name+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_id' => 'Servis Kategorisi', + 'Class:ResponseTicket/Attribute:servicesubcategory_id+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_name' => 'Adı', + 'Class:ResponseTicket/Attribute:servicesubcategory_name+' => '', + 'Class:ResponseTicket/Attribute:product' => 'Ürün', + 'Class:ResponseTicket/Attribute:product+' => '', + 'Class:ResponseTicket/Attribute:impact' => 'Etkisi', + 'Class:ResponseTicket/Attribute:impact+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:1' => 'Etkilenen Bölüm', + 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:2' => 'Etkilenen Servis', + 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:3' => 'Etkilenen Kişi', + 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', + 'Class:ResponseTicket/Attribute:urgency' => 'Aciliyeti', + 'Class:ResponseTicket/Attribute:urgency+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => 'Yüksek', + 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:2' => 'Orta', + 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => 'Düşük', + 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', + 'Class:ResponseTicket/Attribute:priority' => 'Önceliği', + 'Class:ResponseTicket/Attribute:priority+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:1' => 'Yüksek', + 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:2' => 'Orta', + 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:3' => 'Düşük', + 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', + 'Class:ResponseTicket/Attribute:workgroup_id' => 'Çalışma Grubu', + 'Class:ResponseTicket/Attribute:workgroup_id+' => '', + 'Class:ResponseTicket/Attribute:workgroup_name' => 'Çalışma Grubu', + 'Class:ResponseTicket/Attribute:workgroup_name+' => '', + 'Class:ResponseTicket/Attribute:agent_id' => 'Kişi', + 'Class:ResponseTicket/Attribute:agent_id+' => '', + 'Class:ResponseTicket/Attribute:agent_name' => 'Kişi', + 'Class:ResponseTicket/Attribute:agent_name+' => '', + 'Class:ResponseTicket/Attribute:agent_email' => 'Kişi E-posta', + 'Class:ResponseTicket/Attribute:agent_email+' => '', + 'Class:ResponseTicket/Attribute:related_problem_id' => 'İlgili Problem', + 'Class:ResponseTicket/Attribute:related_problem_id+' => '', + 'Class:ResponseTicket/Attribute:related_problem_ref' => 'Referans', + 'Class:ResponseTicket/Attribute:related_problem_ref+' => '', + 'Class:ResponseTicket/Attribute:related_change_id' => 'İlgili değişiklik', + 'Class:ResponseTicket/Attribute:related_change_id+' => '', + 'Class:ResponseTicket/Attribute:related_change_ref' => 'İlgili değişiklik', + 'Class:ResponseTicket/Attribute:related_change_ref+' => '', + 'Class:ResponseTicket/Attribute:close_date' => 'Kapatıldı', + 'Class:ResponseTicket/Attribute:close_date+' => '', + 'Class:ResponseTicket/Attribute:last_update' => 'Son güncelleme', + 'Class:ResponseTicket/Attribute:last_update+' => '', + 'Class:ResponseTicket/Attribute:assignment_date' => 'Atama tarihi ', + 'Class:ResponseTicket/Attribute:assignment_date+' => '', + 'Class:ResponseTicket/Attribute:resolution_date' => 'Çözüm tarihi', + 'Class:ResponseTicket/Attribute:resolution_date+' => '', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline' => 'TTO Yönetime aktarılma hedef tarihi', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline' => 'TTR Yönetime aktarılma hedef tarihi', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:closure_deadline' => 'Kapatma hedef tarihi', + 'Class:ResponseTicket/Attribute:closure_deadline+' => '', + 'Class:ResponseTicket/Attribute:resolution_code' => 'Çözüm Kodu', + 'Class:ResponseTicket/Attribute:resolution_code+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce' => 'Tekrar yaratılamadı', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate' => 'Çift kayıt', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed' => 'Düzeltildi', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant' => 'İlgisiz', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant+' => '', + 'Class:ResponseTicket/Attribute:solution' => 'Çözüm', + 'Class:ResponseTicket/Attribute:solution+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction' => 'Kullanıcı memnuniyeti', + 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => 'Çok tatminkar', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => 'Çok tatminkar', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => 'Tatminkar', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => 'Tatminkar', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => 'Memnun değil', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => 'Memnun değil', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => 'Hayal kırıklığı', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => 'Hayal kırıklığı', + 'Class:ResponseTicket/Attribute:user_commment' => 'Kullanıcı yorumu', + 'Class:ResponseTicket/Attribute:user_commment+' => '', + 'Class:ResponseTicket/Stimulus:ev_assign' => 'Ata', + 'Class:ResponseTicket/Stimulus:ev_assign+' => '', + 'Class:ResponseTicket/Stimulus:ev_reassign' => 'Tekrar Ata', + 'Class:ResponseTicket/Stimulus:ev_reassign+' => '', + 'Class:ResponseTicket/Stimulus:ev_timeout' => 'Yönetime aktarma', + 'Class:ResponseTicket/Stimulus:ev_timeout+' => '', + 'Class:ResponseTicket/Stimulus:ev_resolve' => 'Çözüldü', + 'Class:ResponseTicket/Stimulus:ev_resolve+' => '', + 'Class:ResponseTicket/Stimulus:ev_close' => 'Kapatıldı', + 'Class:ResponseTicket/Stimulus:ev_close+' => '', +)); + + + + +?> diff --git a/readme.txt b/readme.txt index a21983afed..efa9634c20 100644 --- a/readme.txt +++ b/readme.txt @@ -48,6 +48,7 @@ The JQuery team and the all the jQuery plugins authors for developing such a pow Phil Eddies for the numerous feedbacks provided, and the first implementation of CKEdit Marco Tulio for the Portuguese (Brazilian) translation Vladimir Shilov for the Russian translation +Izzet Sirin for the Turkish translation 2. INSTALLATION ============ From 6c73b3ed2e7ed91b6b2cb79e76607f22bc5e5e4b Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 20 Dec 2010 13:44:15 +0000 Subject: [PATCH 949/970] Localized in chinese SVN:trunk[1034] --- core/config.class.inc.php | 2 + dictionaries/zh.dictionary.itop.core.php | 385 ++++++ dictionaries/zh.dictionary.itop.ui.php | 868 ++++++++++++++ .../module.authent-external.php | 1 + .../zh.dict.authent-external.php | 45 + modules/authent-ldap/module.authent-ldap.php | 1 + modules/authent-ldap/zh.dict.authent-ldap.php | 47 + .../authent-local/module.authent-local.php | 1 + .../authent-local/zh.dict.authent-local.php | 47 + .../disabled.module.itop-basic.php | 1 + .../module.itop-change-mgmt.php | 1 + .../zh.dict.itop-change-mgmt.php | 345 ++++++ .../module.itop-config-mgmt.php | 1 + .../zh.dict.itop-config-mgmt.php | 1054 +++++++++++++++++ .../module.itop-incident-mgmt.php | 1 + .../zh.dict.itop-incident-mgmt.php | 73 ++ .../module.itop-knownerror-mgmt.php | 1 + .../zh.dict.itop-knownerror-mgmt.php | 147 +++ .../module.itop-problem-mgmt.php | 1 + .../zh.dict.itop-problem-mgmt.php | 165 +++ .../module.itop-request-mgmt.php | 1 + .../zh.dict.itop-request-mgmt.php | 84 ++ .../module.itop-service-mgmt.php | 1 + .../zh.dict.itop-service-mgmt.php | 453 +++++++ .../module.itop-tickets.php | 1 + .../zh.dict.itop-tickets.php | 267 +++++ 26 files changed, 3994 insertions(+) create mode 100644 dictionaries/zh.dictionary.itop.core.php create mode 100644 dictionaries/zh.dictionary.itop.ui.php create mode 100644 modules/authent-external/zh.dict.authent-external.php create mode 100644 modules/authent-ldap/zh.dict.authent-ldap.php create mode 100644 modules/authent-local/zh.dict.authent-local.php create mode 100644 modules/itop-change-mgmt-1.0.0/zh.dict.itop-change-mgmt.php create mode 100644 modules/itop-config-mgmt-1.0.0/zh.dict.itop-config-mgmt.php create mode 100644 modules/itop-incident-mgmt-1.0.0/zh.dict.itop-incident-mgmt.php create mode 100644 modules/itop-knownerror-mgmt-1.0.0/zh.dict.itop-knownerror-mgmt.php create mode 100644 modules/itop-problem-mgmt-1.0.0/zh.dict.itop-problem-mgmt.php create mode 100644 modules/itop-request-mgmt-1.0.0/zh.dict.itop-request-mgmt.php create mode 100644 modules/itop-service-mgmt-1.0.0/zh.dict.itop-service-mgmt.php create mode 100644 modules/itop-tickets-1.0.0/zh.dict.itop-tickets.php diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 04af9f3742..4896ce0bc4 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -366,6 +366,8 @@ class Config 'dictionaries/ru.dictionary.itop.core.php', // Support for Russian 'dictionaries/tr.dictionary.itop.ui.php', // Support for Turkish 'dictionaries/tr.dictionary.itop.core.php', // Support for Turkish + 'dictionaries/zh.dictionary.itop.ui.php', // Support for Chinese + 'dictionaries/zh.dictionary.itop.core.php', // Support for Chinese ); foreach($this->m_aSettings as $sPropCode => $aSettingInfo) { diff --git a/dictionaries/zh.dictionary.itop.core.php b/dictionaries/zh.dictionary.itop.core.php new file mode 100644 index 0000000000..6d6ca1aef3 --- /dev/null +++ b/dictionaries/zh.dictionary.itop.core.php @@ -0,0 +1,385 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +////////////////////////////////////////////////////////////////////// +// Classes in 'core/cmdb' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: CMDBChange +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChange' => '变更', + 'Class:CMDBChange+' => '变更跟踪', + 'Class:CMDBChange/Attribute:date' => '日期', + 'Class:CMDBChange/Attribute:date+' => '变更被记录的日期和时间', + 'Class:CMDBChange/Attribute:userinfo' => '杂项. 信息', + 'Class:CMDBChange/Attribute:userinfo+' => '呼叫者已定义的信息', +)); + +// +// Class: CMDBChangeOp +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOp' => '变更操作', + 'Class:CMDBChangeOp+' => '变更操作跟踪', + 'Class:CMDBChangeOp/Attribute:change' => '变更', + 'Class:CMDBChangeOp/Attribute:change+' => '变更', + 'Class:CMDBChangeOp/Attribute:date' => '日期', + 'Class:CMDBChangeOp/Attribute:date+' => '变更的日期和时间', + 'Class:CMDBChangeOp/Attribute:userinfo' => '用户', + 'Class:CMDBChangeOp/Attribute:userinfo+' => '变更造成者', + 'Class:CMDBChangeOp/Attribute:objclass' => '对象类', + 'Class:CMDBChangeOp/Attribute:objclass+' => '对象类', + 'Class:CMDBChangeOp/Attribute:objkey' => '对象 id', + 'Class:CMDBChangeOp/Attribute:objkey+' => '对象 id', + 'Class:CMDBChangeOp/Attribute:finalclass' => '类别', + 'Class:CMDBChangeOp/Attribute:finalclass+' => '', +)); + +// +// Class: CMDBChangeOpCreate +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOpCreate' => '对象创建', + 'Class:CMDBChangeOpCreate+' => '对象创建跟踪', +)); + +// +// Class: CMDBChangeOpDelete +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOpDelete' => '对象删除', + 'Class:CMDBChangeOpDelete+' => '对象删除跟踪', +)); + +// +// Class: CMDBChangeOpSetAttribute +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOpSetAttribute' => '对象变更', + 'Class:CMDBChangeOpSetAttribute+' => '对象属性变更跟踪', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => '属性', + 'Class:CMDBChangeOpSetAttribute/Attribute:attcode+' => '被修改属性的编码', +)); + +// +// Class: CMDBChangeOpSetAttributeScalar +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOpSetAttributeScalar' => '属性变更', + 'Class:CMDBChangeOpSetAttributeScalar+' => '对象标量属性变更跟踪', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => '原值', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue+' => '属性原值', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => '新值', + 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => '属性新值', +)); +// Used by CMDBChangeOp... & derived classes +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Change:ObjectCreated' => 'Object created', + 'Change:ObjectDeleted' => 'Object deleted', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s set to %2$s (previous value: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s appended to %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modified, previous value: %2$s', + 'Change:AttName_Changed' => '%1$s modified', +)); + +// +// Class: CMDBChangeOpSetAttributeBlob +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOpSetAttributeBlob' => '数据变更', + 'Class:CMDBChangeOpSetAttributeBlob+' => '数据变更跟踪', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => '原数据', + 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata+' => '属性原来的内容', +)); + +// +// Class: CMDBChangeOpSetAttributeText +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CMDBChangeOpSetAttributeText' => '文本变更', + 'Class:CMDBChangeOpSetAttributeText+' => '文本变更跟踪', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => '原数据', + 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata+' => '属性原来的内容', +)); + +// +// Class: Event +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Event' => '记录事项', + 'Class:Event+' => '应用程序的内部事项', + 'Class:Event/Attribute:message' => '消息', + 'Class:Event/Attribute:message+' => '事项简述', + 'Class:Event/Attribute:date' => '日期', + 'Class:Event/Attribute:date+' => '变更被记录的日期和时间', + 'Class:Event/Attribute:userinfo' => '用户信息', + 'Class:Event/Attribute:userinfo+' => '用户标识, 该用户的活动触发了该事项', + 'Class:Event/Attribute:finalclass' => '类别', + 'Class:Event/Attribute:finalclass+' => '', +)); + +// +// Class: EventNotification +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:EventNotification' => '通知事项', + 'Class:EventNotification+' => '被发送通知的踪迹', + 'Class:EventNotification/Attribute:trigger_id' => '触发器', + 'Class:EventNotification/Attribute:trigger_id+' => '用户帐户', + 'Class:EventNotification/Attribute:action_id' => '用户', + 'Class:EventNotification/Attribute:action_id+' => '用户帐户', + 'Class:EventNotification/Attribute:object_id' => '对象 id', + 'Class:EventNotification/Attribute:object_id+' => '对象 id (由触发器定义的类 ?)', +)); + +// +// Class: EventNotificationEmail +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:EventNotificationEmail' => 'Email 发出事项', + 'Class:EventNotificationEmail+' => '被发送的Email的踪迹', + 'Class:EventNotificationEmail/Attribute:to' => 'TO', + 'Class:EventNotificationEmail/Attribute:to+' => 'TO', + 'Class:EventNotificationEmail/Attribute:cc' => 'CC', + 'Class:EventNotificationEmail/Attribute:cc+' => 'CC', + 'Class:EventNotificationEmail/Attribute:bcc' => 'BCC', + 'Class:EventNotificationEmail/Attribute:bcc+' => 'BCC', + 'Class:EventNotificationEmail/Attribute:from' => 'From', + 'Class:EventNotificationEmail/Attribute:from+' => '消息发送者', + 'Class:EventNotificationEmail/Attribute:subject' => '主题', + 'Class:EventNotificationEmail/Attribute:subject+' => '主题', + 'Class:EventNotificationEmail/Attribute:body' => '邮件体', + 'Class:EventNotificationEmail/Attribute:body+' => '邮件体', +)); + +// +// Class: EventIssue +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:EventIssue' => '议题事项', + 'Class:EventIssue+' => '议题踪迹 (警告, 错误, 等等.)', + 'Class:EventIssue/Attribute:issue' => '议题', + 'Class:EventIssue/Attribute:issue+' => '发生了什么', + 'Class:EventIssue/Attribute:impact' => '影响', + 'Class:EventIssue/Attribute:impact+' => '结果是什么', + 'Class:EventIssue/Attribute:page' => '页面', + 'Class:EventIssue/Attribute:page+' => 'HTTP 入口', + 'Class:EventIssue/Attribute:arguments_post' => 'Posted arguments', + 'Class:EventIssue/Attribute:arguments_post+' => 'HTTP POST arguments', + 'Class:EventIssue/Attribute:arguments_get' => 'URL arguments', + 'Class:EventIssue/Attribute:arguments_get+' => 'HTTP GET arguments', + 'Class:EventIssue/Attribute:callstack' => 'Callstack', + 'Class:EventIssue/Attribute:callstack+' => 'Call stack', + 'Class:EventIssue/Attribute:data' => 'Data', + 'Class:EventIssue/Attribute:data+' => 'More information', +)); + +// +// Class: EventWebService +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:EventWebService' => 'Web 服务事项', + 'Class:EventWebService+' => 'Web 服务调用的踪迹', + 'Class:EventWebService/Attribute:verb' => 'Verb', + 'Class:EventWebService/Attribute:verb+' => '操作的名称', + 'Class:EventWebService/Attribute:result' => '结果', + 'Class:EventWebService/Attribute:result+' => '概览 成功/失败', + 'Class:EventWebService/Attribute:log_info' => '信息记录', + 'Class:EventWebService/Attribute:log_info+' => '结果信息记录', + 'Class:EventWebService/Attribute:log_warning' => '警告记录', + 'Class:EventWebService/Attribute:log_warning+' => '结果警告记录', + 'Class:EventWebService/Attribute:log_error' => '错误记录', + 'Class:EventWebService/Attribute:log_error+' => '结果错误记录', + 'Class:EventWebService/Attribute:data' => '数据', + 'Class:EventWebService/Attribute:data+' => '结果数据', +)); + +// +// Class: Action +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Action' => '客户化动作', + 'Class:Action+' => '用户定义的动作', + 'Class:Action/Attribute:name' => '名称', + 'Class:Action/Attribute:name+' => '', + 'Class:Action/Attribute:description' => '描述', + 'Class:Action/Attribute:description+' => '', + 'Class:Action/Attribute:status' => '状态', + 'Class:Action/Attribute:status+' => '生产中 或 ?', + 'Class:Action/Attribute:status/Value:test' => '测试中', + 'Class:Action/Attribute:status/Value:test+' => '测试中', + 'Class:Action/Attribute:status/Value:enabled' => '生产中', + 'Class:Action/Attribute:status/Value:enabled+' => '生产中', + 'Class:Action/Attribute:status/Value:disabled' => '非活动', + 'Class:Action/Attribute:status/Value:disabled+' => '非活动', + 'Class:Action/Attribute:trigger_list' => '相关触发器', + 'Class:Action/Attribute:trigger_list+' => '关联到该动作的触发器', + 'Class:Action/Attribute:finalclass' => '类别', + 'Class:Action/Attribute:finalclass+' => '', +)); + +// +// Class: ActionNotification +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ActionNotification' => '通知', + 'Class:ActionNotification+' => '通知 (摘要)', +)); + +// +// Class: ActionEmail +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ActionEmail' => 'Email 通知', + 'Class:ActionEmail+' => '', + 'Class:ActionEmail/Attribute:test_recipient' => '测试收件人', + 'Class:ActionEmail/Attribute:test_recipient+' => '状态被设置为"测试"时的目的地', + 'Class:ActionEmail/Attribute:from' => '来自', + 'Class:ActionEmail/Attribute:from+' => '将发送到邮件头', + 'Class:ActionEmail/Attribute:reply_to' => '回复到', + 'Class:ActionEmail/Attribute:reply_to+' => '将发送到邮件头', + 'Class:ActionEmail/Attribute:to' => 'To', + 'Class:ActionEmail/Attribute:to+' => '邮件的目的地', + 'Class:ActionEmail/Attribute:cc' => 'Cc', + 'Class:ActionEmail/Attribute:cc+' => 'Carbon Copy', + 'Class:ActionEmail/Attribute:bcc' => 'bcc', + 'Class:ActionEmail/Attribute:bcc+' => 'Blind Carbon Copy', + 'Class:ActionEmail/Attribute:subject' => '主题', + 'Class:ActionEmail/Attribute:subject+' => '邮件标题', + 'Class:ActionEmail/Attribute:body' => '邮件体', + 'Class:ActionEmail/Attribute:body+' => '邮件内容', + 'Class:ActionEmail/Attribute:importance' => '重要', + 'Class:ActionEmail/Attribute:importance+' => '重要标志', + 'Class:ActionEmail/Attribute:importance/Value:low' => '低', + 'Class:ActionEmail/Attribute:importance/Value:low+' => '低', + 'Class:ActionEmail/Attribute:importance/Value:normal' => '一般', + 'Class:ActionEmail/Attribute:importance/Value:normal+' => '一般', + 'Class:ActionEmail/Attribute:importance/Value:high' => '高', + 'Class:ActionEmail/Attribute:importance/Value:high+' => '高', +)); + +// +// Class: Trigger +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Trigger' => '触发器', + 'Class:Trigger+' => '客户化事项句柄', + 'Class:Trigger/Attribute:description' => '描述', + 'Class:Trigger/Attribute:description+' => '单行描述', + 'Class:Trigger/Attribute:action_list' => '被触发的动作', + 'Class:Trigger/Attribute:action_list+' => '触发器击发时执行的动作', + 'Class:Trigger/Attribute:finalclass' => '类别', + 'Class:Trigger/Attribute:finalclass+' => '', +)); + +// +// Class: TriggerOnObject +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:TriggerOnObject' => '触发器 (类依赖的)', + 'Class:TriggerOnObject+' => '在一个给定类对象上的触发器', + 'Class:TriggerOnObject/Attribute:target_class' => '目标类', + 'Class:TriggerOnObject/Attribute:target_class+' => '', +)); + +// +// Class: TriggerOnStateChange +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:TriggerOnStateChange' => '触发器 (状态变化时)', + 'Class:TriggerOnStateChange+' => '对象状态变化时的触发器', + 'Class:TriggerOnStateChange/Attribute:state' => '状态', + 'Class:TriggerOnStateChange/Attribute:state+' => '', +)); + +// +// Class: TriggerOnStateEnter +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:TriggerOnStateEnter' => '触发器 (进入一个状态时)', + 'Class:TriggerOnStateEnter+' => '对象状态变化时触发器 - 进入时', +)); + +// +// Class: TriggerOnStateLeave +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:TriggerOnStateLeave' => '触发器 (离开一个状态时)', + 'Class:TriggerOnStateLeave+' => '对象状态变化时触发器 - 离开时', +)); + +// +// Class: TriggerOnObjectCreate +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:TriggerOnObjectCreate' => '触发器 (对象创建时)', + 'Class:TriggerOnObjectCreate+' => '一个给定类[子类]对象创建时触发器', +)); + +// +// Class: lnkTriggerAction +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkTriggerAction' => '动作/触发器', + 'Class:lnkTriggerAction+' => '触发器和动作间的链接', + 'Class:lnkTriggerAction/Attribute:action_id' => '动作', + 'Class:lnkTriggerAction/Attribute:action_id+' => '要执行的动作', + 'Class:lnkTriggerAction/Attribute:action_name' => '动作', + 'Class:lnkTriggerAction/Attribute:action_name+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_id' => '触发器', + 'Class:lnkTriggerAction/Attribute:trigger_id+' => '', + 'Class:lnkTriggerAction/Attribute:trigger_name' => '触发器', + 'Class:lnkTriggerAction/Attribute:trigger_name+' => '', + 'Class:lnkTriggerAction/Attribute:order' => '顺序', + 'Class:lnkTriggerAction/Attribute:order+' => '动作执行顺序', +)); + + +?> diff --git a/dictionaries/zh.dictionary.itop.ui.php b/dictionaries/zh.dictionary.itop.ui.php new file mode 100644 index 0000000000..f4387d8a2f --- /dev/null +++ b/dictionaries/zh.dictionary.itop.ui.php @@ -0,0 +1,868 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + + +////////////////////////////////////////////////////////////////////// +// Classes in 'gui' +////////////////////////////////////////////////////////////////////// +// + +////////////////////////////////////////////////////////////////////// +// Classes in 'application' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: AuditCategory +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:AuditCategory' => '审计类目', + 'Class:AuditCategory+' => '全部审计中的一个区段', + 'Class:AuditCategory/Attribute:name' => '类目名称', + 'Class:AuditCategory/Attribute:name+' => '类目简称', + 'Class:AuditCategory/Attribute:description' => '审计类目描述', + 'Class:AuditCategory/Attribute:description+' => '该审计类目的详细描述', + 'Class:AuditCategory/Attribute:definition_set' => '定义', + 'Class:AuditCategory/Attribute:definition_set+' => '定义用于审计的对象的OQL表达式', + 'Class:AuditCategory/Attribute:rules_list' => '审计规则', + 'Class:AuditCategory/Attribute:rules_list+' => '该类目的审计规则', +)); + +// +// Class: AuditRule +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:AuditRule' => '审计规则', + 'Class:AuditRule+' => '用于检查给定审计类目的规则', + 'Class:AuditRule/Attribute:name' => '规则名称', + 'Class:AuditRule/Attribute:name+' => '规则简称', + 'Class:AuditRule/Attribute:description' => '审计规则描述', + 'Class:AuditRule/Attribute:description+' => '审计规则详细描述', + 'Class:AuditRule/Attribute:query' => '要运行的查询', + 'Class:AuditRule/Attribute:query+' => '要运行的OQL表达式', + 'Class:AuditRule/Attribute:valid_flag' => '有效对象?', + 'Class:AuditRule/Attribute:valid_flag+' => '若规则返回有效对象则True,否则False', + 'Class:AuditRule/Attribute:valid_flag/Value:true' => 'true', + 'Class:AuditRule/Attribute:valid_flag/Value:true+' => 'true', + 'Class:AuditRule/Attribute:valid_flag/Value:false' => 'false', + 'Class:AuditRule/Attribute:valid_flag/Value:false+' => 'false', + 'Class:AuditRule/Attribute:category_id' => '类目', + 'Class:AuditRule/Attribute:category_id+' => '该规则对应的类目', + 'Class:AuditRule/Attribute:category_name' => '类目', + 'Class:AuditRule/Attribute:category_name+' => '该规则对应类目的名称', +)); + +////////////////////////////////////////////////////////////////////// +// Classes in 'addon/userrights' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: User +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:User' => '用户', + 'Class:User+' => '用户登录名', + 'Class:User/Attribute:finalclass' => '帐户类别', + 'Class:User/Attribute:finalclass+' => '', + 'Class:User/Attribute:contactid' => '联系人 (个人)', + 'Class:User/Attribute:contactid+' => '来自业务数据的个人明细信息', + 'Class:User/Attribute:last_name' => '名', + 'Class:User/Attribute:last_name+' => '对应联系人的名字', + 'Class:User/Attribute:first_name' => '姓', + 'Class:User/Attribute:first_name+' => '对应联系人的姓氏', + 'Class:User/Attribute:email' => 'Email', + 'Class:User/Attribute:email+' => '对应联系人的Email', + 'Class:User/Attribute:login' => '登录名', + 'Class:User/Attribute:login+' => '用户标识字符串', + 'Class:User/Attribute:language' => '语言', + 'Class:User/Attribute:language+' => '用户语言', + 'Class:User/Attribute:language/Value:EN US' => 'English', + 'Class:User/Attribute:language/Value:EN US+' => 'English (U.S.)', + 'Class:User/Attribute:language/Value:FR FR' => 'French', + 'Class:User/Attribute:language/Value:FR FR+' => 'French (France)', + 'Class:User/Attribute:profile_list' => '简档', + 'Class:User/Attribute:profile_list+' => '角色, 为该人员授权', + 'Class:User/Attribute:allowed_org_list' => '被许可的组织', + 'Class:User/Attribute:allowed_org_list+' => '终端用户被许可看到下述组织的数据. 如果没有指定的组织,则无限制.', + + 'Class:User/Error:LoginMustBeUnique' => '登录名必须唯一 - "%1s" 已经被使用.', + 'Class:User/Error:AtLeastOneProfileIsNeeded' => '至少一个简档必须指定给该用户.', +)); + +// +// Class: URP_Profiles +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_Profiles' => '简档', + 'Class:URP_Profiles+' => '用户简档', + 'Class:URP_Profiles/Attribute:name' => '名称', + 'Class:URP_Profiles/Attribute:name+' => '标签', + 'Class:URP_Profiles/Attribute:description' => '描述', + 'Class:URP_Profiles/Attribute:description+' => '单行描述', + 'Class:URP_Profiles/Attribute:user_list' => '用户', + 'Class:URP_Profiles/Attribute:user_list+' => '拥有该角色的人员', +)); + +// +// Class: URP_Dimensions +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_Dimensions' => '维度', + 'Class:URP_Dimensions+' => '应用维度 (定义纵深)', + 'Class:URP_Dimensions/Attribute:name' => '名称', + 'Class:URP_Dimensions/Attribute:name+' => '标签', + 'Class:URP_Dimensions/Attribute:description' => '描述', + 'Class:URP_Dimensions/Attribute:description+' => '单行描述', + 'Class:URP_Dimensions/Attribute:type' => '类别', + 'Class:URP_Dimensions/Attribute:type+' => '类名称或数据类别 (投影单位)', +)); + +// +// Class: URP_UserProfile +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_UserProfile' => '简档目标用户', + 'Class:URP_UserProfile+' => '用户的简档', + 'Class:URP_UserProfile/Attribute:userid' => '用户', + 'Class:URP_UserProfile/Attribute:userid+' => '用户帐户', + 'Class:URP_UserProfile/Attribute:userlogin' => '登录名', + 'Class:URP_UserProfile/Attribute:userlogin+' => '用户的登录名', + 'Class:URP_UserProfile/Attribute:profileid' => '简档', + 'Class:URP_UserProfile/Attribute:profileid+' => '使用简档', + 'Class:URP_UserProfile/Attribute:profile' => '简档', + 'Class:URP_UserProfile/Attribute:profile+' => '简档名称', + 'Class:URP_UserProfile/Attribute:reason' => '原因', + 'Class:URP_UserProfile/Attribute:reason+' => '解释为什么该人员需要拥有该角色', +)); + +// +// Class: URP_UserOrg +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_UserOrg' => '用户组织', + 'Class:URP_UserOrg+' => '被许可的组织', + 'Class:URP_UserOrg/Attribute:userid' => '用户', + 'Class:URP_UserOrg/Attribute:userid+' => '用户帐户', + 'Class:URP_UserOrg/Attribute:userlogin' => '登录名', + 'Class:URP_UserOrg/Attribute:userlogin+' => '用户的登录名', + 'Class:URP_UserOrg/Attribute:allowed_org_id' => '组织', + 'Class:URP_UserOrg/Attribute:allowed_org_id+' => '被许可的组织', + 'Class:URP_UserOrg/Attribute:allowed_org_name' => '组织', + 'Class:URP_UserOrg/Attribute:allowed_org_name+' => '被许可的组织', + 'Class:URP_UserOrg/Attribute:reason' => '原因', + 'Class:URP_UserOrg/Attribute:reason+' => '解释为什么该人员被许可查阅该组织的数据', +)); + +// +// Class: URP_ProfileProjection +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_ProfileProjection' => '简档投射', + 'Class:URP_ProfileProjection+' => '简档投射', + 'Class:URP_ProfileProjection/Attribute:dimensionid' => '维度', + 'Class:URP_ProfileProjection/Attribute:dimensionid+' => '应用维度', + 'Class:URP_ProfileProjection/Attribute:dimension' => '维度', + 'Class:URP_ProfileProjection/Attribute:dimension+' => '应用维度', + 'Class:URP_ProfileProjection/Attribute:profileid' => '简档', + 'Class:URP_ProfileProjection/Attribute:profileid+' => '使用简档', + 'Class:URP_ProfileProjection/Attribute:profile' => '简档', + 'Class:URP_ProfileProjection/Attribute:profile+' => '简档名称', + 'Class:URP_ProfileProjection/Attribute:value' => '值表达式', + 'Class:URP_ProfileProjection/Attribute:value+' => 'OQL expression (using $user) | constant | | +attribute code', + 'Class:URP_ProfileProjection/Attribute:attribute' => '属性', + 'Class:URP_ProfileProjection/Attribute:attribute+' => '目标属性编码 (可选)', +)); + +// +// Class: URP_ClassProjection +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_ClassProjection' => '类投射', + 'Class:URP_ClassProjection+' => '类投射', + 'Class:URP_ClassProjection/Attribute:dimensionid' => '维度', + 'Class:URP_ClassProjection/Attribute:dimensionid+' => '应用维度', + 'Class:URP_ClassProjection/Attribute:dimension' => '维度', + 'Class:URP_ClassProjection/Attribute:dimension+' => '应用维度', + 'Class:URP_ClassProjection/Attribute:class' => '类', + 'Class:URP_ClassProjection/Attribute:class+' => '目标类', + 'Class:URP_ClassProjection/Attribute:value' => '值表达式', + 'Class:URP_ClassProjection/Attribute:value+' => 'OQL expression (using $this) | constant | | +attribute code', + 'Class:URP_ClassProjection/Attribute:attribute' => '属性', + 'Class:URP_ClassProjection/Attribute:attribute+' => '目标属性编码 (可选)', +)); + +// +// Class: URP_ActionGrant +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_ActionGrant' => '动作许可', + 'Class:URP_ActionGrant+' => '类上的许可', + 'Class:URP_ActionGrant/Attribute:profileid' => '简档', + 'Class:URP_ActionGrant/Attribute:profileid+' => '使用简档', + 'Class:URP_ActionGrant/Attribute:profile' => '简档', + 'Class:URP_ActionGrant/Attribute:profile+' => '使用简档', + 'Class:URP_ActionGrant/Attribute:class' => '类', + 'Class:URP_ActionGrant/Attribute:class+' => '目标类', + 'Class:URP_ActionGrant/Attribute:permission' => '许可', + 'Class:URP_ActionGrant/Attribute:permission+' => '允许或不允许?', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes' => '是', + 'Class:URP_ActionGrant/Attribute:permission/Value:yes+' => '是', + 'Class:URP_ActionGrant/Attribute:permission/Value:no' => '否', + 'Class:URP_ActionGrant/Attribute:permission/Value:no+' => '否', + 'Class:URP_ActionGrant/Attribute:action' => '动作', + 'Class:URP_ActionGrant/Attribute:action+' => '可用于给定类上的操作', +)); + +// +// Class: URP_StimulusGrant +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_StimulusGrant' => '刺激许可', + 'Class:URP_StimulusGrant+' => '对象生命周期中刺激的许可', + 'Class:URP_StimulusGrant/Attribute:profileid' => '简档', + 'Class:URP_StimulusGrant/Attribute:profileid+' => '使用简档', + 'Class:URP_StimulusGrant/Attribute:profile' => '简档', + 'Class:URP_StimulusGrant/Attribute:profile+' => '使用简档', + 'Class:URP_StimulusGrant/Attribute:class' => '类', + 'Class:URP_StimulusGrant/Attribute:class+' => '目标类', + 'Class:URP_StimulusGrant/Attribute:permission' => '许可', + 'Class:URP_StimulusGrant/Attribute:permission+' => '允许或不允许?', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes' => '是', + 'Class:URP_StimulusGrant/Attribute:permission/Value:yes+' => '是', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no' => '否', + 'Class:URP_StimulusGrant/Attribute:permission/Value:no+' => '否', + 'Class:URP_StimulusGrant/Attribute:stimulus' => '刺激', + 'Class:URP_StimulusGrant/Attribute:stimulus+' => '刺激编码', +)); + +// +// Class: URP_AttributeGrant +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:URP_AttributeGrant' => '属性许可', + 'Class:URP_AttributeGrant+' => '属性层次上的许可', + 'Class:URP_AttributeGrant/Attribute:actiongrantid' => '动作准许', + 'Class:URP_AttributeGrant/Attribute:actiongrantid+' => '动作准许', + 'Class:URP_AttributeGrant/Attribute:attcode' => '属性', + 'Class:URP_AttributeGrant/Attribute:attcode+' => '属性编码', +)); + +// +// String from the User Interface: menu, messages, buttons, etc... +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:WelcomeMenu' => '欢迎', + 'Menu:WelcomeMenu+' => '欢迎来到iTop', + 'Menu:WelcomeMenuPage' => '欢迎', + 'Menu:WelcomeMenuPage+' => '欢迎来到iTop', + 'UI:WelcomeMenu:Title' => '欢迎来到iTop', + + 'UI:WelcomeMenu:LeftBlock' => '

        iTop 是完全的, 开发源码的, IT 操作门户.

        +
          系统包括: +
        • 完全的 CMDB (Configuration management database) 记录和管理 IT 资产清册.
        • +
        • 事件管理模块跟踪和传递所有发生在 IT 系统中的事件.
        • +
        • 变更管理模块规划和跟踪 IT 环境中发生的变化.
        • +
        • 已知的错误数据库加速事件的处理.
        • +
        • 停损模块记录所有计划的停机并通知对应的联系人.
        • +
        • 通过仪表板迅速获得 IT 的概览.
        • +
        +

        所有模块可以各自独立地、一步一步地搭建.

        ', + + 'UI:WelcomeMenu:RightBlock' => '

        iTop 是面向服务提供商的, 它使得 IT 工程师方便地管理多客户和多组织. +

          iTop, 提供功能丰富的业务处理流程: +
        • 提高 IT 管理效率
        • +
        • 驱动 IT 操作能力
        • +
        • 提高用户满意度,从业务能力方面提供执行力.
        • +
        +

        +

        iTop 是完全开放的,可被集成到您当前的IT管理架构中.

        +

        +

          利用这个新一代的 IT 操作门户, 可以帮助您: +
        • 更好地管理越来越复杂的 IT 环境.
        • +
        • 按照您的步骤实现 ITIL 流程.
        • +
        • 管理您的 IT 中最重要的设施: 文档化.
        • +
        +

        ', + 'UI:WelcomeMenu:AllOpenRequests' => '待处理的请求: %1$d', + 'UI:WelcomeMenu:MyCalls' => '我的请求', + 'UI:WelcomeMenu:OpenIncidents' => '待处理的事件: %1$d', + 'UI:WelcomeMenu:AllConfigItems' => '配置项: %1$d', + 'UI:WelcomeMenu:MyIncidents' => '指派给我的事件', + 'UI:AllOrganizations' => ' 所有组织 ', + 'UI:YourSearch' => '您的搜索', + 'UI:LoggedAsMessage' => '以 %1$s 登录', + 'UI:LoggedAsMessage+Admin' => '以 %1$s 登录(Administrator)', + 'UI:Button:Logoff' => '注销', + 'UI:Button:GlobalSearch' => '搜索', + 'UI:Button:Search' => '搜索', + 'UI:Button:Query' => ' 查询 ', + 'UI:Button:Ok' => '确认', + 'UI:Button:Cancel' => '取消', + 'UI:Button:Apply' => '应用', + 'UI:Button:Back' => ' << Back ', + 'UI:Button:Next' => ' Next >> ', + 'UI:Button:Finish' => ' 结束 ', + 'UI:Button:DoImport' => ' 运行导入 ! ', + 'UI:Button:Done' => ' 完成 ', + 'UI:Button:SimulateImport' => ' 激活导入 ', + 'UI:Button:Test' => '测试!', + 'UI:Button:Evaluate' => ' 评价 ', + 'UI:Button:AddObject' => ' 添加... ', + 'UI:Button:BrowseObjects' => ' 浏览... ', + 'UI:Button:Add' => ' 添加 ', + 'UI:Button:AddToList' => ' << 添加 ', + 'UI:Button:RemoveFromList' => ' 移除 >> ', + 'UI:Button:FilterList' => ' 过滤... ', + 'UI:Button:Create' => ' 创建 ', + 'UI:Button:Delete' => ' 删除 ! ', + 'UI:Button:ChangePassword' => ' 改变密码 ', + 'UI:Button:ResetPassword' => ' 重置密码 ', + + 'UI:SearchToggle' => '搜索', + 'UI:ClickToCreateNew' => '创建一个新的 %1$s', + 'UI:SearchFor_Class' => '搜索 %1$s 对象', + 'UI:NoObjectToDisplay' => '没有对象可显示.', + 'UI:Error:MandatoryTemplateParameter_object_id' => '当link_attr被指定时,参数 object_id 是必须的. 检查显示模板的定义.', + 'UI:Error:MandatoryTemplateParameter_target_attr' => '当 link_attr被指定时, 参数 target_attr 是必须的. 检查显示模板的定义.', + 'UI:Error:MandatoryTemplateParameter_group_by' => '参数 group_by 是必须的. 检查显示模板的定义.', + 'UI:Error:InvalidGroupByFields' => 'group by 的栏目列表是无效的: "%1$s".', + 'UI:Error:UnsupportedStyleOfBlock' => '错误: 不被支持的 block 格式: "%1$s".', + 'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => '错误的链接定义: the class of objects to manage: %1$s was not found as an external key in the class %2$s', + 'UI:Error:Object_Class_Id_NotFound' => 'Object: %1$s:%2$d not found.', + 'UI:Error:WizardCircularReferenceInDependencies' => '错误: 栏目之间的依赖性出现循环引用, 检查数据模型.', + 'UI:Error:UploadedFileTooBig' => '上传文件太大. (允许的最大限制是 %1$s). 检查您的 PHP configuration 中 upload_max_filesize 和 post_max_size.', + 'UI:Error:UploadedFileTruncated.' => '上传的文件被截断 !', + 'UI:Error:NoTmpDir' => '临时目录未定义.', + 'UI:Error:CannotWriteToTmp_Dir' => '无法向硬盘写入临时文件. upload_tmp_dir = "%1$s".', + 'UI:Error:UploadStoppedByExtension_FileName' => '上传因为扩展名被停止. (Original file name = "%1$s").', + 'UI:Error:UploadFailedUnknownCause_Code' => '文件上传失败, 未知原因. (Error code = "%1$s").', + + 'UI:Error:1ParametersMissing' => '错误: 必须为该操作指定下述参数: %1$s.', + 'UI:Error:2ParametersMissing' => '错误: 必须为该操作指定下述参数: %1$s and %2$s.', + 'UI:Error:3ParametersMissing' => '错误: 必须为该操作指定下述参数: %1$s, %2$s and %3$s.', + 'UI:Error:4ParametersMissing' => '错误: 必须为该操作指定下述参数: %1$s, %2$s, %3$s and %4$s.', + 'UI:Error:IncorrectOQLQuery_Message' => '错误: 错误的 OQL 查询: %1$s', + 'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => '运行该查询时产生了一个错误: %1$s', + 'UI:Error:ObjectAlreadyUpdated' => '错误: 该对象已被更新.', + 'UI:Error:ObjectCannotBeUpdated' => '错误: 对象不能被更新.', + 'UI:Error:ObjectsAlreadyDeleted' => '错误: 对象已被删除!', + 'UI:Error:BulkDeleteNotAllowedOn_Class' => '您未被允许进行 %1$s 类对象的批量删除', + 'UI:Error:DeleteNotAllowedOn_Class' => '您未被允许删除 %1$s 类的对象', + 'UI:Error:BulkModifyNotAllowedOn_Class' => '您未被允许进行 %1$s 类对象的批量更新', + 'UI:Error:ObjectAlreadyCloned' => '错误: 该对象已被克隆!', + 'UI:Error:ObjectAlreadyCreated' => '错误: 该对象已被创建!', + 'UI:Error:Invalid_Stimulus_On_Object_In_State' => '错误: 在对象 %2$s 的 "%3$s" 状态上的无效刺激 "%1$s" .', + + + 'UI:GroupBy:Count' => '计数', + 'UI:GroupBy:Count+' => '元素数量', + 'UI:CountOfObjects' => '%1$d 个对象匹配给定的条件.', + 'UI_CountOfObjectsShort' => '%1$d 个对象.', + 'UI:NoObject_Class_ToDisplay' => '没有 %1$s 可以显示', + 'UI:History:LastModified_On_By' => '最后修改 %1$s 被 %2$s.', + 'UI:HistoryTab' => '历史', + 'UI:NotificationsTab' => '通知', + 'UI:History:Date' => '日期', + 'UI:History:Date+' => '变更日期', + 'UI:History:User' => '用户', + 'UI:History:User+' => '造成变更的用户', + 'UI:History:Changes' => '变更', + 'UI:History:Changes+' => '对该对象所做的变更', + 'UI:Loading' => '载入...', + 'UI:Menu:Actions' => '动作', + 'UI:Menu:New' => '新建...', + 'UI:Menu:Add' => '添加...', + 'UI:Menu:Manage' => '管理...', + 'UI:Menu:EMail' => 'eMail', + 'UI:Menu:CSVExport' => 'CSV 导出', + 'UI:Menu:Modify' => '修改...', + 'UI:Menu:Delete' => '删除...', + 'UI:Menu:Manage' => '管理...', + 'UI:Menu:BulkDelete' => '删除...', + 'UI:UndefinedObject' => '未定义', + 'UI:Document:OpenInNewWindow:Download' => '在新窗口打开: %1$s, 下载: %2$s', + 'UI:SelectAllToggle+' => '选择 / 清除选择 全部', + 'UI:TruncatedResults' => '%1$d objects displayed out of %2$d', + 'UI:DisplayAll' => '显示全部', + 'UI:CollapseList' => '收缩', + 'UI:CountOfResults' => '%1$d 个对象', + 'UI:ChangesLogTitle' => '变更记录 (%1$d):', + 'UI:EmptyChangesLogTitle' => '变更记录为空', + 'UI:SearchFor_Class_Objects' => '搜索 %1$s 对象', + 'UI:OQLQueryBuilderTitle' => 'OQL Query Builder', + 'UI:OQLQueryTab' => 'OQL 查询', + 'UI:SimpleSearchTab' => '简单搜索', + 'UI:Details+' => '明细', + 'UI:SearchValue:Any' => '* 任何 *', + 'UI:SearchValue:Mixed' => '* 混合 *', + 'UI:SelectOne' => '-- 选择一个 --', + 'UI:Login:Welcome' => '欢迎来到 iTop!', + 'UI:Login:IncorrectLoginPassword' => '错误的登录名/密码, 请重试.', + 'UI:Login:IdentifyYourself' => '在继续之前, 确定您自己的身份', + 'UI:Login:UserNamePrompt' => '用户名称', + 'UI:Login:PasswordPrompt' => '密码', + 'UI:Login:ChangeYourPassword' => '改变您的密码', + 'UI:Login:OldPasswordPrompt' => '旧密码', + 'UI:Login:NewPasswordPrompt' => '新密码', + 'UI:Login:RetypeNewPasswordPrompt' => '重复新密码', + 'UI:Login:IncorrectOldPassword' => '错误: 旧密码错误', + 'UI:LogOffMenu' => '注销', + 'UI:LogOff:ThankYou' => '谢谢使用iTop', + 'UI:LogOff:ClickHereToLoginAgain' => '点击这里再次登录...', + 'UI:ChangePwdMenu' => '改变密码...', + 'UI:Login:RetypePwdDoesNotMatch' => '新密码和重录的新密码不符!', + 'UI:Button:Login' => '进入 iTop', + 'UI:Login:Error:AccessRestricted' => 'iTop 访问被限制. 请联系iTop系统管理员.', + 'UI:Login:Error:AccessAdmin' => '有系统管理员权限才能访问. 请联系iTop系统管理员.', + 'UI:CSVImport:MappingSelectOne' => '-- 选择一个 --', + 'UI:CSVImport:MappingNotApplicable' => '-- 忽略该栏 --', + 'UI:CSVImport:NoData' => '空的数据..., 请提供数据!', + 'UI:Title:DataPreview' => '数据预览', + 'UI:CSVImport:ErrorOnlyOneColumn' => '错误: 数据仅包含一列. 您选择了合适的分隔字符了吗?', + 'UI:CSVImport:FieldName' => '栏 %1$d', + 'UI:CSVImport:DataLine1' => '数据行 1', + 'UI:CSVImport:DataLine2' => '数据行 2', + 'UI:CSVImport:idField' => 'id (主键)', + 'UI:Title:BulkImport' => 'iTop - 大批量导入', + 'UI:Title:BulkImport+' => 'CSV 导入 Wizard', + 'UI:CSVImport:ClassesSelectOne' => '-- 选择一个 --', + 'UI:CSVImport:ErrorExtendedAttCode' => '内部错误: "%1$s" 是错误的编码, 因为 "%2$s" 不是类 "%3$s" 的外部健', + 'UI:CSVImport:ObjectsWillStayUnchanged' => '%1$d 对象将保持不变.', + 'UI:CSVImport:ObjectsWillBeModified' => '%1$d 对象将被修改.', + 'UI:CSVImport:ObjectsWillBeAdded' => '%1$d 对象将被添加.', + 'UI:CSVImport:ObjectsWillHaveErrors' => '%1$d 对象将有错误.', + 'UI:CSVImport:ObjectsRemainedUnchanged' => '%1$d 对象保持不变.', + 'UI:CSVImport:ObjectsWereModified' => '%1$d 对象已被修改.', + 'UI:CSVImport:ObjectsWereAdded' => '%1$d 对象已被添加.', + 'UI:CSVImport:ObjectsHadErrors' => '%1$d 对象已有错误.', + 'UI:Title:CSVImportStep2' => '步骤 2 of 5: CSV 数据选项', + 'UI:Title:CSVImportStep3' => '步骤 3 of 5: 数据映射', + 'UI:Title:CSVImportStep4' => '步骤 4 of 5: 导入模拟', + 'UI:Title:CSVImportStep5' => '步骤 5 of 5: 导入完成', + 'UI:CSVImport:LinesNotImported' => '无法装载的行:', + 'UI:CSVImport:LinesNotImported+' => '以下行无法导入因为其中包含错误', + 'UI:CSVImport:SeparatorComma+' => ', (逗号)', + 'UI:CSVImport:SeparatorSemicolon+' => '; (分号)', + 'UI:CSVImport:SeparatorTab+' => 'tab', + 'UI:CSVImport:SeparatorOther' => '其他:', + 'UI:CSVImport:QualifierDoubleQuote+' => '" (双引号)', + 'UI:CSVImport:QualifierSimpleQuote+' => '\' (单引号)', + 'UI:CSVImport:QualifierOther' => '其他:', + 'UI:CSVImport:TreatFirstLineAsHeader' => '将第一行视做标题头(列名称)', + 'UI:CSVImport:Skip_N_LinesAtTheBeginning' => '跳过文件开始的 %1$s 行', + 'UI:CSVImport:CSVDataPreview' => 'CSV 数据预览', + 'UI:CSVImport:SelectFile' => '选择导入的文件:', + 'UI:CSVImport:Tab:LoadFromFile' => '从文件装载', + 'UI:CSVImport:Tab:CopyPaste' => '复制和粘贴数据', + 'UI:CSVImport:Tab:Templates' => '模板', + 'UI:CSVImport:PasteData' => '粘贴数据以导入:', + 'UI:CSVImport:PickClassForTemplate' => '选择下载的模板: ', + 'UI:CSVImport:SeparatorCharacter' => '分隔字符:', + 'UI:CSVImport:TextQualifierCharacter' => '文本限定字符', + 'UI:CSVImport:CommentsAndHeader' => '注释和头', + 'UI:CSVImport:SelectClass' => '选择类以导入:', + 'UI:CSVImport:AdvancedMode' => '高级模式', + 'UI:CSVImport:AdvancedMode+' => '在高级模式中,对象的"id" (主键) 可以被用来修改和重命名对象.' . + '不管怎样,列 "id" (如果存在) 只能被用做一个搜索条件,不能与其它搜索条件混用.', + 'UI:CSVImport:SelectAClassFirst' => '首先选择一个类以配置映射.', + 'UI:CSVImport:HeaderFields' => '栏目', + 'UI:CSVImport:HeaderMappings' => '映射', + 'UI:CSVImport:HeaderSearch' => '搜索?', + 'UI:CSVImport:AlertIncompleteMapping' => '请为每个栏选择一个映射.', + 'UI:CSVImport:AlertNoSearchCriteria' => '请选择至少一个搜索条件', + 'UI:CSVImport:Encoding' => '字符编码', + 'UI:UniversalSearchTitle' => 'iTop - 通用搜索', + 'UI:UniversalSearch:Error' => '错误: %1$s', + 'UI:UniversalSearch:LabelSelectTheClass' => '选择类以搜索: ', + + 'UI:Audit:Title' => 'iTop - CMDB 审计', + 'UI:Audit:InteractiveAudit' => '交互审计', + 'UI:Audit:HeaderAuditRule' => '设计规则', + 'UI:Audit:HeaderNbObjects' => '# 对象', + 'UI:Audit:HeaderNbErrors' => '# 错误', + 'UI:Audit:PercentageOk' => '% Ok', + + 'UI:RunQuery:Title' => 'iTop - OQL 查询评估', + 'UI:RunQuery:QueryExamples' => '查询样例', + 'UI:RunQuery:HeaderPurpose' => '目的', + 'UI:RunQuery:HeaderPurpose+' => '该查询的解释', + 'UI:RunQuery:HeaderOQLExpression' => 'OQL 表达式', + 'UI:RunQuery:HeaderOQLExpression+' => 'OQL 语法表示的查询', + 'UI:RunQuery:ExpressionToEvaluate' => '待评估的表达式: ', + 'UI:RunQuery:MoreInfo' => '该查询的更多信息: ', + 'UI:RunQuery:DevelopedQuery' => '重新开发的查询表达式: ', + 'UI:RunQuery:SerializedFilter' => '序列化的过滤器: ', + 'UI:RunQuery:Error' => '运行该查询时产生了一个错误: %1$s', + + 'UI:Schema:Title' => 'iTop 对象 schema', + 'UI:Schema:CategoryMenuItem' => '类目 %1$s', + 'UI:Schema:Relationships' => '关联', + 'UI:Schema:AbstractClass' => '抽象类: 该类不能实例化对象.', + 'UI:Schema:NonAbstractClass' => '非抽象类: 该类可以实例化对象.', + 'UI:Schema:ClassHierarchyTitle' => '类层级', + 'UI:Schema:AllClasses' => '所有类', + 'UI:Schema:ExternalKey_To' => '%1$s的外部键', + 'UI:Schema:Columns_Description' => '列: %1$s', + 'UI:Schema:Default_Description' => '缺省: "%1$s"', + 'UI:Schema:NullAllowed' => '允许空', + 'UI:Schema:NullNotAllowed' => '不允许空', + 'UI:Schema:Attributes' => '属性', + 'UI:Schema:AttributeCode' => '属性编码', + 'UI:Schema:AttributeCode+' => '属性的内部编码', + 'UI:Schema:Label' => '标签', + 'UI:Schema:Label+' => '属性标签', + 'UI:Schema:Type' => '类别', + + 'UI:Schema:Type+' => '属性的数据类型', + 'UI:Schema:Origin' => '起源', + 'UI:Schema:Origin+' => '该属性被定义的基类', + 'UI:Schema:Description' => '描述', + 'UI:Schema:Description+' => '属性的描述', + 'UI:Schema:AllowedValues' => '允许值', + 'UI:Schema:AllowedValues+' => '该属性取值的限制', + 'UI:Schema:MoreInfo' => '更多信息', + 'UI:Schema:MoreInfo+' => '该栏目在数据库中被定义的更多信息', + 'UI:Schema:SearchCriteria' => '搜索条件', + 'UI:Schema:FilterCode' => '过滤器编码', + 'UI:Schema:FilterCode+' => '该搜索条件的编码', + 'UI:Schema:FilterDescription' => '描述', + 'UI:Schema:FilterDescription+' => '该搜索条件的描述', + 'UI:Schema:AvailOperators' => '可用的算子', + 'UI:Schema:AvailOperators+' => '该搜索条件可能的算子', + 'UI:Schema:ChildClasses' => '子类', + 'UI:Schema:ReferencingClasses' => '参考类', + 'UI:Schema:RelatedClasses' => '关联类', + 'UI:Schema:LifeCycle' => '生命周期', + 'UI:Schema:Triggers' => '触发器', + 'UI:Schema:Relation_Code_Description' => '关联 %1$s (%2$s)', + 'UI:Schema:RelationDown_Description' => 'Down: %1$s', + 'UI:Schema:RelationUp_Description' => 'Up: %1$s', + 'UI:Schema:RelationPropagates' => '%1$s: 繁殖到 %2$d 个层级, 查询: %3$s', + 'UI:Schema:RelationDoesNotPropagate' => '%1$s: 没有繁殖 (%2$d 层级), 查询: %3$s', + 'UI:Schema:Class_ReferencingClasses_From_By' => '%1$s 被类 %2$s 参照, 通过栏目 %3$s', + 'UI:Schema:Class_IsLinkedTo_Class_Via_ClassAndAttribute' => '%1$s 被链接到 %2$s 通过 %3$s::%4$s', + 'UI:Schema:Links:1-n' => '类指向 %1$s (1:n 链接):', + 'UI:Schema:Links:n-n' => '类链接到 %1$s (n:n 链接):', + 'UI:Schema:Links:All' => '全部相关类的图', + 'UI:Schema:NoLifeCyle' => '该类没有生命周期的定义.', + 'UI:Schema:LifeCycleTransitions' => '转换', + 'UI:Schema:LifeCyleAttributeOptions' => '属性选项', + 'UI:Schema:LifeCycleHiddenAttribute' => '隐藏', + 'UI:Schema:LifeCycleReadOnlyAttribute' => '只读', + 'UI:Schema:LifeCycleMandatoryAttribute' => '必须', + 'UI:Schema:LifeCycleAttributeMustChange' => '必须变更', + 'UI:Schema:LifeCycleAttributeMustPrompt' => '用户将被提示改变值', + 'UI:Schema:LifeCycleEmptyList' => '空列表', + + 'UI:LinksWidget:Autocomplete+' => '输入前3个字符...', + 'UI:Combo:SelectValue' => '--- 选择一个值 ---', + 'UI:Label:SelectedObjects' => '被选对象: ', + 'UI:Label:AvailableObjects' => '可用对象: ', + 'UI:Link_Class_Attributes' => '%1$s 属性', + 'UI:SelectAllToggle+' => '选择全部 / 清楚全部选择', + 'UI:AddObjectsOf_Class_LinkedWith_Class_Instance' => '添加 %1$s 个对象, 链接 %2$s: %3$s', + 'UI:AddObjectsOf_Class_LinkedWith_Class' => '添加 %1$s 个对象与 %2$s 链接', + 'UI:ManageObjectsOf_Class_LinkedWith_Class_Instance' => '管理 %1$s 个对象, 链接 %2$s: %3$s', + 'UI:AddLinkedObjectsOf_Class' => '添加 %1$s...', + 'UI:RemoveLinkedObjectsOf_Class' => '移除选择的对象', + 'UI:Message:EmptyList:UseAdd' => '列表是空的, 使用 "添加..." 按扭以添加元素.', + 'UI:Message:EmptyList:UseSearchForm' => '使用上面的搜索表单, 以搜索要添加的对象.', + + 'UI:Wizard:FinalStepTitle' => '最后步骤: 确认', + 'UI:Title:DeletionOf_Object' => '删除 %1$s', + 'UI:Title:BulkDeletionOf_Count_ObjectsOf_Class' => '批量删除 %1$d 个 %2$s 类的对象', + 'UI:Delete:NotAllowedToDelete' => '您未被允许删除该对象', + 'UI:Delete:NotAllowedToUpdate_Fields' => '您未被允许更新下述栏目: %1$s', + 'UI:Error:NotEnoughRightsToDelete' => '该对象不能被删除, 因为当前用户没有足够权限', + 'UI:Error:CannotDeleteBecauseOfDepencies' => '该对象不能被删除, 因为一些手工操作必须事先完成', + 'UI:Archive_User_OnBehalfOf_User' => '%1$s on behalf of %2$s', + 'UI:Delete:AutomaticallyDeleted' => '自动删除了', + 'UI:Delete:AutomaticResetOf_Fields' => '自动重置栏目: %1$s', + 'UI:Delete:CleaningUpRefencesTo_Object' => '清除所有对 %1$s 的参照...', + 'UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class' => '清除所有对 %2$s 类的 %1$d 个对象的参照...', + 'UI:Delete:Done+' => '做了什么...', + 'UI:Delete:_Name_Class_Deleted' => '%1$s - %2$s 删除了.', + 'UI:Delete:ConfirmDeletionOf_Name' => '删除 %1$s', + 'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '删除 %2$s 类的 %1$d 个对象', + 'UI:Delete:ShouldBeDeletedAtomaticallyButNotAllowed' => '应该自动删除, 但您未被允许这样做', + 'UI:Delete:MustBeDeletedManuallyButNotAllowed' => '必须手工删除 - 但您未被允许删除该对象, 请联系您的应用程序系统管理员', + 'UI:Delete:WillBeDeletedAutomatically' => '将被自动删除', + 'UI:Delete:MustBeDeletedManually' => '必须手工删除', + 'UI:Delete:CannotUpdateBecause_Issue' => '应该被自动更新, 但是: %1$s', + 'UI:Delete:WillAutomaticallyUpdate_Fields' => '将被自动更新 (重置: %1$s)', + 'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d 个对象/链接参照了 %2$s', + 'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d 个对象/链接参照了一些将删除的对象', + 'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => '为确保数据库的完整性, 任何参照应该更进一步清除', + 'UI:Delete:Consequence+' => '将做什么', + 'UI:Delete:SorryDeletionNotAllowed' => '抱歉, 您未被允许删除该对象, 请看上述详细解释', + 'UI:Delete:PleaseDoTheManualOperations' => '在要求删除该对象之前, 请先手工完成上述列出的操作', + 'UI:Delect:Confirm_Object' => '请确认您要删除 %1$s.', + 'UI:Delect:Confirm_Count_ObjectsOf_Class' => '请确认您要删除下列 %2$s 类的 %1$d 个对象.', + 'UI:WelcomeToITop' => '欢迎来到 iTop', + 'UI:DetailsPageTitle' => 'iTop - %1$s - %2$s 详细内容', + 'UI:ErrorPageTitle' => 'iTop - 错误', + 'UI:ObjectDoesNotExist' => '抱歉, 该对象不存在 (或您未被允许浏览该对象).', + 'UI:SearchResultsPageTitle' => 'iTop - 搜索结果', + 'UI:Search:NoSearch' => '没有可搜索的内容', + 'UI:FullTextSearchTitle_Text' => '"%1$s" 的结果:', + 'UI:Search:Count_ObjectsOf_Class_Found' => '发现 %2$s 类的 %1$d 个对象.', + 'UI:Search:NoObjectFound' => '未发现对象.', + 'UI:ModificationPageTitle_Object_Class' => 'iTop - %1$s - %2$s 修改', + 'UI:ModificationTitle_Class_Object' => '修改 %1$s: %2$s', + 'UI:ClonePageTitle_Object_Class' => 'iTop - 克隆 %1$s - %2$s 修改', + 'UI:CloneTitle_Class_Object' => '克隆 %1$s: %2$s', + 'UI:CreationPageTitle_Class' => 'iTop - 创建一个新的 %1$s ', + 'UI:CreationTitle_Class' => '创建一个新的 %1$s', + 'UI:SelectTheTypeOf_Class_ToCreate' => '选择要创建的 %1$s 的类别:', + 'UI:Class_Object_NotUpdated' => '未发现变化, %1$s (%2$s) 没有 被更新.', + 'UI:Class_Object_Updated' => '%1$s (%2$s) 更新了.', + 'UI:BulkDeletePageTitle' => 'iTop - 批量删除', + 'UI:BulkDeleteTitle' => '选择您要删除的对象:', + 'UI:PageTitle:ObjectCreated' => 'iTop 对象创建了.', + 'UI:Title:Object_Of_Class_Created' => '%1$s - %2$s 创建了.', + 'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => '应用 %1$s 在对象: %2$s 上, 从 %3$s 状态到目标状态: %4$s.', + 'UI:ObjectCouldNotBeWritten' => '对象不能写入: %1$s', + 'UI:PageTitle:FatalError' => 'iTop - 致命错误', + 'UI:SystemIntrusion' => '访问被禁止. 您正尝试未被许可的操作.', + 'UI:FatalErrorMessage' => '致命错误, iTop 无法继续.', + 'UI:Error_Details' => '错误: %1$s.', + + 'UI:PageTitle:ClassProjections' => 'iTop 用户管理 - 类投射', + 'UI:PageTitle:ProfileProjections' => 'iTop 用户管理 - 简档投射', + 'UI:UserManagement:Class' => '类', + 'UI:UserManagement:Class+' => '对象的类', + 'UI:UserManagement:ProjectedObject' => '对象', + 'UI:UserManagement:ProjectedObject+' => '被投射的对象', + 'UI:UserManagement:AnyObject' => '* 任何 *', + 'UI:UserManagement:User' => '用户', + 'UI:UserManagement:User+' => '与该投射相关的用户', + 'UI:UserManagement:Profile' => '简档', + 'UI:UserManagement:Profile+' => '投射被指定的简档', + 'UI:UserManagement:Action:Read' => '读', + 'UI:UserManagement:Action:Read+' => '读/显示 对象', + 'UI:UserManagement:Action:Modify' => '修改', + 'UI:UserManagement:Action:Modify+' => '创建和编辑(修改)对象', + 'UI:UserManagement:Action:Delete' => '删除', + 'UI:UserManagement:Action:Delete+' => '删除对象', + 'UI:UserManagement:Action:BulkRead' => '大批量读 (导出)', + 'UI:UserManagement:Action:BulkRead+' => '列出对象或批量导出', + 'UI:UserManagement:Action:BulkModify' => '批量修改', + 'UI:UserManagement:Action:BulkModify+' => '批量创建/编辑 (CSV 导入)', + 'UI:UserManagement:Action:BulkDelete' => '批量删除', + 'UI:UserManagement:Action:BulkDelete+' => '批量删除对象', + 'UI:UserManagement:Action:Stimuli' => 'Stimuli', + 'UI:UserManagement:Action:Stimuli+' => '许可的 (复合的) 动作', + 'UI:UserManagement:Action' => '动作', + 'UI:UserManagement:Action+' => '该用户进行的动作', + 'UI:UserManagement:TitleActions' => '动作', + 'UI:UserManagement:Permission' => '许可', + 'UI:UserManagement:Permission+' => '用户的许可', + 'UI:UserManagement:Attributes' => '属性', + 'UI:UserManagement:ActionAllowed:Yes' => '是', + 'UI:UserManagement:ActionAllowed:No' => '否', + 'UI:UserManagement:AdminProfile+' => '系统管理员拥有数据库中所有对象的完全读/写访问权限.', + 'UI:UserManagement:NoLifeCycleApplicable' => 'N/A', + 'UI:UserManagement:NoLifeCycleApplicable+' => '该类未定义生命周期', + 'UI:UserManagement:GrantMatrix' => '授权矩阵', + 'UI:UserManagement:LinkBetween_User_And_Profile' => '链接 %1$s 和 %2$s', + 'UI:UserManagement:LinkBetween_User_And_Org' => '链接 %1$s 和 %2$s', + + 'Menu:AdminTools' => '管理工具', + 'Menu:AdminTools+' => '管理工具', + 'Menu:AdminTools?' => '具有系统管理员简档的用户才能获得的工具', + + 'UI:ChangeManagementMenu' => '变更管理', + 'UI:ChangeManagementMenu+' => '变更管理', + 'UI:ChangeManagementMenu:Title' => '变更概览', + 'UI-ChangeManagementMenu-ChangesByType' => '按类别划分的变更', + 'UI-ChangeManagementMenu-ChangesByStatus' => '按状态划分的变更', + 'UI-ChangeManagementMenu-ChangesByWorkgroup' => '按工作组划分的变更', + 'UI-ChangeManagementMenu-ChangesNotYetAssigned' => '尚未指派的变更', + + 'UI:ConfigurationItemsMenu'=> '配置项目', + 'UI:ConfigurationItemsMenu+'=> '所有设备', + 'UI:ConfigurationItemsMenu:Title' => '配置项概览', + 'UI-ConfigurationItemsMenu-ServersByCriticity' => '按关键性划分服务器', + 'UI-ConfigurationItemsMenu-PCsByCriticity' => '按关键性划分PC', + 'UI-ConfigurationItemsMenu-NWDevicesByCriticity' => '按关键性划分网络设备', + 'UI-ConfigurationItemsMenu-ApplicationsByCriticity' => '按关键性划分应用程序', + + 'UI:ConfigurationManagementMenu' => '配置管理', + 'UI:ConfigurationManagementMenu+' => '配置管理', + 'UI:ConfigurationManagementMenu:Title' => '基础架构概览', + 'UI-ConfigurationManagementMenu-InfraByType' => '按类别划分基础架构对象', + 'UI-ConfigurationManagementMenu-InfraByStatus' => '按状态划分基础架构对象', + +'UI:ConfigMgmtMenuOverview:Title' => '配置管理仪表板', +'UI-ConfigMgmtMenuOverview-FunctionalCIbyStatus' => '按状态配置项目', +'UI-ConfigMgmtMenuOverview-FunctionalCIByType' => '按类别配置项目', + +'UI:RequestMgmtMenuOverview:Title' => '请求管理仪表板', +'UI-RequestManagementOverview-RequestByService' => '按服务划分用户请求', +'UI-RequestManagementOverview-RequestByPriority' => '按优先级划分用户请求', +'UI-RequestManagementOverview-RequestUnassigned' => '尚未指派办理人的用户请求', + +'UI:IncidentMgmtMenuOverview:Title' => '事件管理仪表板', +'UI-IncidentManagementOverview-IncidentByService' => '按服务级划分事件', +'UI-IncidentManagementOverview-IncidentByPriority' => '按优先级划分事件', +'UI-IncidentManagementOverview-IncidentUnassigned' => '尚未指派办理人的事件', + +'UI:ChangeMgmtMenuOverview:Title' => '变更管理仪表板', +'UI-ChangeManagementOverview-ChangeByType' => '按类别划分变更', +'UI-ChangeManagementOverview-ChangeUnassigned' => '尚未指派办理人的变更', +'UI-ChangeManagementOverview-ChangeWithOutage' => '变更引起的停机', + +'UI:ServiceMgmtMenuOverview:Title' => '服务管理仪表板', +'UI-ServiceManagementOverview-CustomerContractToRenew' => '客户联系人需在30日内更新', +'UI-ServiceManagementOverview-ProviderContractToRenew' => '供应商联系人需在30日内更新', + + 'UI:ContactsMenu' => '联系人', + 'UI:ContactsMenu+' => '联系人', + 'UI:ContactsMenu:Title' => '联系人概览', + 'UI-ContactsMenu-ContactsByLocation' => '按地域划分联系人', + 'UI-ContactsMenu-ContactsByType' => '按类别划分联系人', + 'UI-ContactsMenu-ContactsByStatus' => '按状态划分联系人', + + 'Menu:CSVImportMenu' => 'CSV 导入', + 'Menu:CSVImportMenu+' => '大批量创建或修改', + + 'Menu:DataModelMenu' => '数据模型', + 'Menu:DataModelMenu+' => '数据模型概览', + + 'Menu:ExportMenu' => '导出', + 'Menu:ExportMenu+' => '以HTML, CSV or XML格式导出任何查询的结果', + + 'Menu:NotificationsMenu' => '通知', + 'Menu:NotificationsMenu+' => '通知的配置', + 'UI:NotificationsMenu:Title' => '配置 通知', + 'UI:NotificationsMenu:Help' => '帮助', + 'UI:NotificationsMenu:HelpContent' => '

        在 iTop 中, 通知可以被完全客户化定制. 它们是基于两个对象集: 触发器和动作.

        +

        触发器 定义了什么时候一个通知将被执行. 有3种触发器, 覆盖了一个对象生命周期的3个阶段: +

          +
        1. "OnCreate" 触发器, 当某个特定类的对象创建时将触发
        2. +
        3. "OnStateEnter" 触发器, 在某个给定类的对象进入某个特定状态前将触发(从另外一个状态而来)
        4. +
        5. "OnStateLeave" 触发器, 在某个给定类的对象离开某个特定状态时将触发
        6. +
        +

        +

        +动作 定义了触发器触发时要执行的动作. 目前, 仅有一种动作存在于发送邮件过程中. +这些动作还定义了用于发送邮件及收件人,重要性等的模板. +

        +

        一个专门页面: email.test.php 可用于测试和调试您的 PHP mail 配置.

        +

        若要执行, 动作必须和触发器相关联. +当与一个触发器关联时, 每个动作都被赋予一个顺序号, 规定了按什么样的顺序执行这些动作.

        ', + 'UI:NotificationsMenu:Triggers' => '触发器', + 'UI:NotificationsMenu:AvailableTriggers' => '可用的触发器', + 'UI:NotificationsMenu:OnCreate' => '当一个对象被创建', + 'UI:NotificationsMenu:OnStateEnter' => '当一个对象进入给定状态', + 'UI:NotificationsMenu:OnStateLeave' => '当一个对象离开给定状态', + 'UI:NotificationsMenu:Actions' => '动作', + 'UI:NotificationsMenu:AvailableActions' => '有效的动作', + + 'Menu:AuditCategories' => '审计类目', + 'Menu:AuditCategories+' => '审计类目', + 'Menu:Notifications:Title' => '审计类目', + + 'Menu:RunQueriesMenu' => '运行查询', + 'Menu:RunQueriesMenu+' => '运行任何查询', + + 'Menu:DataAdministration' => '数据管理', + 'Menu:DataAdministration+' => '数据管理', + + 'Menu:UniversalSearchMenu' => '通用搜索', + 'Menu:UniversalSearchMenu+' => '搜索所有...', + + 'Menu:ApplicationLogMenu' => 'Log de l\'application', + 'Menu:ApplicationLogMenu+' => 'Log de l\'application', + 'Menu:ApplicationLogMenu:Title' => 'Log de l\'application', + + 'Menu:UserManagementMenu' => '用户管理', + 'Menu:UserManagementMenu+' => '用户管理', + + 'Menu:ProfilesMenu' => '简档', + 'Menu:ProfilesMenu+' => '简档', + 'Menu:ProfilesMenu:Title' => '简档', + + 'Menu:UserAccountsMenu' => '用户帐户', + 'Menu:UserAccountsMenu+' => '用户帐户', + 'Menu:UserAccountsMenu:Title' => '用户帐户', + + 'UI:iTopVersion:Short' => 'iTop version %1$s', + 'UI:iTopVersion:Long' => 'iTop version %1$s-%2$s built on %3$s', + 'UI:PropertiesTab' => '属性', + + 'UI:OpenDocumentInNewWindow_' => '在新窗口打开文档: %1$s', + 'UI:DownloadDocument_' => '下载该文档: %1$s', + 'UI:Document:NoPreview' => '该类文档无法预览', + + 'UI:DeadlineMissedBy_duration' => 'Missed by %1$s', + 'UI:Deadline_LessThan1Min' => '< 1 min', + 'UI:Deadline_Minutes' => '%1$d min', + 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', + 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', + 'UI:Help' => '帮助', + 'UI:PasswordConfirm' => '(确认)', + 'UI:BeforeAdding_Class_ObjectsSaveThisObject' => '在添加更多 %1$s 对象前, 保存该对象.', + 'UI:DisplayThisMessageAtStartup' => '在启动时显示该消息', + 'UI:RelationshipGraph' => '图览', + 'UI:RelationshipList' => '列表', + + 'Portal:Title' => 'iTop 用户门户', + 'Portal:Refresh' => '刷新', + 'Portal:Back' => '返回', + 'Portal:CreateNewRequest' => '创建一个新的请求', + 'Portal:ChangeMyPassword' => '改变我的密码', + 'Portal:Disconnect' => '断开', + 'Portal:OpenRequests' => '我的待解决的请求', + 'Portal:ResolvedRequests' => '我的已解决的请求', + 'Portal:SelectService' => '从类目中选择一个服务:', + 'Portal:PleaseSelectOneService' => '请选择一个服务', + 'Portal:SelectSubcategoryFrom_Service' => '从服务中选择一个子类 %1$s:', + 'Portal:PleaseSelectAServiceSubCategory' => '请选择一个子类', + 'Portal:DescriptionOfTheRequest' => '输入您的请求描述:', + 'Portal:TitleRequestDetailsFor_Request' => '请求明细内容 %1$s:', + 'Portal:NoOpenRequest' => '该类目中没有请求.', + 'Portal:Button:CloseTicket' => '关闭这个单据', + 'Portal:EnterYourCommentsOnTicket' => '输入您对于该单据解决情况的评述:', + 'Portal:ErrorNoContactForThisUser' => '错误: 当前用户没有和一个联系人或人员关联. 请联系您的系统管理员.', + + 'Enum:Undefined' => '未定义', +)); + + + +?> diff --git a/modules/authent-external/module.authent-external.php b/modules/authent-external/module.authent-external.php index b299d72c90..6c7aafe2bc 100644 --- a/modules/authent-external/module.authent-external.php +++ b/modules/authent-external/module.authent-external.php @@ -51,6 +51,7 @@ SetupWebPage::AddModule( 'de.dict.authent-external.php', 'ru.dict.authent-external.php', 'tr.dict.authent-external.php', + 'zh.dict.authent-external.php', ), 'data.struct' => array( //'data.struct.authent-ldap.xml', diff --git a/modules/authent-external/zh.dict.authent-external.php b/modules/authent-external/zh.dict.authent-external.php new file mode 100644 index 0000000000..c4f264edbe --- /dev/null +++ b/modules/authent-external/zh.dict.authent-external.php @@ -0,0 +1,45 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserExternal +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:UserExternal' => '外部用户', + 'Class:UserExternal+' => '用户在 iTop 外部验证身份', +)); + + + +?> diff --git a/modules/authent-ldap/module.authent-ldap.php b/modules/authent-ldap/module.authent-ldap.php index ceece994dd..bdece9037a 100644 --- a/modules/authent-ldap/module.authent-ldap.php +++ b/modules/authent-ldap/module.authent-ldap.php @@ -34,6 +34,7 @@ SetupWebPage::AddModule( 'de.dict.authent-ldap.php', 'ru.dict.authent-ldap.php', 'tr.dict.authent-ldap.php', + 'zh.dict.authent-ldap.php', ), 'data.struct' => array( //'data.struct.authent-ldap.xml', diff --git a/modules/authent-ldap/zh.dict.authent-ldap.php b/modules/authent-ldap/zh.dict.authent-ldap.php new file mode 100644 index 0000000000..f488eb2b09 --- /dev/null +++ b/modules/authent-ldap/zh.dict.authent-ldap.php @@ -0,0 +1,47 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserLDAP +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:UserLDAP' => 'LDAP 用户', + 'Class:UserLDAP+' => '用户由 LDAP 鉴别身份', + 'Class:UserLDAP/Attribute:password' => '密码', + 'Class:UserLDAP/Attribute:password+' => '用户身份验证串', +)); + + + +?> diff --git a/modules/authent-local/module.authent-local.php b/modules/authent-local/module.authent-local.php index 7359c25cb6..1334968c6b 100644 --- a/modules/authent-local/module.authent-local.php +++ b/modules/authent-local/module.authent-local.php @@ -28,6 +28,7 @@ SetupWebPage::AddModule( 'de.dict.authent-local.php', 'ru.dict.authent-local.php', 'tr.dict.authent-local.php', + 'zh.dict.authent-local.php', ), 'data.struct' => array( //'data.struct.authent-local.xml', diff --git a/modules/authent-local/zh.dict.authent-local.php b/modules/authent-local/zh.dict.authent-local.php new file mode 100644 index 0000000000..807d7a0bce --- /dev/null +++ b/modules/authent-local/zh.dict.authent-local.php @@ -0,0 +1,47 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserLocal +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:UserLocal' => 'iTop 用户', + 'Class:UserLocal+' => '用户由 iTop 验证身份', + 'Class:UserLocal/Attribute:password' => '密码', + 'Class:UserLocal/Attribute:password+' => '用户身份验证串', +)); + + + +?> diff --git a/modules/itop-basic-1.0.0/disabled.module.itop-basic.php b/modules/itop-basic-1.0.0/disabled.module.itop-basic.php index a163e9f0f9..58b9a68331 100644 --- a/modules/itop-basic-1.0.0/disabled.module.itop-basic.php +++ b/modules/itop-basic-1.0.0/disabled.module.itop-basic.php @@ -28,6 +28,7 @@ SetupWebPage::AddModule( 'fr.dict.itop-basic.php', 'pt_br.dict.itop-basic.php', 'tr.dict.itop-basic.php', + 'zh.dict.itop-basic.php', ), 'data.struct' => array( //'data.struct.itop-basic.xml', diff --git a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php index 544053f8e8..7bab8682b7 100644 --- a/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/module.itop-change-mgmt.php @@ -32,6 +32,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-change-mgmt.php', 'ru.dict.itop-change-mgmt.php', 'tr.dict.itop-change-mgmt.php', + 'zh.dict.itop-change-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-change-mgmt.xml', diff --git a/modules/itop-change-mgmt-1.0.0/zh.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/zh.dict.itop-change-mgmt.php new file mode 100644 index 0000000000..25f1806616 --- /dev/null +++ b/modules/itop-change-mgmt-1.0.0/zh.dict.itop-change-mgmt.php @@ -0,0 +1,345 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:ChangeManagement' => '变更管理', + 'Menu:Change:Overview' => '概览', + 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => '新变更', + 'Menu:NewChange+' => '创建新变更的单据', + 'Menu:SearchChanges' => '搜索变更', + 'Menu:SearchChanges+' => '搜索变更单据', + 'Menu:Change:Shortcuts' => '快捷方式', + 'Menu:Change:Shortcuts+' => '', + 'Menu:WaitingAcceptance' => '等待接受的变更', + 'Menu:WaitingAcceptance+' => '', + 'Menu:WaitingApproval' => '等待批准的变更', + 'Menu:WaitingApproval+' => '', + 'Menu:Changes' => '待处理的变更', + 'Menu:Changes+' => '', + 'Menu:MyChanges' => '指派给我的变更', + 'Menu:MyChanges+' => '指派给我的变更 (作为办理人)', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Change +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Change' => '变更', + 'Class:Change+' => '', + 'Class:Change/Attribute:start_date' => '计划启动', + 'Class:Change/Attribute:start_date+' => '', + 'Class:Change/Attribute:status' => '状态', + 'Class:Change/Attribute:status+' => '', + 'Class:Change/Attribute:status/Value:new' => '新', + 'Class:Change/Attribute:status/Value:new+' => '', + 'Class:Change/Attribute:status/Value:validated' => '已生效', + 'Class:Change/Attribute:status/Value:validated+' => '', + 'Class:Change/Attribute:status/Value:rejected' => '已拒绝', + 'Class:Change/Attribute:status/Value:rejected+' => '', + 'Class:Change/Attribute:status/Value:assigned' => '已指派', + 'Class:Change/Attribute:status/Value:assigned+' => '', + 'Class:Change/Attribute:status/Value:plannedscheduled' => '已计划和安排', + 'Class:Change/Attribute:status/Value:plannedscheduled+' => '', + 'Class:Change/Attribute:status/Value:approved' => '已批准', + 'Class:Change/Attribute:status/Value:approved+' => '', + 'Class:Change/Attribute:status/Value:notapproved' => '未批准', + 'Class:Change/Attribute:status/Value:notapproved+' => '', + 'Class:Change/Attribute:status/Value:implemented' => '已实施', + 'Class:Change/Attribute:status/Value:implemented+' => '', + 'Class:Change/Attribute:status/Value:monitored' => '已监控', + 'Class:Change/Attribute:status/Value:monitored+' => '', + 'Class:Change/Attribute:status/Value:closed' => '已关闭', + 'Class:Change/Attribute:status/Value:closed+' => '', + 'Class:Change/Attribute:reason' => '原因', + 'Class:Change/Attribute:reason+' => '', + 'Class:Change/Attribute:requestor_id' => '申请者', + 'Class:Change/Attribute:requestor_id+' => '', + 'Class:Change/Attribute:requestor_email' => '申请者', + 'Class:Change/Attribute:requestor_email+' => '', + 'Class:Change/Attribute:org_id' => '客户', + 'Class:Change/Attribute:org_id+' => '', + 'Class:Change/Attribute:org_name' => '客户', + 'Class:Change/Attribute:org_name+' => '', + 'Class:Change/Attribute:workgroup_id' => '工作组', + 'Class:Change/Attribute:workgroup_id+' => '', + 'Class:Change/Attribute:workgroup_name' => '工作组', + 'Class:Change/Attribute:workgroup_name+' => '', + 'Class:Change/Attribute:creation_date' => '创建', + 'Class:Change/Attribute:creation_date+' => '', + 'Class:Change/Attribute:last_update' => '最后更新', + 'Class:Change/Attribute:last_update+' => '', + 'Class:Change/Attribute:end_date' => '结束', + 'Class:Change/Attribute:end_date+' => '', + 'Class:Change/Attribute:close_date' => '关闭', + 'Class:Change/Attribute:close_date+' => '', + 'Class:Change/Attribute:impact' => '影响', + 'Class:Change/Attribute:impact+' => '', + 'Class:Change/Attribute:agent_id' => '办理人', + 'Class:Change/Attribute:agent_id+' => '', + 'Class:Change/Attribute:agent_name' => '办理人', + 'Class:Change/Attribute:agent_name+' => '', + 'Class:Change/Attribute:agent_email' => '办理人', + 'Class:Change/Attribute:agent_email+' => '', + 'Class:Change/Attribute:supervisor_group_id' => '监督团队', + 'Class:Change/Attribute:supervisor_group_id+' => '', + 'Class:Change/Attribute:supervisor_group_name' => '监督团队', + 'Class:Change/Attribute:supervisor_group_name+' => '', + 'Class:Change/Attribute:supervisor_id' => '监督者', + 'Class:Change/Attribute:supervisor_id+' => '', + 'Class:Change/Attribute:supervisor_email' => '监督者', + 'Class:Change/Attribute:supervisor_email+' => '', + 'Class:Change/Attribute:manager_group_id' => '管理团队', + 'Class:Change/Attribute:manager_group_id+' => '', + 'Class:Change/Attribute:manager_group_name' => '管理团队', + 'Class:Change/Attribute:manager_group_name+' => '', + 'Class:Change/Attribute:manager_id' => '管理人', + 'Class:Change/Attribute:manager_id+' => '', + 'Class:Change/Attribute:manager_email' => '管理人', + 'Class:Change/Attribute:manager_email+' => '', + 'Class:Change/Attribute:outage' => '停歇', + 'Class:Change/Attribute:outage+' => '', + 'Class:Change/Attribute:outage/Value:yes' => '是', + 'Class:Change/Attribute:outage/Value:yes+' => '', + 'Class:Change/Attribute:outage/Value:no' => '否', + 'Class:Change/Attribute:outage/Value:no+' => '', + 'Class:Change/Attribute:change_request' => '申请', + 'Class:Change/Attribute:change_request+' => '', + 'Class:Change/Attribute:fallback' => '回退计划', + 'Class:Change/Attribute:fallback+' => '', + 'Class:Change/Stimulus:ev_validate' => '生效', + 'Class:Change/Stimulus:ev_validate+' => '', + 'Class:Change/Stimulus:ev_reject' => '拒绝', + 'Class:Change/Stimulus:ev_reject+' => '', + 'Class:Change/Stimulus:ev_assign' => '指派', + 'Class:Change/Stimulus:ev_assign+' => '', + 'Class:Change/Stimulus:ev_reopen' => '重开', + 'Class:Change/Stimulus:ev_reopen+' => '', + 'Class:Change/Stimulus:ev_plan' => '计划', + 'Class:Change/Stimulus:ev_plan+' => '', + 'Class:Change/Stimulus:ev_approve' => '批准', + 'Class:Change/Stimulus:ev_approve+' => '', + 'Class:Change/Stimulus:ev_replan' => '重新计划', + 'Class:Change/Stimulus:ev_replan+' => '', + 'Class:Change/Stimulus:ev_notapprove' => '不批准', + 'Class:Change/Stimulus:ev_notapprove+' => '', + 'Class:Change/Stimulus:ev_implement' => '实施', + 'Class:Change/Stimulus:ev_implement+' => '', + 'Class:Change/Stimulus:ev_monitor' => '监控', + 'Class:Change/Stimulus:ev_monitor+' => '', + 'Class:Change/Stimulus:ev_finish' => '完成', + 'Class:Change/Stimulus:ev_finish+' => '', +)); + +// +// Class: RoutineChange +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:RoutineChange' => '例行变更', + 'Class:RoutineChange+' => '', + 'Class:RoutineChange/Attribute:status/Value:new' => '新', + 'Class:RoutineChange/Attribute:status/Value:new+' => '', + 'Class:RoutineChange/Attribute:status/Value:assigned' => '已指派', + 'Class:RoutineChange/Attribute:status/Value:assigned+' => '', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled' => '已计划和安排', + 'Class:RoutineChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:RoutineChange/Attribute:status/Value:approved' => '已批准', + 'Class:RoutineChange/Attribute:status/Value:approved+' => '', + 'Class:RoutineChange/Attribute:status/Value:implemented' => '已实施', + 'Class:RoutineChange/Attribute:status/Value:implemented+' => '', + 'Class:RoutineChange/Attribute:status/Value:monitored' => '已监控', + 'Class:RoutineChange/Attribute:status/Value:monitored+' => '', + 'Class:RoutineChange/Attribute:status/Value:closed' => '已关闭', + 'Class:RoutineChange/Attribute:status/Value:closed+' => '', + 'Class:RoutineChange/Stimulus:ev_validate' => '生效', + 'Class:RoutineChange/Stimulus:ev_validate+' => '', + 'Class:RoutineChange/Stimulus:ev_assign' => '指派', + 'Class:RoutineChange/Stimulus:ev_assign+' => '', + 'Class:RoutineChange/Stimulus:ev_reopen' => '重开', + 'Class:RoutineChange/Stimulus:ev_reopen+' => '', + 'Class:RoutineChange/Stimulus:ev_plan' => '计划', + 'Class:RoutineChange/Stimulus:ev_plan+' => '', + 'Class:RoutineChange/Stimulus:ev_replan' => '重新计划', + 'Class:RoutineChange/Stimulus:ev_replan+' => '', + 'Class:RoutineChange/Stimulus:ev_implement' => '实施', + 'Class:RoutineChange/Stimulus:ev_implement+' => '', + 'Class:RoutineChange/Stimulus:ev_monitor' => '监控', + 'Class:RoutineChange/Stimulus:ev_monitor+' => '', + 'Class:RoutineChange/Stimulus:ev_finish' => '完成', + 'Class:RoutineChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: ApprovedChange +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ApprovedChange' => '批准的变更', + 'Class:ApprovedChange+' => '', + 'Class:ApprovedChange/Attribute:approval_date' => '批准日期', + 'Class:ApprovedChange/Attribute:approval_date+' => '', + 'Class:ApprovedChange/Attribute:approval_comment' => '批准说明', + 'Class:ApprovedChange/Attribute:approval_comment+' => '', + 'Class:ApprovedChange/Stimulus:ev_validate' => '生效', + 'Class:ApprovedChange/Stimulus:ev_validate+' => '', + 'Class:ApprovedChange/Stimulus:ev_reject' => '拒绝', + 'Class:ApprovedChange/Stimulus:ev_reject+' => '', + 'Class:ApprovedChange/Stimulus:ev_assign' => '指派', + 'Class:ApprovedChange/Stimulus:ev_assign+' => '', + 'Class:ApprovedChange/Stimulus:ev_reopen' => '重开', + 'Class:ApprovedChange/Stimulus:ev_reopen+' => '', + 'Class:ApprovedChange/Stimulus:ev_plan' => '计划', + 'Class:ApprovedChange/Stimulus:ev_plan+' => '', + 'Class:ApprovedChange/Stimulus:ev_approve' => '批准', + 'Class:ApprovedChange/Stimulus:ev_approve+' => '', + 'Class:ApprovedChange/Stimulus:ev_replan' => '重新计划', + 'Class:ApprovedChange/Stimulus:ev_replan+' => '', + 'Class:ApprovedChange/Stimulus:ev_notapprove' => '拒绝批准', + 'Class:ApprovedChange/Stimulus:ev_notapprove+' => '', + 'Class:ApprovedChange/Stimulus:ev_implement' => '实施', + 'Class:ApprovedChange/Stimulus:ev_implement+' => '', + 'Class:ApprovedChange/Stimulus:ev_monitor' => '监控', + 'Class:ApprovedChange/Stimulus:ev_monitor+' => '', + 'Class:ApprovedChange/Stimulus:ev_finish' => '完成', + 'Class:ApprovedChange/Stimulus:ev_finish+' => '', +)); +// +// Class: NormalChange +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:NormalChange' => '正常变更', + 'Class:NormalChange+' => '', + 'Class:NormalChange/Attribute:status/Value:new' => '新', + 'Class:NormalChange/Attribute:status/Value:new+' => '', + 'Class:NormalChange/Attribute:status/Value:validated' => '已生效', + 'Class:NormalChange/Attribute:status/Value:validated+' => '', + 'Class:NormalChange/Attribute:status/Value:rejected' => '已拒绝', + 'Class:NormalChange/Attribute:status/Value:rejected+' => '', + 'Class:NormalChange/Attribute:status/Value:assigned' => '已指派', + 'Class:NormalChange/Attribute:status/Value:assigned+' => '', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled' => '已计划和安排', + 'Class:NormalChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:NormalChange/Attribute:status/Value:approved' => '已批准', + 'Class:NormalChange/Attribute:status/Value:approved+' => '', + 'Class:NormalChange/Attribute:status/Value:notapproved' => '不批准', + 'Class:NormalChange/Attribute:status/Value:notapproved+' => '', + 'Class:NormalChange/Attribute:status/Value:implemented' => '已实施', + 'Class:NormalChange/Attribute:status/Value:implemented+' => '', + 'Class:NormalChange/Attribute:status/Value:monitored' => '已监控', + 'Class:NormalChange/Attribute:status/Value:monitored+' => '', + 'Class:NormalChange/Attribute:status/Value:closed' => '已关闭', + 'Class:NormalChange/Attribute:status/Value:closed+' => '', + 'Class:NormalChange/Attribute:acceptance_date' => '接受日期', + 'Class:NormalChange/Attribute:acceptance_date+' => '', + 'Class:NormalChange/Attribute:acceptance_comment' => '接受说明', + 'Class:NormalChange/Attribute:acceptance_comment+' => '', + 'Class:NormalChange/Stimulus:ev_validate' => '生效', + 'Class:NormalChange/Stimulus:ev_validate+' => '', + 'Class:NormalChange/Stimulus:ev_reject' => '拒绝', + 'Class:NormalChange/Stimulus:ev_reject+' => '', + 'Class:NormalChange/Stimulus:ev_assign' => '指派', + 'Class:NormalChange/Stimulus:ev_assign+' => '', + 'Class:NormalChange/Stimulus:ev_reopen' => '重开', + 'Class:NormalChange/Stimulus:ev_reopen+' => '', + 'Class:NormalChange/Stimulus:ev_plan' => '计划', + 'Class:NormalChange/Stimulus:ev_plan+' => '', + 'Class:NormalChange/Stimulus:ev_approve' => '批准', + 'Class:NormalChange/Stimulus:ev_approve+' => '', + 'Class:NormalChange/Stimulus:ev_replan' => '重新计划', + 'Class:NormalChange/Stimulus:ev_replan+' => '', + 'Class:NormalChange/Stimulus:ev_notapprove' => '拒绝批准', + 'Class:NormalChange/Stimulus:ev_notapprove+' => '', + 'Class:NormalChange/Stimulus:ev_implement' => '实施', + 'Class:NormalChange/Stimulus:ev_implement+' => '', + 'Class:NormalChange/Stimulus:ev_monitor' => '监控', + 'Class:NormalChange/Stimulus:ev_monitor+' => '', + 'Class:NormalChange/Stimulus:ev_finish' => '完成', + 'Class:NormalChange/Stimulus:ev_finish+' => '', +)); + +// +// Class: EmergencyChange +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:EmergencyChange' => '紧急变更', + 'Class:EmergencyChange+' => '', + 'Class:EmergencyChange/Attribute:status/Value:new' => '新', + 'Class:EmergencyChange/Attribute:status/Value:new+' => '', + 'Class:EmergencyChange/Attribute:status/Value:validated' => '已生效', + 'Class:EmergencyChange/Attribute:status/Value:validated+' => '', + 'Class:EmergencyChange/Attribute:status/Value:rejected' => '已拒绝', + 'Class:EmergencyChange/Attribute:status/Value:rejected+' => '', + 'Class:EmergencyChange/Attribute:status/Value:assigned' => '已指派', + 'Class:EmergencyChange/Attribute:status/Value:assigned+' => '', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled' => '已计划和安排', + 'Class:EmergencyChange/Attribute:status/Value:plannedscheduled+' => '', + 'Class:EmergencyChange/Attribute:status/Value:approved' => '已批准', + 'Class:EmergencyChange/Attribute:status/Value:approved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:notapproved' => '不批准', + 'Class:EmergencyChange/Attribute:status/Value:notapproved+' => '', + 'Class:EmergencyChange/Attribute:status/Value:implemented' => '已实施', + 'Class:EmergencyChange/Attribute:status/Value:implemented+' => '', + 'Class:EmergencyChange/Attribute:status/Value:monitored' => '已监控', + 'Class:EmergencyChange/Attribute:status/Value:monitored+' => '', + 'Class:EmergencyChange/Attribute:status/Value:closed' => '已关闭', + 'Class:EmergencyChange/Attribute:status/Value:closed+' => '', + 'Class:EmergencyChange/Stimulus:ev_validate' => '生效', + 'Class:EmergencyChange/Stimulus:ev_validate+' => '', + 'Class:EmergencyChange/Stimulus:ev_reject' => '拒绝', + 'Class:EmergencyChange/Stimulus:ev_reject+' => '', + 'Class:EmergencyChange/Stimulus:ev_assign' => '指派', + 'Class:EmergencyChange/Stimulus:ev_assign+' => '', + 'Class:EmergencyChange/Stimulus:ev_reopen' => '重开', + 'Class:EmergencyChange/Stimulus:ev_reopen+' => '', + 'Class:EmergencyChange/Stimulus:ev_plan' => '计划', + 'Class:EmergencyChange/Stimulus:ev_plan+' => '', + 'Class:EmergencyChange/Stimulus:ev_approve' => '批准', + 'Class:EmergencyChange/Stimulus:ev_approve+' => '', + 'Class:EmergencyChange/Stimulus:ev_replan' => '重新计划', + 'Class:EmergencyChange/Stimulus:ev_replan+' => '', + 'Class:EmergencyChange/Stimulus:ev_notapprove' => '拒绝批准', + 'Class:EmergencyChange/Stimulus:ev_notapprove+' => '', + 'Class:EmergencyChange/Stimulus:ev_implement' => '实施', + 'Class:EmergencyChange/Stimulus:ev_implement+' => '', + 'Class:EmergencyChange/Stimulus:ev_monitor' => '监控', + 'Class:EmergencyChange/Stimulus:ev_monitor+' => '', + 'Class:EmergencyChange/Stimulus:ev_finish' => '完成', + 'Class:EmergencyChange/Stimulus:ev_finish+' => '', +)); + +?> diff --git a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php index 5bcb491acb..dee08021ab 100644 --- a/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/module.itop-config-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-config-mgmt.php', 'ru.dict.itop-config-mgmt.php', 'tr.dict.itop-config-mgmt.php', + 'zh.dict.itop-config-mgmt.php', ), 'data.struct' => array( 'data.struct.Audit.xml', diff --git a/modules/itop-config-mgmt-1.0.0/zh.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/zh.dict.itop-config-mgmt.php new file mode 100644 index 0000000000..4423aff90c --- /dev/null +++ b/modules/itop-config-mgmt-1.0.0/zh.dict.itop-config-mgmt.php @@ -0,0 +1,1054 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +////////////////////////////////////////////////////////////////////// +// Relations +////////////////////////////////////////////////////////////////////// +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Relation:impacts/Description' => '被影响的元素', + 'Relation:impacts/VerbUp' => '影响...', + 'Relation:impacts/VerbDown' => '被影响的元素...', + 'Relation:depends on/Description' => '该元素依赖的元素...', + 'Relation:depends on/VerbUp' => '依赖于...', + 'Relation:depends on/VerbDown' => '影响...', +)); + + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Note: The classes have been grouped by categories: bizmodel +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// +// Class: Organization +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Organization' => '组织', + 'Class:Organization+' => '', + 'Class:Organization/Attribute:name' => '名称', + 'Class:Organization/Attribute:name+' => '常用名称', + 'Class:Organization/Attribute:code' => '编码', + 'Class:Organization/Attribute:code+' => '组织编码(Siret, DUNS,...)', + 'Class:Organization/Attribute:status' => '状态', + 'Class:Organization/Attribute:status+' => '', + 'Class:Organization/Attribute:status/Value:active' => '活动', + 'Class:Organization/Attribute:status/Value:active+' => '活动', + 'Class:Organization/Attribute:status/Value:inactive' => '非活动', + 'Class:Organization/Attribute:status/Value:inactive+' => '非活动', + 'Class:Organization/Attribute:parent_id' => '父级', + 'Class:Organization/Attribute:parent_id+' => '父级组织', + 'Class:Organization/Attribute:parent_name' => '父级名称', + 'Class:Organization/Attribute:parent_name+' => '父级组织名称', +)); + + +// +// Class: Location +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Location' => '位置', + 'Class:Location+' => '任何类型的地理位置: 区域, 国家, 城市, 位置, 建筑, 楼层, 房间, 机架,...', + 'Class:Location/Attribute:name' => '名称', + 'Class:Location/Attribute:name+' => '', + 'Class:Location/Attribute:status' => '状态', + 'Class:Location/Attribute:status+' => '', + 'Class:Location/Attribute:status/Value:active' => '活动', + 'Class:Location/Attribute:status/Value:active+' => '活动', + 'Class:Location/Attribute:status/Value:inactive' => '非活动', + 'Class:Location/Attribute:status/Value:inactive+' => '非活动', + 'Class:Location/Attribute:org_id' => '拥有者组织', + 'Class:Location/Attribute:org_id+' => '', + 'Class:Location/Attribute:org_name' => '拥有者组织名称', + 'Class:Location/Attribute:org_name+' => '', + 'Class:Location/Attribute:address' => '地址', + 'Class:Location/Attribute:address+' => '门户地址', + 'Class:Location/Attribute:postal_code' => '邮编', + 'Class:Location/Attribute:postal_code+' => 'ZIP/邮政编码', + 'Class:Location/Attribute:city' => '城市', + 'Class:Location/Attribute:city+' => '', + 'Class:Location/Attribute:country' => '国家', + 'Class:Location/Attribute:country+' => '', + 'Class:Location/Attribute:parent_id' => '父级位置', + 'Class:Location/Attribute:parent_id+' => '', + 'Class:Location/Attribute:parent_name' => '父级名称', + 'Class:Location/Attribute:parent_name+' => '', + 'Class:Location/Attribute:contact_list' => '联系人', + 'Class:Location/Attribute:contact_list+' => '该场点的联系人', + 'Class:Location/Attribute:infra_list' => '基础架构', + 'Class:Location/Attribute:infra_list+' => '该场点的CI', +)); +// +// Class: Group +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Group' => '组', + 'Class:Group+' => '', + 'Class:Group/Attribute:name' => '名称', + 'Class:Group/Attribute:name+' => '', + 'Class:Group/Attribute:status' => '状态', + 'Class:Group/Attribute:status+' => '', + 'Class:Group/Attribute:status/Value:implementation' => '实施', + 'Class:Group/Attribute:status/Value:implementation+' => '实施', + 'Class:Group/Attribute:status/Value:obsolete' => '荒废', + 'Class:Group/Attribute:status/Value:obsolete+' => '荒废', + 'Class:Group/Attribute:status/Value:production' => '生产', + 'Class:Group/Attribute:status/Value:production+' => '生产', + 'Class:Group/Attribute:org_id' => '组织', + 'Class:Group/Attribute:org_id+' => '', + 'Class:Group/Attribute:owner_name' => '名称', + 'Class:Group/Attribute:owner_name+' => '常用名称', + 'Class:Group/Attribute:description' => '描述', + 'Class:Group/Attribute:description+' => '', + 'Class:Group/Attribute:type' => '种类', + 'Class:Group/Attribute:type+' => '', + 'Class:Group/Attribute:parent_id' => '父级组别', + 'Class:Group/Attribute:parent_id+' => '', + 'Class:Group/Attribute:parent_name' => '名称', + 'Class:Group/Attribute:parent_name+' => '', + 'Class:Group/Attribute:ci_list' => '连接的CI', + 'Class:Group/Attribute:ci_list+' => '', +)); + +// +// Class: lnkGroupToCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkGroupToCI' => '组 / CI', + 'Class:lnkGroupToCI+' => '', + 'Class:lnkGroupToCI/Attribute:group_id' => '组', + 'Class:lnkGroupToCI/Attribute:group_id+' => '', + 'Class:lnkGroupToCI/Attribute:group_name' => '名称', + 'Class:lnkGroupToCI/Attribute:group_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_id' => 'CI', + 'Class:lnkGroupToCI/Attribute:ci_id+' => '', + 'Class:lnkGroupToCI/Attribute:ci_name' => '名称', + 'Class:lnkGroupToCI/Attribute:ci_name+' => '', + 'Class:lnkGroupToCI/Attribute:ci_status' => 'CI状态', + 'Class:lnkGroupToCI/Attribute:ci_status+' => '', + 'Class:lnkGroupToCI/Attribute:reason' => '原因', + 'Class:lnkGroupToCI/Attribute:reason+' => '', +)); + + +// +// Class: Contact +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Contact' => '联系人', + 'Class:Contact+' => '', + 'Class:Contact/Attribute:name' => '名称', + 'Class:Contact/Attribute:name+' => '', + 'Class:Contact/Attribute:status' => '状态', + 'Class:Contact/Attribute:status+' => '', + 'Class:Contact/Attribute:status/Value:active' => '活动', + 'Class:Contact/Attribute:status/Value:active+' => '活动', + 'Class:Contact/Attribute:status/Value:inactive' => '非活动', + 'Class:Contact/Attribute:status/Value:inactive+' => '非活动', + 'Class:Contact/Attribute:org_id' => '组织', + 'Class:Contact/Attribute:org_id+' => '', + 'Class:Contact/Attribute:org_name' => '组织', + 'Class:Contact/Attribute:org_name+' => '', + 'Class:Contact/Attribute:email' => 'Email', + 'Class:Contact/Attribute:email+' => '', + 'Class:Contact/Attribute:phone' => '电话', + 'Class:Contact/Attribute:phone+' => '', + 'Class:Contact/Attribute:location_id' => '位置', + 'Class:Contact/Attribute:location_id+' => '', + 'Class:Contact/Attribute:location_name' => '位置', + 'Class:Contact/Attribute:location_name+' => '', + 'Class:Contact/Attribute:ci_list' => 'CI', + 'Class:Contact/Attribute:ci_list+' => '该联系人相关CI', + 'Class:Contact/Attribute:contract_list' => '合同', + 'Class:Contact/Attribute:contract_list+' => '该联系人相关合同', + 'Class:Contact/Attribute:service_list' => '服务', + 'Class:Contact/Attribute:service_list+' => '该联系人相关服务', + 'Class:Contact/Attribute:ticket_list' => 'Tickets', + 'Class:Contact/Attribute:ticket_list+' => '该联系人相关', + 'Class:Contact/Attribute:team_list' => '团队', + 'Class:Contact/Attribute:team_list+' => '该联系人所属团队', + 'Class:Contact/Attribute:finalclass' => '类别', + 'Class:Contact/Attribute:finalclass+' => '', +)); + +// +// Class: Person +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Person' => '人员', + 'Class:Person+' => '', + 'Class:Person/Attribute:first_name' => '姓', + 'Class:Person/Attribute:first_name+' => '', + 'Class:Person/Attribute:employee_id' => '员工ID', + 'Class:Person/Attribute:employee_id+' => '', +)); + +// +// Class: Team +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Team' => '团队', + 'Class:Team+' => '', + 'Class:Team/Attribute:member_list' => '成员', + 'Class:Team/Attribute:member_list+' => '团队所辖成员', +)); + +// +// Class: lnkTeamToContact +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkTeamToContact' => '团队成员', + 'Class:lnkTeamToContact+' => '团队中的成员', + 'Class:lnkTeamToContact/Attribute:team_id' => '团队', + 'Class:lnkTeamToContact/Attribute:team_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_id' => '成员', + 'Class:lnkTeamToContact/Attribute:contact_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_location_id' => '位置', + 'Class:lnkTeamToContact/Attribute:contact_location_id+' => '', + 'Class:lnkTeamToContact/Attribute:contact_email' => 'Email', + 'Class:lnkTeamToContact/Attribute:contact_email+' => '', + 'Class:lnkTeamToContact/Attribute:contact_phone' => '电话', + 'Class:lnkTeamToContact/Attribute:contact_phone+' => '', + 'Class:lnkTeamToContact/Attribute:role' => '角色', + 'Class:lnkTeamToContact/Attribute:role+' => '', +)); + +// +// Class: Document +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Document' => '文档', + 'Class:Document+' => '', + 'Class:Document/Attribute:name' => '名称', + 'Class:Document/Attribute:name+' => '', + 'Class:Document/Attribute:org_id' => '组织', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:org_name' => '组织名称', + 'Class:Document/Attribute:org_name+' => '', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:description' => '描述', + 'Class:Document/Attribute:description+' => '', + 'Class:Document/Attribute:type' => '类别', + 'Class:Document/Attribute:type+' => '', + 'Class:Document/Attribute:type/Value:contract' => '合同', + 'Class:Document/Attribute:type/Value:contract+' => '', + 'Class:Document/Attribute:type/Value:networkmap' => '网络图', + 'Class:Document/Attribute:type/Value:networkmap+' => '', + 'Class:Document/Attribute:type/Value:presentation' => '展现', + 'Class:Document/Attribute:type/Value:presentation+' => '', + 'Class:Document/Attribute:type/Value:training' => '培训', + 'Class:Document/Attribute:type/Value:training+' => '', + 'Class:Document/Attribute:type/Value:whitePaper' => '白皮书', + 'Class:Document/Attribute:type/Value:whitePaper+' => '', + 'Class:Document/Attribute:type/Value:workinginstructions' => '工作指南', + 'Class:Document/Attribute:type/Value:workinginstructions+' => '', + 'Class:Document/Attribute:type/Value:design' => '设计', + 'Class:Document/Attribute:type/Value:design+' => '', + 'Class:Document/Attribute:status' => '状态', + 'Class:Document/Attribute:status+' => '', + 'Class:Document/Attribute:status/Value:draft' => '草案', + 'Class:Document/Attribute:status/Value:draft+' => '', + 'Class:Document/Attribute:status/Value:obsolete' => '荒废', + 'Class:Document/Attribute:status/Value:obsolete+' => '', + 'Class:Document/Attribute:status/Value:published' => '已发布', + 'Class:Document/Attribute:status/Value:published+' => '', + 'Class:Document/Attribute:ci_list' => 'CI', + 'Class:Document/Attribute:ci_list+' => '参照该文档的CI', + 'Class:Document/Attribute:contract_list' => '合同', + 'Class:Document/Attribute:contract_list+' => '参照该文档的合同', + 'Class:Document/Attribute:service_list' => '服务', + 'Class:Document/Attribute:service_list+' => '参照该文档的服务', + 'Class:Document/Attribute:ticket_list' => '单据', + 'Class:Document/Attribute:ticket_list+' => '引用该文档的单据', + 'Class:Document:PreviewTab' => '预览', +)); + +// +// Class: WebDoc +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:WebDoc' => 'Web 文档', + 'Class:WebDoc+' => '其他web服务器上可获得的文档', + 'Class:WebDoc/Attribute:url' => 'Url', + 'Class:WebDoc/Attribute:url+' => '', +)); + +// +// Class: Note +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Note' => '备注', + 'Class:Note+' => '', + 'Class:Note/Attribute:note' => '文本', + 'Class:Note/Attribute:note+' => '', +)); + +// +// Class: FileDoc +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:FileDoc' => '文档', + 'Class:FileDoc+' => '', + 'Class:FileDoc/Attribute:contents' => '内容', + 'Class:FileDoc/Attribute:contents+' => '', +)); + +// +// Class: Licence +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Licence' => 'Licence', + 'Class:Licence+' => '', + 'Class:Licence/Attribute:provider' => '提供商', + 'Class:Licence/Attribute:provider+' => '', + 'Class:Licence/Attribute:org_id' => '所有者', + 'Class:Licence/Attribute:org_id+' => '', + 'Class:Licence/Attribute:org_name' => '名称', + 'Class:Licence/Attribute:org_name+' => '常用名称', + 'Class:Licence/Attribute:product' => '产品', + 'Class:Licence/Attribute:product+' => '', + 'Class:Licence/Attribute:name' => '名称', + 'Class:Licence/Attribute:name+' => '', + 'Class:Licence/Attribute:start' => '启始日期', + 'Class:Licence/Attribute:start+' => '', + 'Class:Licence/Attribute:end' => '终止日期', + 'Class:Licence/Attribute:end+' => '', + 'Class:Licence/Attribute:licence_key' => 'Key', + 'Class:Licence/Attribute:licence_key+' => '', + 'Class:Licence/Attribute:scope' => '范围', + 'Class:Licence/Attribute:scope+' => '', + 'Class:Licence/Attribute:usage_limit' => '使用限制', + 'Class:Licence/Attribute:usage_limit+' => '', + 'Class:Licence/Attribute:usage_list' => '用途', + 'Class:Licence/Attribute:usage_list+' => '使用该License的应用程序实例', +)); + + +// +// Class: Subnet +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Subnet' => '子网', + 'Class:Subnet+' => '', + //'Class:Subnet/Attribute:name' => 'Name', + //'Class:Subnet/Attribute:name+' => '', + 'Class:Subnet/Attribute:org_id' => '拥有者组织', + 'Class:Subnet/Attribute:org_id+' => '', + 'Class:Subnet/Attribute:description' => '描述', + 'Class:Subnet/Attribute:description+' => '', + 'Class:Subnet/Attribute:ip' => 'IP', + 'Class:Subnet/Attribute:ip+' => '', + 'Class:Subnet/Attribute:ip_mask' => 'IP Mask', + 'Class:Subnet/Attribute:ip_mask+' => '', +)); + +// +// Class: Patch +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Patch' => '补丁', + 'Class:Patch+' => '', + 'Class:Patch/Attribute:name' => '名称', + 'Class:Patch/Attribute:name+' => '', + 'Class:Patch/Attribute:description' => '描述', + 'Class:Patch/Attribute:description+' => '', + 'Class:Patch/Attribute:target_sw' => '应用程序范围', + 'Class:Patch/Attribute:target_sw+' => '目标软件 (OS 或应用程序)', + 'Class:Patch/Attribute:version' => '版本', + 'Class:Patch/Attribute:version+' => '', + 'Class:Patch/Attribute:type' => '类别', + 'Class:Patch/Attribute:type+' => '', + 'Class:Patch/Attribute:type/Value:application' => '应用程序', + 'Class:Patch/Attribute:type/Value:application+' => '', + 'Class:Patch/Attribute:type/Value:os' => 'OS', + 'Class:Patch/Attribute:type/Value:os+' => '', + 'Class:Patch/Attribute:type/Value:security' => '安全', + 'Class:Patch/Attribute:type/Value:security+' => '', + 'Class:Patch/Attribute:type/Value:servicepack' => '服务包', + 'Class:Patch/Attribute:type/Value:servicepack+' => '', + 'Class:Patch/Attribute:ci_list' => '设备', + 'Class:Patch/Attribute:ci_list+' => '安装该补丁的设备', +)); + +// +// Class: Software +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Software' => '软件', + 'Class:Software+' => '', + 'Class:Software/Attribute:name' => '名称', + 'Class:Software/Attribute:name+' => '', + 'Class:Software/Attribute:description' => '描述', + 'Class:Software/Attribute:description+' => '', + 'Class:Software/Attribute:instance_list' => '安装', + 'Class:Software/Attribute:instance_list+' => '该软件的实例', + 'Class:Software/Attribute:finalclass' => '类别', + 'Class:Software/Attribute:finalclass+' => '', +)); + +// +// Class: Application +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Application' => '应用程序', + 'Class:Application+' => '', + 'Class:Application/Attribute:name' => '名称', + 'Class:Application/Attribute:name+' => '', + 'Class:Application/Attribute:description' => '描述', + 'Class:Application/Attribute:description+' => '', + 'Class:Application/Attribute:instance_list' => '安装', + 'Class:Application/Attribute:instance_list+' => '应用程序实例', +)); + +// +// Class: DBServer +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:DBServer' => '数据库', + 'Class:DBServer+' => '数据库服务器SW', + 'Class:DBServer/Attribute:instance_list' => '安装', + 'Class:DBServer/Attribute:instance_list+' => '数据库服务器实例', +)); + +// +// Class: lnkPatchToCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkPatchToCI' => '补丁使用范围', + 'Class:lnkPatchToCI+' => '', + 'Class:lnkPatchToCI/Attribute:patch_id' => '补丁', + 'Class:lnkPatchToCI/Attribute:patch_id+' => '', + 'Class:lnkPatchToCI/Attribute:patch_name' => '补丁', + 'Class:lnkPatchToCI/Attribute:patch_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_id' => 'CI', + 'Class:lnkPatchToCI/Attribute:ci_id+' => '', + 'Class:lnkPatchToCI/Attribute:ci_name' => 'CI', + 'Class:lnkPatchToCI/Attribute:ci_name+' => '', + 'Class:lnkPatchToCI/Attribute:ci_status' => 'CI状态', + 'Class:lnkPatchToCI/Attribute:ci_status+' => '', +)); + +// +// Class: FunctionalCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:FunctionalCI' => '功能 CI', + 'Class:FunctionalCI+' => '', + 'Class:FunctionalCI/Attribute:name' => '名称', + 'Class:FunctionalCI/Attribute:name+' => '', + 'Class:FunctionalCI/Attribute:status' => '状态', + 'Class:FunctionalCI/Attribute:status+' => '', + 'Class:FunctionalCI/Attribute:status/Value:implementation' => '实施', + 'Class:FunctionalCI/Attribute:status/Value:implementation+' => '', + 'Class:FunctionalCI/Attribute:status/Value:obsolete' => '废弃', + 'Class:FunctionalCI/Attribute:status/Value:obsolete+' => '', + 'Class:FunctionalCI/Attribute:status/Value:production' => '生产', + 'Class:FunctionalCI/Attribute:status/Value:production+' => '', + 'Class:FunctionalCI/Attribute:org_id' => '拥有者组织', + 'Class:FunctionalCI/Attribute:org_id+' => '', + 'Class:FunctionalCI/Attribute:owner_name' => '拥有者组织', + 'Class:FunctionalCI/Attribute:owner_name+' => '', + 'Class:FunctionalCI/Attribute:importance' => '业务关键性', + 'Class:FunctionalCI/Attribute:importance+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:high' => '高', + 'Class:FunctionalCI/Attribute:importance/Value:high+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:low' => '低', + 'Class:FunctionalCI/Attribute:importance/Value:low+' => '', + 'Class:FunctionalCI/Attribute:importance/Value:medium' => '中', + 'Class:FunctionalCI/Attribute:importance/Value:medium+' => '', + 'Class:FunctionalCI/Attribute:contact_list' => '联系人', + 'Class:FunctionalCI/Attribute:contact_list+' => '该 CI 的联系人', + 'Class:FunctionalCI/Attribute:document_list' => '文档', + 'Class:FunctionalCI/Attribute:document_list+' => '该 CI 的文档', + 'Class:FunctionalCI/Attribute:solution_list' => '应用方案', + 'Class:FunctionalCI/Attribute:solution_list+' => '使用该 CI 的应用方案', + 'Class:FunctionalCI/Attribute:contract_list' => '合同', + 'Class:FunctionalCI/Attribute:contract_list+' => '支持该 CI 合同', + 'Class:FunctionalCI/Attribute:ticket_list' => '单据', + 'Class:FunctionalCI/Attribute:ticket_list+' => '与该 CI 相关的单据', + 'Class:FunctionalCI/Attribute:finalclass' => '类别', + 'Class:FunctionalCI/Attribute:finalclass+' => '', +)); + +// +// Class: SoftwareInstance +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:SoftwareInstance' => '软件实例', + 'Class:SoftwareInstance+' => '', + 'Class:SoftwareInstance/Attribute:device_id' => '设备', + 'Class:SoftwareInstance/Attribute:device_id+' => '', + 'Class:SoftwareInstance/Attribute:device_name' => '设备', + 'Class:SoftwareInstance/Attribute:device_name+' => '', + 'Class:SoftwareInstance/Attribute:licence_id' => 'Licence', + 'Class:SoftwareInstance/Attribute:licence_id+' => '', + 'Class:SoftwareInstance/Attribute:licence_name' => 'Licence', + 'Class:SoftwareInstance/Attribute:licence_name+' => '', + 'Class:SoftwareInstance/Attribute:software_name' => '软件', + 'Class:SoftwareInstance/Attribute:software_name+' => '', + 'Class:SoftwareInstance/Attribute:version' => '版本', + 'Class:SoftwareInstance/Attribute:version+' => '', + 'Class:SoftwareInstance/Attribute:description' => '描述', + 'Class:SoftwareInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationInstance +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ApplicationInstance' => '应用实例', + 'Class:ApplicationInstance+' => '', + 'Class:ApplicationInstance/Attribute:software_id' => '软件', + 'Class:ApplicationInstance/Attribute:software_id+' => '', + 'Class:ApplicationInstance/Attribute:software_name' => '名称', + 'Class:ApplicationInstance/Attribute:software_name+' => '', +)); + + +// +// Class: DBServerInstance +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:DBServerInstance' => 'DB Server 实例', + 'Class:DBServerInstance+' => '', + 'Class:DBServerInstance/Attribute:software_id' => '软件', + 'Class:DBServerInstance/Attribute:software_id+' => '', + 'Class:DBServerInstance/Attribute:software_name' => '名称', + 'Class:DBServerInstance/Attribute:software_name+' => '', + 'Class:DBServerInstance/Attribute:dbinstance_list' => '数据库', + 'Class:DBServerInstance/Attribute:dbinstance_list+' => '数据库源', +)); + + +// +// Class: DatabaseInstance +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:DatabaseInstance' => 'Database 实例', + 'Class:DatabaseInstance+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_id' => '数据库服务器', + 'Class:DatabaseInstance/Attribute:db_server_instance_id+' => '', + 'Class:DatabaseInstance/Attribute:db_server_instance_version' => '数据库版本', + 'Class:DatabaseInstance/Attribute:db_server_instance_version+' => '', + 'Class:DatabaseInstance/Attribute:description' => '描述', + 'Class:DatabaseInstance/Attribute:description+' => '', +)); + +// +// Class: ApplicationSolution +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ApplicationSolution' => '应用方案', + 'Class:ApplicationSolution+' => '', + 'Class:ApplicationSolution/Attribute:description' => '描述', + 'Class:ApplicationSolution/Attribute:description+' => '', + 'Class:ApplicationSolution/Attribute:ci_list' => 'CI', + 'Class:ApplicationSolution/Attribute:ci_list+' => '构成该方案的 CI', + 'Class:ApplicationSolution/Attribute:process_list' => '业务流程', + 'Class:ApplicationSolution/Attribute:process_list+' => '依赖于该方案的业务流程', +)); + +// +// Class: BusinessProcess +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:BusinessProcess' => '业务流程', + 'Class:BusinessProcess+' => '', + 'Class:BusinessProcess/Attribute:description' => '描述', + 'Class:BusinessProcess/Attribute:description+' => '', + 'Class:BusinessProcess/Attribute:used_solution_list' => '应用方案', + 'Class:BusinessProcess/Attribute:used_solution_list+' => '业务流程所依赖的应用方案', +)); + +// +// Class: ConnectableCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ConnectableCI' => '可连接的 CI', + 'Class:ConnectableCI+' => '物理 CI', + 'Class:ConnectableCI/Attribute:brand' => '品牌', + 'Class:ConnectableCI/Attribute:brand+' => '', + 'Class:ConnectableCI/Attribute:model' => '型号', + 'Class:ConnectableCI/Attribute:model+' => '', + 'Class:ConnectableCI/Attribute:serial_number' => '序列号', + 'Class:ConnectableCI/Attribute:serial_number+' => '', + 'Class:ConnectableCI/Attribute:asset_ref' => '资产参考资料', + 'Class:ConnectableCI/Attribute:asset_ref+' => '', +)); + +// +// Class: NetworkInterface +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:NetworkInterface' => '网络接口', + 'Class:NetworkInterface+' => '', + 'Class:NetworkInterface/Attribute:device_id' => '设备', + 'Class:NetworkInterface/Attribute:device_id+' => '', + 'Class:NetworkInterface/Attribute:device_name' => '设备', + 'Class:NetworkInterface/Attribute:device_name+' => '', + 'Class:NetworkInterface/Attribute:logical_type' => '逻辑类别', + 'Class:NetworkInterface/Attribute:logical_type+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup' => '备份', + 'Class:NetworkInterface/Attribute:logical_type/Value:backup+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical' => '逻辑的', + 'Class:NetworkInterface/Attribute:logical_type/Value:logical+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:port' => '端口', + 'Class:NetworkInterface/Attribute:logical_type/Value:port+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary' => 'Primary', + 'Class:NetworkInterface/Attribute:logical_type/Value:primary+' => '', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary' => 'Secondary', + 'Class:NetworkInterface/Attribute:logical_type/Value:secondary+' => '', + 'Class:NetworkInterface/Attribute:physical_type' => '物理类别', + 'Class:NetworkInterface/Attribute:physical_type+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm' => 'ATM', + 'Class:NetworkInterface/Attribute:physical_type/Value:atm+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet' => 'Ethernet', + 'Class:NetworkInterface/Attribute:physical_type/Value:ethernet+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay' => 'Frame Relay', + 'Class:NetworkInterface/Attribute:physical_type/Value:framerelay+' => '', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan' => 'VLAN', + 'Class:NetworkInterface/Attribute:physical_type/Value:vlan+' => '', + 'Class:NetworkInterface/Attribute:ip_address' => 'IP 地址', + 'Class:NetworkInterface/Attribute:ip_address+' => '', + 'Class:NetworkInterface/Attribute:ip_mask' => 'IP 掩码', + 'Class:NetworkInterface/Attribute:ip_mask+' => '', + 'Class:NetworkInterface/Attribute:mac_address' => 'MAC 地址', + 'Class:NetworkInterface/Attribute:mac_address+' => '', + 'Class:NetworkInterface/Attribute:speed' => '速率', + 'Class:NetworkInterface/Attribute:speed+' => '', + 'Class:NetworkInterface/Attribute:duplex' => 'Duplex', + 'Class:NetworkInterface/Attribute:duplex+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:auto' => 'Auto', + 'Class:NetworkInterface/Attribute:duplex/Value:auto+' => 'Auto', + 'Class:NetworkInterface/Attribute:duplex/Value:full' => 'Full', + 'Class:NetworkInterface/Attribute:duplex/Value:full+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:half' => 'Half', + 'Class:NetworkInterface/Attribute:duplex/Value:half+' => '', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown' => '未知', + 'Class:NetworkInterface/Attribute:duplex/Value:unknown+' => '', + 'Class:NetworkInterface/Attribute:connected_if' => '连接到', + 'Class:NetworkInterface/Attribute:connected_if+' => '连接的接口', + 'Class:NetworkInterface/Attribute:connected_name' => '连接到', + 'Class:NetworkInterface/Attribute:connected_name+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id' => '连接的设备', + 'Class:NetworkInterface/Attribute:connected_if_device_id+' => '', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name' => '设备', + 'Class:NetworkInterface/Attribute:connected_if_device_id_name+' => '', + 'Class:NetworkInterface/Attribute:link_type' => '连接类别', + 'Class:NetworkInterface/Attribute:link_type+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink' => 'Down link', + 'Class:NetworkInterface/Attribute:link_type/Value:downlink+' => '', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink' => 'Up link', + 'Class:NetworkInterface/Attribute:link_type/Value:uplink+' => '', +)); + + + +// +// Class: Device +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Device' => '设备', + 'Class:Device+' => '', + 'Class:Device/Attribute:nwinterface_list' => '网络接口', + 'Class:Device/Attribute:nwinterface_list+' => '', +)); + +// +// Class: PC +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:PC' => 'PC', + 'Class:PC+' => '', + 'Class:PC/Attribute:cpu' => 'CPU', + 'Class:PC/Attribute:cpu+' => '', + 'Class:PC/Attribute:ram' => '内存', + 'Class:PC/Attribute:ram+' => '', + 'Class:PC/Attribute:hdd' => '硬盘', + 'Class:PC/Attribute:hdd+' => '', + 'Class:PC/Attribute:os_family' => 'OS 族', + 'Class:PC/Attribute:os_family+' => '', + 'Class:PC/Attribute:os_version' => 'OS 版本', + 'Class:PC/Attribute:os_version+' => '', + 'Class:PC/Attribute:application_list' => '应用程序', + 'Class:PC/Attribute:application_list+' => '安装在该 PC 上的应用程序', + 'Class:PC/Attribute:patch_list' => '补丁', + 'Class:PC/Attribute:patch_list+' => '安装在该 PC 上的补丁', +)); + +// +// Class: MobileCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:MobileCI' => '移动 CI', + 'Class:MobileCI+' => '', +)); + +// +// Class: MobilePhone +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:MobilePhone' => '移动电话', + 'Class:MobilePhone+' => '', + 'Class:MobilePhone/Attribute:number' => '电话号码', + 'Class:MobilePhone/Attribute:number+' => '', + 'Class:MobilePhone/Attribute:imei' => 'IMEI', + 'Class:MobilePhone/Attribute:imei+' => '', + 'Class:MobilePhone/Attribute:hw_pin' => '硬件 PIN', + 'Class:MobilePhone/Attribute:hw_pin+' => '', +)); + +// +// Class: InfrastructureCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:InfrastructureCI' => '基础架构 CI', + 'Class:InfrastructureCI+' => '', + 'Class:InfrastructureCI/Attribute:description' => '描述', + 'Class:InfrastructureCI/Attribute:description+' => '', + 'Class:InfrastructureCI/Attribute:location_id' => '位置', + 'Class:InfrastructureCI/Attribute:location_id+' => '', + 'Class:InfrastructureCI/Attribute:location_name' => '位置', + 'Class:InfrastructureCI/Attribute:location_name+' => '', + 'Class:InfrastructureCI/Attribute:location_details' => '位置明细', + 'Class:InfrastructureCI/Attribute:location_details+' => '', + 'Class:InfrastructureCI/Attribute:management_ip' => '管理 IP', + 'Class:InfrastructureCI/Attribute:management_ip+' => '', + 'Class:InfrastructureCI/Attribute:default_gateway' => '缺省网关', + 'Class:InfrastructureCI/Attribute:default_gateway+' => '', +)); + +// +// Class: NetworkDevice +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:NetworkDevice' => '网络设备', + 'Class:NetworkDevice+' => '', + 'Class:NetworkDevice/Attribute:type' => '类别', + 'Class:NetworkDevice/Attribute:type+' => '', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator' => 'WAN 加速器', + 'Class:NetworkDevice/Attribute:type/Value:wanaccelerator+' => '', + 'Class:NetworkDevice/Attribute:type/Value:firewall' => '防火墙', + 'Class:NetworkDevice/Attribute:type/Value:firewall+' => '', + 'Class:NetworkDevice/Attribute:type/Value:hub' => '集线器', + 'Class:NetworkDevice/Attribute:type/Value:hub+' => '', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer' => '负载均衡', + 'Class:NetworkDevice/Attribute:type/Value:loadbalancer+' => '', + 'Class:NetworkDevice/Attribute:type/Value:router' => '路由器', + 'Class:NetworkDevice/Attribute:type/Value:router+' => '', + 'Class:NetworkDevice/Attribute:type/Value:switch' => '交换机', + 'Class:NetworkDevice/Attribute:type/Value:switch+' => '', + 'Class:NetworkDevice/Attribute:ios_version' => 'IOS 版本', + 'Class:NetworkDevice/Attribute:ios_version+' => '', + 'Class:NetworkDevice/Attribute:ram' => '内存', + 'Class:NetworkDevice/Attribute:ram+' => '', + 'Class:NetworkDevice/Attribute:snmp_read' => 'SNMP 读', + 'Class:NetworkDevice/Attribute:snmp_read+' => '', + 'Class:NetworkDevice/Attribute:snmp_write' => 'SNMP 写', + 'Class:NetworkDevice/Attribute:snmp_write+' => '', +)); + +// +// Class: Server +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Server' => '服务器', + 'Class:Server+' => '', + 'Class:Server/Attribute:cpu' => 'CPU', + 'Class:Server/Attribute:cpu+' => '', + 'Class:Server/Attribute:ram' => '内存', + 'Class:Server/Attribute:ram+' => '', + 'Class:Server/Attribute:hdd' => '硬盘', + 'Class:Server/Attribute:hdd+' => '', + 'Class:Server/Attribute:os_family' => 'OS族', + 'Class:Server/Attribute:os_family+' => '', + 'Class:Server/Attribute:os_version' => 'OS版本 ', + 'Class:Server/Attribute:os_version+' => '', + 'Class:Server/Attribute:application_list' => '应用程序', + 'Class:Server/Attribute:application_list+' => '服务器上安装的应用程序', + 'Class:Server/Attribute:patch_list' => '补丁', + 'Class:Server/Attribute:patch_list+' => '服务器上安装的补丁', +)); + +// +// Class: Printer +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Printer' => '打印机', + 'Class:Printer+' => '', + 'Class:Printer/Attribute:type' => '类别', + 'Class:Printer/Attribute:type+' => '', + 'Class:Printer/Attribute:type/Value:mopier' => 'Mopier', + 'Class:Printer/Attribute:type/Value:mopier+' => '', + 'Class:Printer/Attribute:type/Value:printer' => '打印机', + 'Class:Printer/Attribute:type/Value:printer+' => '', + 'Class:Printer/Attribute:technology' => 'Technology', + 'Class:Printer/Attribute:technology+' => '', + 'Class:Printer/Attribute:technology/Value:inkjet' => '喷墨', + 'Class:Printer/Attribute:technology/Value:inkjet+' => '', + 'Class:Printer/Attribute:technology/Value:laser' => '激光', + 'Class:Printer/Attribute:technology/Value:laser+' => '', + 'Class:Printer/Attribute:technology/Value:tracer' => '绘图', + 'Class:Printer/Attribute:technology/Value:tracer+' => '', +)); + +// +// Class: lnkCIToDoc +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkCIToDoc' => 'Doc/CI', + 'Class:lnkCIToDoc+' => '', + 'Class:lnkCIToDoc/Attribute:ci_id' => 'CI', + 'Class:lnkCIToDoc/Attribute:ci_id+' => '', + 'Class:lnkCIToDoc/Attribute:ci_name' => 'CI', + 'Class:lnkCIToDoc/Attribute:ci_name+' => '', + 'Class:lnkCIToDoc/Attribute:ci_status' => 'CI 状态', + 'Class:lnkCIToDoc/Attribute:ci_status+' => '', + 'Class:lnkCIToDoc/Attribute:document_id' => '文档', + 'Class:lnkCIToDoc/Attribute:document_id+' => '', + 'Class:lnkCIToDoc/Attribute:document_name' => '文档', + 'Class:lnkCIToDoc/Attribute:document_name+' => '', + 'Class:lnkCIToDoc/Attribute:document_type' => '文档类别', + 'Class:lnkCIToDoc/Attribute:document_type+' => '', + 'Class:lnkCIToDoc/Attribute:document_status' => '文档状态', + 'Class:lnkCIToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkCIToContact +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkCIToContact' => 'CI/联系人', + 'Class:lnkCIToContact+' => '', + 'Class:lnkCIToContact/Attribute:ci_id' => 'CI', + 'Class:lnkCIToContact/Attribute:ci_id+' => '', + 'Class:lnkCIToContact/Attribute:ci_name' => 'CI', + 'Class:lnkCIToContact/Attribute:ci_name+' => '', + 'Class:lnkCIToContact/Attribute:ci_status' => 'CI 状态', + 'Class:lnkCIToContact/Attribute:ci_status+' => '', + 'Class:lnkCIToContact/Attribute:contact_id' => '联系人', + 'Class:lnkCIToContact/Attribute:contact_id+' => '', + 'Class:lnkCIToContact/Attribute:contact_name' => '联系人', + 'Class:lnkCIToContact/Attribute:contact_name+' => '', + 'Class:lnkCIToContact/Attribute:contact_email' => '联系人 Email', + 'Class:lnkCIToContact/Attribute:contact_email+' => '', + 'Class:lnkCIToContact/Attribute:role' => '角色', + 'Class:lnkCIToContact/Attribute:role+' => '和该 CI 相关的联系人的角色', +)); + +// +// Class: lnkSolutionToCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkSolutionToCI' => 'CI/方案', + 'Class:lnkSolutionToCI+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_id' => '应用方案', + 'Class:lnkSolutionToCI/Attribute:solution_id+' => '', + 'Class:lnkSolutionToCI/Attribute:solution_name' => '应用方案', + 'Class:lnkSolutionToCI/Attribute:solution_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_id' => 'CI', + 'Class:lnkSolutionToCI/Attribute:ci_id+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_name' => 'CI', + 'Class:lnkSolutionToCI/Attribute:ci_name+' => '', + 'Class:lnkSolutionToCI/Attribute:ci_status' => 'CI 状态', + 'Class:lnkSolutionToCI/Attribute:ci_status+' => '', + 'Class:lnkSolutionToCI/Attribute:utility' => '用途', + 'Class:lnkSolutionToCI/Attribute:utility+' => '在方案中该 CI 的用途', +)); + +// +// Class: lnkProcessToSolution +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkProcessToSolution' => '业务流程/方案', + 'Class:lnkProcessToSolution+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_id' => '应用方案', + 'Class:lnkProcessToSolution/Attribute:solution_id+' => '', + 'Class:lnkProcessToSolution/Attribute:solution_name' => '应用方案', + 'Class:lnkProcessToSolution/Attribute:solution_name+' => '', + 'Class:lnkProcessToSolution/Attribute:process_id' => '流程', + 'Class:lnkProcessToSolution/Attribute:process_id+' => '', + 'Class:lnkProcessToSolution/Attribute:process_name' => '流程', + 'Class:lnkProcessToSolution/Attribute:process_name+' => '', + 'Class:lnkProcessToSolution/Attribute:reason' => '原因', + 'Class:lnkProcessToSolution/Attribute:reason+' => '联系流程和方案的更多信息', +)); + + + +// +// Class extensions +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( +'Class:Subnet/Tab:IPUsage' => 'IP 使用情况', +'Class:Subnet/Tab:IPUsage-explain' => '接口拥有一个下述范围内的IP: %1$s%2$s', +'Class:Subnet/Tab:FreeIPs' => '未用的 IP', +'Class:Subnet/Tab:FreeIPs-count' => '未用的 IP: %1$s', +'Class:Subnet/Tab:FreeIPs-explain' => '这里有10个抽取的未用的 IP 地址', +)); + +// +// Application Menu +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( +'Menu:Catalogs' => '目录', +'Menu:Catalogs+' => '数据类别', +'Menu:Audit' => '审计', +'Menu:Audit+' => '审计', +'Menu:Organization' => '组织', +'Menu:Organization+' => '所有组织', +'Menu:Application' => '应用程序', +'Menu:Application+' => '所有应用程序', +'Menu:DBServer' => '数据库服务器', +'Menu:DBServer+' => '数据库服务器', +'Menu:Audit' => '审计', +'Menu:ConfigManagement' => '配置管理', +'Menu:ConfigManagement+' => '配置管理', +'Menu:ConfigManagementOverview' => '总览', +'Menu:ConfigManagementOverview+' => '总览', +'Menu:Contact' => '联系人', +'Menu:Contact+' => '联系人', +'Menu:Person' => '人员', +'Menu:Person+' => '所有人员', +'Menu:Team' => '团队', +'Menu:Team+' => '所有团队', +'Menu:Document' => '文档', +'Menu:Document+' => '所有文档', +'Menu:Location' => '位置', +'Menu:Location+' => '所有位置', +'Menu:ConfigManagementCI' => '配置项', +'Menu:ConfigManagementCI+' => '配置项', +'Menu:BusinessProcess' => '业务过程', +'Menu:BusinessProcess+' => '所有业务过程', +'Menu:ApplicationSolution' => '应用方案', +'Menu:ApplicationSolution+' => '所有应用方案', +'Menu:ConfigManagementSoftware' => '应用管理', +'Menu:Licence' => 'Licences', +'Menu:Licence+' => '所有Licences', +'Menu:Patch' => '补丁', +'Menu:Patch+' => '所有补丁', +'Menu:ApplicationInstance' => '已安装的软件', +'Menu:ApplicationInstance+' => '应用程序和数据库服务器', +'Menu:ConfigManagementHardware' => '基础架构管理', +'Menu:Subnet' => '子网', +'Menu:Subnet+' => '所有子网', +'Menu:NetworkDevice' => '网络设备', +'Menu:NetworkDevice+' => '所有网络设备', +'Menu:Server' => '服务器', +'Menu:Server+' => '所有服务器', +'Menu:Printer' => '打印机', +'Menu:Printer+' => '所有打印机', +'Menu:MobilePhone' => '手机', +'Menu:MobilePhone+' => '所有手机', +'Menu:PC' => '个人电脑', +'Menu:PC+' => '所有个人电脑', +'Menu:NewContact' => '新联系人', +'Menu:NewContact+' => '新联系人', +'Menu:SearchContacts' => '查找联系人', +'Menu:SearchContacts+' => '查找联系人', +'Menu:NewCI' => '新CI', +'Menu:NewCI+' => '新CI', +'Menu:SearchCIs' => '查找CI', +'Menu:SearchCIs+' => '查找CI', +'Menu:ConfigManagement:Devices' => '设备', +'Menu:ConfigManagement:AllDevices' => '设备数量: %1$d', +'Menu:ConfigManagement:SWAndApps' => '软件和应用程序', +'Menu:ConfigManagement:Misc' => '杂项', +'Menu:Group' => 'CI族', +'Menu:Group+' => 'CI族', +'Menu:ConfigManagement:Shortcuts' => '快捷方式', +'Menu:ConfigManagement:AllContacts' => '所有联系人: %1$d', + +)); +?> diff --git a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php index c75ab27b72..1b4dea6664 100644 --- a/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/module.itop-incident-mgmt.php @@ -33,6 +33,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-incident-mgmt.php', 'ru.dict.itop-incident-mgmt.php', 'tr.dict.itop-incident-mgmt.php', + 'zh.dict.itop-incident-mgmt.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', diff --git a/modules/itop-incident-mgmt-1.0.0/zh.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/zh.dict.itop-incident-mgmt.php new file mode 100644 index 0000000000..f1db39ddac --- /dev/null +++ b/modules/itop-incident-mgmt-1.0.0/zh.dict.itop-incident-mgmt.php @@ -0,0 +1,73 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:IncidentManagement' => '事件管理', + 'Menu:IncidentManagement+' => '时间管理', + 'Menu:Incident:Overview' => '概览', + 'Menu:Incident:Overview+' => '概览', + 'Menu:NewIncident' => '新事件', + 'Menu:NewIncident+' => '创建新的事件单据', + 'Menu:SearchIncidents' => '搜索事件', + 'Menu:SearchIncidents+' => '搜索事件单据', + 'Menu:Incident:Shortcuts' => '快捷方式', + 'Menu:Incident:Shortcuts+' => '', + 'Menu:Incident:MyIncidents' => '指派给我的事件', + 'Menu:Incident:MyIncidents+' => '指派给我的事件 (作为办理人)', + 'Menu:Incident:EscalatedIncidents' => '升级的事件', + 'Menu:Incident:EscalatedIncidents+' => '升级的事件', + 'Menu:Incident:OpenIncidents' => '所有待处理的事件', + 'Menu:Incident:OpenIncidents+' => '所有待处理的事件', + +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: Incident +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Incident' => '事件', + 'Class:Incident+' => '', + 'Class:Incident/Stimulus:ev_assign' => '指派', + 'Class:Incident/Stimulus:ev_assign+' => '', + 'Class:Incident/Stimulus:ev_reassign' => '重新指派', + 'Class:Incident/Stimulus:ev_reassign+' => '', + 'Class:Incident/Stimulus:ev_timeout' => 'ev_timeout', + 'Class:Incident/Stimulus:ev_timeout+' => '', + 'Class:Incident/Stimulus:ev_resolve' => '标记为已解决', + 'Class:Incident/Stimulus:ev_resolve+' => '', + 'Class:Incident/Stimulus:ev_close' => '关闭', + 'Class:Incident/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php index 82af122ad0..f9b203c681 100644 --- a/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php +++ b/modules/itop-knownerror-mgmt-1.0.0/module.itop-knownerror-mgmt.php @@ -33,6 +33,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-knownerror-mgmt.php', 'ru.dict.itop-knownerror-mgmt.php', 'tr.dict.itop-knownerror-mgmt.php', + 'zh.dict.itop-knownerror-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-knownerror-mgmt.xml', diff --git a/modules/itop-knownerror-mgmt-1.0.0/zh.dict.itop-knownerror-mgmt.php b/modules/itop-knownerror-mgmt-1.0.0/zh.dict.itop-knownerror-mgmt.php new file mode 100644 index 0000000000..6b67a3fe97 --- /dev/null +++ b/modules/itop-knownerror-mgmt-1.0.0/zh.dict.itop-knownerror-mgmt.php @@ -0,0 +1,147 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: KnownError +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:KnownError' => '已知错误', + 'Class:KnownError+' => '被归档为已知议题的错误', + 'Class:KnownError/Attribute:name' => '名称', + 'Class:KnownError/Attribute:name+' => '', + 'Class:KnownError/Attribute:org_id' => '客户', + 'Class:KnownError/Attribute:org_id+' => '', + 'Class:KnownError/Attribute:cust_name' => '客户', + 'Class:KnownError/Attribute:cust_name+' => '', + 'Class:KnownError/Attribute:problem_id' => '相关的问题', + 'Class:KnownError/Attribute:problem_id+' => '', + 'Class:KnownError/Attribute:problem_ref' => '参考', + 'Class:KnownError/Attribute:problem_ref+' => '', + 'Class:KnownError/Attribute:symptom' => '症状', + 'Class:KnownError/Attribute:symptom+' => '', + 'Class:KnownError/Attribute:root_cause' => '根源原因', + 'Class:KnownError/Attribute:root_cause+' => '', + 'Class:KnownError/Attribute:workaround' => '工作区', + 'Class:KnownError/Attribute:workaround+' => '', + 'Class:KnownError/Attribute:solution' => '方案', + 'Class:KnownError/Attribute:solution+' => '', + 'Class:KnownError/Attribute:error_code' => '错误编码', + 'Class:KnownError/Attribute:error_code+' => '', + 'Class:KnownError/Attribute:domain' => '域', + 'Class:KnownError/Attribute:domain+' => '', + 'Class:KnownError/Attribute:domain/Value:Application' => '应用程序', + 'Class:KnownError/Attribute:domain/Value:Application+' => '应用程序', + 'Class:KnownError/Attribute:domain/Value:Desktop' => '桌面', + 'Class:KnownError/Attribute:domain/Value:Desktop+' => '桌面', + 'Class:KnownError/Attribute:domain/Value:Network' => '网络', + 'Class:KnownError/Attribute:domain/Value:Network+' => '网络', + 'Class:KnownError/Attribute:domain/Value:Server' => '服务器', + 'Class:KnownError/Attribute:domain/Value:Server+' => '服务器', + 'Class:KnownError/Attribute:vendor' => '卖主', + 'Class:KnownError/Attribute:vendor+' => '', + 'Class:KnownError/Attribute:model' => '型号', + 'Class:KnownError/Attribute:model+' => '', + 'Class:KnownError/Attribute:version' => '版本', + 'Class:KnownError/Attribute:version+' => '', + 'Class:KnownError/Attribute:ci_list' => 'CIs', + 'Class:KnownError/Attribute:ci_list+' => '', + 'Class:KnownError/Attribute:document_list' => '文档', + 'Class:KnownError/Attribute:document_list+' => '', +)); + + +// +// Class: lnkInfraError +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkInfraError' => 'InfraErrorLinks', + 'Class:lnkInfraError+' => 'Infra related to a known error', + 'Class:lnkInfraError/Attribute:infra_id' => 'CI', + 'Class:lnkInfraError/Attribute:infra_id+' => '', + 'Class:lnkInfraError/Attribute:infra_name' => 'CI 名称', + 'Class:lnkInfraError/Attribute:infra_name+' => '', + 'Class:lnkInfraError/Attribute:infra_status' => 'CI 状态', + 'Class:lnkInfraError/Attribute:infra_status+' => '', + 'Class:lnkInfraError/Attribute:error_id' => '错误', + 'Class:lnkInfraError/Attribute:error_id+' => '', + 'Class:lnkInfraError/Attribute:error_name' => '错误名称', + 'Class:lnkInfraError/Attribute:error_name+' => '', + 'Class:lnkInfraError/Attribute:reason' => '原因', + 'Class:lnkInfraError/Attribute:reason+' => '', +)); + +// +// Class: lnkDocumentError +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkDocumentError' => 'DocumentsErrorLinks', + 'Class:lnkDocumentError+' => '在文档和已知错误间的链接', + 'Class:lnkDocumentError/Attribute:doc_id' => '文档', + 'Class:lnkDocumentError/Attribute:doc_id+' => '', + 'Class:lnkDocumentError/Attribute:doc_name' => '文档名称', + 'Class:lnkDocumentError/Attribute:doc_name+' => '', + 'Class:lnkDocumentError/Attribute:error_id' => '错误', + 'Class:lnkDocumentError/Attribute:error_id+' => '', + 'Class:lnkDocumentError/Attribute:error_name' => '错误名称', + 'Class:lnkDocumentError/Attribute:error_name+' => '', + 'Class:lnkDocumentError/Attribute:link_type' => '信息', + 'Class:lnkDocumentError/Attribute:link_type+' => '', +)); + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:NewError' => '新的已知错误', + 'Menu:NewError+' => '新已知错误的创建', + 'Menu:SearchError' => '搜索已知错误', + 'Menu:SearchError+' => '搜索已知错误', + 'Menu:Problem:KnownErrors' => '所有已知错误', + 'Menu:Problem:KnownErrors+' => '所有已知错误', +)); +?> diff --git a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php index feb36a8b74..1058009cd7 100644 --- a/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php +++ b/modules/itop-problem-mgmt-1.0.0/module.itop-problem-mgmt.php @@ -33,6 +33,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-problem-mgmt.php', 'ru.dict.itop-problem-mgmt.php', 'tr.dict.itop-problem-mgmt.php', + 'zh.dict.itop-problem-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-problem-mgmt.xml', diff --git a/modules/itop-problem-mgmt-1.0.0/zh.dict.itop-problem-mgmt.php b/modules/itop-problem-mgmt-1.0.0/zh.dict.itop-problem-mgmt.php new file mode 100644 index 0000000000..c5e3cf0e9a --- /dev/null +++ b/modules/itop-problem-mgmt-1.0.0/zh.dict.itop-problem-mgmt.php @@ -0,0 +1,165 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +////////////////////////////////////////////////////////////////////// +// Classes in 'bizmodel' +////////////////////////////////////////////////////////////////////// +// + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + + + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:ProblemManagement' => '问题管理', + 'Menu:ProblemManagement+' => '问题管理', + 'Menu:Problem:Overview' => '概览', + 'Menu:Problem:Overview+' => '概览', + 'Menu:NewProblem' => '新问题', + 'Menu:NewProblem+' => '新问题', + 'Menu:SearchProblems' => '搜索问题', + 'Menu:SearchProblems+' => '搜索问题', + 'Menu:Problem:Shortcuts' => '快捷方式', + 'Menu:Problem:MyProblems' => '我的问题', + 'Menu:Problem:MyProblems+' => '我的问题', + 'Menu:Problem:OpenProblems' => '所有待解决的问题', + 'Menu:Problem:OpenProblems+' => '所有待解决的问题', + 'UI-ProblemManagementOverview-ProblemByService' => '按服务划分的问题', + 'UI-ProblemManagementOverview-ProblemByService+' => '按服务划分的问题', + 'UI-ProblemManagementOverview-ProblemByPriority' => '按优先级划分的问题', + 'UI-ProblemManagementOverview-ProblemByPriority+' => '按优先级划分的问题', + 'UI-ProblemManagementOverview-ProblemUnassigned' => '未指派的问题', + 'UI-ProblemManagementOverview-ProblemUnassigned+' => '未指派的问题', + 'UI:ProblemMgmtMenuOverview:Title' => '问题管理仪表板', + 'UI:ProblemMgmtMenuOverview:Title+' => '问题管理仪表板', + +)); +// +// Class: Problem +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Problem' => '问题', + 'Class:Problem+' => '', + 'Class:Problem/Attribute:status' => '状态', + 'Class:Problem/Attribute:status+' => '', + 'Class:Problem/Attribute:status/Value:new' => '新', + 'Class:Problem/Attribute:status/Value:new+' => '', + 'Class:Problem/Attribute:status/Value:assigned' => '已指派', + 'Class:Problem/Attribute:status/Value:assigned+' => '', + 'Class:Problem/Attribute:status/Value:resolved' => '已解决', + 'Class:Problem/Attribute:status/Value:resolved+' => '', + 'Class:Problem/Attribute:status/Value:closed' => '已关闭', + 'Class:Problem/Attribute:status/Value:closed+' => '', + 'Class:Problem/Attribute:org_id' => '客户', + 'Class:Problem/Attribute:org_id+' => '', + 'Class:Problem/Attribute:org_name' => '名称', + 'Class:Problem/Attribute:org_name+' => '常用名称', + 'Class:Problem/Attribute:service_id' => '服务', + 'Class:Problem/Attribute:service_id+' => '', + 'Class:Problem/Attribute:service_name' => '名称', + 'Class:Problem/Attribute:service_name+' => '', + 'Class:Problem/Attribute:servicesubcategory_id' => '服务类目', + 'Class:Problem/Attribute:servicesubcategory_id+' => '', + 'Class:Problem/Attribute:servicesubcategory_name' => '名称', + 'Class:Problem/Attribute:servicesubcategory_name+' => '', + 'Class:Problem/Attribute:product' => '生产', + 'Class:Problem/Attribute:product+' => '', + 'Class:Problem/Attribute:impact' => '影响', + 'Class:Problem/Attribute:impact+' => '', + 'Class:Problem/Attribute:impact/Value:1' => '个人', + 'Class:Problem/Attribute:impact/Value:1+' => '', + 'Class:Problem/Attribute:impact/Value:2' => '服务', + 'Class:Problem/Attribute:impact/Value:2+' => '', + 'Class:Problem/Attribute:impact/Value:3' => '部门', + 'Class:Problem/Attribute:impact/Value:3+' => '', + 'Class:Problem/Attribute:urgency' => '紧急', + 'Class:Problem/Attribute:urgency+' => '', + 'Class:Problem/Attribute:urgency/Value:1' => '低', + 'Class:Problem/Attribute:urgency/Value:1+' => '低', + 'Class:Problem/Attribute:urgency/Value:2' => '中', + 'Class:Problem/Attribute:urgency/Value:2+' => '中', + 'Class:Problem/Attribute:urgency/Value:3' => '高', + 'Class:Problem/Attribute:urgency/Value:3+' => '高', + 'Class:Problem/Attribute:priority' => '优先级', + 'Class:Problem/Attribute:priority+' => '', + 'Class:Problem/Attribute:priority/Value:1' => '低', + 'Class:Problem/Attribute:priority/Value:1+' => '', + 'Class:Problem/Attribute:priority/Value:2' => '中', + 'Class:Problem/Attribute:priority/Value:2+' => '', + 'Class:Problem/Attribute:priority/Value:3' => '高', + 'Class:Problem/Attribute:priority/Value:3+' => '', + 'Class:Problem/Attribute:workgroup_id' => '工作组', + 'Class:Problem/Attribute:workgroup_id+' => '', + 'Class:Problem/Attribute:workgroup_name' => '名称', + 'Class:Problem/Attribute:workgroup_name+' => '', + 'Class:Problem/Attribute:agent_id' => '办理人', + 'Class:Problem/Attribute:agent_id+' => '', + 'Class:Problem/Attribute:agent_name' => '办理人名称', + 'Class:Problem/Attribute:agent_name+' => '', + 'Class:Problem/Attribute:agent_email' => '办理人 Email', + 'Class:Problem/Attribute:agent_email+' => '', + 'Class:Problem/Attribute:related_change_id' => '关联变更', + 'Class:Problem/Attribute:related_change_id+' => '', + 'Class:Problem/Attribute:related_change_ref' => '参考', + 'Class:Problem/Attribute:related_change_ref+' => '', + 'Class:Problem/Attribute:close_date' => '关闭日期', + 'Class:Problem/Attribute:close_date+' => '', + 'Class:Problem/Attribute:last_update' => '最后的更新', + 'Class:Problem/Attribute:last_update+' => '', + 'Class:Problem/Attribute:assignment_date' => '指派日期', + 'Class:Problem/Attribute:assignment_date+' => '', + 'Class:Problem/Attribute:resolution_date' => '解决日期', + 'Class:Problem/Attribute:resolution_date+' => '', + 'Class:Problem/Attribute:knownerrors_list' => '已知错误', + 'Class:Problem/Attribute:knownerrors_list+' => '', + 'Class:Problem/Stimulus:ev_assign' => '指派', + 'Class:Problem/Stimulus:ev_assign+' => '', + 'Class:Problem/Stimulus:ev_reassign' => '重新指派', + 'Class:Problem/Stimulus:ev_reassign+' => '', + 'Class:Problem/Stimulus:ev_resolve' => '解决', + 'Class:Problem/Stimulus:ev_resolve+' => '', + 'Class:Problem/Stimulus:ev_close' => '关闭', + 'Class:Problem/Stimulus:ev_close+' => '', +)); + +?> diff --git a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php index d4ec3f4d09..b974e0c4a3 100644 --- a/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/module.itop-request-mgmt.php @@ -32,6 +32,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-request-mgmt.php', 'ru.dict.itop-request-mgmt.php', 'tr.dict.itop-request-mgmt.php', + 'zh.dict.itop-request-mgmt.php', ), 'data.struct' => array( 'data.struct.ta-triggers.xml', diff --git a/modules/itop-request-mgmt-1.0.0/zh.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/zh.dict.itop-request-mgmt.php new file mode 100644 index 0000000000..1d946e9fc9 --- /dev/null +++ b/modules/itop-request-mgmt-1.0.0/zh.dict.itop-request-mgmt.php @@ -0,0 +1,84 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Menu:RequestManagement' => '帮助中心', + 'Menu:RequestManagement+' => '帮助中心', + 'Menu:UserRequest:Overview' => '概览', + 'Menu:UserRequest:Overview+' => '概览', + 'Menu:NewUserRequest' => '新的用户请求', + 'Menu:NewUserRequest+' => '创建新的用户请求单据', + 'Menu:SearchUserRequests' => '搜索用户请求', + 'Menu:SearchUserRequests+' => '搜索用户请求单据', + 'Menu:UserRequest:Shortcuts' => '快捷方式', + 'Menu:UserRequest:Shortcuts+' => '', + 'Menu:UserRequest:MyRequests' => '指派给我的请求', + 'Menu:UserRequest:MyRequests+' => '指派给我的请求 (作为办理人)', + 'Menu:UserRequest:EscalatedRequests' => '升级的请求', + 'Menu:UserRequest:EscalatedRequests+' => '升级的请求', + 'Menu:UserRequest:OpenRequests' => '所有待处理的请求', + 'Menu:UserRequest:OpenRequests+' => '所有待处理的请求', +)); + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + +// +// Class: UserRequest +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:UserRequest' => '用户请求', + 'Class:UserRequest+' => '', + 'Class:UserRequest/Attribute:request_type' => '请求类别', + 'Class:UserRequest/Attribute:request_type+' => '', + 'Class:UserRequest/Attribute:request_type/Value:information' => '信息', + 'Class:UserRequest/Attribute:request_type/Value:information+' => '信息', + 'Class:UserRequest/Attribute:request_type/Value:issue' => '议题', + 'Class:UserRequest/Attribute:request_type/Value:issue+' => '议题', + 'Class:UserRequest/Attribute:request_type/Value:service request' => '服务请求', + 'Class:UserRequest/Attribute:request_type/Value:service request+' => '服务请求', + 'Class:UserRequest/Attribute:freeze_reason' => '未决原因', + 'Class:UserRequest/Attribute:freeze_reason+' => '', + 'Class:UserRequest/Stimulus:ev_assign' => '指派', + 'Class:UserRequest/Stimulus:ev_assign+' => '', + 'Class:UserRequest/Stimulus:ev_reassign' => '重新指派', + 'Class:UserRequest/Stimulus:ev_reassign+' => '', + 'Class:UserRequest/Stimulus:ev_timeout' => 'ev_timeout', + 'Class:UserRequest/Stimulus:ev_timeout+' => '', + 'Class:UserRequest/Stimulus:ev_resolve' => '标记为已解决', + 'Class:UserRequest/Stimulus:ev_resolve+' => '', + 'Class:UserRequest/Stimulus:ev_close' => '关闭', + 'Class:UserRequest/Stimulus:ev_close+' => '', + 'Class:UserRequest/Stimulus:ev_freeze' => '标记为未决', + 'Class:UserRequest/Stimulus:ev_freeze+' => '', +)); + +?> \ No newline at end of file diff --git a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php index 9becde3a0e..9595d620f7 100644 --- a/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/module.itop-service-mgmt.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-service-mgmt.php', 'ru.dict.itop-service-mgmt.php', 'tr.dict.itop-service-mgmt.php', + 'zh.dict.itop-service-mgmt.php', ), 'data.struct' => array( //'data.struct.itop-service-mgmt.xml', diff --git a/modules/itop-service-mgmt-1.0.0/zh.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/zh.dict.itop-service-mgmt.php new file mode 100644 index 0000000000..bb534e1fa6 --- /dev/null +++ b/modules/itop-service-mgmt-1.0.0/zh.dict.itop-service-mgmt.php @@ -0,0 +1,453 @@ + + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( +'Menu:ServiceManagement' => '服务管理', +'Menu:ServiceManagement+' => '服务管理概览', +'Menu:Service:Overview' => '概览', +'Menu:Service:Overview+' => '', +'UI-ServiceManagementMenu-ContractsBySrvLevel' => '按服务层级划分合同', +'UI-ServiceManagementMenu-ContractsByStatus' => '按状态划分合同', +'UI-ServiceManagementMenu-ContractsEndingIn30Days' => '合同30天内终止', + +'Menu:ServiceType' => '服务类别', +'Menu:ServiceType+' => '服务类别', +'Menu:ProviderContract' => '供应商合同', +'Menu:ProviderContract+' => '供应商合同', +'Menu:CustomerContract' => '客户合同', +'Menu:CustomerContract+' => '客户合同', +'Menu:ServiceSubcategory' => '服务子级类目', +'Menu:ServiceSubcategory+' => '服务子级类目', +'Menu:Service' => '服务', +'Menu:Service+' => '服务', +'Menu:SLA' => 'SLAs', +'Menu:SLA+' => '服务级别协议', +'Menu:SLT' => 'SLTs', +'Menu:SLT+' => '服务级别目标', + +)); + + +/* + 'UI:ServiceManagementMenu' => 'Gestion des Services', + 'UI:ServiceManagementMenu+' => 'Gestion des Services', + 'UI:ServiceManagementMenu:Title' => 'Résumé des services & contrats', + 'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contrats par niveau de service', + 'UI-ServiceManagementMenu-ContractsByStatus' => 'Contrats par état', + 'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contrats se terminant dans moins de 30 jours', +*/ + + +// +// Class: Contract +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Contract' => '合同', + 'Class:Contract+' => '', + 'Class:Contract/Attribute:name' => '名称', + 'Class:Contract/Attribute:name+' => '', + 'Class:Contract/Attribute:description' => '描述', + 'Class:Contract/Attribute:description+' => '', + 'Class:Contract/Attribute:start_date' => '启始日期', + 'Class:Contract/Attribute:start_date+' => '', + 'Class:Contract/Attribute:end_date' => '截止日期', + 'Class:Contract/Attribute:end_date+' => '', + 'Class:Contract/Attribute:cost' => '费用', + 'Class:Contract/Attribute:cost+' => '', + 'Class:Contract/Attribute:cost_currency' => '费用货币', + 'Class:Contract/Attribute:cost_currency+' => '', + 'Class:Contract/Attribute:cost_currency/Value:dollars' => '美元', + 'Class:Contract/Attribute:cost_currency/Value:dollars+' => '', + 'Class:Contract/Attribute:cost_currency/Value:euros' => '欧元', + 'Class:Contract/Attribute:cost_currency/Value:euros+' => '', + 'Class:Contract/Attribute:cost_unit' => '费用单位', + 'Class:Contract/Attribute:cost_unit+' => '', + 'Class:Contract/Attribute:billing_frequency' => '付款周期', + 'Class:Contract/Attribute:billing_frequency+' => '', + 'Class:Contract/Attribute:contact_list' => '合同', + 'Class:Contract/Attribute:contact_list+' => '与该合同相关的合同能够', + 'Class:Contract/Attribute:document_list' => '文档', + 'Class:Contract/Attribute:document_list+' => '与该合同关联的文档', + 'Class:Contract/Attribute:ci_list' => 'CIs', + 'Class:Contract/Attribute:ci_list+' => '该合同所支持的 CI', + 'Class:Contract/Attribute:finalclass' => '类别', + 'Class:Contract/Attribute:finalclass+' => '', +)); + +// +// Class: ProviderContract +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ProviderContract' => '供应商合同', + 'Class:ProviderContract+' => '', + 'Class:ProviderContract/Attribute:provider_id' => '供应商', + 'Class:ProviderContract/Attribute:provider_id+' => '', + 'Class:ProviderContract/Attribute:provider_name' => '供应商名称', + 'Class:ProviderContract/Attribute:provider_name+' => '', + 'Class:ProviderContract/Attribute:sla' => 'SLA', + 'Class:ProviderContract/Attribute:sla+' => '服务级别协议', + 'Class:ProviderContract/Attribute:coverage' => '服务小时数', + 'Class:ProviderContract/Attribute:coverage+' => '', +)); + +// +// Class: CustomerContract +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:CustomerContract' => '客户合同', + 'Class:CustomerContract+' => '', + 'Class:CustomerContract/Attribute:org_id' => '客户', + 'Class:CustomerContract/Attribute:org_id+' => '', + 'Class:CustomerContract/Attribute:org_name' => '客户名称', + 'Class:CustomerContract/Attribute:org_name+' => '', + 'Class:CustomerContract/Attribute:provider_id' => '供应商', + 'Class:CustomerContract/Attribute:provider_id+' => '', + 'Class:CustomerContract/Attribute:provider_name' => '供应商名称', + 'Class:CustomerContract/Attribute:provider_name+' => '', + 'Class:CustomerContract/Attribute:support_team_id' => '支持团队', + 'Class:CustomerContract/Attribute:support_team_id+' => '', + 'Class:CustomerContract/Attribute:support_team_name' => '支持团队', + 'Class:CustomerContract/Attribute:support_team_name+' => '', + 'Class:CustomerContract/Attribute:provider_list' => '供应商', + 'Class:CustomerContract/Attribute:provider_list+' => '', + 'Class:CustomerContract/Attribute:sla_list' => 'SLAs', + 'Class:CustomerContract/Attribute:sla_list+' => '与该合同相关的 SLA 列表', + 'Class:CustomerContract/Attribute:provider_list' => '支撑合同', + 'Class:CustomerContract/Attribute:sla_list+' => '', +)); +// +// Class: lnkCustomerContractToProviderContract +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkCustomerContractToProviderContract' => 'lnkCustomerContractToProviderContract', + 'Class:lnkCustomerContractToProviderContract+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id' => '客户合同', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name' => '名称', + 'Class:lnkCustomerContractToProviderContract/Attribute:customer_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id' => '供应商合同', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_id+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name' => '名称', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_contract_name+' => '', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla' => '供应商 SLA', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_sla+' => '服务级别协议', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage' => '服务小时数', + 'Class:lnkCustomerContractToProviderContract/Attribute:provider_coverage+' => '', +)); + + +// +// Class: lnkContractToSLA +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkContractToSLA' => '合同/SLA', + 'Class:lnkContractToSLA+' => '', + 'Class:lnkContractToSLA/Attribute:contract_id' => '合同', + 'Class:lnkContractToSLA/Attribute:contract_id+' => '', + 'Class:lnkContractToSLA/Attribute:contract_name' => '合同', + 'Class:lnkContractToSLA/Attribute:contract_name+' => '', + 'Class:lnkContractToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_id+' => '', + 'Class:lnkContractToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkContractToSLA/Attribute:sla_name+' => '', + 'Class:lnkContractToSLA/Attribute:coverage' => '服务小时数', + 'Class:lnkContractToSLA/Attribute:coverage+' => '', +)); + +// +// Class: lnkContractToDoc +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkContractToDoc' => '合同/文档', + 'Class:lnkContractToDoc+' => '', + 'Class:lnkContractToDoc/Attribute:contract_id' => '合同', + 'Class:lnkContractToDoc/Attribute:contract_id+' => '', + 'Class:lnkContractToDoc/Attribute:contract_name' => '合同', + 'Class:lnkContractToDoc/Attribute:contract_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_id' => '文档', + 'Class:lnkContractToDoc/Attribute:document_id+' => '', + 'Class:lnkContractToDoc/Attribute:document_name' => '文档', + 'Class:lnkContractToDoc/Attribute:document_name+' => '', + 'Class:lnkContractToDoc/Attribute:document_type' => '文档类别', + 'Class:lnkContractToDoc/Attribute:document_type+' => '', + 'Class:lnkContractToDoc/Attribute:document_status' => '文档状态', + 'Class:lnkContractToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkContractToContact +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkContractToContact' => '合同/联系人', + 'Class:lnkContractToContact+' => '', + 'Class:lnkContractToContact/Attribute:contract_id' => '合同', + 'Class:lnkContractToContact/Attribute:contract_id+' => '', + 'Class:lnkContractToContact/Attribute:contract_name' => '合同', + 'Class:lnkContractToContact/Attribute:contract_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_id' => '联系人', + 'Class:lnkContractToContact/Attribute:contact_id+' => '', + 'Class:lnkContractToContact/Attribute:contact_name' => '联系人', + 'Class:lnkContractToContact/Attribute:contact_name+' => '', + 'Class:lnkContractToContact/Attribute:contact_email' => '联系人 email', + 'Class:lnkContractToContact/Attribute:contact_email+' => '', + 'Class:lnkContractToContact/Attribute:role' => '角色', + 'Class:lnkContractToContact/Attribute:role+' => '', +)); + +// +// Class: lnkContractToCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkContractToCI' => '合同/CI', + 'Class:lnkContractToCI+' => '', + 'Class:lnkContractToCI/Attribute:contract_id' => '合同', + 'Class:lnkContractToCI/Attribute:contract_id+' => '', + 'Class:lnkContractToCI/Attribute:contract_name' => '合同', + 'Class:lnkContractToCI/Attribute:contract_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_id' => 'CI', + 'Class:lnkContractToCI/Attribute:ci_id+' => '', + 'Class:lnkContractToCI/Attribute:ci_name' => 'CI', + 'Class:lnkContractToCI/Attribute:ci_name+' => '', + 'Class:lnkContractToCI/Attribute:ci_status' => 'CI 状态', + 'Class:lnkContractToCI/Attribute:ci_status+' => '', +)); + +// +// Class: Service +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Service' => '服务', + 'Class:Service+' => '', + 'Class:Service/Attribute:org_id' => '供应商', + 'Class:Service/Attribute:org_id+' => '', + 'Class:Service/Attribute:provider_name' => '供应商', + 'Class:Service/Attribute:provider_name+' => '', + 'Class:Service/Attribute:name' => '名称', + 'Class:Service/Attribute:name+' => '', + 'Class:Service/Attribute:description' => '描述', + 'Class:Service/Attribute:description+' => '', + 'Class:Service/Attribute:type' => '类别', + 'Class:Service/Attribute:type+' => '', + 'Class:Service/Attribute:type/Value:IncidentManagement' => '事件管理', + 'Class:Service/Attribute:type/Value:IncidentManagement+' => '事件管理', + 'Class:Service/Attribute:type/Value:RequestManagement' => '请求管理', + 'Class:Service/Attribute:type/Value:RequestManagement+' => '请求管理', + 'Class:Service/Attribute:status' => '状态', + 'Class:Service/Attribute:status+' => '', + 'Class:Service/Attribute:status/Value:design' => '设计', + 'Class:Service/Attribute:status/Value:design+' => '', + 'Class:Service/Attribute:status/Value:obsolete' => '废弃', + 'Class:Service/Attribute:status/Value:obsolete+' => '', + 'Class:Service/Attribute:status/Value:production' => '生产', + 'Class:Service/Attribute:status/Value:production+' => '', + 'Class:Service/Attribute:subcategory_list' => '服务子级类目', + 'Class:Service/Attribute:subcategory_list+' => '', + 'Class:Service/Attribute:sla_list' => 'SLAs', + 'Class:Service/Attribute:sla_list+' => '', + 'Class:Service/Attribute:document_list' => '文档', + 'Class:Service/Attribute:document_list+' => '与此服务相关的文档', + 'Class:Service/Attribute:contact_list' => '联系人', + 'Class:Service/Attribute:contact_list+' => '在此服务中承担角色的联系人', + 'Class:Service/Tab:Related_Contracts' => '相关合同', + 'Class:Service/Tab:Related_Contracts+' => '为此服务签订的合同', +)); + +// +// Class: ServiceSubcategory +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ServiceSubcategory' => '服务子级类目', + 'Class:ServiceSubcategory+' => '', + 'Class:ServiceSubcategory/Attribute:name' => '名称', + 'Class:ServiceSubcategory/Attribute:name+' => '', + 'Class:ServiceSubcategory/Attribute:description' => '描述', + 'Class:ServiceSubcategory/Attribute:description+' => '', + 'Class:ServiceSubcategory/Attribute:service_id' => '服务', + 'Class:ServiceSubcategory/Attribute:service_id+' => '', + 'Class:ServiceSubcategory/Attribute:service_name' => '服务', + 'Class:ServiceSubcategory/Attribute:service_name+' => '', +)); + +// +// Class: SLA +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:SLA' => 'SLA', + 'Class:SLA+' => '', + 'Class:SLA/Attribute:name' => '名称', + 'Class:SLA/Attribute:name+' => '', + 'Class:SLA/Attribute:service_id' => '服务', + 'Class:SLA/Attribute:service_id+' => '', + 'Class:SLA/Attribute:service_name' => '服务', + 'Class:SLA/Attribute:service_name+' => '', + 'Class:SLA/Attribute:slt_list' => 'SLTs', + 'Class:SLA/Attribute:slt_list+' => 'List Service Level Thresholds', +)); + +// +// Class: SLT +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:SLT' => 'SLT', + 'Class:SLT+' => '', + 'Class:SLT/Attribute:name' => '名称', + 'Class:SLT/Attribute:name+' => '', + 'Class:SLT/Attribute:metric' => 'Metric', + 'Class:SLT/Attribute:metric+' => '', + 'Class:SLT/Attribute:metric/Value:TTO' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTO+' => 'TTO', + 'Class:SLT/Attribute:metric/Value:TTR' => 'TTR', + 'Class:SLT/Attribute:metric/Value:TTR+' => 'TTR', + 'Class:SLT/Attribute:ticket_priority' => '单据优先级', + 'Class:SLT/Attribute:ticket_priority+' => '', + 'Class:SLT/Attribute:ticket_priority/Value:1' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:1+' => '1', + 'Class:SLT/Attribute:ticket_priority/Value:2' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:2+' => '2', + 'Class:SLT/Attribute:ticket_priority/Value:3' => '3', + 'Class:SLT/Attribute:ticket_priority/Value:3+' => '3', + 'Class:SLT/Attribute:value' => '值', + 'Class:SLT/Attribute:value+' => '', + 'Class:SLT/Attribute:value_unit' => '单位', + 'Class:SLT/Attribute:value_unit+' => '', + 'Class:SLT/Attribute:value_unit/Value:days' => '天', + 'Class:SLT/Attribute:value_unit/Value:days+' => '天', + 'Class:SLT/Attribute:value_unit/Value:hours' => '小时', + 'Class:SLT/Attribute:value_unit/Value:hours+' => '小时', + 'Class:SLT/Attribute:value_unit/Value:minutes' => '分钟', + 'Class:SLT/Attribute:value_unit/Value:minutes+' => '分钟', + 'Class:SLT/Attribute:sla_list' => 'SLAs', + 'Class:SLT/Attribute:sla_list+' => '使用此 SLT 的 SLAs', +)); + +// +// Class: lnkSLTToSLA +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkSLTToSLA' => 'SLT/SLA', + 'Class:lnkSLTToSLA+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_id' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_id+' => '', + 'Class:lnkSLTToSLA/Attribute:sla_name' => 'SLA', + 'Class:lnkSLTToSLA/Attribute:sla_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_id' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_id+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_name' => 'SLT', + 'Class:lnkSLTToSLA/Attribute:slt_name+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_metric' => 'Metric', + 'Class:lnkSLTToSLA/Attribute:slt_metric+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority' => '单据优先级', + 'Class:lnkSLTToSLA/Attribute:slt_ticket_priority+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value' => '值', + 'Class:lnkSLTToSLA/Attribute:slt_value+' => '', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit' => '单位', + 'Class:lnkSLTToSLA/Attribute:slt_value_unit+' => '', +)); + +// +// Class: lnkServiceToDoc +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkServiceToDoc' => '服务/文档', + 'Class:lnkServiceToDoc+' => '', + 'Class:lnkServiceToDoc/Attribute:service_id' => '服务', + 'Class:lnkServiceToDoc/Attribute:service_id+' => '', + 'Class:lnkServiceToDoc/Attribute:service_name' => '服务', + 'Class:lnkServiceToDoc/Attribute:service_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_id' => '文档', + 'Class:lnkServiceToDoc/Attribute:document_id+' => '', + 'Class:lnkServiceToDoc/Attribute:document_name' => '文档', + 'Class:lnkServiceToDoc/Attribute:document_name+' => '', + 'Class:lnkServiceToDoc/Attribute:document_type' => '文档类别', + 'Class:lnkServiceToDoc/Attribute:document_type+' => '', + 'Class:lnkServiceToDoc/Attribute:document_status' => '文档状态', + 'Class:lnkServiceToDoc/Attribute:document_status+' => '', +)); + +// +// Class: lnkServiceToContact +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkServiceToContact' => '服务/联系人', + 'Class:lnkServiceToContact+' => '', + 'Class:lnkServiceToContact/Attribute:service_id' => '服务', + 'Class:lnkServiceToContact/Attribute:service_id+' => '', + 'Class:lnkServiceToContact/Attribute:service_name' => '服务', + 'Class:lnkServiceToContact/Attribute:service_name+' => '', + 'Class:lnkServiceToContact/Attribute:contact_id' => '联系人', + 'Class:lnkServiceToContact/Attribute:contact_id+' => '', + 'Class:lnkServiceToContact/Attribute:contact_name' => '联系人', + 'Class:lnkServiceToContact/Attribute:contact_name+' => '', + 'Class:lnkServiceToContact/Attribute:contact_email' => '联系人 email', + 'Class:lnkServiceToContact/Attribute:contact_email+' => '', + 'Class:lnkServiceToContact/Attribute:role' => '角色', + 'Class:lnkServiceToContact/Attribute:role+' => '', +)); + +// +// Class: lnkServiceToCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkServiceToCI' => '服务/CI', + 'Class:lnkServiceToCI+' => '', + 'Class:lnkServiceToCI/Attribute:service_id' => '服务', + 'Class:lnkServiceToCI/Attribute:service_id+' => '', + 'Class:lnkServiceToCI/Attribute:service_name' => '服务', + 'Class:lnkServiceToCI/Attribute:service_name+' => '', + 'Class:lnkServiceToCI/Attribute:ci_id' => 'CI', + 'Class:lnkServiceToCI/Attribute:ci_id+' => '', + 'Class:lnkServiceToCI/Attribute:ci_name' => 'CI', + 'Class:lnkServiceToCI/Attribute:ci_name+' => '', + 'Class:lnkServiceToCI/Attribute:ci_status' => 'CI 状态', + 'Class:lnkServiceToCI/Attribute:ci_status+' => '', +)); + + +?> diff --git a/modules/itop-tickets-1.0.0/module.itop-tickets.php b/modules/itop-tickets-1.0.0/module.itop-tickets.php index 47b7fa7e86..24d4f57474 100644 --- a/modules/itop-tickets-1.0.0/module.itop-tickets.php +++ b/modules/itop-tickets-1.0.0/module.itop-tickets.php @@ -31,6 +31,7 @@ SetupWebPage::AddModule( 'pt_br.dict.itop-tickets.php', 'ru.dict.itop-tickets.php', 'tr.dict.itop-tickets.php', + 'zh.dict.itop-tickets.php', ), 'data.struct' => array( 'data.struct.ta-actions.xml', diff --git a/modules/itop-tickets-1.0.0/zh.dict.itop-tickets.php b/modules/itop-tickets-1.0.0/zh.dict.itop-tickets.php new file mode 100644 index 0000000000..7322795250 --- /dev/null +++ b/modules/itop-tickets-1.0.0/zh.dict.itop-tickets.php @@ -0,0 +1,267 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +// Dictionnay conventions +// Class: +// Class:+ +// Class:/Attribute: +// Class:/Attribute:+ +// Class:/Attribute:/Value: +// Class:/Attribute:/Value:+ +// Class:/Stimulus: +// Class:/Stimulus:+ + + +// +// Class: Ticket +// + +// +// Class: Ticket +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:Ticket' => '单据', + 'Class:Ticket+' => '', + 'Class:Ticket/Attribute:ref' => '参照', + 'Class:Ticket/Attribute:ref+' => '', + 'Class:Ticket/Attribute:title' => '标题', + 'Class:Ticket/Attribute:title+' => '', + 'Class:Ticket/Attribute:description' => '描述', + 'Class:Ticket/Attribute:description+' => '', + 'Class:Ticket/Attribute:ticket_log' => '日志', + 'Class:Ticket/Attribute:ticket_log+' => '', + 'Class:Ticket/Attribute:start_date' => '启始', + 'Class:Ticket/Attribute:start_date+' => '', + 'Class:Ticket/Attribute:document_list' => '文档', + 'Class:Ticket/Attribute:document_list+' => '与该单据相关的文档', + 'Class:Ticket/Attribute:ci_list' => 'CIs', + 'Class:Ticket/Attribute:ci_list+' => '事件关联的 CIs', + 'Class:Ticket/Attribute:contact_list' => '联系人', + 'Class:Ticket/Attribute:contact_list+' => '相关的团队及人员', + 'Class:Ticket/Attribute:incident_list' => '相关的事件', + 'Class:Ticket/Attribute:incident_list+' => '', + 'Class:Ticket/Attribute:finalclass' => '类别', + 'Class:Ticket/Attribute:finalclass+' => '', +)); + + +// +// Class: lnkTicketToDoc +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkTicketToDoc' => '单据/文档', + 'Class:lnkTicketToDoc+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_id' => '单据', + 'Class:lnkTicketToDoc/Attribute:ticket_id+' => '', + 'Class:lnkTicketToDoc/Attribute:ticket_ref' => '单据 #', + 'Class:lnkTicketToDoc/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToDoc/Attribute:document_id' => '文档', + 'Class:lnkTicketToDoc/Attribute:document_id+' => '', + 'Class:lnkTicketToDoc/Attribute:document_name' => '文档', + 'Class:lnkTicketToDoc/Attribute:document_name+' => '', +)); + +// +// Class: lnkTicketToContact +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkTicketToContact' => '单据/联系人', + 'Class:lnkTicketToContact+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_id' => '单据', + 'Class:lnkTicketToContact/Attribute:ticket_id+' => '', + 'Class:lnkTicketToContact/Attribute:ticket_ref' => '单据 #', + 'Class:lnkTicketToContact/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToContact/Attribute:contact_id' => '联系人', + 'Class:lnkTicketToContact/Attribute:contact_id+' => '', + 'Class:lnkTicketToContact/Attribute:contact_name' => '联系人', + 'Class:lnkTicketToContact/Attribute:contact_name+' => '', + 'Class:lnkTicketToContact/Attribute:contact_email' => 'Email', + 'Class:lnkTicketToContact/Attribute:contact_email+' => '', + 'Class:lnkTicketToContact/Attribute:role' => '角色', + 'Class:lnkTicketToContact/Attribute:role+' => '', +)); + +// +// Class: lnkTicketToCI +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:lnkTicketToCI' => '单据/CI', + 'Class:lnkTicketToCI+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_id' => '单据', + 'Class:lnkTicketToCI/Attribute:ticket_id+' => '', + 'Class:lnkTicketToCI/Attribute:ticket_ref' => '单据 #', + 'Class:lnkTicketToCI/Attribute:ticket_ref+' => '', + 'Class:lnkTicketToCI/Attribute:ci_id' => 'CI', + 'Class:lnkTicketToCI/Attribute:ci_id+' => '', + 'Class:lnkTicketToCI/Attribute:ci_name' => 'CI', + 'Class:lnkTicketToCI/Attribute:ci_name+' => '', + 'Class:lnkTicketToCI/Attribute:ci_status' => 'CI 状态', + 'Class:lnkTicketToCI/Attribute:ci_status+' => '', + 'Class:lnkTicketToCI/Attribute:impact' => '影响', + 'Class:lnkTicketToCI/Attribute:impact+' => '', +)); + + +// +// Class: ResponseTicket +// + +Dict::Add('ZH CN', 'Chinese', '简体中文', array( + 'Class:ResponseTicket' => '回复单据', + 'Class:ResponseTicket+' => '', + 'Class:ResponseTicket/Attribute:status' => '状态', + 'Class:ResponseTicket/Attribute:status+' => '', + 'Class:ResponseTicket/Attribute:status/Value:new' => '新', + 'Class:ResponseTicket/Attribute:status/Value:new+' => '新的待处理的', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto' => '升级/TTO', + 'Class:ResponseTicket/Attribute:status/Value:escalated_tto+' => '', + 'Class:ResponseTicket/Attribute:status/Value:assigned' => '已指派', + 'Class:ResponseTicket/Attribute:status/Value:assigned+' => '', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr' => '升级/TTR', + 'Class:ResponseTicket/Attribute:status/Value:escalated_ttr+' => '', + 'Class:ResponseTicket/Attribute:status/Value:frozen' => '未决', + 'Class:ResponseTicket/Attribute:status/Value:frozen+' => '', + 'Class:ResponseTicket/Attribute:status/Value:resolved' => '已解决', + 'Class:ResponseTicket/Attribute:status/Value:resolved+' => '', + 'Class:ResponseTicket/Attribute:status/Value:closed' => '关闭', + 'Class:ResponseTicket/Attribute:status/Value:closed+' => '', + 'Class:ResponseTicket/Attribute:caller_id' => '呼叫者', + 'Class:ResponseTicket/Attribute:caller_id+' => '', + 'Class:ResponseTicket/Attribute:caller_email' => 'Email', + 'Class:ResponseTicket/Attribute:caller_email+' => '', + 'Class:ResponseTicket/Attribute:org_id' => '客户', + 'Class:ResponseTicket/Attribute:org_id+' => '', + 'Class:ResponseTicket/Attribute:org_name' => '客户', + 'Class:ResponseTicket/Attribute:org_name+' => '', + 'Class:ResponseTicket/Attribute:service_id' => '服务', + 'Class:ResponseTicket/Attribute:service_id+' => '', + 'Class:ResponseTicket/Attribute:service_name' => '名称', + 'Class:ResponseTicket/Attribute:service_name+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_id' => '服务元素', + 'Class:ResponseTicket/Attribute:servicesubcategory_id+' => '', + 'Class:ResponseTicket/Attribute:servicesubcategory_name' => '名称', + 'Class:ResponseTicket/Attribute:servicesubcategory_name+' => '', + 'Class:ResponseTicket/Attribute:product' => '生产', + 'Class:ResponseTicket/Attribute:product+' => '', + 'Class:ResponseTicket/Attribute:impact' => '影响', + 'Class:ResponseTicket/Attribute:impact+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:1' => '单个部门', + 'Class:ResponseTicket/Attribute:impact/Value:1+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:2' => '单个服务', + 'Class:ResponseTicket/Attribute:impact/Value:2+' => '', + 'Class:ResponseTicket/Attribute:impact/Value:3' => '个人', + 'Class:ResponseTicket/Attribute:impact/Value:3+' => '', + 'Class:ResponseTicket/Attribute:urgency' => '紧急', + 'Class:ResponseTicket/Attribute:urgency+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:1' => '高', + 'Class:ResponseTicket/Attribute:urgency/Value:1+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:2' => '中', + 'Class:ResponseTicket/Attribute:urgency/Value:2+' => '', + 'Class:ResponseTicket/Attribute:urgency/Value:3' => '低', + 'Class:ResponseTicket/Attribute:urgency/Value:3+' => '', + 'Class:ResponseTicket/Attribute:priority' => '优先级', + 'Class:ResponseTicket/Attribute:priority+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:1' => '高', + 'Class:ResponseTicket/Attribute:priority/Value:1+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:2' => '中', + 'Class:ResponseTicket/Attribute:priority/Value:2+' => '', + 'Class:ResponseTicket/Attribute:priority/Value:3' => '低', + 'Class:ResponseTicket/Attribute:priority/Value:3+' => '', + 'Class:ResponseTicket/Attribute:workgroup_id' => '工作组', + 'Class:ResponseTicket/Attribute:workgroup_id+' => '', + 'Class:ResponseTicket/Attribute:workgroup_name' => '工作组', + 'Class:ResponseTicket/Attribute:workgroup_name+' => '', + 'Class:ResponseTicket/Attribute:agent_id' => '办理人', + 'Class:ResponseTicket/Attribute:agent_id+' => '', + 'Class:ResponseTicket/Attribute:agent_name' => '办理人', + 'Class:ResponseTicket/Attribute:agent_name+' => '', + 'Class:ResponseTicket/Attribute:agent_email' => '办理人 email', + 'Class:ResponseTicket/Attribute:agent_email+' => '', + 'Class:ResponseTicket/Attribute:related_problem_id' => '关联问题', + 'Class:ResponseTicket/Attribute:related_problem_id+' => '', + 'Class:ResponseTicket/Attribute:related_problem_ref' => '参照', + 'Class:ResponseTicket/Attribute:related_problem_ref+' => '', + 'Class:ResponseTicket/Attribute:related_change_id' => '关联变更', + 'Class:ResponseTicket/Attribute:related_change_id+' => '', + 'Class:ResponseTicket/Attribute:related_change_ref' => '关联变更', + 'Class:ResponseTicket/Attribute:related_change_ref+' => '', + 'Class:ResponseTicket/Attribute:close_date' => '关闭', + 'Class:ResponseTicket/Attribute:close_date+' => '', + 'Class:ResponseTicket/Attribute:last_update' => '最后更新', + 'Class:ResponseTicket/Attribute:last_update+' => '', + 'Class:ResponseTicket/Attribute:assignment_date' => '指派日期', + 'Class:ResponseTicket/Attribute:assignment_date+' => '', + 'Class:ResponseTicket/Attribute:resolution_date' => '解决日期', + 'Class:ResponseTicket/Attribute:resolution_date+' => '', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline' => 'TTO 升级的最后期限', + 'Class:ResponseTicket/Attribute:tto_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline' => 'TTR 升级的最后期限', + 'Class:ResponseTicket/Attribute:ttr_escalation_deadline+' => '', + 'Class:ResponseTicket/Attribute:closure_deadline' => '关闭的最后期限', + 'Class:ResponseTicket/Attribute:closure_deadline+' => '', + 'Class:ResponseTicket/Attribute:resolution_code' => '解决编码', + 'Class:ResponseTicket/Attribute:resolution_code+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce' => '不能重生', + 'Class:ResponseTicket/Attribute:resolution_code/Value:couldnotreproduce+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate' => '复制单据', + 'Class:ResponseTicket/Attribute:resolution_code/Value:duplicate+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed' => '搞定', + 'Class:ResponseTicket/Attribute:resolution_code/Value:fixed+' => '', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant' => '无关的', + 'Class:ResponseTicket/Attribute:resolution_code/Value:irrelevant+' => '', + 'Class:ResponseTicket/Attribute:solution' => '方案', + 'Class:ResponseTicket/Attribute:solution+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction' => '用户满意', + 'Class:ResponseTicket/Attribute:user_satisfaction+' => '', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1' => '非常满意', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:1+' => '非常满意', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2' => '比较满意', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:2+' => '比较满意', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3' => '一般', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:3+' => '一般', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4' => '非常不满', + 'Class:ResponseTicket/Attribute:user_satisfaction/Value:4+' => '非常不满', + 'Class:ResponseTicket/Attribute:user_commment' => '用户意见', + 'Class:ResponseTicket/Attribute:user_commment+' => '', + 'Class:ResponseTicket/Stimulus:ev_assign' => '指派', + 'Class:ResponseTicket/Stimulus:ev_assign+' => '', + 'Class:ResponseTicket/Stimulus:ev_reassign' => '再指派', + 'Class:ResponseTicket/Stimulus:ev_reassign+' => '', + 'Class:ResponseTicket/Stimulus:ev_timeout' => '升级', + 'Class:ResponseTicket/Stimulus:ev_timeout+' => '', + 'Class:ResponseTicket/Stimulus:ev_resolve' => '标记为已解决', + 'Class:ResponseTicket/Stimulus:ev_resolve+' => '', + 'Class:ResponseTicket/Stimulus:ev_close' => '关闭', + 'Class:ResponseTicket/Stimulus:ev_close+' => '', +)); + + + + +?> From 83e7e9c44ba3293b8105036a0756be555bec98c6 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 20 Dec 2010 13:47:06 +0000 Subject: [PATCH 950/970] Chinese: added to the readme.txt SVN:trunk[1035] --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index efa9634c20..dcde4532c3 100644 --- a/readme.txt +++ b/readme.txt @@ -49,6 +49,7 @@ Phil Eddies for the numerous feedbacks provided, and the first implementation of Marco Tulio for the Portuguese (Brazilian) translation Vladimir Shilov for the Russian translation Izzet Sirin for the Turkish translation +Deng lixin (alias Robert) for the Chinese translation 2. INSTALLATION ============ From a8a77fdebbb26b9aa4cab5740bda20ab6a50c8b6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 12:54:05 +0000 Subject: [PATCH 951/970] Make sure that the "+" (Create) button is never displayed for an abstract class. SVN:trunk[1036] --- application/ui.extkeywidget.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index b4c32690cb..6ebe4684c0 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -94,7 +94,7 @@ class UIExtKeyWidget */ public function Display(WebPage $oPage, $aArgs = array()) { - $bCreate = (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $this->oAttDef->AllowTargetCreation()); + $bCreate = (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $this->oAttDef->AllowTargetCreation()); $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm'); $sHTMLValue = ""; // no wrap From 64f452cabac8c138d1aa8a717570c08bde49b8b4 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 12:56:07 +0000 Subject: [PATCH 952/970] FIxed the processing of hierarchical ZLists to keep the display order when plain fields and fieldsets are mixed at the same level. SVN:trunk[1037] --- application/cmdbabstract.class.inc.php | 101 ++++++++++++++++++++----- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 98220e4484..268e2adc20 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -303,13 +303,33 @@ abstract class cmdbAbstractObject extends CMDBObject { $oPage->add(''); //$aDetails[$sTab][$sColIndex] = array(); + $sLabel = ''; + $sPreviousLabel = ''; + $aDetails[$sTab][$sColIndex] = array(); foreach($aFieldsets as $sFieldsetName => $aFields) { - $aDetails[$sTab][$sColIndex] = array(); - if (!empty($sFieldsetName)) + if (!empty($sFieldsetName) && ($sFieldsetName[0] != '_')) { - $oPage->add('
        '); - $oPage->add(''.Dict::S($sFieldsetName).''); + $sLabel = $sFieldsetName; + } + else + { + $sLabel = ''; + } + if ($sLabel != $sPreviousLabel) + { + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); + $oPage->add(''.Dict::S($sPreviousLabel).''); + } + $oPage->Details($aDetails[$sTab][$sColIndex]); + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); + } + $aDetails[$sTab][$sColIndex] = array(); + $sPreviousLabel = $sLabel; } foreach($aFields as $sAttCode) { @@ -320,11 +340,16 @@ abstract class cmdbAbstractObject extends CMDBObject $aDetails[$sTab][$sColIndex][] = $val; } } - $oPage->Details($aDetails[$sTab][$sColIndex]); - if (!empty($sFieldsetName)) - { - $oPage->add('
        '); - } + } + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); + $oPage->add(''.Dict::S($sFieldsetName).''); + } + $oPage->Details($aDetails[$sTab][$sColIndex]); + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); } $oPage->add(''); } @@ -1244,15 +1269,35 @@ EOF $oPage->add(''); foreach($aCols as $sColIndex => $aFieldsets) { + $sLabel = ''; + $sPreviousLabel = ''; + $aDetails[$sTab][$sColIndex] = array(); $oPage->add(''); } @@ -1435,6 +1485,7 @@ EOF //echo "
        ZList: ";
         		//print_r($aList);
         		//echo "
        \n"; + $index = 0; foreach($aList as $sKey => $value) { if (is_array($value)) @@ -1449,7 +1500,7 @@ EOF //echo "

        Found a tab: $sName ($sKey)

        \n"; if(!isset($aDetails[$sName])) { - $aDetails[$sName] = array('col1' => array('' => array())); + $aDetails[$sName] = array('col1' => array()); } $aDetails = self::ProcessZlist($value, $aDetails, $sName, 'col1', ''); break; @@ -1468,7 +1519,7 @@ EOF //echo "

        Found a column: $sName ($sKey)

        \n"; if(!isset($aDetails[$sCurrentTab][$sName])) { - $aDetails[$sCurrentTab][$sName] = array('' => array()); + $aDetails[$sCurrentTab][$sName] = array(); } $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sName, ''); break; @@ -1478,8 +1529,16 @@ EOF else { //echo "

        Scalar value: $value, in [$sCurrentTab][$sCurrentCol][$sCurrentSet][]

        \n"; - $aDetails[$sCurrentTab][$sCurrentCol][$sCurrentSet][] = $value; + if (empty($sCurrentSet)) + { + $aDetails[$sCurrentTab][$sCurrentCol]['_'.$index][] = $value; + } + else + { + $aDetails[$sCurrentTab][$sCurrentCol][$sCurrentSet][] = $value; + } } + $index++; } return $aDetails; } From e1d7e17c5d5eb200b284407b65596856dbaf2aca Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 12:58:21 +0000 Subject: [PATCH 953/970] Added support for hierarchical ZLists when checking the data model consistency SVN:trunk[1038] --- core/metamodel.class.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 19c681218f..90de8317c9 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1371,6 +1371,23 @@ abstract class MetaModel } } + public static function FlattenZList($aList) + { + $aResult = array(); + foreach($aList as $value) + { + if (!is_array($value)) + { + $aResult[] = $value; + } + else + { + $aResult = array_merge($aResult, self::FlattenZList($value)); + } + } + return $aResult; + } + public static function Init_DefineState($sStateCode, $aStateDef) { $sTargetClass = self::GetCallersPHPClass("Init"); @@ -2342,7 +2359,7 @@ abstract class MetaModel // foreach(self::EnumZLists() as $sListCode) { - foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode) + foreach (self::FlattenZList(self::GetZListItems($sClass, $sListCode)) as $sMyAttCode) { if (!self::IsValidAttCode($sClass, $sMyAttCode)) { @@ -2352,6 +2369,7 @@ abstract class MetaModel } } } + if (count($aErrors) > 0) { echo "
        "; From abc2c7b5e128c1622d42ceb857d7d7f7cb0e92a1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 13:02:17 +0000 Subject: [PATCH 954/970] Small fix: using an unassigned variable. SVN:trunk[1039] --- portal/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/index.php b/portal/index.php index f61b2c2558..aab0a59c1e 100644 --- a/portal/index.php +++ b/portal/index.php @@ -118,7 +118,7 @@ function SelectService($oP, $oUserOrg) { $id = $oService->GetKey(); $sChecked = ""; - if ($id == $aParameters['service_id']) + if (isset($aParameters['service_id']) && ($id == $aParameters['service_id'])) { $sChecked = "checked"; } From 3b629ab83c8be6058968b592b876ff8978eabf3f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 13:46:06 +0000 Subject: [PATCH 955/970] SVN:trunk[1040] --- dictionaries/dictionary.itop.ui.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 294582d009..ef6643d24b 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -875,7 +875,10 @@ When associated with a trigger, each action is given an "order" number, specifyi 'Portal:Button:CloseTicket' => 'Close this ticket', 'Portal:EnterYourCommentsOnTicket' => 'Enter your comments about the resolution of this ticket:', 'Portal:ErrorNoContactForThisUser' => 'Error: the current user is not associated with a Contact/Person. Please contact your administrator.', - + 'Portal:Attachments' => 'Attachments', + 'Portal:AddAttachment' => ' Add Attachment ', + 'Portal:RemoveAttachment' => ' Remove Attachment ', + 'Portal:Attachment_No_To_Ticket_Name' => 'Attachment #%1$d to %2$s (%3$s)', 'Enum:Undefined' => 'Undefined', )); From 8145c2389983c07cd6223c5ffcf781cd2116f9a5 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 14:38:58 +0000 Subject: [PATCH 956/970] Avoid using 'gethostname()' which is not always available. SVN:trunk[1041] --- core/action.class.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/action.class.inc.php b/core/action.class.inc.php index e0eb200855..7dfa724d90 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -237,7 +237,7 @@ class ActionEmail extends ActionNotification $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs); $oObj = $aContextArgs['this->object()']; - $sServerIP = gethostbyname(gethostname()); + $sServerIP = $_SERVER['SERVER_ADDR']; //gethostbyname(gethostname()); $sReference = 'GetKey().'@'.$sServerIP.'>'; $oEmail = new EMail(); From 78256bae0b3dcc1b86e697f9ec03c7bdad57f5fb Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 14:56:28 +0000 Subject: [PATCH 957/970] - Display PHP errors during setup instead of hiding them ! (Trac #321) - Set a longer timeout during setup (Trac #314) SVN:trunk[1042] --- setup/index.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup/index.php b/setup/index.php index 9715a14667..e434f83411 100644 --- a/setup/index.php +++ b/setup/index.php @@ -1245,6 +1245,12 @@ catch(Exception $e) } try { + // Set a long (at least 4 minutes) execution time for the setup to avoid timeouts during this phase + ini_set('max_execution_time', max(240, ini_get('max_execution_time'))); + // While running the setup it is desirable to see any error that may happen + ini_set('display_errors', true); + ini_set('display_startup_errors', true); + $aParams = array('licence_ok', 'db_server', 'db_user', 'db_pwd','db_name', 'new_db_name', 'db_prefix', 'module', 'sample_data', 'auth_user', 'auth_pwd', 'language'); foreach($aParams as $sName) { From 35429228a2866f4aa19a76bd370416086a56e943 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 15:02:05 +0000 Subject: [PATCH 958/970] Fixed a typo in German translation (Trac #339), thanks to ulmerspatz and Jonathan Lucas SVN:trunk[1043] --- modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php b/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php index 2ac966dd9a..8c403d510e 100644 --- a/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php +++ b/modules/itop-service-mgmt-1.0.0/de.dict.itop-service-mgmt.php @@ -290,7 +290,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:Service/Attribute:document_list+' => 'Dokumente beigefügt zu dem Service', 'Class:Service/Attribute:contact_list' => 'Kontakte', 'Class:Service/Attribute:contact_list+' => 'Kontakte, die für diesen Service eine Rolle wahrnehmen', - 'Class:Service/Tab:Related_Contracts' => 'Dazugehörige Verträge', + 'Class:Service/Tab:Related_Contracts' => 'Zugehörige Verträge', 'Class:Service/Tab:Related_Contracts+' => 'Verträge, die für diesen Vertrag unterschrieben wurden', )); From 56ddf960a5cf42b830c027e6ffeb8f365b3137c5 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 3 Jan 2011 16:05:09 +0000 Subject: [PATCH 959/970] Readme for 1.0.2 -draft SVN:trunk[1044] --- readme.txt | 131 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/readme.txt b/readme.txt index dcde4532c3..1f78b28d7b 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -iTop - version 1.0.? - ??-??-201? +iTop - version 1.0.2 - 05-01-2011 Readme file 1. ABOUT THIS RELEASE @@ -14,7 +14,7 @@ Readme file 1. ABOUT THIS RELEASE ================== Thank you for downloading the eigth packaged release of iTop. This version is -a maintenance release. It aims at upgrading seemlessly an existing 1.0 installation. +a maintenance release. It aims at upgrading seemlessly an existing 1.0 or 1.0.1 installation. Additional documentation can be downloaded from http://www.combodo.com/itopdocumentation - User guide @@ -25,19 +25,21 @@ Additional documentation can be downloaded from http://www.combodo.com/itopdocum iTop is released under the GPL (v3) license. (Check license.txt in this directory). The source code of iTop can be found on SourceForge: http://itop.sourceforge.net -1.1 Should I upgrade to 1.0.1 ? +1.1 What's new? --------------------------- -This maintenance release fixes a number of usability issues of iTop 1.0: -- Better handling of forms: fields validation and default values handling have been improved -- Support of IE8 and Safari -- Support of IIS -- Support of localized texts in the User Portal + + +1.2 Should I upgrade to 1.0.2? + --------------------------- +This maintenance release fixes brings the following improvements: +- Added localization for Turkish, Russian and Chinese +- Fixed issues with Internet Explorer +- Improved the usability of the CSV import feature, both in interactive and command-line modes If any of the above items is important for you, then you should upgrade your version of iTop. -If you are Brazilian and want to run iTop on IIS with IE8, then this release is for you ! -1.2 Special Thanks To: +1.3 Special Thanks To: ----------------- Bruno Bonfils for his guidance about LDAP and authentication. Randall Badilla Castro for the Spanish translation. @@ -49,7 +51,7 @@ Phil Eddies for the numerous feedbacks provided, and the first implementation of Marco Tulio for the Portuguese (Brazilian) translation Vladimir Shilov for the Russian translation Izzet Sirin for the Turkish translation -Deng lixin (alias Robert) for the Chinese translation +Deng Lixin for the Chinese translation 2. INSTALLATION ============ @@ -107,67 +109,92 @@ Version 1.0.1 is a maintenance release. Localization ------------ -Portuguese (Brazil) has been added. +Turkish, Russian and Chinese were added. German localization reviewed. Major changes ------------- None: this is a maintenance release! +#320 Integrated an HTML Editor Minor changes ------------- -#246 The page import.php can be used in CLI mode, allowing for massive data load -#311 Improved the reporting in the bulk import GUI (reconciliation of external keys, how to specify "undefined") -#293 Show the IP address against the device in the IP usage table for a subnet -#276 Show mandatory fields during CSV import -#285 Email addresses displayed as Mailto hyperlinks -Nicer display of the CSV import results... -Special passthrough mode for big XML pages output. -Allow n:n links to link several times to the same remote object (if "duplicates)=> true in the linkedset definition) -#284 Improved the behavior and reporting when attempting to create a document after a huge file -#111 Improved the data loader, and added a REST service to load data from a file. - This is particularly interesting to facilitate the migration from an older installation. +Use XMLPage passthrough mode to speed up and consume less memory for big XML exports. +- Improved feedback while searching and reloading added objects. (N-N links) +REVIEWED THE FILE INCLUSION POLICY -> the application can be moved !!! +Read-only mode relying successively on a DB property, and an application setting +Improved change tracking: user login replaced by the full name if available + +Improved implementation of the 'autocomplete' input and fix of quite a few related issue with aysnchronous inputs. Autocompletes are now restricted to external keys only. +Some details: +- Autocomplete now matches on 'contains' instead of 'begins with' +- The minimum size of this match is configurable in the config file and per attribute ('min_autocomplete_chars'). +- The maximum size that turns a drop-down list into an autocomplete is configurable in the config-file and per attribute ('max_combo_length'). +- Better feedback when expanding/collapsing search results lists. +- 'Pointer' cursor on the link to Expand/Collapse results lists. +- The 'mandatory' state of an attribute is no longer lost when some part of a form is reloaded asynchronously +- added the ability to create objects pointed by ExtKeys even when the edit mode is a drop-down list and not an autocomplete +- made this behavior configurable globally or per external key, using the config-flag/option: allow_target_creation. +Renamed 'autocompleteWidget' to 'extkeyWidget' since it's not always an autocomplete... +Make sure that the "+" (Create) button is never displayed for an abstract class. + + +Customize iTop +.............. +Added the capability to enable/disable menus based on the rights to apply a given stimulus. +Allow a module to provide a handler to override application settings: OnMetaModelStarted() +Menus created via a handler, at runtime +Patch for supporting a data model without any Organization. +Patch for supporting a data model without any Person. +The hyperlink to the online-help file is now configurable +Modularity: allow a module to execute some specific installation procedures (customize the config file, do something in the database) +User profiles: created in dedicated module itop-profiles-itil +Welcome page moved out the application, into a dedicated module: itop-welcome-itil +Moved the standards menus into the "welcome" module +Added the capability to enable/disable menus based on the rights to apply a given stimulus. +FIxed the processing of hierarchical ZLists to keep the display order when plain fields and fieldsets are mixed at the same level. +Added support for hierarchical ZLists when checking the data model consistency + Browser compatibility ---------------------- +..................... Tested successfully with IE8 and Chrome. Fixed the "Relationships" Flash navigator so that it works also on Safari. (tested with Safari 5.0.2 on Windows) (Trac #310) - Fixed the search form, and also fixed the search/selection of objects to link (n:n links) that was broken on IE8. Fix to prevent IE 8 from running in IE7 compatibility mode... to be tested... +CSV +...... +- Added the new "synchro" mode to the CSV load page. +- Ask for confirmation when doing a CSV import/synchro that is considered as "risky" (based on thresholds from the config file) +- Added a "Restart" button to quickly start over a CSV import/synchro +Added a tab into the CSV import: browse the CSV imports history -Security improvements ---------------------- -#300 When logged onto an iTop instance, you are allowed on any other instance Bugs fixed ---------- -The complete list can be reviewed on http://sourceforge.net/apps/trac/itop/report/1 +The complete list of active tickets can be reviewed on http://sourceforge.net/apps/trac/itop/report/1 -#286 GetAbsoluteUrl creates broken links on IIS -#278 Missing PHP5 modules not detected properly -#289 Misleading errors when apache not authorized to write files in "setup" directory -#295 Unable to update or insert data -#313 Provider Contracts are not filtered by Allowed Organizations -#309 some of php-ofc-library files are missing -#315 Default organization not handled properly when there is just one organization allowed for the user -#308 Subnet / Free IPs: the subnet address is reserved (e.g. x.x.x.0) -#312 Exclamation sign not displayed for mandatory fields -#307 Auto-complete not reporting wrong selection -#302 Error: Unknown variable sIcon -#298 CSV template file opened in the browser instead of "downloaded" -#245 Search form gets too specialized -#306 Password gets corrupted if the admin forgets to select a profile -#297 Fixed a reporting issue on the SOAP service CreateIncidentTicket -#296 Incorrect display of Service/Subcategory localized characters in the portal -#292 Could not leave "User Satisfaction" field undefined -#258 Context automatically selected when searching on organization -#282 OQL Error when using functions -#288 Some multi objects OQL queries do not work -Fixed a bug in the XML encoding function -Fixed the issue "Object already modified". The mechanism that prevents a user from submitting the same form twice has been redesigned. -#283 Fixed issue with the default value of Enum attributes -Fixed limitation: tickets named automatically even if a name is specified (attribute : ref) ; this is stopper when importing tickets from an existing workflow tool +#331 Import.php could not be run in HTTP mode (when PHP running in CGI mode) +- Fixed bug #334: proper handling of the "remove objects" button (was working only for the first linkset in the object). +- Allow DBObjects to be deleted by the standard UI 'Delete', which may be useful in case a DBObject has to be deleted as a dependent object of a CMDBObject. +Fixed a typo in German translation (Trac #339), thanks to ulmerspatz and Jonathan Lucas +Fix for Trac #337: email validation. Use a simpler regular expression that is much faster to execute. +- Bug fix: support resizable elements inside tabs. + +Setup/installation +Force a dummy timezone to prevent a warning during the setup... +- Display PHP errors during setup instead of hiding them ! (Trac #321) +- Set a longer timeout during setup (Trac #314) +- Fixed Trac #318 (and #335): added the check of the mandatory DOM extension. + +IE8 support +- Cosmetic on the iTop logo. Removed an unneeded size=100% that bothers IE. +- Fixed Trac #332: improved usability of the CSV import wizard with IE8. +- Fixed Trac #333: organizations' drop-down list is truncated on IE when the name of an organization is too long. + +Security...??? +XML data loader to request for credentials 3.2. Known limitations (https://sourceforge.net/apps/trac/itop/report/3) From 331b7ec2f0462b14f8e6872fdbbe74ae09863f6d Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 3 Jan 2011 16:39:16 +0000 Subject: [PATCH 960/970] Added the ability to (easily) attach files to a user request from the "portal" page. SVN:trunk[1045] --- portal/index.php | 73 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/portal/index.php b/portal/index.php index aab0a59c1e..eb69451e8e 100644 --- a/portal/index.php +++ b/portal/index.php @@ -233,6 +233,7 @@ function RequestCreationForm($oP, $oUserOrg) $oRequest->Set($sAttCode, $value); } } + $aFieldsMap = array(); foreach($aList as $sAttCode) { $value = ''; @@ -244,14 +245,24 @@ function RequestCreationForm($oP, $oUserOrg) } $aArgs = array('this' => $oRequest); + $aFieldsMap[$sAttCode] = 'attr_'.$sAttCode; $sValue = $oRequest->GetFormElementForField($oP, get_class($oRequest), $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue); } + $aDetails[] = array('label' => Dict::S('Portal:Attachments'), 'value' => ' '); + $aDetails[] = array('label' => ' ', 'value' => '

        '); + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/extkeywidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add("
        \n"); $oP->add("

        ".Dict::S('Portal:DescriptionOfTheRequest')."

        \n"); - $oP->add("
        \n"); + $oP->add("\n"); $oP->add("
        '); //$aDetails[$sTab][$sColIndex] = array(); foreach($aFieldsets as $sFieldsetName => $aFields) { - $aDetails[$sTab][$sColIndex] = array(); - if (!empty($sFieldsetName)) + if (!empty($sFieldsetName) && ($sFieldsetName[0]!='_')) { - $oPage->add('
        '); - $oPage->add(''.Dict::S($sFieldsetName).''); + $sLabel = $sFieldsetName; + } + else + { + $sLabel = ''; + } + if ($sLabel != $sPreviousLabel) + { + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); + $oPage->add(''.Dict::S($sPreviousLabel).''); + } + $oPage->Details($aDetails[$sTab][$sColIndex]); + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); + } + $aDetails[$sTab][$sColIndex] = array(); + $sPreviousLabel = $sLabel; } foreach($aFields as $sAttCode) { @@ -1308,11 +1353,16 @@ EOF $aDetails[$sTab][$sColIndex][] = $aVal; } } - $oPage->Details($aDetails[$sTab][$sColIndex]); - if (!empty($sFieldsetName)) - { - $oPage->add('
        '); - } + } + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); + $oPage->add(''.Dict::S($sPreviousLabel).''); + } + $oPage->Details($aDetails[$sTab][$sColIndex]); + if (!empty($sPreviousLabel)) + { + $oPage->add('
        '); } $oPage->add('
        \n"); - $oP->details($aDetails); + $oP->details($aDetails); DumpHiddenParams($oP, $aList, $aParameters); $oP->add(""); $oP->add(""); @@ -259,14 +270,36 @@ function RequestCreationForm($oP, $oUserOrg) $oP->p(" "); $oP->add(""); $oP->add("\n"); + $iFieldsCount = count($aFieldsMap); + $sJsonFieldsMap = json_encode($aFieldsMap); $oP->add_ready_script( <<add_script( +<< 

        '); + index++; + } + function RemoveAttachment(id_attachment) + { + $('#attachment_'+id_attachment).remove(); + } +EOF ); } else @@ -331,6 +364,29 @@ function DoCreateRequest($oP, $oUserOrg) $iChangeId = $oMyChange->DBInsert(); $oRequest->DBInsertTracked($oMyChange); $oP->add("

        ".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName(get_class($oRequest)))."

        \n"); + + // Now process the attachements (if any) + $index = 0; + foreach($_FILES as $sName => $void) + { + $oAttachment = utils::ReadPostedDocument($sName); + if (!$oAttachment->IsEmpty()) + { + $index++; + // Create a document and attach it to the created ticket + $oDoc = new FileDoc(); + $oDoc->Set('name', Dict::Format('Portal:Attachment_No_To_Ticket_Name', $index, $oRequest->GetName(), $oAttachment->GetFileName())); + $oDoc->Set('org_id', $oUserOrg->GetKey()); + $oDoc->Set('description', $oAttachment->GetFileName()); + $oDoc->Set('contents', $oAttachment); + $oDoc->DBInsertTracked($oMyChange); + // Link the document to the ticket + $oLink = new lnkTicketToDoc(); + $oLink->Set('ticket_id', $oRequest->GetKey()); + $oLink->Set('document_id', $oDoc->GetKey()); + $oLink->DBInsertTracked($oMyChange); + } + } DisplayMainMenu($oP); } else @@ -507,6 +563,19 @@ function DisplayRequestDetails($oP, UserRequest $oRequest) } } $oP->add('
        '); + $sOQL = 'SELECT FileDoc AS Doc JOIN lnkTicketToDoc AS L ON L.document_id = Doc.id WHERE L.ticket_id = :request_id'; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new CMDBObjectSet($oSearch, array(), array('request_id' => $oRequest->GetKey())); + if ($oSet->Count() > 0) + { + $sAttachements = '
        '; + while($oDoc = $oSet->Fetch()) + { + $sAttachements .= ''; + } + $sAttachements .= '
        '.$oDoc->GetAsHtml('contents').'
        '; + $aDetails[] = array('label' => Dict::S('Portal:Attachments'), 'value' => $sAttachements); + } $oP->details($aDetails); $oP->add('
    '); } From 019959504fab2c2c052069af76d45896fb052502 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 Jan 2011 09:35:17 +0000 Subject: [PATCH 961/970] Added the ability to (easily) attach files to a user request from the "portal" page. (French translation) SVN:trunk[1046] --- dictionaries/fr.dictionary.itop.ui.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 80ddb3f8b3..9cd88c1909 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -884,6 +884,10 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'Portal:Button:CloseTicket' => 'Clôre cette requête', 'Portal:EnterYourCommentsOnTicket' => 'Vos commentaires à propos du traitement de cette requête:', 'Portal:ErrorNoContactForThisUser' => 'Erreur: l\'utilisateur courant n\'est pas associé à une Personne/Contact. Contactez votre administrateur.', + 'Portal:Attachments' => 'Pièces jointes', + 'Portal:AddAttachment' => ' Ajouter une pièce jointe ', + 'Portal:RemoveAttachment' => ' Enlever la pièce jointe ', + 'Portal:Attachment_No_To_Ticket_Name' => 'Pièce jointe #%1$d à %2$s (%3$s)', 'Enum:Undefined' => 'Non défini', )); From f6bfcb83558a074e2f2d206a7cadab1d1d4ce8cb Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 4 Jan 2011 09:44:47 +0000 Subject: [PATCH 962/970] Preparing the readme for the 1.0.2 release SVN:trunk[1047] --- readme.txt | 69 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/readme.txt b/readme.txt index 1f78b28d7b..b1756c4e77 100644 --- a/readme.txt +++ b/readme.txt @@ -14,7 +14,7 @@ Readme file 1. ABOUT THIS RELEASE ================== Thank you for downloading the eigth packaged release of iTop. This version is -a maintenance release. It aims at upgrading seemlessly an existing 1.0 or 1.0.1 installation. +a maintenance release. It aims at upgrading seamlessly an existing 1.0 or 1.0.1 installation. Additional documentation can be downloaded from http://www.combodo.com/itopdocumentation - User guide @@ -27,13 +27,18 @@ The source code of iTop can be found on SourceForge: http://itop.sourceforge.net 1.1 What's new? --------------------------- +- Three new localizations were added: Chinese, Russian and Turkish +- User Interface enhancements: quick search and create within a form when dealing with external keys +- Improved support of IE8: a few cosmetic enhancements +- Enhanced CSV import (with a special CSV "synchro" mode, confirmation dialogs for "risky operations", + better history tracking of imports) and command line support for importing objects 1.2 Should I upgrade to 1.0.2? --------------------------- This maintenance release fixes brings the following improvements: - Added localization for Turkish, Russian and Chinese -- Fixed issues with Internet Explorer +- Fixed some usability issues with Internet Explorer - Improved the usability of the CSV import feature, both in interactive and command-line modes If any of the above items is important for you, then you should upgrade your version of iTop. @@ -88,8 +93,8 @@ innodb_flush_method = O_DSYNC On some systems you'll see a 5 to 10 times performance boost for writing data into the MySQL database ! -2.3. Migrating from 1.0 - ------------------ +2.3. Migrating from 1.0 or 1.0.1 + --------------------------- Overwrite your current installation files with the new ones. Configuration file, Data model files, and the database made by iTop 1.0 are fully compatible with iTop 1.0.1. @@ -102,10 +107,10 @@ Please refer to the migration guide available at http://www.combodo.com/itopdocu 3. FEATURES ======== -3.1. Changes since 1.0 - ----------------- +3.1. Changes since 1.0.1 + ------------------- -Version 1.0.1 is a maintenance release. +Version 1.0.2 is a maintenance release. Localization ------------ @@ -115,10 +120,11 @@ German localization reviewed. Major changes ------------- None: this is a maintenance release! -#320 Integrated an HTML Editor +#320 Integrated an HTML Editor, though none of the fields of the standard iTop data model uses it! Minor changes ------------- +Added the ability to attach files to a user request from the "portal" page. Use XMLPage passthrough mode to speed up and consume less memory for big XML exports. - Improved feedback while searching and reloading added objects. (N-N links) REVIEWED THE FILE INCLUSION POLICY -> the application can be moved !!! @@ -138,9 +144,14 @@ Some details: Renamed 'autocompleteWidget' to 'extkeyWidget' since it's not always an autocomplete... Make sure that the "+" (Create) button is never displayed for an abstract class. +Support resizable elements inside tabs. +Allow DBObjects to be deleted by the standard UI 'Delete', which may be useful in case a DBObject has to be deleted as a dependent object of a CMDBObject. +Force a dummy timezone to prevent a warning during the setup... +Cosmetic on the iTop logo (under IE8). Removed an unneeded size=100% that bothers IE. +XML data loader now requests credentials + +* iTop Customization -Customize iTop -.............. Added the capability to enable/disable menus based on the rights to apply a given stimulus. Allow a module to provide a handler to override application settings: OnMetaModelStarted() Menus created via a handler, at runtime @@ -156,15 +167,15 @@ FIxed the processing of hierarchical ZLists to keep the display order when plain Added support for hierarchical ZLists when checking the data model consistency -Browser compatibility -..................... -Tested successfully with IE8 and Chrome. +* Browser compatibility + +iTop was tested successfully ON FF 3.6, IE8, Chrome and Safari 5 (Windows). Fixed the "Relationships" Flash navigator so that it works also on Safari. (tested with Safari 5.0.2 on Windows) (Trac #310) - Fixed the search form, and also fixed the search/selection of objects to link (n:n links) that was broken on IE8. Fix to prevent IE 8 from running in IE7 compatibility mode... to be tested... -CSV -...... +* CSV Import + - Added the new "synchro" mode to the CSV load page. - Ask for confirmation when doing a CSV import/synchro that is considered as "risky" (based on thresholds from the config file) - Added a "Restart" button to quickly start over a CSV import/synchro @@ -173,28 +184,18 @@ Added a tab into the CSV import: browse the CSV imports history Bugs fixed ---------- -The complete list of active tickets can be reviewed on http://sourceforge.net/apps/trac/itop/report/1 +The complete list of active tickets can be reviewed at http://sourceforge.net/apps/trac/itop/report/1 +#314 Set a longer timeout during setup +#318 (and #335) added the check of the mandatory DOM extension. +#321 Display PHP errors during setup instead of hiding them! #331 Import.php could not be run in HTTP mode (when PHP running in CGI mode) -- Fixed bug #334: proper handling of the "remove objects" button (was working only for the first linkset in the object). -- Allow DBObjects to be deleted by the standard UI 'Delete', which may be useful in case a DBObject has to be deleted as a dependent object of a CMDBObject. -Fixed a typo in German translation (Trac #339), thanks to ulmerspatz and Jonathan Lucas -Fix for Trac #337: email validation. Use a simpler regular expression that is much faster to execute. -- Bug fix: support resizable elements inside tabs. +#332 Improved usability of the CSV import wizard with IE8. +#333 Organizations' drop-down list is truncated on IE when the name of an organization is too long. +#334 Proper handling of the "remove objects" button (was working only for the first linkset in the object). +#337 email validation. Use a simpler regular expression that is much faster to execute. +#339 Fixed a typo in German translation thanks to ulmerspatz and Jonathan Lucas -Setup/installation -Force a dummy timezone to prevent a warning during the setup... -- Display PHP errors during setup instead of hiding them ! (Trac #321) -- Set a longer timeout during setup (Trac #314) -- Fixed Trac #318 (and #335): added the check of the mandatory DOM extension. - -IE8 support -- Cosmetic on the iTop logo. Removed an unneeded size=100% that bothers IE. -- Fixed Trac #332: improved usability of the CSV import wizard with IE8. -- Fixed Trac #333: organizations' drop-down list is truncated on IE when the name of an organization is too long. - -Security...??? -XML data loader to request for credentials 3.2. Known limitations (https://sourceforge.net/apps/trac/itop/report/3) From 15e07b69d695a7b93cc3b6568884420a46ebedcd Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 10 Jan 2011 18:07:54 +0000 Subject: [PATCH 963/970] Fixed Trac #341: broken form when an external key is part of the attrbiutes of an n-n link ! SVN:trunk[1048] --- application/ui.linkswidget.class.inc.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index d9aec58a7e..49e2b7e57f 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -111,8 +111,10 @@ class UILinksWidget $aRow['form::checkbox'] .= ""; foreach($this->m_aEditableFields as $sFieldCode) { + $sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']'; + $sSafeId = str_replace(array('[',']','-'), '_', $sFieldId); $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); - $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $this->m_iInputId.'_'.$sFieldCode.'['.$key.']', $sNameSuffix, 0, $aArgs); + $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $sSafeId, $sNameSuffix, 0, $aArgs); } } else @@ -125,8 +127,10 @@ class UILinksWidget $aRow['form::checkbox'] .= ""; foreach($this->m_aEditableFields as $sFieldCode) { + $sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']'; + $sSafeId = str_replace(array('[',']','-'), '_', $sFieldId); $oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode); - $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']' /* id */, $sNameSuffix, 0, $aArgs); + $aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, '' /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs); } } From 5a419927393530f0ed8d10eac8b3309f9f7410e1 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 10 Jan 2011 18:15:15 +0000 Subject: [PATCH 964/970] Trying to make CKEditor work with the "ExtKey" widget. (Trac #343) Seems to be functional but there is still an "uncaught exception" that pops up from nowhere when closing the dialog. SVN:trunk[1049] --- application/cmdbabstract.class.inc.php | 2 +- application/itopwebpage.class.inc.php | 2 ++ application/ui.htmleditorwidget.class.inc.php | 9 ++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 268e2adc20..68423c6f45 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1110,7 +1110,7 @@ EOF break; case 'HTML': - $oWidget = new UIHTMLEditorWidget($iId, $sAttCode, $sNameSuffix, $sHelpText, $sValidationField, $value, $bMandatory); + $oWidget = new UIHTMLEditorWidget($iId, $sAttCode, $sNameSuffix, $sFieldPrefix, $sHelpText, $sValidationField, $value, $bMandatory); $sHTMLValue = $oWidget->Display($oPage, $aArgs); break; diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index ab25a98ea8..ae5b2fa95c 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -67,6 +67,8 @@ class iTopWebPage extends NiceWebPage $this->add_linked_script("../js/jquery.blockUI.js"); $this->add_linked_script("../js/utils.js"); $this->add_linked_script("../js/swfobject.js"); + $this->add_linked_script("../js/ckeditor/ckeditor.js"); + $this->add_linked_script("../js/ckeditor/adapters/jquery.js"); $this->add_ready_script( <<m_iId = $iInputId; $this->m_sAttCode = $sAttCode; @@ -43,6 +44,7 @@ class UIHTMLEditorWidget $this->m_sValidationField = $sValidationField; $this->m_sValue = $sValue; $this->m_sMandatory = $sMandatory; + $this->m_sFieldPrefix = $sFieldPrefix; } /** @@ -53,16 +55,13 @@ class UIHTMLEditorWidget */ public function Display(WebPage $oPage, $aArgs = array()) { - $oPage->add_linked_script("../js/ckeditor/ckeditor.js"); - $oPage->add_linked_script("../js/ckeditor/adapters/jquery.js"); - $iId = $this->m_iId; $sCode = $this->m_sAttCode.$this->m_sNameSuffix; $sValue = $this->m_sValue; $sHelpText = $this->m_sHelpText; $sValidationField = $this->m_sValidationField; - $sHtmlValue = "
    $sValidationField
    "; + $sHtmlValue = "
    $sValidationField
    "; // Replace the text area with CKEditor // To change the default settings of the editor, From 57a0e2d47e1bafc76f253b883ae016ea6290bc01 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 17 Jan 2011 15:13:28 +0000 Subject: [PATCH 965/970] Fixed Trac #345: hidden or read-only parameters in a form were not taken into account for computing the allowed values. SVN:trunk[1050] --- application/cmdbabstract.class.inc.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 68423c6f45..d1872461e8 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -1317,9 +1317,12 @@ EOF else { $iFlags = $this->GetAttributeFlags($sAttCode); + $sInputId = $this->m_iFormId.'_'.$sAttCode; if ($iFlags & OPT_ATT_HIDDEN) { - // Attribute is hidden, do nothing + // Attribute is hidden, add a hidden input + $oPage->add(''); + $aFieldsMap[$sAttCode] = $sInputId; } else { @@ -1327,13 +1330,14 @@ EOF { // Attribute is read-only $sHTMLValue = $this->GetAsHTML($sAttCode); + $sHTMLValue .= ''; + $aFieldsMap[$sAttCode] = $sInputId; } else { $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); - $sInputId = $this->m_iFormId.'_'.$sAttCode; $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; $aFieldsMap[$sAttCode] = $sInputId; From a4883076f85fda63d438d1eb596ab31b83990312 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Mon, 17 Jan 2011 17:11:03 +0000 Subject: [PATCH 966/970] Fixed Trac #338: when updating a 'dependent' field, don't forget to cascade the refresh in case this field has its own dependent fields. SVN:trunk[1051] --- js/wizardhelper.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/js/wizardhelper.js b/js/wizardhelper.js index 4733f9487c..123a567cf5 100644 --- a/js/wizardhelper.js +++ b/js/wizardhelper.js @@ -1,4 +1,22 @@ // Wizard Helper JavaScript class to communicate with the WizardHelper PHP class + +if (!Array.prototype.indexOf) // Emulation of the indexOf function for IE and old browsers +{ + Array.prototype.indexOf = function(elt /*, from*/) + { + var len = this.length; + var from = Number(arguments[1]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + + if (from < 0) from += len; + for (; from < len; from++) + { + if (from in this && this[from] === elt) return from; + } + return -1; + }; +} + function WizardHelper(sClass, sFormPrefix) { this.m_oData = { 'm_sClass' : '', @@ -69,6 +87,7 @@ function WizardHelper(sClass, sFormPrefix) this.UpdateFields = function () { + var aRefreshed = []; //console.log('** UpdateFields **'); // Set the full HTML for the input field for(i=0; i Date: Tue, 18 Jan 2011 09:00:26 +0000 Subject: [PATCH 967/970] Fixed Trac #348: no validation needed when pressing "Restart". SVN:trunk[1052] --- pages/csvimport.php | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/csvimport.php b/pages/csvimport.php index 23551d5356..114216b079 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -927,6 +927,7 @@ EOF function CSVRestart() { $('input[name=step]').val(1); + $('#wizForm').removeAttr('onsubmit'); // No need to perform validation checks when going back $('#wizForm').submit(); } From fde336d8dd7168506b94588089882e65f4dad3e6 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 18 Jan 2011 09:07:46 +0000 Subject: [PATCH 968/970] Cosmetics: make the login & change password form look the same on all browsers. SVN:trunk[1053] --- application/loginwebpage.class.inc.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index c680505722..320a77073f 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -68,6 +68,10 @@ body { border-left: 1px solid #000; border-right: 1px solid #000; border-top: 0; + text-align: center; +} +#pwd, #user,#old_pwd, #new_pwd, #retype_new_pwd { + width: 10em; } .center { text-align: center; @@ -115,9 +119,9 @@ EOF $this->add("

    ".Dict::S('UI:Login:IdentifyYourself')."

    \n"); } $this->add("
    \n"); - $this->add("\n"); - $this->add("\n"); - $this->add("\n"); + $this->add("
    \n"); + $this->add("\n"); + $this->add("\n"); $this->add("\n"); $this->add("
    \n"); $this->add("\n"); @@ -159,10 +163,10 @@ EOF $this->add("

    ".Dict::S('UI:Login:IncorrectOldPassword')."

    \n"); } $this->add("\n"); - $this->add("\n"); - $this->add("\n"); - $this->add("\n"); - $this->add("\n"); + $this->add("
    \n"); + $this->add("\n"); + $this->add("\n"); + $this->add("\n"); $this->add("\n"); $this->add("
      
    \n"); $this->add("\n"); From 20ea4644b4cb55fb34dc73d3050bf1dd415ab915 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 18 Jan 2011 11:37:21 +0000 Subject: [PATCH 969/970] Remove an ambiguous naming when performing a CSV import of DBServerInstance objects. SVN:trunk[1054] --- modules/itop-config-mgmt-1.0.0/de.dict.itop-config-mgmt.php | 2 +- modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php | 3 +-- modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php | 4 +++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/itop-config-mgmt-1.0.0/de.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/de.dict.itop-config-mgmt.php index 5f499259ad..014900085f 100644 --- a/modules/itop-config-mgmt-1.0.0/de.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/de.dict.itop-config-mgmt.php @@ -585,7 +585,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array( 'Class:DBServerInstance+' => '', 'Class:DBServerInstance/Attribute:software_id' => 'Software', 'Class:DBServerInstance/Attribute:software_id+' => '', - 'Class:DBServerInstance/Attribute:software_name' => 'Name', + 'Class:DBServerInstance/Attribute:software_name' => 'Software Name', 'Class:DBServerInstance/Attribute:software_name+' => '', 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Datenbanken', 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Datenbanken-Quellen', diff --git a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php index 0e164e60c0..7bb54cc476 100644 --- a/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/en.dict.itop-config-mgmt.php @@ -585,7 +585,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:DBServerInstance+' => '', 'Class:DBServerInstance/Attribute:software_id' => 'Software', 'Class:DBServerInstance/Attribute:software_id+' => '', - 'Class:DBServerInstance/Attribute:software_name' => 'Name', + 'Class:DBServerInstance/Attribute:software_name' => 'Software Name', 'Class:DBServerInstance/Attribute:software_name+' => '', 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Databases', 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Database sources', @@ -1049,6 +1049,5 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:Group+' => 'Groups of CIs', 'Menu:ConfigManagement:Shortcuts' => 'Shortcuts', 'Menu:ConfigManagement:AllContacts' => 'All contacts: %1$d', - )); ?> diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php index ca1459a10e..ccee243ed5 100644 --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php @@ -572,8 +572,10 @@ Dict::Add('FR FR', 'French', 'Français', array( Dict::Add('FR FR', 'French', 'Français', array( 'Class:DBServerInstance' => 'Instance de serveur de base de données', 'Class:DBServerInstance+' => '', + 'Class:DBServerInstance/Attribute:software_id' => 'Logiciel', + 'Class:DBServerInstance/Attribute:software_name' => 'Logiciel Serveur', 'Class:DBServerInstance/Attribute:dbinstance_list' => 'Bases', - 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Liste des sources de données', + 'Class:DBServerInstance/Attribute:dbinstance_list+' => 'Liste des bases de données', )); // From cb0610bf0b27846edde5b6469c9be532a8b7e385 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Tue, 18 Jan 2011 11:38:24 +0000 Subject: [PATCH 970/970] Readme for iTop 1.0.2 SVN:trunk[1055] --- readme.txt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/readme.txt b/readme.txt index b1756c4e77..dfd8e949cb 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -iTop - version 1.0.2 - 05-01-2011 +iTop - version 1.0.2 - 19-01-2011 Readme file 1. ABOUT THIS RELEASE @@ -149,6 +149,7 @@ Allow DBObjects to be deleted by the standard UI 'Delete', which may be useful i Force a dummy timezone to prevent a warning during the setup... Cosmetic on the iTop logo (under IE8). Removed an unneeded size=100% that bothers IE. XML data loader now requests credentials +The configuration file now contains "relative paths" only. This means that if you installed iTop 1.0.2, you can move the directory containing the iTop installation. * iTop Customization @@ -163,7 +164,7 @@ User profiles: created in dedicated module itop-profiles-itil Welcome page moved out the application, into a dedicated module: itop-welcome-itil Moved the standards menus into the "welcome" module Added the capability to enable/disable menus based on the rights to apply a given stimulus. -FIxed the processing of hierarchical ZLists to keep the display order when plain fields and fieldsets are mixed at the same level. +Fixed the processing of hierarchical ZLists to keep the display order when plain fields and fieldsets are mixed at the same level. Added support for hierarchical ZLists when checking the data model consistency @@ -172,7 +173,8 @@ Added support for hierarchical ZLists when checking the data model consistency iTop was tested successfully ON FF 3.6, IE8, Chrome and Safari 5 (Windows). Fixed the "Relationships" Flash navigator so that it works also on Safari. (tested with Safari 5.0.2 on Windows) (Trac #310) - Fixed the search form, and also fixed the search/selection of objects to link (n:n links) that was broken on IE8. -Fix to prevent IE 8 from running in IE7 compatibility mode... to be tested... +- Fix to prevent IE 8 from running in IE7 compatibility mode +- Cosmetics: The login and change password forms now look the same on all browsers (FF, IE8, Safari 5, Chrome) * CSV Import @@ -186,6 +188,7 @@ Bugs fixed ---------- The complete list of active tickets can be reviewed at http://sourceforge.net/apps/trac/itop/report/1 +#299 "Show all" should provide some feedback (progress) #314 Set a longer timeout during setup #318 (and #335) added the check of the mandatory DOM extension. #321 Display PHP errors during setup instead of hiding them! @@ -194,9 +197,10 @@ The complete list of active tickets can be reviewed at http://sourceforge.net/ap #333 Organizations' drop-down list is truncated on IE when the name of an organization is too long. #334 Proper handling of the "remove objects" button (was working only for the first linkset in the object). #337 email validation. Use a simpler regular expression that is much faster to execute. +#338 Service Element not updated if service is autofilled #339 Fixed a typo in German translation thanks to ulmerspatz and Jonathan Lucas - - +#345 Impossible to reassign to another workgroup +#346 CSV Import prompts to enter the mapping when pressing 'Restart' 3.2. Known limitations (https://sourceforge.net/apps/trac/itop/report/3) -----------------