Compare commits

...

446 Commits

Author SHA1 Message Date
Denis Flaven
c615234988 SVN:2.0-beta1[2235] 2012-10-08 12:00:20 +00:00
Romain Quetiez
60cf926fdb Toolkit: deharcoded source directory
SVN:trunk[2234]
2012-10-05 12:02:05 +00:00
Denis Flaven
5b93504709 - new Configuration option 'source_dir'
- pretty print of the configuration

SVN:trunk[2233]
2012-10-05 10:57:47 +00:00
Romain Quetiez
6c1866ce89 Fixed cosmetic regression (seen on first login if the welcome popup is shown)
SVN:trunk[2232]
2012-10-05 08:19:37 +00:00
Romain Quetiez
eab09c5e17 Toolkit : compile only installed modules (in database)
SVN:trunk[2231]
2012-10-04 15:47:31 +00:00
Denis Flaven
c69eb2e5d7 Don't perform computations inside GetAsHTML because this may cause an infinite recursion since GetAsHTML is called by ToArgs
SVN:trunk[2230]
2012-10-04 09:09:25 +00:00
Romain Quetiez
55cc5ad2b7 Dev productivity: SVN to ignore the folder env-*
SVN:trunk[2229]
2012-10-03 08:52:41 +00:00
Romain Quetiez
18933c33b3 Finalized the translation of some dashboards (1.2.1)
SVN:trunk[2228]
2012-10-03 08:46:07 +00:00
Denis Flaven
5c7604f950 - Implemented the 'symlinks' options
- Better handling of errors during the installation

SVN:trunk[2227]
2012-10-03 08:18:40 +00:00
Romain Quetiez
6b92cab445 Added a utility for the toolkit (adaptation to 2.0) + removed non-issues revealed by the toolkit
SVN:trunk[2226]
2012-10-02 16:06:10 +00:00
Denis Flaven
9af1de0bb4 Cleaner handling of "pure PHP" classes inside the data model
SVN:trunk[2225]
2012-10-02 13:54:22 +00:00
Denis Flaven
ddd98ebab2 Some progress on the 2.0 setup...
SVN:trunk[2224]
2012-09-29 16:47:13 +00:00
Denis Flaven
f5de7987bb Implementation of a new extension "iPopupMenuExtension" to allow a module to add menu items almost anywhere inside iTop. Oops...
SVN:trunk[2223]
2012-09-29 16:45:36 +00:00
Denis Flaven
12c06262ea Typos
SVN:trunk[2222]
2012-09-29 16:43:22 +00:00
Denis Flaven
1b55986e39 Protect against duplicate class definition in case of unattended installation
SVN:trunk[2221]
2012-09-29 16:42:29 +00:00
Denis Flaven
ef59badf5b Implementation of a new extension "iPopupMenuExtension" to allow a module to add menu items almost anywhere inside iTop.
SVN:trunk[2220]
2012-09-29 16:03:16 +00:00
Denis Flaven
cb57335c0b Work in progress on the 2.0 setup/ the "install from scratch" is complete.
SVN:trunk[2219]
2012-09-27 10:53:45 +00:00
Denis Flaven
5fc4775903 Bug fix: AddToDashboard crashed the menu.
SVN:trunk[2218]
2012-09-26 10:19:41 +00:00
Denis Flaven
088fcf9449 Protect the download of documents against spurious blank lines coming from nowhere !!
SVN:trunk[2217]
2012-09-26 09:19:43 +00:00
Erwan Taloc
5ed95f3a1c Mahe sure a unique ID is define for DIV
SVN:trunk[2216]
2012-09-26 08:08:44 +00:00
Romain Quetiez
a2e33634f5 Compiler: fixed issue with 'initial_state_path' (the real fix...)
SVN:trunk[2215]
2012-09-25 17:57:12 +00:00
Romain Quetiez
2d641a0de3 Compiler: fixed issue with 'initial_state_path'
SVN:trunk[2214]
2012-09-25 17:41:46 +00:00
Romain Quetiez
da75724296 Dashboards for 2.0, replaced external fields by the external key (group by) + fixed an issue in the numbering of dashlets
SVN:trunk[2213]
2012-09-25 16:35:17 +00:00
Romain Quetiez
50f0358556 Fixed issues with accentuated characters in the graphs (bars or pie) - added a comment
SVN:trunk[2212]
2012-09-25 14:35:31 +00:00
Romain Quetiez
0e4c79b2e7 Fixed issues with accentuated characters in the graphs (bars or pie)
SVN:trunk[2211]
2012-09-25 14:30:52 +00:00
Denis Flaven
a008107d38 Bug fix: properly show which column the table is sorted on, even for external keys.
Enhancement: sort the non-selected columns alphabetically

SVN:trunk[2210]
2012-09-25 12:57:39 +00:00
Romain Quetiez
dc4bfc6b30 Compiler: added 'initial_state_path'
SVN:trunk[2209]
2012-09-25 12:08:50 +00:00
Denis Flaven
2da369431c Properly parse accented characters inside the "autocomplete" widget.
Protects the dialog resizing against some JS errors
Code cleanup...

SVN:trunk[2208]
2012-09-25 11:10:17 +00:00
Denis Flaven
5e40d7b009 - Protect against a JS error when opening the dialog box
- Syntax cleaning

SVN:trunk[2207]
2012-09-25 10:15:43 +00:00
Denis Flaven
8d56d8eb79 Enhancement: prevent reloading a list while the configuration dialog is open.
SVN:trunk[2206]
2012-09-25 10:03:41 +00:00
Denis Flaven
ab1143e0e1 Bug fix: prevent 'assertion failed' when a block auto reloads: '0' is indeed a valid ID for a display block !!
SVN:trunk[2205]
2012-09-25 10:01:58 +00:00
Romain Quetiez
d747b1fe26 Fixed an issue revealed by fix [2201], occurring when filtering on organization (context) - the fix is not complete (see Trac #588)
SVN:trunk[2204]
2012-09-25 09:43:03 +00:00
Denis Flaven
20c5e1b6ad Protects against temporary objects that exist only in memory (id < 0)
SVN:trunk[2203]
2012-09-25 08:31:42 +00:00
Denis Flaven
aa9c7c7091 Protects against temporary objects that exist only in memory (id < 0)
SVN:trunk[2202]
2012-09-25 08:29:06 +00:00
Romain Quetiez
2f9922f7df Fixed issue in OQL rendering, reproduced with FROM and JOIN clauses
SVN:trunk[2201]
2012-09-24 07:56:35 +00:00
Denis Flaven
45bfee7ef0 Do not let portal users configure their lists... not bullet proof from the security point of view, but better than opening it wide.
SVN:trunk[2200]
2012-09-21 16:57:53 +00:00
Romain Quetiez
2eddb57cbd Dashboards - Fixed issues + added 3 confirmation (on exit, save or cancel)
SVN:trunk[2199]
2012-09-21 15:15:05 +00:00
Denis Flaven
4979a653ee Support of pre-2.0 modules by transforming the icon paths.
SVN:trunk[2198]
2012-09-21 15:07:16 +00:00
Denis Flaven
82ba3962b0 Special processing for extra "Well Known" parent classes.
SVN:trunk[2197]
2012-09-21 13:53:05 +00:00
Denis Flaven
04424f9762 - Small fixes to the custom display of lists.
- CSS improvement for dashboards

SVN:trunk[2196]
2012-09-21 11:05:10 +00:00
Romain Quetiez
dac73c1669 Dashboards - Finalized the implementation (inc. translation in french) - still some known issues
SVN:trunk[2195]
2012-09-20 15:56:15 +00:00
Denis Flaven
77129d010e Do NOT grab cursor hotkeys (CTRL + left arrow) to hide/show the menu pane.
SVN:trunk[2194]
2012-09-20 08:38:44 +00:00
Denis Flaven
d19116a88e Don't put $_FILES inside a javascript comment !!
SVN:trunk[2193]
2012-09-20 08:02:55 +00:00
Denis Flaven
d38b79de76 Various fixes for the selection of columns / sort order in the lists.
SVN:trunk[2192]
2012-09-20 07:55:25 +00:00
Denis Flaven
54a3a1a89a First integration pass of the new "2.0" setup, but still work in progress...
SVN:trunk[2191]
2012-09-19 12:36:44 +00:00
Romain Quetiez
d18ff8339a The new 2.0 setup is under way... disabled copy and backup
SVN:trunk[2190]
2012-09-19 09:24:29 +00:00
Denis Flaven
e463bd8b48 Preparing for the integration of the version 2.0 of the setup.
Some changes to the application layout: logs now go to the ./log folder ./data should be used to store application's data.

SVN:trunk[2189]
2012-09-18 19:55:56 +00:00
Romain Quetiez
93791255e6 The new 2.0 setup is under way... (forgot the class 'DBBackup')
SVN:trunk[2188]
2012-09-18 14:47:15 +00:00
Romain Quetiez
bb404f8a95 The new 2.0 setup is under way... (added backup + file copy + sample data + admin language)
SVN:trunk[2187]
2012-09-18 09:59:36 +00:00
Denis Flaven
308f40c11b Enhancement: added a new (hidden) configuration setting 'synchro_prevent_delete_all' (default to true) to deactivate the "safety belt" and allow the deletion of all replicas of a synchro task in one go.
SVN:trunk[2186]
2012-09-17 17:12:43 +00:00
Romain Quetiez
645b2f5ad8 Profiles defined in XML: fixed regression preventing from creating an object
SVN:trunk[2185]
2012-09-17 07:49:00 +00:00
Romain Quetiez
71208d362a The new 2.0 setup is under way...
SVN:trunk[2184]
2012-09-13 09:00:53 +00:00
Romain Quetiez
39d2534bda Friendly names: systematic rebuild of views (could be improved)
SVN:trunk[2183]
2012-09-13 08:51:36 +00:00
Romain Quetiez
6052e5c848 The new 2.0 setup is under way...
SVN:trunk[2182]
2012-09-12 15:54:37 +00:00
Denis Flaven
10aaa3f9bf Adaptations to potentially run the setup from the CLI
SVN:trunk[2181]
2012-09-12 13:44:12 +00:00
Denis Flaven
b372a38c5d Adaptations to potentially run the setup from the CLI
SVN:trunk[2180]
2012-09-12 13:43:18 +00:00
Denis Flaven
5eb97ae133 The new 2.0 setup is under way...
SVN:trunk[2179]
2012-09-12 13:31:46 +00:00
Denis Flaven
4cfeae9ca0 Adaptations to potentially run the setup from the CLI
SVN:trunk[2178]
2012-09-12 13:28:49 +00:00
Romain Quetiez
949a4639e2 #585 Error in CSV export (from a search result)
SVN:trunk[2176]
2012-09-11 14:39:09 +00:00
Romain Quetiez
e47251f2f4 Stop watches - removed inefficient workaround to issue #502 (modif not recorded in the history), the issue #502 must be fixed properly
SVN:trunk[2175]
2012-09-11 14:29:41 +00:00
Romain Quetiez
7026eb3b76 Stop watches - reviewed history of changes (+ started refactorization of change tracking into AttributeDef)
SVN:trunk[2174]
2012-09-11 10:55:11 +00:00
Romain Quetiez
e48a842881 Stop watches - reset of deadlines whenever the SW is stopped
SVN:trunk[2170]
2012-09-07 15:32:50 +00:00
Romain Quetiez
819b2663ca Stop watches - reset of deadlines + overrun
SVN:trunk[2169]
2012-09-07 15:23:28 +00:00
Romain Quetiez
95785ee22d Stop watches - workaround for the triggers
SVN:trunk[2168]
2012-09-07 14:39:29 +00:00
Denis Flaven
c37135af55 Support previous datamodels (profiles à la mode 1.2.x)
SVN:trunk[2167]
2012-09-07 13:25:59 +00:00
Romain Quetiez
f05f5865e9 Stop watches - beta (change tracking to be reviewed)
SVN:trunk[2166]
2012-09-07 12:51:26 +00:00
Romain Quetiez
2d9193e60e Friendly names: improved the behavior. Now fully compliant with end users expectations (e.g. a list of contacts shows the friendly name of the persons and team, not only the attribute 'name', the search can be performed on the friendly name as well)
SVN:trunk[2165]
2012-09-04 12:59:01 +00:00
Denis Flaven
8fe559e5d1 - Support of iTop 2.0
- Fix for deleting "suggested attachments"

SVN:trunk[2164]
2012-08-31 14:08:35 +00:00
Denis Flaven
d799585958 Bug fix: do NOT request the display value of a linkset when this value is useless and may generate an exception !!
SVN:trunk[2163]
2012-08-31 13:31:27 +00:00
Denis Flaven
4eb018ac50 Bug fix for supporting CKEditor (HTMLAttribute) inside a reloaded form
SVN:trunk[2162]
2012-08-31 13:30:12 +00:00
Denis Flaven
f0d87adc87 Bug fix: Support edition of the "latest modified" entry of a case log
SVN:trunk[2161]
2012-08-31 13:28:51 +00:00
Denis Flaven
bc557f6e42 Bug fix for queries where the selected class is not the first one in the list
SVN:trunk[2160]
2012-08-31 13:28:15 +00:00
Denis Flaven
c8393a7d3a Typo !
SVN:trunk[2159]
2012-08-31 13:15:02 +00:00
Denis Flaven
c44420c985 Menu icon for editing lists config.
SVN:trunk[2158]
2012-08-28 16:04:42 +00:00
Denis Flaven
9064b81461 Integration of the latest version of CKEditor: version 3.6.4, released on 17 July 2012
SVN:trunk[2157]
2012-08-28 13:52:07 +00:00
Denis Flaven
175e2351d6 Support edition of the "latest modified" entry of a case log
SVN:trunk[2156]
2012-08-28 12:09:02 +00:00
Denis Flaven
9eb1fbee1d Support edition of the "latest modified" entry of a case log
SVN:trunk[2155]
2012-08-28 12:08:15 +00:00
Denis Flaven
5726b2cd5c Bug fix: properly export boolean attributes to XML (a value of false was creating an empty XML tag)
SVN:trunk[2154]
2012-08-23 14:42:34 +00:00
Romain Quetiez
083c3d8613 Profiles defined in XML: reviewed the internal extension capability (GetReadOnlyAttributes and GetPredefinedObjects)
SVN:trunk[2152]
2012-08-21 13:25:32 +00:00
Romain Quetiez
cf63e128b1 Profiles defined in XML: aligned the user rights with 1.2.1 (current version)
SVN:trunk[2151]
2012-08-21 08:19:25 +00:00
Romain Quetiez
c01cdbb569 Profiles defined in XML: include subclasses (explicit declarations have precedence)
SVN:trunk[2150]
2012-08-20 13:25:11 +00:00
Romain Quetiez
38cca0c144 Profiles defined in XML (setup + runtime), beta version (stable, upgrade required)
SVN:trunk[2149]
2012-08-14 16:06:51 +00:00
Romain Quetiez
3b46af6a4f Fixed bug with the serialization of a multi-object OQL (hard to reproduce)
SVN:trunk[2147]
2012-08-06 13:43:26 +00:00
Romain Quetiez
b001476a6f Fixed typo in the menu reflection API
SVN:trunk[2146]
2012-08-06 13:17:04 +00:00
Denis Flaven
f0cd1cddb0 - Use the 'style' of the MenuBlock (inherited from DisplayBlock) to distinguish between a list of one object and the details of the same object.
SVN:trunk[2145]
2012-07-31 15:18:46 +00:00
Denis Flaven
a707e9fde1 In the 'context', pass menus by ID and no longer by index.
SVN:trunk[2144]
2012-07-31 14:07:35 +00:00
Denis Flaven
c612732037 In the 'context', pass menus by ID and no longer by index.
SVN:trunk[2143]
2012-07-31 14:06:38 +00:00
Denis Flaven
2b5c7a7f59 Move the "favorites" organization at the bottom of the page.
SVN:trunk[2142]
2012-07-31 13:25:58 +00:00
Denis Flaven
aa8ea8f2fe Enhancement of the Email class to preserve backward compatibility...
SVN:trunk[2141]
2012-07-27 10:09:41 +00:00
Denis Flaven
16651d53e1 Fixed a regression due to (apparently?) the upgrade of jQuery
SVN:trunk[2140]
2012-07-27 10:08:23 +00:00
Denis Flaven
b0ec706f79 The "Preferences" pages now allows a user to:
- change her/his favorite language
- set a global default for the length of all lists, overriding the system-wide configuration.

SVN:trunk[2139]
2012-07-27 10:07:20 +00:00
Denis Flaven
54f81153f3 The "Preferences" pages now allows a user to:
- change her/his favorite language
- set a global default for the length of all lists, overriding the system-wide configuration.

SVN:trunk[2138]
2012-07-27 10:06:07 +00:00
Denis Flaven
e726420a94 The "Preferences" pages now allows a user to:
- change her/his favorite language
- set a global default for the length of all lists, overriding the system-wide configuration.

SVN:trunk[2137]
2012-07-27 10:04:08 +00:00
Denis Flaven
148df28345 The "Preferences" pages now allows a user to:
- change her/his favorite language
- set a global default for the length of all lists, overriding the system-wide configuration.

SVN:trunk[2136]
2012-07-27 10:03:06 +00:00
Denis Flaven
e126137a91 Implemented Track #582: "stable name" for synchro_data_xxx tables; translations.
SVN:trunk[2134]
2012-07-25 14:30:43 +00:00
Denis Flaven
f9924cd635 Implemented Track #582: "stable name" for synchro_data_xxx tables.
Do not allow changing read-only attributes by CSV import.

SVN:trunk[2133]
2012-07-25 14:09:19 +00:00
Denis Flaven
55e0898587 Implemented Track #582: "stable name" for synchro_data_xxx tables.
SVN:trunk[2132]
2012-07-25 14:06:54 +00:00
Denis Flaven
dce0bce4ce Customizable tables implementation, beta version done.
SVN:trunk[2131]
2012-07-24 15:16:06 +00:00
Denis Flaven
82cf7776ca Customizable tables implementation, beta version done.
SVN:trunk[2130]
2012-07-24 15:15:36 +00:00
Denis Flaven
5430f52145 Customizable tables implementation...
SVN:trunk[2129]
2012-07-24 15:13:02 +00:00
Denis Flaven
5125000994 Customizable tables implementation...
SVN:trunk[2128]
2012-07-24 13:58:13 +00:00
Denis Flaven
367d97ffb2 Customizable tables implementation...
SVN:trunk[2127]
2012-07-24 13:56:45 +00:00
Denis Flaven
4a3321dd0a Customizable tables implementation...
SVN:trunk[2126]
2012-07-24 13:51:22 +00:00
Denis Flaven
fe55d35930 Customizable tables implementation...
SVN:trunk[2125]
2012-07-24 13:46:58 +00:00
Denis Flaven
fad06f8f91 Customizable tables implementation...
SVN:trunk[2124]
2012-07-24 13:32:54 +00:00
Denis Flaven
1840e24a3c Customizable tables implementation...
SVN:trunk[2123]
2012-07-24 13:32:09 +00:00
Romain Quetiez
b6c3b3c7ab Stop watches - alpha (GUI is missing)
SVN:trunk[2122]
2012-07-12 13:55:26 +00:00
Romain Quetiez
1757554f37 Reintegrated fixes from branch 1.2.1 (2116,2118,2119)
- HTML attributes > 64 Kb
- Log of notification displayed as HTML

SVN:trunk[2121]
2012-07-04 09:34:49 +00:00
Denis Flaven
9bfd7ae435 Prototype of extensibility rules
SVN:trunk[2114]
2012-06-26 12:05:13 +00:00
Denis Flaven
7c9e2df888 Prototype of extensibility rules
SVN:trunk[2113]
2012-06-26 10:04:39 +00:00
Denis Flaven
5a16c08bc9 Fix for Trac #559: ldap user can login with blank password
SVN:trunk[2110]
2012-06-14 16:31:16 +00:00
Denis Flaven
0ffe040546 Updated the test list to take into account Trac #558 (hexadecimal numbers inside strings)
SVN:trunk[2107]
2012-06-14 09:31:33 +00:00
Romain Quetiez
dc90de5a2b XML Export: do not export "unimplemented" link sets, so that the resulting output can be used as sample data in the setup
SVN:trunk[2106]
2012-06-14 09:26:23 +00:00
Denis Flaven
51ba8df159 Fixed Trac #558: properly parse strings containing hexadecimal sequences (i.e. 'QWERTY0xCUIOP').
Note that for now hexadecimal numbers are parsed but not interpreted properly...

SVN:trunk[2103]
2012-06-14 09:18:24 +00:00
Romain Quetiez
1e97829d7d Fixed regression (See #556) due to the existence of an overload of the protected API GetUserOrgs, reintegrated from branch 1.2.1
SVN:trunk[2101]
2012-06-14 09:12:56 +00:00
Romain Quetiez
cacad13702 Compiler: fixed issue with quotes in the OQL of a menu.
SVN:trunk[2098]
2012-06-13 05:56:00 +00:00
Denis Flaven
1a8b802276 Fixed the broken n:n links management UI (Add button remained disabled)
SVN:trunk[2097]
2012-06-12 15:21:11 +00:00
Denis Flaven
08deefb5a4 Fixed the broken n:n links management UI (Add button remained disabled)
SVN:trunk[2096]
2012-06-12 15:19:09 +00:00
Denis Flaven
e5bb006ba6 Fixed the broken n:n links management UI (Add button remained disabled)
SVN:trunk[2095]
2012-06-12 14:34:14 +00:00
Denis Flaven
08f3e36cc8 Fixed the broken n:n links management UI (Add button remained disabled)
SVN:trunk[2094]
2012-06-12 14:33:38 +00:00
Denis Flaven
a69c49e77a Proper management of scroll bars in the left (west) pane.
SVN:trunk[2093]
2012-06-12 14:31:57 +00:00
Denis Flaven
039f5042d9 Proper management of scroll bars in the left (west) pane.
SVN:trunk[2092]
2012-06-12 14:31:08 +00:00
Romain Quetiez
4562edb4ea Charts : disabled hrefs until it works
SVN:trunk[2091]
2012-06-12 13:06:54 +00:00
Romain Quetiez
463ade4bf4 Compiler: discard empty menu groups
SVN:trunk[2090]
2012-06-11 16:21:03 +00:00
Romain Quetiez
4f653d63b5 Compiler: fixed issue with simple quotes in the OQL of a menu.
SVN:trunk[2089]
2012-06-11 14:15:53 +00:00
Denis Flaven
34578ff831 Reload the impact/depends on graph only on demand for better performance, via the new Refresh button
SVN:trunk[2087]
2012-06-07 13:41:22 +00:00
Denis Flaven
d40f7aa7ba Reload the impact/depends on graph only on demand for better performance
SVN:trunk[2086]
2012-06-07 13:31:57 +00:00
Denis Flaven
d73e1f5fd3 Protect against a reload of the XML data while loading
SVN:trunk[2085]
2012-06-07 13:28:57 +00:00
Romain Quetiez
47f13d6b70 #556 Merged in trunk
SVN:trunk[2084]
2012-06-05 15:49:31 +00:00
Romain Quetiez
18031b0c34 Dashboard: bug fix
SVN:trunk[2079]
2012-05-31 07:57:42 +00:00
Denis Flaven
d953506950 Prototype of HeatMap dashlet
SVN:trunk[2078]
2012-05-30 16:30:59 +00:00
Denis Flaven
1a040a3a3a Prototype of HeatMap dashlet
SVN:trunk[2077]
2012-05-30 16:30:26 +00:00
Denis Flaven
e3dd7d4371 Better track unknown classes exceptions
SVN:trunk[2076]
2012-05-30 14:59:33 +00:00
Denis Flaven
814ff60c07 Allow editing the "Welcome" dashboard
SVN:trunk[2075]
2012-05-30 14:57:57 +00:00
Romain Quetiez
5cfc82437f Dashboard: improved the group by on two columns (to be continued)
SVN:trunk[2074]
2012-05-30 14:51:01 +00:00
Denis Flaven
ad1b2901be Protect dashboard/dashlets against exceptions, with a graceful degradation for non-existing classes
SVN:trunk[2073]
2012-05-30 14:21:48 +00:00
Denis Flaven
e64e008aad Protect dashboard/dashlets against exceptions, with a graceful degradation for non-existing classes
SVN:trunk[2072]
2012-05-30 14:21:10 +00:00
Denis Flaven
9e10de70c9 Typo
SVN:trunk[2071]
2012-05-30 11:55:39 +00:00
Denis Flaven
cc0590cd3d replace the 'static' template menu by a dashboard menu
SVN:trunk[2070]
2012-05-30 11:20:56 +00:00
Denis Flaven
716ba04f8e replace the 'static' template menu by a dashboard menu
SVN:trunk[2069]
2012-05-30 11:20:30 +00:00
Denis Flaven
04f9e823db replace the 'static' template menu by a dashboard menu
SVN:trunk[2068]
2012-05-30 11:19:55 +00:00
Denis Flaven
15a7517e4e Change in default values...
SVN:trunk[2067]
2012-05-30 11:02:30 +00:00
Romain Quetiez
2f0d058bfc Dashboard: improved the group by on two columns (to be continued)
SVN:trunk[2066]
2012-05-30 11:01:03 +00:00
Denis Flaven
9a1f0241ff Prevent an error in case of exception...
SVN:trunk[2065]
2012-05-30 10:57:52 +00:00
Denis Flaven
f0cfe78043 No more find/replace of : in Dict strings
SVN:trunk[2064]
2012-05-30 10:56:49 +00:00
Denis Flaven
d30cf0655d replace the 'static' template menu by a dashboard menu
SVN:trunk[2063]
2012-05-30 10:50:49 +00:00
Denis Flaven
2e26c056f5 replace the 'static' template menu by a dashboard menu
SVN:trunk[2062]
2012-05-30 10:50:26 +00:00
Denis Flaven
8d4bb6bcdf replace the 'static' template menu by a dashboard menu
SVN:trunk[2061]
2012-05-30 10:27:54 +00:00
Denis Flaven
c5a4a02971 replace the 'static' template menu by a dashboard menu
SVN:trunk[2060]
2012-05-30 09:58:39 +00:00
Denis Flaven
8c0fbe1913 Dashboards edition final touch...
SVN:trunk[2059]
2012-05-30 09:07:41 +00:00
Denis Flaven
5959be5929 Dashboards edition final touch...
SVN:trunk[2058]
2012-05-30 09:06:33 +00:00
Denis Flaven
d0bf023f42 Transparent icon to replace a missing Class' icon
SVN:trunk[2057]
2012-05-30 09:05:12 +00:00
Denis Flaven
38df2c71a2 Added icon_select widget
SVN:trunk[2056]
2012-05-30 09:04:12 +00:00
Romain Quetiez
8a8b8b87e3 Dashboard: finalized the beta version of the header dashlets
SVN:trunk[2055]
2012-05-29 16:00:38 +00:00
Denis Flaven
8a85abf6b4 Refresh the whole dashboard when extending the layout
SVN:trunk[2054]
2012-05-29 15:50:57 +00:00
Denis Flaven
eb9c5d8fe2 Refresh the whole dashboard when extending the layout
SVN:trunk[2053]
2012-05-29 15:50:25 +00:00
Denis Flaven
7cdc90354c More dashlet icons
SVN:trunk[2052]
2012-05-29 15:47:38 +00:00
Romain Quetiez
5ecf5815b8 Dashboard: fixed issue with Null values (group by)
SVN:trunk[2051]
2012-05-29 14:02:25 +00:00
Denis Flaven
74726ff742 New icons for some dashlets
SVN:trunk[2050]
2012-05-29 11:57:57 +00:00
Denis Flaven
79259814f3 Prototype of Pie chart using RaphaelJS / gRaphael
SVN:trunk[2049]
2012-05-29 11:56:20 +00:00
Denis Flaven
264d10ffd7 Starting to use RapahelJS for charts rendering
SVN:trunk[2048]
2012-05-29 11:55:32 +00:00
Romain Quetiez
5dcbdbad95 Compiler and co: defensive programming (computing the delta)
SVN:trunk[2047]
2012-05-29 11:48:24 +00:00
Denis Flaven
00873c1cb9 Fix issue when editing property sheets with subforms
SVN:trunk[2046]
2012-05-29 09:04:32 +00:00
Romain Quetiez
4b34fd4ecd Dashboard: fixed issue with subforms
SVN:trunk[2045]
2012-05-29 09:04:09 +00:00
Romain Quetiez
59e754d8db Dashboard: invoke directly the display blocks (fixes bugs with titles)
SVN:trunk[2044]
2012-05-27 16:37:21 +00:00
Romain Quetiez
4115ddbe13 Dashboard: display the most relevant label in the group by dashlets (eg: hyperlink to an ext key)
SVN:trunk[2043]
2012-05-27 15:33:54 +00:00
Romain Quetiez
479bef14e6 Compiler and Co: fixed bugs in the data model delta computation
SVN:trunk[2042]
2012-05-27 14:03:47 +00:00
Romain Quetiez
e4c113e412 Dashboard: optimized group by (done by MySQL) + proto of a group by on 2 dimensions
SVN:trunk[2041]
2012-05-25 16:04:18 +00:00
Denis Flaven
2c00c115d6 User editable dashboards... implementation in progress
SVN:trunk[2040]
2012-05-25 15:59:24 +00:00
Denis Flaven
a40e902808 User editable dashboards... implementation in progress
SVN:trunk[2039]
2012-05-25 15:58:59 +00:00
Denis Flaven
341a97d2ac User editable dashboards... implementation in progress
SVN:trunk[2038]
2012-05-25 15:58:17 +00:00
Denis Flaven
a970be510b User editable dashboards... implementation in progress
SVN:trunk[2037]
2012-05-25 15:57:55 +00:00
Denis Flaven
e56aac9ef8 User editable dashboards... implementation in progress
SVN:trunk[2036]
2012-05-25 10:22:22 +00:00
Denis Flaven
d6aa4118b0 User editable dashboards... implementation in progress
SVN:trunk[2035]
2012-05-25 10:18:38 +00:00
Denis Flaven
bb7c81a914 Cosmetics
SVN:trunk[2034]
2012-05-25 10:17:51 +00:00
Denis Flaven
0f0a881238 User editable dashboards... implementation in progress
SVN:trunk[2033]
2012-05-25 09:23:10 +00:00
Denis Flaven
f4d2ffcf34 User editable dashboards... implementation in progress
SVN:trunk[2032]
2012-05-25 09:22:29 +00:00
Denis Flaven
11ac749c24 User editable dashboards... implementation in progress
SVN:trunk[2031]
2012-05-25 09:21:50 +00:00
Denis Flaven
d681e58671 User editable dashboards... implementation in progress
SVN:trunk[2030]
2012-05-25 09:20:38 +00:00
Denis Flaven
d71a663208 User editable dashboards... implementation in progress
SVN:trunk[2029]
2012-05-25 09:19:27 +00:00
Denis Flaven
90202ec232 User editable Dashboards: allow several dashlets per cell.
SVN:trunk[2028]
2012-05-25 09:18:31 +00:00
Denis Flaven
f6559d43fc User editable Dashboards: allow several dashlets per cell.
SVN:trunk[2027]
2012-05-25 09:18:08 +00:00
Denis Flaven
5bf31905ac User editable Dashboards: allow several dashlets per cell.
SVN:trunk[2026]
2012-05-25 09:17:36 +00:00
Denis Flaven
7de5315a01 User editable dashboards... implementation in progress
SVN:trunk[2025]
2012-05-24 14:22:01 +00:00
Denis Flaven
536ea2cc21 User editable dashboards... implementation in progress
SVN:trunk[2024]
2012-05-24 14:21:42 +00:00
Denis Flaven
11a107b982 User Editable Dashboards update
SVN:trunk[2023]
2012-05-24 14:20:21 +00:00
Denis Flaven
5d797afbd8 User Editable Dashboards update
SVN:trunk[2022]
2012-05-24 14:19:45 +00:00
Denis Flaven
e540df4de9 User Editable Dashboards update
SVN:trunk[2021]
2012-05-24 14:19:20 +00:00
Romain Quetiez
b31b2a5939 Dashboard: fixed a bug in subforms having the same fields (the last one was submitted even if hidden)
SVN:trunk[2020]
2012-05-24 13:26:53 +00:00
Denis Flaven
a53be218f1 User editable dashboards... implementation in progress
SVN:trunk[2019]
2012-05-24 08:37:15 +00:00
Denis Flaven
ae22563177 User editable dashboards... implementation in progress
SVN:trunk[2018]
2012-05-24 08:36:50 +00:00
Denis Flaven
c87368ff42 User editable dashboards... implementation in progress
SVN:trunk[2017]
2012-05-24 08:36:29 +00:00
Denis Flaven
b2bf8df779 User editable dashboards... implementation in progress
SVN:trunk[2016]
2012-05-24 08:33:55 +00:00
Denis Flaven
debd0cf5bd User editable dashboards... implementation in progress
SVN:trunk[2015]
2012-05-24 08:33:28 +00:00
Romain Quetiez
28a7930a13 Dashboard: transformed a few overviews into customizable dashboards
SVN:trunk[2014]
2012-05-23 10:35:53 +00:00
Denis Flaven
ef453537d3 User editable dashboards... implementation in progress
SVN:trunk[2013]
2012-05-23 10:16:08 +00:00
Denis Flaven
f44a701d12 User editable dashboards... implementation in progress
SVN:trunk[2012]
2012-05-23 10:15:59 +00:00
Denis Flaven
08350f7c65 User editable dashboards... implementation in progress
SVN:trunk[2011]
2012-05-23 10:15:39 +00:00
Denis Flaven
25ff0e064c User editable dashboards... implementation in progress
SVN:trunk[2010]
2012-05-23 08:06:20 +00:00
Denis Flaven
050a4813be User editable dashboards... implementation in progress
SVN:trunk[2009]
2012-05-23 08:06:04 +00:00
Denis Flaven
1c7d6348c6 User editable dashboards... implementation in progress
SVN:trunk[2008]
2012-05-23 08:05:43 +00:00
Romain Quetiez
b31c54c979 Dashboard: continuing...
SVN:trunk[2007]
2012-05-22 16:17:28 +00:00
Romain Quetiez
7133788518 Dashboard: prerequisites to save/load dashlets
SVN:trunk[2006]
2012-05-22 15:06:17 +00:00
Romain Quetiez
b5576df143 Dashboard: alpha version of dashlets
SVN:trunk[2005]
2012-05-22 11:51:22 +00:00
Denis Flaven
eedb28e97b User editable dashboards... implementation in progress
SVN:trunk[2004]
2012-05-22 09:36:43 +00:00
Denis Flaven
25ddd1c2c8 User editable dashboards... implementation in progress
SVN:trunk[2003]
2012-05-22 09:24:46 +00:00
Denis Flaven
a6e5b0b087 User editable dashboards... implementation in progress
SVN:trunk[2002]
2012-05-22 09:22:43 +00:00
Denis Flaven
907d96b18d User editable dashboards... implementation in progress
SVN:trunk[2001]
2012-05-22 09:22:01 +00:00
Romain Quetiez
9b276405b9 Dashboard: forgot to commit new file
SVN:trunk[2000]
2012-05-22 09:21:47 +00:00
Denis Flaven
fe8a5080cb User editable dashboards... implementation in progress
SVN:trunk[1999]
2012-05-22 09:21:21 +00:00
Romain Quetiez
8e3b2d7da6 Dashboard: read user defined dashboards (setup needed to create new table)
SVN:trunk[1998]
2012-05-21 15:11:30 +00:00
Denis Flaven
c47b2b57ec User editable dashboards... implementation in progress
SVN:trunk[1997]
2012-05-21 13:52:12 +00:00
Denis Flaven
1e605c43b0 User editable dashboards... implementation in progress
SVN:trunk[1996]
2012-05-21 13:48:45 +00:00
Denis Flaven
3121cf602a User editable dashboards... implementation in progress
SVN:trunk[1995]
2012-05-21 13:48:23 +00:00
Denis Flaven
c68fbf6cd9 User editable dashboards... implementation in progress
SVN:trunk[1994]
2012-05-21 11:43:25 +00:00
Denis Flaven
508744262c User editable dashboards... implementation in progress
SVN:trunk[1993]
2012-05-21 11:42:37 +00:00
Denis Flaven
214a107792 User editable dashboards... implementation in progress
SVN:trunk[1992]
2012-05-21 11:42:08 +00:00
Romain Quetiez
c7db32f845 Compiler and co: fixed issue with the reset of the APC cache
SVN:trunk[1990]
2012-05-14 15:56:06 +00:00
Romain Quetiez
719870663a Compiler and co: fixed bug with 'is_null_allowed' missing
SVN:trunk[1989]
2012-05-14 14:19:17 +00:00
Romain Quetiez
7487a46888 Compiler and co: create a model.x file as soon as there are some menus OR classes
SVN:trunk[1988]
2012-05-14 13:19:02 +00:00
Denis Flaven
a4034bbaca Let the creation of profiles adapt automatically when some classes do not exist...
SVN:trunk[1987]
2012-05-14 12:44:56 +00:00
Romain Quetiez
33a8a594f8 Compiler and co: fixed issue with the deletion (read a delta)
SVN:trunk[1986]
2012-05-14 12:19:11 +00:00
Romain Quetiez
c009599ff8 Compiler and co: implicit naming and reconciliation scheme
SVN:trunk[1985]
2012-05-14 10:03:22 +00:00
Denis Flaven
014d25bcda Typo in a comment !
SVN:trunk[1984]
2012-05-14 07:12:17 +00:00
Romain Quetiez
1b686a3164 Compiler and co: added optional parameters for the attributes
SVN:trunk[1983]
2012-05-11 11:47:47 +00:00
Romain Quetiez
39be3b449e Fixed setup issues (improved modularity)
SVN:trunk[1982]
2012-05-10 09:30:48 +00:00
Denis Flaven
fb8d93319d Implementation and integration...
SVN:trunk[1981]
2012-05-10 09:06:36 +00:00
Denis Flaven
d27ab4128c Support several successive renaming of the same object
SVN:trunk[1980]
2012-05-09 09:03:13 +00:00
Denis Flaven
c4ffbf1798 Added capability to store/load the DOM in one go !
SVN:trunk[1979]
2012-05-07 09:10:32 +00:00
Denis Flaven
44c2beff51 Make GetConfig independent of the MetaModel
SVN:trunk[1978]
2012-05-07 09:09:25 +00:00
Denis Flaven
9c633bf0b8 Named tab containers instead of non-unique numbering !
SVN:trunk[1977]
2012-05-03 14:36:19 +00:00
Denis Flaven
793e0be473 Faster FindNode
SVN:trunk[1976]
2012-05-03 14:33:08 +00:00
Denis Flaven
3e3195d6cf Implementation on-going.
SVN:trunk[1975]
2012-04-27 12:43:16 +00:00
Romain Quetiez
dcecbee7d0 Compiler and co: fixed issue on the interpretation of the menus
SVN:trunk[1974]
2012-04-27 12:37:51 +00:00
Denis Flaven
dfc02ccb77 Bug fixes: add the namespace definition to the delta and cope with empty deltas
SVN:trunk[1973]
2012-04-25 12:20:32 +00:00
Romain Quetiez
554ececf51 Compiler qnd co: Added _delta="define" for the menus
SVN:trunk[1972]
2012-04-25 06:44:44 +00:00
Romain Quetiez
f9d5b88c06 Reviewed ModelFactory and the compiler
SVN:trunk[1971]
2012-04-24 16:06:11 +00:00
Denis Flaven
0dfbc28577 ModelFactory debugging
SVN:trunk[1970]
2012-04-24 12:55:02 +00:00
Denis Flaven
e0e7a2d71e added GetChildClasses
SVN:trunk[1969]
2012-04-23 10:03:20 +00:00
Denis Flaven
cc4f9800c7 New version of jQuery
SVN:trunk[1968]
2012-04-23 09:27:42 +00:00
Denis Flaven
f9cbd7d81a New version of jQuery
SVN:trunk[1967]
2012-04-23 09:25:32 +00:00
Denis Flaven
f1ae18afcf New version of jQuery
SVN:trunk[1966]
2012-04-23 09:23:40 +00:00
Denis Flaven
dc3dc9e904 Added high-level methods for class and fields processing
SVN:trunk[1965]
2012-04-20 16:25:55 +00:00
Romain Quetiez
2f4cbcac25 Compiler and co: fixed issue on delta load (delete then merge a node)
SVN:trunk[1964]
2012-04-19 14:37:41 +00:00
Romain Quetiez
65e55e0927 Compiler and co: reorganized the code (still some cleanup to perform)
SVN:trunk[1963]
2012-04-19 13:48:02 +00:00
Denis Flaven
d41e77823f Protects against too long string when logging web services events
SVN:trunk[1960]
2012-04-19 11:15:04 +00:00
Denis Flaven
2d51bf9bc1 Bug fix for layout panes
SVN:trunk[1959]
2012-04-19 07:52:42 +00:00
Denis Flaven
78cfb3229d New version of jQuery Layout
SVN:trunk[1958]
2012-04-19 07:51:51 +00:00
Denis Flaven
048fafca08 ModelFactory integration
SVN:trunk[1957]
2012-04-19 07:51:10 +00:00
Romain Quetiez
d0b201e73f Compiler and co: improved the robustness of xml datamodel
SVN:trunk[1956]
2012-04-18 14:57:57 +00:00
Romain Quetiez
60d63839f6 Compiler: reviewed the xml format to facilitate generalization of operations (e.g. compute a delta)
SVN:trunk[1955]
2012-04-15 08:35:31 +00:00
Romain Quetiez
b6ac94a8f1 Fixed cosmetic issue in the error reporting on OQL normalization (using a tree operator on a NON hierarchical key)
SVN:trunk[1954]
2012-04-13 07:54:25 +00:00
Romain Quetiez
46e3eac149 Setup: added detailed reporting on the data model validation issues
SVN:trunk[1953]
2012-04-11 15:21:08 +00:00
Romain Quetiez
6826e132a8 Compiler: clearer reporting on XML format issues + additional validation by the mean of an XSD (same reporting chanel). The validation provided by the XSD is not strict, but this is a good starting point.
SVN:trunk[1952]
2012-04-11 14:54:51 +00:00
Romain Quetiez
11c85d7710 Compiler: new format, checkable by the mean of a XSD (will come soon)
SVN:trunk[1951]
2012-04-11 14:06:11 +00:00
Romain Quetiez
d0025358da Fixed regression in the setup (due to a recent change in NiceWebPage)
SVN:trunk[1950]
2012-04-06 15:32:29 +00:00
Romain Quetiez
a7b7264903 Compiler: added the auto_load option + fixed issue with parent menu declared in another module
SVN:trunk[1949]
2012-04-06 15:09:18 +00:00
Romain Quetiez
da2abef030 When there is still no dictionary available, the menus / classes / attributes have a default label based on their raw names (replacing _ by a blank)
SVN:trunk[1948]
2012-04-06 13:25:32 +00:00
Romain Quetiez
3a841269ec #541 Fixed bug in the export for spreadsheet (time format)
SVN:trunk[1947]
2012-04-06 09:51:25 +00:00
Romain Quetiez
1241592fee Compiler: added the flag enable_admin_only for the menus
SVN:trunk[1944]
2012-04-05 09:16:38 +00:00
Romain Quetiez
885d5ecf9f Compiler: compile menus (updated the reference datamodel)
SVN:trunk[1943]
2012-04-05 08:57:05 +00:00
Denis Flaven
3205d48f84 Properly log-off (and report the issue in the log) in case we fail to create a user during the CAS Synchro
SVN:trunk[1940]
2012-04-04 09:28:13 +00:00
Romain Quetiez
c354fecad4 Updated schema.php to add web link to link class on linked set attributes
SVN:trunk[1939]
2012-04-03 11:29:48 +00:00
Romain Quetiez
009a91ab59 Itop design: added an intermediate root node above 'classes'
SVN:trunk[1936]
2012-04-03 10:09:52 +00:00
Romain Quetiez
7ef5c8ddd7 Compiler: allow optional zlists
SVN:trunk[1935]
2012-04-03 10:07:38 +00:00
Romain Quetiez
a2affcc224 #540 Data synchro: the option "write if empty" was not implemented
SVN:trunk[1934]
2012-03-29 13:34:46 +00:00
Denis Flaven
529b5d53fc Sorting of lists: sort is now always executed server-side via the mechanism used by paginated lists. This fixes Trac #411, #421, #520
SVN:trunk[1931]
2012-03-27 15:27:15 +00:00
Denis Flaven
144b5ae39f Bug fix: to do not try to access a DataSource while it's being deleted
SVN:trunk[1928]
2012-03-27 11:22:50 +00:00
Romain Quetiez
3679e7f8d5 Internal: reworked default ordering (added DBObjectSet::GetRealSortOrder)
SVN:trunk[1927]
2012-03-23 14:24:06 +00:00
Denis Flaven
645802ed32 Bug fix: support [+] button inside linkedsets.. with constraints
SVN:trunk[1925]
2012-03-22 17:15:53 +00:00
Romain Quetiez
38461b7be6 Compiler : fixed cosmetic issue
SVN:trunk[1924]
2012-03-22 16:25:36 +00:00
Romain Quetiez
c1ee1d4e8a Cleanup in data model XML files
SVN:trunk[1923]
2012-03-22 16:24:16 +00:00
Denis Flaven
66e1890b27 CAS integration:
- regression fix: support patterns for the MemberOf groups filtering
- activate/de-activate the profiles synchronization using the 'cas_update_profiles' configuration flag
- provide default profile(s) when creating a new user from CAS, only if no match is found for assigning profiles from the CAS MemberOf group(s).

SVN:trunk[1922]
2012-03-22 15:35:46 +00:00
Denis Flaven
a4fd923759 Bug fix: support [+] button inside linkedsets.. with constraints
SVN:trunk[1916]
2012-03-22 10:04:16 +00:00
Denis Flaven
2533e2eca3 Bug fix: support [+] button inside linkedsets.. with constraints
SVN:trunk[1915]
2012-03-22 10:02:25 +00:00
Denis Flaven
c96952a369 The date picker fills the "time" part of the field with 00:00:00 when picking a DateTime instead of just a Date.
SVN:trunk[1911]
2012-03-21 10:58:43 +00:00
Denis Flaven
94b6730697 Bug fix: support [+] button inside linkedsets
SVN:trunk[1909]
2012-03-21 10:39:15 +00:00
Denis Flaven
1cc2884541 Allow to specify the name of the PDF to download
SVN:trunk[1906]
2012-03-20 17:01:11 +00:00
Romain Quetiez
b37414df59 MetaModel: by default, show most recent tickets first
SVN:trunk[1903]
2012-03-20 15:40:50 +00:00
Romain Quetiez
eb5475cf3f Formatted XML files (line breaks and indentation)
SVN:trunk[1902]
2012-03-20 15:22:44 +00:00
Romain Quetiez
ef50592a8d Allow blanks (line breaks and indentation) in the XML model files
SVN:trunk[1901]
2012-03-20 11:08:42 +00:00
Romain Quetiez
18a28df55f #520 Capability to define a default sort order (PHP/XML)
SVN:trunk[1900]
2012-03-20 10:52:11 +00:00
Denis Flaven
be4ac95549 LinkedSets can be read-only too...
SVN:trunk[1899]
2012-03-19 17:21:11 +00:00
Romain Quetiez
619ce5f130 #421 Sort IP addresses on INET_ATON (API only, see #520 to have this as the default sort order for NW Interfaces)
SVN:trunk[1897]
2012-03-19 08:28:04 +00:00
Denis Flaven
18b234c200 Make the class "TriggerOnPortalUpdate" importable
SVN:trunk[1895]
2012-03-16 12:56:12 +00:00
Romain Quetiez
35fe13fb52 #467 Friendly names not up to date when sending notifications
SVN:trunk[1894]
2012-03-14 16:45:50 +00:00
Denis Flaven
794c111ee1 Added detecting of missing columns in the synchro_data_xxx tables (in case of duplicate SQL column names in the orignal data model). See Trac #503.
SVN:trunk[1893]
2012-03-14 16:41:32 +00:00
Romain Quetiez
6570b6a250 #527 Typo: license get an S in the US
SVN:trunk[1890]
2012-03-14 08:53:28 +00:00
Romain Quetiez
78ec612ba0 Dictionary cleanup: removed all entries related to the menu 'ConfigurationItemsMenu' which has been removed a long time ago
SVN:trunk[1889]
2012-03-14 08:47:30 +00:00
Romain Quetiez
de915644a2 #528 Typo: criticALity
SVN:trunk[1888]
2012-03-14 08:39:34 +00:00
Denis Flaven
9c4ef578f6 On going implementation of the designer
SVN:trunk[1887]
2012-03-09 08:45:12 +00:00
Denis Flaven
73e715efb4 Protect against a non-existent "MapContextParams" function
SVN:trunk[1886]
2012-03-09 08:44:06 +00:00
Denis Flaven
2c49d1f96f Restore the previous URLMaker in case the sending of a notification is not the last action of a page... (e.g. if the page displays the details of an object after sending the notifications...)
SVN:trunk[1885]
2012-03-07 18:57:54 +00:00
Denis Flaven
97647c5a5a Portal fixes (relative URLs and parameter validation)...
SVN:trunk[1884]
2012-03-07 18:55:16 +00:00
Denis Flaven
114adc3b0b Experimental support of PDF output for iTop pages, provided that mPDF is installed in lib/MPDF [1876]
SVN:trunk[1883]
2012-03-07 18:54:03 +00:00
Denis Flaven
166102da5f Update to the Italian dictionary (thanks Laura !)
SVN:trunk[1882]
2012-03-07 18:51:50 +00:00
Denis Flaven
57bb66fe41 Typo
SVN:trunk[1881]
2012-03-07 18:50:58 +00:00
Denis Flaven
4a16a76e37 - Current block Id not passed to the chart ?
SVN:trunk[1880]
2012-03-07 18:48:15 +00:00
Denis Flaven
52ef62258b Small fix for genericity
SVN:trunk[1879]
2012-03-07 18:42:15 +00:00
Denis Flaven
475f11e4bc Experimental support of PDF output for iTop pages, provided that mPDF is installed in lib/MPDF [1876]
SVN:trunk[1878]
2012-03-07 18:35:57 +00:00
Denis Flaven
1de3672173 - Current block Id not passed to the chart ? [1871]
SVN:trunk[1877]
2012-03-07 17:31:56 +00:00
Denis Flaven
0d6244b9eb On going implementation of the designer
SVN:trunk[1870]
2012-03-02 15:52:21 +00:00
Denis Flaven
453da6be38 - Bug fix: the hierarchical key in Organizations is not always named 'parent_id' !
SVN:trunk[1869]
2012-02-27 16:25:14 +00:00
Denis Flaven
22acd83abb Make sure that the creation of the data_synchro_xxx tables uses the utf8 charset and collation and the same DB Engine as the rest of the database.
SVN:trunk[1867]
2012-02-23 13:59:45 +00:00
Denis Flaven
b172c53aad Typo!
SVN:trunk[1863]
2012-02-21 14:05:23 +00:00
Denis Flaven
ff6ccacf9f Added the ability to Find then Remove a tab inside a page
SVN:trunk[1861]
2012-02-20 17:17:53 +00:00
Romain Quetiez
b3dadcba77 Reintegrated from branch 1.2: capability to develop a module to share objects between organizations (beta)
SVN:trunk[1859]
2012-02-17 15:11:00 +00:00
Denis Flaven
92c1a4af63 Delay the storage of the dictionary in the cache to allow for its alteration during the initialization of the classes
SVN:trunk[1857]
2012-02-17 12:50:22 +00:00
Denis Flaven
60fb1f1126 Distinguish between creation and modification rights
SVN:trunk[1855]
2012-02-17 12:46:34 +00:00
Denis Flaven
fe7f65893e Allow more than 64K for the email content (including attachments)
SVN:trunk[1851]
2012-02-10 12:31:02 +00:00
Romain Quetiez
d4816ddc51 Implemented the capability to modify queries by the mean of a plugin (reintegrated from branch 1.2, release candidate)
SVN:trunk[1849]
2012-02-08 15:16:16 +00:00
Denis Flaven
20077a4c3a Update to the Brazilian translation, thanks to Marco Tulio
SVN:trunk[1835]
2012-01-31 17:50:01 +00:00
Denis Flaven
392344dcd8 (Tried to) improve the display of the Synchronization Tooltip that "sometimes" does not work on IE 8...
SVN:trunk[1833]
2012-01-31 17:37:47 +00:00
Denis Flaven
137f712a96 Removed a (useless) hardcoded reference to FunctionalCI
SVN:trunk[1831]
2012-01-31 15:46:18 +00:00
Denis Flaven
429cd73e99 SQL Block with parameters were always displayed as table, whatever their type...
SVN:trunk[1829]
2012-01-31 15:08:48 +00:00
Denis Flaven
3a5f73fe65 Fixed typos during the copy/paste for automatic account creation
SVN:trunk[1827]
2012-01-31 12:44:16 +00:00
Denis Flaven
2645194f51 Typo !
SVN:trunk[1824]
2012-01-30 17:53:21 +00:00
Denis Flaven
b97066dad7 Make the OQLQuery class import-able
SVN:trunk[1823]
2012-01-30 17:52:16 +00:00
Denis Flaven
b7e5e7b1da Put some default reconciliation keys to ease the use of CSV import
SVN:trunk[1821]
2012-01-30 17:44:56 +00:00
Denis Flaven
a8f53294a9 Protect against empty list of reconciliation keys
SVN:trunk[1819]
2012-01-30 17:41:01 +00:00
Romain Quetiez
17f3318d63 Export for spreadsheets: transform keys (id of the queried object or external keys) into the corresponding friendly name (reintegrated from branch 1.2.1)
SVN:trunk[1816]
2012-01-30 11:09:07 +00:00
Romain Quetiez
cc1dc4a938 Object details: log always displayed AFTER plugin data (reintegrated from branch)
SVN:trunk[1813]
2012-01-27 13:56:00 +00:00
Denis Flaven
20b7944118 Fixed Trac#518: properly pass the context (i.e. org_id) to the auto-refresh lists
SVN:trunk[1811]
2012-01-25 10:31:03 +00:00
Denis Flaven
f0b3ec77da GetIcon now returns an absolute URL...
SVN:trunk[1808]
2012-01-24 17:38:41 +00:00
Denis Flaven
3c46ac9011 Added the ability to display a custom welcome/disclaimer message at the bottom of the login form.
SVN:trunk[1806]
2012-01-24 15:20:47 +00:00
Denis Flaven
ea1193b90f Fix for Trac#519 - change password bug !
SVN:trunk[1805]
2012-01-24 15:11:20 +00:00
Romain Quetiez
8b493177e6 Improved the check on data model consistency: detection of SQL columns used by two attributes (reintegrated from branch 1.2)
SVN:trunk[1803]
2012-01-24 13:09:41 +00:00
Romain Quetiez
e9f707e480 Improved the check on data model consistency: detection of SQL columns used by two attributes
SVN:trunk[1802]
2012-01-24 13:07:05 +00:00
Romain Quetiez
104959f9ff Fixed cosmetic bug in the tool to test queries (issue with double quote in the queries)
SVN:trunk[1798]
2012-01-23 08:34:17 +00:00
Romain Quetiez
e90e35dbc6 Optimized memory usage when auditing large volumes of CIs (10'000 items was requiring 200 Mb, it now runs with 32 Mb -including the 30Mb overhead!)
SVN:trunk[1797]
2012-01-20 15:26:45 +00:00
Denis Flaven
68ecf03f19 Properly initialize source_dir (and target_dir) to avoid scanning the complete subdirectory, which causes a duplicate load of the modules files in case of an upgrade and thus causes the upgrade to fail.
SVN:trunk[1792]
2012-01-19 16:39:43 +00:00
Romain Quetiez
afc6efe91c Case log now largely bigger than 64 Kilobytes...
SVN:trunk[1790]
2012-01-19 15:29:05 +00:00
Romain Quetiez
9fce2da012 Fixed regression in the tool to test queries: losing the query when there is a syntax error
+ Improved the reporting in case of OQL syntax error, and if the fix is straightforward

SVN:trunk[1789]
2012-01-19 10:19:09 +00:00
Romain Quetiez
5767d14c3e #516 and #517 Improved the export (specify fields for multi-column queries) and web queries (default field list) (reintegrated from branch 1.2)
SVN:trunk[1787]
2012-01-18 17:09:53 +00:00
Romain Quetiez
a401212e31 Updated the setup to fetch modules from 'datamodel'
SVN:trunk[1784]
2012-01-17 16:53:59 +00:00
Romain Quetiez
5510b3964d Moved modules to datamodel
SVN:trunk[1783]
2012-01-17 16:38:25 +00:00
Romain Quetiez
5e01e0659e Dehardcoded the datamodel source files from the subdirectory "modules" - done!
SVN:trunk[1782]
2012-01-17 16:26:52 +00:00
Romain Quetiez
1da33effd8 Dehardcoded the datamodel source files from the subdirectory "modules" - updated the compiler to take this into account
SVN:trunk[1781]
2012-01-17 16:13:12 +00:00
Romain Quetiez
f253b10ddd Dehardcoded the datamodel source files from the subdirectory "modules"
SVN:trunk[1780]
2012-01-17 15:54:48 +00:00
Romain Quetiez
39de3e9af7 De-hardcoded against the subdirectory "modules" (excepted the data model sources files)
SVN:trunk[1779]
2012-01-17 15:31:51 +00:00
Denis Flaven
851db98745 Added support for environment switching
SVN:trunk[1777]
2012-01-17 15:09:30 +00:00
Denis Flaven
123bac0e56 Added support for the 'replaced' operation which is useful for display lists
SVN:trunk[1776]
2012-01-17 15:08:17 +00:00
Denis Flaven
e888b11fff Make sure that we reference the config file using an absolute path... just in case...
SVN:trunk[1775]
2012-01-17 14:58:07 +00:00
Romain Quetiez
8dc13026a0 Environments: banner displaying a message if env is NOT 'production' + factorized some code to create/update a config from the page parameters
SVN:trunk[1774]
2012-01-16 16:41:18 +00:00
Romain Quetiez
2931a0fd42 Added 'is_link' to the definition of classes
SVN:trunk[1773]
2012-01-13 15:32:36 +00:00
Romain Quetiez
1392983a92 Compiler: added flag 'mandatory'
SVN:trunk[1772]
2012-01-13 13:37:29 +00:00
Romain Quetiez
b6b9ef35e9 Fixed issue with flags in modules
SVN:trunk[1771]
2012-01-13 13:36:39 +00:00
Denis Flaven
989f649405 Support pages that do not track tabs selection in their history
SVN:trunk[1770]
2012-01-13 11:06:34 +00:00
Romain Quetiez
01bae34a90 Ignore the conf dir in SVN
SVN:trunk[1769]
2012-01-13 10:52:44 +00:00
Romain Quetiez
c7c69660bc Setup: the conf dir is created by the setup program
SVN:trunk[1768]
2012-01-13 10:49:43 +00:00
Romain Quetiez
da4f475751 Bidouilling... to have the checkout create the empty "conf" directory
SVN:trunk[1767]
2012-01-13 09:52:51 +00:00
Romain Quetiez
a362494354 Fixed cosmetic bug (class icon - loss of styles)
SVN:trunk[1766]
2012-01-13 09:19:12 +00:00
Romain Quetiez
64e5d57ac3 Finalized the setup (missing files)
SVN:trunk[1765]
2012-01-13 09:11:10 +00:00
Romain Quetiez
cb5f09cbf2 Modules replaced by the brand new datamodel source (xml) - known issue : the flags are not correct (or not correctly interpreted in the compilation)
SVN:trunk[1764]
2012-01-12 17:15:27 +00:00
Denis Flaven
780fb6dc27 Fixed absolute/relative path issues in the JS and href places
SVN:trunk[1763]
2012-01-12 16:58:26 +00:00
Romain Quetiez
87bf09995d Updated session mechanisms: itop_env defaults to 'production', and adapted the setup to create the configuration
SVN:trunk[1762]
2012-01-12 14:46:49 +00:00
Romain Quetiez
63f4ec3f82 Factorized the tools to create/update a given installation (environment)
SVN:trunk[1761]
2012-01-11 16:06:23 +00:00
Denis Flaven
308ec94f8c Added self-registering / user synchronization extensibility
SVN:trunk[1759]
2012-01-11 15:59:29 +00:00
Romain Quetiez
6a9ea25b27 Setup based on either compiled modules or xml datamodel files (or both).
SetupWebPage is an alias for ModuleDiscovery and the module files should be updated progressively to invoke ModuleDiscovery::AddModule() instead.
The implementation of the module still assumes they are in the directory 'modules'... this has to be changed later to ensure the distinction between the source modules and the executed modules

SVN:trunk[1758]
2012-01-11 15:04:15 +00:00
Denis Flaven
f29d673ffb Added self-registering / user synchronization extensibility
SVN:trunk[1756]
2012-01-11 11:17:08 +00:00
Denis Flaven
cf65b58981 Make sure that the path/href base is correct to display the page (images, CSS...)
SVN:trunk[1755]
2012-01-09 08:57:48 +00:00
Denis Flaven
8f8f575c65 Bug fix: use the proper way to set the message-ID with Swift
SVN:trunk[1754]
2012-01-04 10:45:58 +00:00
Romain Quetiez
79509e5510 Readme updated, ready for 1.2.1 beta
SVN:trunk[1751]
2011-12-22 09:44:47 +00:00
Romain Quetiez
e147d54404 #512 Allow the CLI mode for export.php
SVN:trunk[1749]
2011-12-21 15:21:58 +00:00
Denis Flaven
5976862a3c Updated the readme file to prepare for the 1.2.1 beta release
SVN:trunk[1748]
2011-12-21 15:15:00 +00:00
Denis Flaven
b2d7c51e6d Fixed Trac #480: properly take into account the 'min_autocomplete_chars' settings.
SVN:trunk[1744]
2011-12-21 14:20:24 +00:00
Denis Flaven
248d4d3e10 Allow to filter which class(es) of objects are displayed in the graphical impact analysis view
SVN:trunk[1741]
2011-12-21 14:09:28 +00:00
Romain Quetiez
32dd31b69d #509 Error in the display of a replica not yet synchronized
SVN:trunk[1739]
2011-12-21 13:34:42 +00:00
Denis Flaven
263594bc4a Protects against a JS error while the synchro is running in chunk mode
SVN:trunk[1738]
2011-12-21 13:12:54 +00:00
Romain Quetiez
e07912af34 CSV import (both std and related to the data synchro) can be used with a tab (keyword 'tab', case insensitive)
SVN:trunk[1737]
2011-12-21 12:51:08 +00:00
Romain Quetiez
ea2843f04e Synchro: fixed issue in the counter (deletion of replicas) and improved the behavior in case of an error happening in the underlying process
SVN:trunk[1736]
2011-12-21 12:11:56 +00:00
Romain Quetiez
4869d5e92b Fixed typos in dictionaries
SVN:trunk[1735]
2011-12-21 11:14:53 +00:00
Romain Quetiez
d8fc264adf Synchro: need to order the replicas by id for the split algorithm to work fine
SVN:trunk[1734]
2011-12-21 10:03:23 +00:00
Romain Quetiez
72ac150faa Synchro: keep track of the memory usage peak (SynchroLog), should the synchro be executed at once or in several steps
SVN:trunk[1733]
2011-12-21 08:52:14 +00:00
Romain Quetiez
9ac4f84e22 #489 Run the synchro with parameter max_chunk_size to split the execution into several processes, each loading at most the given count of replica (note: the master process will continue to run while child processes are being forked one by one)
SVN:trunk[1732]
2011-12-20 15:55:07 +00:00
Denis Flaven
d74f562dba Allow a module to restrict the access to a given menu/group by redeclaring the menu with restricted rights. All rights are combined with the AND operator.
SVN:trunk[1731]
2011-12-20 15:26:08 +00:00
Denis Flaven
fe060e30e5 Fixed Trac #486: SQL dashboards limitations
SVN:trunk[1729]
2011-12-20 15:14:10 +00:00
Denis Flaven
3e2d906f0d Bug fix: missing (but useless) parameter in ExpandArgs causes a notice...
SVN:trunk[1727]
2011-12-20 12:39:15 +00:00
Denis Flaven
321e7dc309 Keep the current value iin the search form when reloading the search form for a different (sub)class. For example Contact => Person.
SVN:trunk[1725]
2011-12-19 17:27:24 +00:00
Denis Flaven
123f5c4cf5 When searching objects to add to the current object (n:n relationship), set the default search params in order to stay in the current silo.
SVN:trunk[1723]
2011-12-19 16:28:09 +00:00
Denis Flaven
0563b05b03 Bug fix: apply the AllowedValues constraints(as default values) when selecting elements via the "magnifier" button or creating an new element via the "plus" button... also make sure that allowed values is enforced
SVN:trunk[1720]
2011-12-15 10:41:01 +00:00
Romain Quetiez
701d49f70d Fixed regression in the setup: query class must be included in the default configuration
SVN:trunk[1718]
2011-12-14 17:40:26 +00:00
Romain Quetiez
90f3aa0c9d Forgot new file on previous commit
SVN:trunk[1717]
2011-12-14 17:32:48 +00:00
Romain Quetiez
ea10d202a6 #485 Improved the end-user experience with Excel and the web queries (added a phrasebook) + link to test the OQL attributes (query phrasebook or email actions, etc.) including the handlink of query arguments) + fixed wrong prototypes for a few implementations of GetBareProperties()
SVN:trunk[1716]
2011-12-14 17:23:01 +00:00
Denis Flaven
ec19ef982e Bug fix: apply the AllowedValues constraints(as default values) when selecting elements via the "magnifier" button or creating an new element via the "plus" button.
SVN:trunk[1714]
2011-12-13 14:37:31 +00:00
Romain Quetiez
487aa95ae2 Ooops...
SVN:trunk[1712]
2011-12-13 14:25:29 +00:00
Romain Quetiez
9197c6a17c #485 Export for MS Excel web queries: format=spreadsheet
SVN:trunk[1711]
2011-12-13 14:21:13 +00:00
Denis Flaven
8231420c44 - New way to handle sessions compatible with multiple environments
SVN:trunk[1710]
2011-12-08 15:37:48 +00:00
Denis Flaven
bb3d08b8a3 Make the data model load even without Contacts !
SVN:trunk[1709]
2011-12-08 15:31:10 +00:00
Denis Flaven
8656611dc2 Bug fix: paginated lists were broken in the "List" tab of the Impact Analysis
SVN:trunk[1707]
2011-12-07 10:38:05 +00:00
Romain Quetiez
6cb052043a Added a test for searching on SOAP
SVN:trunk[1706]
2011-12-06 14:29:56 +00:00
Denis Flaven
02b34515f3 Pass the current object to GetEditValue
SVN:trunk[1704]
2011-12-02 16:26:23 +00:00
Denis Flaven
d081d1f4df Pass the current objet to GetAsHTML
SVN:trunk[1703]
2011-12-02 16:16:53 +00:00
Denis Flaven
c16dce00ae - Bug ! Incorrectly appending a parameter ?version= to linked scripts already containing a parameter in their URL !
- changed the parameter name to itopversion to avoid collisions

SVN:trunk[1701]
2011-12-01 17:15:52 +00:00
Denis Flaven
99c4128ee6 Increased Suhosin minimum value for get_max_value to 2048 due to a bug seen on some installations
SVN:trunk[1699]
2011-11-30 13:21:38 +00:00
Denis Flaven
f17f4e1f78 Use the default language when creating a new user from CAS
SVN:trunk[1697]
2011-11-29 15:54:53 +00:00
Denis Flaven
a94ccb9091 Support patterns for the definition of casMemberOf groups.
SVN:trunk[1695]
2011-11-29 15:31:04 +00:00
Denis Flaven
32b312c871 Allow to log entries on behalf of another user
SVN:trunk[1693]
2011-11-24 16:17:09 +00:00
Denis Flaven
c1fce0d68a Implementing the use of GetDisplayValue for editing attributes
SVN:trunk[1692]
2011-11-23 15:51:59 +00:00
Romain Quetiez
b0f21fd656 In french, organiZation takes an S
SVN:trunk[1690]
2011-11-23 15:28:48 +00:00
Denis Flaven
e30bfb53c6 - Don't create warnings for empty ext keys (i.e. empty string)
- Properly record multiple warnings
- Don't record warnings in case of creation error (error has precedence)

SVN:trunk[1688]
2011-11-23 09:51:57 +00:00
Romain Quetiez
7a83237f11 Cosmetic on the API of the dictionnary (internal)
SVN:trunk[1687]
2011-11-21 17:07:58 +00:00
Denis Flaven
22f79b7d99 Properly handle restrictions (e.g. AllowedValues) on objects that are used as n:n relationships managed via LinksWidget.
SVN:trunk[1684]
2011-11-21 13:00:36 +00:00
Denis Flaven
7658f5dba5 Properly handle restrictions (e.g. AllowedValues) on objects that are used as n:n relationships managed via LinksWidget.
SVN:trunk[1682]
2011-11-21 10:40:00 +00:00
Denis Flaven
0c2390cce6 New type of attribute: percentage
SVN:trunk[1681]
2011-11-18 17:01:11 +00:00
Denis Flaven
bac970b5c7 New type of attribute: percentage
SVN:trunk[1680]
2011-11-18 16:53:17 +00:00
Denis Flaven
9f186f3b60 New type of attribute: percentage
SVN:trunk[1679]
2011-11-18 16:44:08 +00:00
Denis Flaven
e1dcdddebf Also improved the search/display of replicas
SVN:trunk[1677]
2011-11-18 11:33:27 +00:00
Denis Flaven
ee6c2fcffe Enhancement: when an (optional) external key cannot be reconciled, log a warning on the replica. the replicas containing a warning are then processed everytime in case the ext key changes
Also improved the search/display of replicas

SVN:trunk[1676]
2011-11-18 11:32:19 +00:00
Denis Flaven
18c9852db3 Enhancement: when an (optional) external key cannot be reconciled, log a warning on the replica. the replicas containing a warning are then processed everytime in case the ext key changes
Also improved the search/display of replicas

SVN:trunk[1675]
2011-11-18 11:29:04 +00:00
Romain Quetiez
a5547d4d26 Fixed bug in change tracking: it was impossible to factorize code creating objects in the background either in the context of an object update or in the context of the application of a stimulus
SVN:trunk[1673]
2011-11-18 10:43:30 +00:00
Denis Flaven
fb234f3b15 Prevent warnings when checking the available stimuli in the menu...
SVN:trunk[1671]
2011-11-16 17:23:37 +00:00
Denis Flaven
25d29da058 Prevent Javascript errors in case a name contains a quote.
SVN:trunk[1669]
2011-11-15 13:09:38 +00:00
Romain Quetiez
169bf4948b Data sync: fixed issue when a replica left in state 'new' becomes obsolete
SVN:trunk[1668]
2011-11-15 09:29:07 +00:00
Denis Flaven
17ef280b27 - Reload the object after applying a stimulus, in case an action has an effect on an external field...
SVN:trunk[1667]
2011-11-14 10:57:52 +00:00
Denis Flaven
017f767a6e - Reload the object after applying a stimulus, in case an action has an effect on an external field...
SVN:trunk[1666]
2011-11-14 10:54:03 +00:00
Denis Flaven
340ff612cd - Regression from previous fix: don't Reload an object before saving it !
SVN:trunk[1663]
2011-11-10 16:38:06 +00:00
Denis Flaven
3746dad8f0 - Removed some obsolete code that was causing a warning !
SVN:trunk[1661]
2011-11-10 10:23:30 +00:00
Denis Flaven
699f18394b - Don't activate triggers if the transition fails
- Reload the object, in case some custom action changed an external object

SVN:trunk[1660]
2011-11-10 10:20:28 +00:00
Denis Flaven
6963d630d5 Better error handling in case of OQL error
SVN:trunk[1656]
2011-11-09 17:01:42 +00:00
Denis Flaven
5c55291391 Better error handling in case of OQL error
SVN:trunk[1655]
2011-11-09 17:01:12 +00:00
Denis Flaven
48fc735d1f Allow computation of default values when managing relationships
SVN:trunk[1654]
2011-11-06 10:21:58 +00:00
Romain Quetiez
dfe7997f53 Added helpers to export the datamodel
SVN:trunk[1653]
2011-11-04 11:48:42 +00:00
Denis Flaven
53a2f9e527 Added the capability for plug-ins to "listen" to add/remove attachment events.
SVN:trunk[1651]
2011-10-28 12:43:46 +00:00
Denis Flaven
14b86abdbe Allow specifying the label of the sender email
SVN:trunk[1650]
2011-10-28 12:41:20 +00:00
Romain Quetiez
e00c32b867 Fixed SOAP test
SVN:trunk[1648]
2011-10-25 13:03:36 +00:00
Romain Quetiez
7ff6e52ede Internal issue: automated tests generating lots of warning
SVN:trunk[1647]
2011-10-25 12:51:37 +00:00
Romain Quetiez
f8a582b375 New helper class: TemplateString - to allow extended syntaxes such as $this->location_id->org_id->parent_id->name$... to be progressively introduced and replace the heavy ToArgs()
SVN:trunk[1645]
2011-10-24 13:37:37 +00:00
Denis Flaven
1eda4dde56 Bug fix: Trac #494. It seems that PHPSoap does not understand the <wsdl:documentation> tag.
SVN:trunk[1643]
2011-10-21 08:35:41 +00:00
Romain Quetiez
b0373f576c Fixed issue: nobody in the list of persons to notify for portal users (security takes precedence)
SVN:trunk[1642]
2011-10-21 08:10:39 +00:00
Romain Quetiez
ffb3750806 Fixed regressions due to the integration of SwiftMailer
SVN:trunk[1639]
2011-10-20 15:52:38 +00:00
Romain Quetiez
649f7c609b Generation of emails based on the lib SwiftMailer (implements the SMTP protocole)
SVN:trunk[1638]
2011-10-20 15:14:17 +00:00
Romain Quetiez
ff201e8b8a Improved the upgrade/toolkit: make sure that NULL is allowed for SQL columns found in the DB but not defined in the data model (attribute removed or renamed)
SVN:trunk[1637]
2011-10-20 11:45:52 +00:00
Denis Flaven
f979dd40e0 Merged-in the fix for Trac #493: incorrect display of the Users' Grant Matrix
SVN:trunk[1636]
2011-10-17 11:27:03 +00:00
Denis Flaven
32be1aafe0 Better way to keep track that an e,ail is linked to a given iTOp object: message-id + references
SVN:trunk[1633]
2011-10-13 14:43:45 +00:00
Denis Flaven
982c55738f Allow to override some default headers when sending an email
SVN:trunk[1632]
2011-10-13 14:37:36 +00:00
Romain Quetiez
bdebb33822 #488 Replaced mysql extension with mysqli
SVN:trunk[1630]
2011-10-10 15:42:10 +00:00
Denis Flaven
48cb402874 Fixed Trac #487: resizable textareas remained invisible when located on the second tab (Merged from 1.2 branch)
SVN:trunk[1629]
2011-10-04 10:57:09 +00:00
Denis Flaven
c15e4ffb10 Small fix for bulk modify: non-homogeneous fields may be mandatory...
SVN:trunk[1626]
2011-10-03 13:56:49 +00:00
Denis Flaven
cbafb5f1d1 Automatic synchro of CAS users
SVN:trunk[1625]
2011-10-03 13:54:58 +00:00
Denis Flaven
b1d0d039a3 More options for CAS to allow automatic synchro of CAS/LDAP users
SVN:trunk[1624]
2011-10-03 13:51:27 +00:00
Denis Flaven
d252d767b6 Initializes the admin contact's phone number, in case it is a mandatory field in the data model...
SVN:trunk[1622]
2011-09-30 08:01:17 +00:00
Denis Flaven
73968a5631 Prevent crash when trying to load the favicon during the setup !
SVN:trunk[1620]
2011-09-29 15:22:52 +00:00
Denis Flaven
0999f00912 Added a link to a favicon (icon in the browser's bar and tab)
SVN:trunk[1618]
2011-09-29 14:50:23 +00:00
Denis Flaven
6fb3195d6e GetValueLabel is used in some dashboards... make sure that it is available for any attribute
SVN:trunk[1615]
2011-09-29 09:35:26 +00:00
Denis Flaven
6ea3b62b55 Make sure that the organisation's drop-down list is not bigger than the left menu...
SVN:trunk[1614]
2011-09-29 09:32:17 +00:00
880 changed files with 76878 additions and 17749 deletions

View File

@@ -274,7 +274,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI
return true;
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$oNullFilter = new DBObjectSearch($sClass);
return $oNullFilter;

View File

@@ -47,7 +47,7 @@ class UserRightsNull extends UserRightsAddOnAPI
return true;
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$oNullFilter = new DBObjectSearch($sClass);
return $oNullFilter;

File diff suppressed because it is too large Load Diff

View File

@@ -734,7 +734,7 @@ exit;
return true;
}
public function GetSelectFilter($oUser, $sClass)
public function GetSelectFilter($oUser, $sClass, $aSettings = array())
{
$aConditions = array();
foreach ($this->m_aDimensions as $iDimension => $oDimension)

View File

@@ -117,31 +117,38 @@ class ajax_page extends WebPage
<<<EOF
// 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()
if ($.bbq)
{
var state = {};
// Get the id of this tab widget.
var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' );
// This selector will be reused when selecting actual tab widget A elements.
var tab_a_selector = 'ul.ui-tabs-nav a';
// Get the index of this tab.
var idx = $(this).parent().prevAll().length;
// Set the state!
state[ id ] = idx;
$.bbq.pushState( state );
});
// 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 );
});
}
else
{
tabs.tabs();
}
EOF
);
}
@@ -154,7 +161,7 @@ EOF
$container_index = 0;
if (count($m_aTabs) > 0)
{
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$sTabContainerName}\" class=\"light\">\n";
$sTabs .= "<ul>\n";
// Display the unordered list that will be rendered as the tabs
$i = 0;
@@ -300,7 +307,7 @@ EOF
*/
public function add_ready_script($sScript)
{
$this->m_sReadyScript .= $sScript;
$this->m_sReadyScript .= $sScript."\n";
}
/**

View File

@@ -29,6 +29,7 @@ require_once(APPROOT.'/application/displayblock.class.inc.php');
require_once(APPROOT.'/application/sqlblock.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/query.class.inc.php');
//require_once(APPROOT.'/application/menunode.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');

View File

@@ -77,12 +77,16 @@ class ApplicationContext
protected $aValues;
protected static $aDefaultValues; // Cache shared among all instances
public function __construct()
public function __construct($bReadContext = true)
{
$this->aNames = array(
'org_id', 'menu'
);
$this->ReadContext();
if ($bReadContext)
{
$this->ReadContext();
}
}
/**
@@ -94,7 +98,7 @@ class ApplicationContext
if (!isset(self::$aDefaultValues))
{
self::$aDefaultValues = array();
$aContext = utils::ReadParam('c', array());
$aContext = utils::ReadParam('c', array(), false, 'context_param');
foreach($this->aNames as $sName)
{
$sValue = isset($aContext[$sName]) ? $aContext[$sName] : '';
@@ -111,6 +115,7 @@ class ApplicationContext
if (MetaModel::IsValidClass('Organization'))
{
$oSearchFilter = new DBObjectSearch('Organization');
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oSet = new CMDBObjectSet($oSearchFilter);
$iCount = $oSet->Count();
if ($iCount == 1)
@@ -216,17 +221,17 @@ class ApplicationContext
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsWritable())
if (MetaModel::IsValidAttCode($sClass, $sAttCode))
{
$value = $this->GetCurrentValue($key, null);
if (!is_null($value))
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef->IsWritable())
{
$oObj->Set($sAttCode, $value);
$value = $this->GetCurrentValue($key, null);
if (!is_null($value))
{
$oObj->Set($sAttCode, $value);
}
}
}
}
@@ -299,5 +304,58 @@ class ApplicationContext
return '';
}
}
protected static $m_aPluginProperties = null;
/**
* Load plugin properties for the current session
* @return void
*/
protected static function LoadPluginProperties()
{
if (isset($_SESSION['PluginProperties']))
{
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
}
else
{
self::$m_aPluginProperties = array();
}
}
/**
* Set plugin properties
* @param sPluginClass string Class implementing any plugin interface
* @param sProperty string Name of the property
* @param value scalar Value (numeric or string)
* @return void
*/
public static function SetPluginProperty($sPluginClass, $sProperty, $value)
{
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
}
/**
* Get plugin properties
* @param sPluginClass string Class implementing any plugin interface
* @return array of sProperty=>value pairs
*/
public static function GetPluginProperties($sPluginClass)
{
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
{
return self::$m_aPluginProperties[$sPluginClass];
}
else
{
return array();
}
}
}
?>

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010 Combodo SARL
// Copyright (C) 2010-2012 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
@@ -48,4 +48,146 @@ interface iApplicationObjectExtension
public function OnDBDelete($oObject, $oChange = null);
}
?>
/**
* New extension to add menu items in the "popup" menus inside iTop. Provides a greater flexibility than
* iApplicationUIExtension::EnumAllowedActions.
*
* To add some menus into iTop, declare a class that implements this interface, it will be called automatically
* by the application, as long as the class definition is included somewhere in the code
*/
interface iPopupMenuExtension
{
// Possible types of menu into which new items can be added
const MENU_OBJLIST_ACTIONS = 1; // $param is a DBObjectSet containing the list of objects
const MENU_OBJLIST_TOOLKIT = 2; // $param is a DBObjectSet containing the list of objects
const MENU_OBJDETAILS_ACTIONS = 3; // $param is a DBObject instance: the object currently displayed
const MENU_DASHBOARD_ACTIONS = 4; // $param is a Dashboard instance: the dashboard currently displayed
const MENU_USER_ACTIONS = 5; // $param is a null ??
/**
* Get the list of items to be added to a menu. The items will be inserted in the menu in the order of the returned array
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx above
* @param mixed $param Depends on $iMenuId, see the constants defined above
* @return Array An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu
*/
public static function EnumItems($iMenuId, $param);
}
/**
* Each menu items is defined by an instance of an object derived from the class
* ApplicationPopupMenu below
*
*/
abstract class ApplicationPopupMenuItem
{
protected $sUID;
protected $sLabel;
public function __construct($sUID, $sLabel)
{
$this->sUID = $sUID;
$this->sLabel = $sLabel;
}
public function GetUID()
{
return $this->sUID;
}
public function GetLabel()
{
return $this->sLabel;
}
/**
* Returns the components to create a popup menu item in HTML
* @return Hash A hash array: array('label' => , 'url' => , 'target' => , 'onclick' => )
*/
abstract public function GetMenuItem();
public function GetLinkedScripts()
{
return array();
}
}
/**
* Class for adding an item into a popup menu that browses to the given URL
*/
class URLPopupMenuItem extends ApplicationPopupMenuItem
{
protected $sURL;
protected $sTarget;
/**
* Class for adding an item that browses to the given URL
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here
* @param string $sTarget In case the menu is an hyperlink and a specific target is needed (_blank for example), pass it here
*/
public function __construct($sUID, $sLabel, $sURL, $sTarget = '_top')
{
parent::__construct($sUID, $sLabel);
$this->sURL = $sURL;
$this->sTarget = $sTarget;
}
public function GetMenuItem()
{
return array ('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget);
}
}
/**
* Class for adding an item into a popup menu that triggers some Javascript code
*/
class JSPopupMenuItem extends ApplicationPopupMenuItem
{
protected $sJSCode;
protected $aIncludeJSFiles;
/**
* Class for adding an item that triggers some Javascript code
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sJSCode In case the menu consists in executing some havascript code inside the page, pass it here. If supplied $sURL ans $sTarget will be ignored
* @param array $aIncludeJSFiles An array of file URLs to be included (once) to provide some JS libraries for the page.
*/
public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array())
{
parent::__construct($sUID, $sLabel);
$this->sJSCode = $sJSCode;
$this->aIncludeJSFiles = $aIncludeJSFiles;
}
public function GetMenuItem()
{
return array ('label' => $this->GetLabel(), 'onclick' => $this->sJSCode, 'url' => '#');
}
public function GetLinkedScripts()
{
return $this->aIncludeJSFiles;
}
}
/**
* Class for adding a separator (horizontal line, not selectable) the output
* will automatically reduce several consecutive separators to just one
*/
class SeparatorPopupMenuItem extends ApplicationPopupMenuItem
{
/**
* Class for inserting a separator into a popup menu
*/
public function __construct()
{
parent::__construct('', '');
}
public function GetMenuItem()
{
return array ('label' => '<hr class="menu-separator">', 'url' => '');
}
}

View File

@@ -26,7 +26,7 @@
require_once(APPROOT."/application/webpage.class.inc.php");
class CLIPage
class CLIPage implements Page
{
function __construct($s_title)
{

View File

@@ -39,6 +39,7 @@ require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php');
require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php');
require_once(APPROOT.'/application/datatable.class.inc.php');
/**
* All objects to be displayed in the application (either as a list or as details)
@@ -98,7 +99,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
// action menu
$oSingletonFilter = new DBObjectSearch(get_class($this));
$oSingletonFilter->AddCondition('id', $this->GetKey(), '=');
$oBlock = new MenuBlock($oSingletonFilter, 'popup', false);
$oBlock = new MenuBlock($oSingletonFilter, 'details', false);
$oBlock->Display($oPage, -1);
// Master data sources
@@ -185,7 +186,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sTip .= Dict::S('Core:Synchro:LastSynchro').'<br/>'.$aStruct['last_synchro']."</p>";
}
$sSynchroIcon = '&nbsp;<img style="vertical-align:middle;" id="synchro_icon" src="../images/locked.png"/>';
$oPage->add_ready_script("$('#synchro_icon').qtip( { content: '$sTip', show: 'mouseover', hide: 'unfocus', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
$oPage->add_ready_script("$('#synchro_icon').qtip( { content: '$sTip', show: 'mouseover', hide: { fixed: true }, style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
$oPage->add("<div class=\"page_header\"><h1>".$this->GetIcon()."&nbsp;\n");
@@ -208,6 +209,11 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
$aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode);
}
// Special case to display the case log, if any...
// WARNING: if you modify the loop below, also check the corresponding code in UpdateObject and DisplayModifyForm
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
@@ -219,11 +225,6 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
}
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode);
}
return $aFieldsMap;
}
@@ -244,7 +245,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
// Display mode
if (!$oAttDef->IsLinkset()) continue; // Process only linkset attributes...
$oSet = new DBObjectSet($this->Get($sAttCode)->GetFilter());
// $oSet = new DBObjectSet($this->Get($sAttCode)->GetFilter()); // Why do something so useless ?
$oSet = $this->Get($sAttCode);
$iCount = $oSet->Count();
$sCount = '';
if ($iCount != 0)
@@ -252,16 +254,17 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sCount = " ($iCount)";
}
$oPage->SetCurrentTab($oAttDef->GetLabel().$sCount);
if ($bEditMode)
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFlags = $this->GetAttributeFlags($sAttCode);
}
$bReadOnly = ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE));
if ($bEditMode && (!$bReadOnly))
{
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFlags = $this->GetAttributeFlags($sAttCode);
}
$sInputId = $this->m_iFormId.'_'.$sAttCode;
if (get_class($oAttDef) == 'AttributeLinkedSet')
{
@@ -293,7 +296,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
'object_id' => $this->GetKey(),
'menu' => true,
'default' => $aDefaults,
);
'table_id' => $sClass.'_'.$sAttCode,
);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oPage, $sInputId, $aParams);
@@ -308,7 +312,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription().'<span id="busy_'.$sInputId.'"></span>');
$sValue = $this->Get($sAttCode);
$sDisplayValue = $this->GetEditValue($sAttCode);
$sDisplayValue = ''; // not used
$aArgs = array('this' => $this);
$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
$aFieldsMap[$sAttCode] = $sInputId;
@@ -338,7 +342,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
'object_id' => $this->GetKey(),
'menu' => false,
'default' => $aDefaults,
);
'table_id' => $sClass.'_'.$sAttCode,
);
}
else
{
@@ -354,6 +359,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
'view_link' => false,
'menu' => false,
'display_limit' => true, // By default limit the list to speed up the initial load & display
'table_id' => $sClass.'_'.$sAttCode,
);
}
$oPage->p(MetaModel::GetClassIcon($sTargetClass)."&nbsp;".$oAttDef->GetDescription());
@@ -655,12 +661,29 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$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();
$aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array();
$aExtraFields = array();
foreach ($aExtraFieldsRaw as $sFieldName)
{
// Ignore attributes not of the main queried class
if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches))
{
$sClassAlias = $aMatches[1];
$sAttCode = $aMatches[2];
if ($sClassAlias == $oSet->GetFilter()->GetClassAlias())
{
$aExtraFields[] = $sAttCode;
}
}
else
{
$aExtraFields[] = $sFieldName;
}
}
$sHtml = '';
$oAppContext = new ApplicationContext();
$sClassName = $oSet->GetFilter()->GetClass();
$aAttribs = array();
$sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list';
if ($sZListName !== false)
{
@@ -683,9 +706,6 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
}
// Load only the requested columns
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
$oSet->OptimizeColumnLoad(array($sClassAlias => $aList));
if (!empty($sLinkageAttribute))
{
@@ -721,218 +741,45 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
// Then display all the attributes linked to the other end of the relationship
$aList = $aDisplayList;
}
if ($bSelectMode)
{
if (!$bSingleSelectMode)
{
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$iListId}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
}
else
{
$aAttribs['form::select'] = array('label' => "", 'description' => '');
}
}
if ($bViewLink)
{
$aAttribs['key'] = array('label' => MetaModel::GetName($sClassName), 'description' => '');
}
foreach($aList as $sAttCode)
{
$aAttribs[$sAttCode] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode));
}
$aValues = array();
$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
$iMaxObjects = -1;
//if ($bDisplayLimit && $bTruncated)
//{
if ($bDisplayLimit && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
{
$iMaxObjects = MetaModel::GetConfig()->GetMinDisplayLimit();
$oSet->SetLimit($iMaxObjects);
}
//}
$oSet->Seek(0);
while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0))
{
$aRow = array();
$sHilightClass = $oObj->GetHilightClass();
if ($sHilightClass != '')
{
$aRow['@class'] = $sHilightClass;
}
if ($bViewLink)
{
$aRow['key'] = $oObj->GetHyperLink();
}
if ($bSelectMode)
{
if (array_key_exists('selection_enabled', $aExtraParams) && isset($aExtraParams['selection_enabled'][$oObj->GetKey()]))
{
$sDisabled = ($aExtraParams['selection_enabled'][$oObj->GetKey()]) ? '' : ' disabled="disabled"';
}
else
{
$sDisabled = '';
}
if ($bSingleSelectMode)
{
$aRow['form::select'] = "<input type=\"radio\" $sDisabled class=\"selectList{$iListId}\" name=\"selectObject\" value=\"".$oObj->GetKey()."\"></input>";
}
else
{
$aRow['form::select'] = "<input type=\"checkBox\" $sDisabled class=\"selectList{$iListId}\" name=\"selectObject[]\" value=\"".$oObj->GetKey()."\"></input>";
}
}
foreach($aList as $sAttCode)
{
$aRow[$sAttCode] = $oObj->GetAsHTML($sAttCode);
}
$aValues[] = $aRow;
$iMaxObjects--;
}
$sHtml .= '<table class="listContainer">';
$sColspan = '';
// if (isset($aExtraParams['block_id']))
// {
// $divId = $aExtraParams['block_id'];
// }
// else
// {
// $divId = 'missingblockid';
// }
$sFilter = $oSet->GetFilter()->serialize();
$iMinDisplayLimit = MetaModel::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 ($bDisplayMenu)
{
$oMenuBlock = new MenuBlock($oSet->GetFilter());
$sColspan = 'colspan="2"';
$aMenuExtraParams = $aExtraParams;
if (!empty($sLinkageAttribute))
{
//$aMenuExtraParams['linkage'] = $sLinkageAttribute;
$aMenuExtraParams = $aExtraParams;
}
$sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams, $iListId);
$sHtml .= '</td></tr>';
}
$sHtml .= "<tr><td $sColspan>";
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
$sHtml .= '</td></tr>';
$sHtml .= '</table>';
$iCount = $oSet->Count();
if ($bSelectMode)
{
$sHeader = Dict::Format('UI:Pagination:HeaderSelection', '<span id="total">'.$iCount.'</span>', '<span class="selectedCount">0</span>');
}
else
{
$sHeader = Dict::Format('UI:Pagination:HeaderNoSelection', '<span id="total">'.$iCount.'</span>');
}
if ($bDisplayLimit && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
{
$sCombo = '<select class="pagesize">';
for($iPage = 1; $iPage < 5; $iPage++)
{
$sSelected = '';
if ($iPage == 1)
{
$sSelected = 'selected="selected"';
}
$iNbItems = $iPage * MetaModel::GetConfig()->GetMinDisplayLimit();
$sCombo .= "<option $sSelected value=\"$iNbItems\">$iNbItems</option>";
}
$sCombo .= "<option $sSelected value=\"-1\">".Dict::S('UI:Pagination:All')."</option>";
$sCombo .= '</select>';
$sPages = Dict::S('UI:Pagination:PagesLabel');
$sPageSizeCombo = Dict::Format('UI:Pagination:PageSize', $sCombo);
$sHtml =
<<<EOF
<div id="pager{$iListId}" class="pager">
<p>$sHeader</p>
<p><table class="pagination"><tr><td>$sPages</td><td><img src="../images/first.png" class="first"/></td>
<td><img src="../images/prev.png" class="prev"/></td>
<td><span id="index"></span></td>
<td><img src="../images/next.png" class="next"/></td>
<td><img src="../images/last.png" class="last"/></td>
<td>$sPageSizeCombo</td>
<td><span id="loading">&nbsp;</span></td>
</tr>
</table>
<input type="hidden" name="selectionMode" value="positive"></input>
</div>
EOF
.$sHtml;
$aArgs = $oSet->GetArgs();
$sExtraParams = addslashes(str_replace('"', "'", json_encode(array_merge($aExtraParams, $aArgs)))); // JSON encode, change the style of the quotes and escape them
$sSelectMode = '';
$sHeaders = '';
if ($bSelectMode)
{
$sSelectMode = $bSingleSelectMode ? 'single' : 'multiple';
$sHeaders = 'headers: { 0: {sorter: false}},';
}
$sDisplayKey = ($bViewLink) ? 'true' : 'false';
$sDisplayList = json_encode($aList);
$sCssCount = isset($aExtraParams['cssCount']) ? ", cssCount: '{$aExtraParams['cssCount']}'" : '';
$iPageSize = MetaModel::GetConfig()->GetMinDisplayLimit();
$oPage->add_ready_script("$('#{$iListId} table.listResults').tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sFilter', extra_params: '$sExtraParams', select_mode: '$sSelectMode', displayKey: $sDisplayKey, displayList: $sDisplayList $sCssCount});\n");
}
else
$sSelectMode = 'none';
if ($bSelectMode)
{
$sHtml =
<<<EOF
<div id="pager{$iListId}" class="pager">
<p>$sHeader</p>
</div>
EOF
.$sHtml;
$sHeaders = '';
if ($bSelectMode)
{
$sHeaders = 'headers: { 0: {sorter: false}},';
}
$oPage->add_ready_script("$('#{$iListId} table.listResults').tableHover().tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} );\n");
// Manage how we update the 'Ok/Add' buttons that depend on the number of selected items
if (isset($aExtraParams['cssCount']))
{
$sCssCount = $aExtraParams['cssCount'];
if ($bSingleSelectMode)
{
$sSelectSelector = ":radio[name^=selectObj]";
}
else
{
$sSelectSelector = ":checkbox[name^=selectObj]";
}
$oPage->add_ready_script(
<<<EOF
$('#{$iListId} table.listResults $sSelectSelector').change(function() {
var c = $('{$sCssCount}');
var v = $('#{$iListId} table.listResults $sSelectSelector:checked').length;
c.val(v);
$('#{$iListId} .selectedCount').text(v);
c.trigger('change');
});
EOF
);
}
$sSelectMode = $bSingleSelectMode ? 'single' : 'multiple';
}
return $sHtml;
$sClassAlias = $oSet->GetClassAlias();
$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
// Load only the requested columns
$oSet->OptimizeColumnLoad(array($sClassAlias => $aList));
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null;
$aClassAliases = array( $sClassAlias => $sClassName);
$oDataTable = new DataTable($iListId, $oSet, $aClassAliases, $sTableId);
$oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList));
if ($bDisplayLimit)
{
$iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oSettings->iDefaultPageSize = $iDefaultPageSize;
}
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
static $iListId = 0;
$iListId++;
if (empty($aExtraParams['currentId']))
{
$iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !!
}
else
{
$iListId = $aExtraParams['currentId'];
}
$aList = array();
// Initialize and check the parameters
@@ -940,7 +787,28 @@ EOF
$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();
$sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list';
$aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array();
$aExtraFields = array();
foreach ($aExtraFieldsRaw as $sFieldName)
{
// Ignore attributes not of the main queried class
if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches))
{
$sClassAlias = $aMatches[1];
$sAttCode = $aMatches[2];
if (array_key_exists($sClassAlias, $oSet->GetSelectedClasses()))
{
$aExtraFields[$sClassAlias][] = $sAttCode;
}
}
else
{
$aExtraFields['*'] = $sAttCode;
}
}
$sHtml = '';
$oAppContext = new ApplicationContext();
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
@@ -954,106 +822,62 @@ EOF
}
}
$aAttribs = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName) // TO DO: check if the user has enough rights to view the classes of the list...
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sClassName] = MetaModel::GetZListItems($sClassName, 'list');
if ($bViewLink)
if (array_key_exists($sAlias, $aExtraFields))
{
$aAttribs['key_'.$sAlias] = array('label' => MetaModel::GetName($sClassName), 'description' => '');
$aList[$sAlias] = $aExtraFields[$sAlias];
}
foreach($aList[$sClassName] as $sAttCode)
else
{
$aAttribs[$sAttCode.'_'.$sAlias] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode));
$aList[$sAlias] = array();
}
if ($sZListName !== false)
{
$aDefaultList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
$aList[$sAlias] = array_merge($aDefaultList, $aList[$sAlias]);
}
// Filter the list to removed linked set since we are not able to display them here
foreach($aList[$sAlias] as $index => $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
if ($oAttDef instanceof AttributeLinkedSet)
{
// Removed from the display list
unset($aList[$sAlias][$index]);
}
}
}
// Load only the requested columns
$aAttToLoad = array(); // attributes to load
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
foreach($aList[$sClassName] as $sAttCode)
foreach($aList[$sAlias] as $sAttCode)
{
$aAttToLoad[$sAlias][] = $sAttCode;
}
}
$oSet->OptimizeColumnLoad($aAttToLoad);
$aValues = array();
$oSet->Seek(0);
$sSelectMode = 'none';
$sClassAlias = $oSet->GetClassAlias();
$oDataTable = new DataTable($iListId, $oSet, $aAuthorizedClasses);
$oSettings = DataTableSettings::GetDataModelSettings($aAuthorizedClasses, $bViewLink, $aList);
$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
$iMaxObjects = -1;
if ($bDisplayLimit)
{
if ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit())
{
$iMaxObjects = MetaModel::GetConfig()->GetMinDisplayLimit();
}
$iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oSettings->iDefaultPageSize = $iDefaultPageSize;
}
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...
{
if ($bViewLink)
{
if (is_null($aObjects[$sAlias]))
{
$aRow['key_'.$sAlias] = '';
}
else
{
$aRow['key_'.$sAlias] = $aObjects[$sAlias]->GetHyperLink();
}
}
foreach($aList[$sClassName] as $sAttCode)
{
if (is_null($aObjects[$sAlias]))
{
$aRow[$sAttCode.'_'.$sAlias] = '';
}
else
{
$aRow[$sAttCode.'_'.$sAlias] = $aObjects[$sAlias]->GetAsHTML($sAttCode);
}
}
}
$aValues[] = $aRow;
$iMaxObjects--;
}
$sHtml .= '<table class="listContainer">';
$sColspan = '';
if ($bDisplayMenu)
{
$oMenuBlock = new MenuBlock($oSet->GetFilter());
$sColspan = 'colspan="2"';
$aMenuExtraParams = $aExtraParams;
if (!empty($sLinkageAttribute))
{
$aMenuExtraParams = $aExtraParams;
}
if ($bDisplayLimit && ($oSet->Count() > MetaModel::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 .= '<tr class="containerHeader"><td>'.Dict::Format('UI:TruncatedResults', MetaModel::GetConfig()->GetMinDisplayLimit(), $oSet->Count()).'&nbsp;&nbsp;<span style=\"cursor:pointer;\" onClick="Javascript:ReloadTruncatedList(\''.$divId.'\', \''.$sFilter.'\', \''.$sExtraParams.'\');">'.Dict::S('UI:DisplayAll').'</span></td><td>';
$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 .= '<tr class="containerHeader"><td>&nbsp;'.Dict::Format('UI:CountOfResults', $oSet->Count()).'</td><td>';
}
$sHtml .= $oMenuBlock->GetRenderContent($oPage, $aMenuExtraParams, $aMenuExtraParams['currentId']);
$sHtml .= '</td></tr>';
}
$sHtml .= "<tr><td $sColspan>";
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
$sHtml .= '</td></tr>';
$sHtml .= '</table>';
return $sHtml;
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array())
@@ -1087,6 +911,8 @@ EOF
$aHeader = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sAlias] = array();
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
if (is_null($aFields) || (count($aFields) == 0))
@@ -1094,20 +920,20 @@ EOF
// Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
{
$aList[$sClassName][$sAttCode] = $oAttDef;
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
else
{
// User defined list of attributes
if (in_array($sAttCode, $aFields))
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$aList[$sClassName][$sAttCode] = $oAttDef;
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
$aHeader[] = 'id';
foreach($aList[$sClassName] as $sAttCode => $oAttDef)
foreach($aList[$sAlias] as $sAttCode => $oAttDef)
{
$sStar = '';
if ($oAttDef->IsExternalField())
@@ -1154,7 +980,7 @@ EOF
{
$aRow[] = $oObj->GetKey();
}
foreach($aList[$sClassName] as $sAttCode => $oAttDef)
foreach($aList[$sAlias] as $sAttCode => $oAttDef)
{
if (is_null($oObj))
{
@@ -1172,6 +998,150 @@ EOF
return $sHtml;
}
static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array())
{
$oPage->add(self::GetSetAsHTMLSpreadsheet($oSet, $aParams));
}
/**
* Spreadsheet output: designed for end users doing some reporting
* Then the ids are excluded and replaced by the corresponding friendlyname
*/
static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array())
{
$aFields = null;
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0))
{
$aFields = explode(',', $aParams['fields']);
}
$aList = array();
$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 || UR_ALLOWED_DEPENDS))
{
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aAttribs = array();
$aHeader = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sAlias] = array();
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
if (is_null($aFields) || (count($aFields) == 0))
{
// Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
{
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
else
{
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
// Replace external key by the corresponding friendly name (if not already in the list)
foreach($aList[$sAlias] as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
unset($aList[$sAlias][$sAttCode]);
$sFriendlyNameAttCode = $sAttCode.'_friendlyname';
if (!array_key_exists($sFriendlyNameAttCode, $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode))
{
$oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode);
$aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt;
}
}
}
foreach($aList[$sAlias] as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalField())
{
$sExtKeyLabel = MetaModel::GetLabel($sClassName, $oAttDef->GetKeyAttCode());
$oExtKeyAttDef = MetaModel::GetAttributeDef($sClassName, $oAttDef->GetKeyAttCode());
$sRemoteAttLabel = MetaModel::GetLabel($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode());
$oTargetAttDef = MetaModel::GetAttributeDef($oAttDef->GetTargetClass(), $oAttDef->GetExtAttCode());
$sSuffix = '';
if ($oTargetAttDef->IsExternalKey())
{
$sSuffix = '->id';
}
$sColLabel = $sExtKeyLabel.'->'.$sRemoteAttLabel.$sSuffix;
}
else
{
$sColLabel = MetaModel::GetLabel($sClassName, $sAttCode);
}
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime')
{
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')';
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')';
}
else
{
$aHeader[] = $sColLabel;
}
}
}
$sHtml = "<table border=\"1\">\n";
$sHtml .= "<tr>\n";
$sHtml .= "<td>".implode("</td><td>", $aHeader)."</td>\n";
$sHtml .= "</tr>\n";
$oSet->Seek(0);
while ($aObjects = $oSet->FetchAssoc())
{
$aRow = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$oObj = $aObjects[$sAlias];
foreach($aList[$sAlias] as $sAttCode => $oAttDef)
{
if (is_null($oObj))
{
$aRow[] = '<td></td>';
}
else
{
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime')
{
$iDate = AttributeDateTime::GetAsUnixSeconds($oObj->Get($sAttCode));
$aRow[] = '<td>'.date('Y-m-d', $iDate).'</td>';
$aRow[] = '<td>'.date('H:i:s', $iDate).'</td>';
}
else
{
$aRow[] = '<td>'.(string) $oObj->Get($sAttCode).'</td>';
}
}
}
}
$sHtml .= implode("\n", $aRow);
$sHtml .= "</tr>\n";
}
$sHtml .= "</table>\n";
return $sHtml;
}
static function DisplaySetAsXML(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array())
{
$oAppContext = new ApplicationContext();
@@ -1186,7 +1156,7 @@ EOF
}
$aAttribs = array();
$aList = array();
$aList[$sClassName] = MetaModel::GetZListItems($sClassName, 'details');
$aList[$sAlias] = MetaModel::GetZListItems($sClassName, 'details');
$oPage->add("<Set>\n");
$oSet->Seek(0);
while ($aObjects = $oSet->FetchAssoc())
@@ -1217,8 +1187,11 @@ EOF
{
if ($oAttDef->IsWritable())
{
$sValue = $oObj->GetAsXML($sAttCode);
$oPage->add("<$sAttCode>$sValue</$sAttCode>\n");
if (!$oAttDef->IsLinkSet())
{
$sValue = $oObj->GetAsXML($sAttCode);
$oPage->add("<$sAttCode>$sValue</$sAttCode>\n");
}
}
}
}
@@ -1305,12 +1278,14 @@ EOF
$aMapCriteria[$aCriteria['filtercode']][] = array('value' => $aCriteria['value'], 'opcode' => $aCriteria['opcode']);
}
$aList = MetaModel::GetZListItems($sClassName, 'standard_search');
$aConsts = $oSet->ListConstantFields(); // Some fields are constants based on the query/context
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
foreach($aList as $sFilterCode)
{
//$oAppContext->Reset($sFilterCode); // Make sure the same parameter will not be passed twice
$sHtml .= '<span style="white-space: nowrap;padding:5px;display:inline-block;">';
$sFilterValue = '';
$sFilterValue = utils::ReadParam($sFilterCode, '', false, 'raw_data');
$sFilterValue = isset($aConsts[$sClassAlias][$sFilterCode]) ? $aConsts[$sClassAlias][$sFilterCode] : '';
$sFilterValue = utils::ReadParam($sFilterCode, $sFilterValue, false, 'raw_data');
$sFilterOpCode = null; // Use the default 'loose' OpCode
if (empty($sFilterValue))
{
@@ -1336,7 +1311,9 @@ EOF
if ($oAttDef->IsExternalKey())
{
$sTargetClass = $oAttDef->GetTargetClass();
$oAllowedValues = new DBObjectSet(new DBObjectSearch($sTargetClass));
$oSearch = new DBObjectSearch($sTargetClass);
$oSearch->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oAllowedValues = new DBObjectSet($oSearch);
$iFieldSize = $oAttDef->GetMaxSize();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
@@ -1391,6 +1368,12 @@ EOF
}
$sHtml .= "</p>\n";
$sHtml .= "<p align=\"right\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Search')."\"></p>\n";
if (isset($aExtraParams['table_id']))
{
// Rename to avoid collisions...
$aExtraParams['_table_id_'] = $aExtraParams['table_id'];
unset($aExtraParams['table_id']);
}
foreach($aExtraParams as $sName => $sValue)
{
$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n";
@@ -1441,6 +1424,10 @@ EOF
$sFieldPrefix = '';
$sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : '';
$sFieldPrefix = isset($aArgs['prefix']) ? $sFormPrefix.$aArgs['prefix'] : $sFormPrefix;
if ($sDisplayValue == '')
{
$sDisplayValue = $value;
}
if (isset($aArgs[$sAttCode]) && empty($value))
{
@@ -1473,14 +1460,14 @@ EOF
$aEventsList[] ='validate';
$aEventsList[] ='keyup';
$aEventsList[] ='change';
$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"12\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
break;
case 'DateTime':
$aEventsList[] ='validate';
$aEventsList[] ='keyup';
$aEventsList[] ='change';
$sHTMLValue = "<input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" size=\"20\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
$sHTMLValue = "<input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"20\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
break;
case 'Duration':
@@ -1507,6 +1494,7 @@ EOF
$sHTMLValue = "<input title=\"$sHelpText\" type=\"password\" size=\"30\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
break;
case 'OQLExpression':
case 'Text':
$aEventsList[] ='validate';
$aEventsList[] ='keyup';
@@ -1515,6 +1503,7 @@ EOF
$aStyles = array();
$sStyle = '';
$sWidth = $oAttDef->GetWidth('width', '');
if (!empty($sWidth))
{
$aStyles[] = 'width:'.$sWidth;
@@ -1528,7 +1517,21 @@ EOF
{
$sStyle = 'style="'.implode('; ', $aStyles).'"';
}
$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea></td><td>{$sValidationField}</td></tr></table>";
if ($oAttDef->GetEditClass() == 'OQLExpression')
{
$sTestResId = 'query_res_'.$sFieldPrefix.$sAttCode.$sNameSuffix; //$oPage->GetUniqueId();
$sBaseUrl = utils::GetAbsoluteUrlAppRoot().'pages/run_query.php?expression=';
$sInitialUrl = $sBaseUrl.urlencode($sEditValue);
$sAdditionalStuff = "<a id=\"$sTestResId\" target=\"_blank\" href=\"$sInitialUrl\">".Dict::S('UI:Edit:TestQuery')."</a>";
$oPage->add_ready_script("$('#$iId').bind('change keyup', function(evt, sFormId) { $('#$sTestResId').attr('href', '$sBaseUrl'+encodeURIComponent($(this).val())); } );");
}
else
{
$sAdditionalStuff = "";
}
// Ok, the text area is drawn here
$sHTMLValue = "<table><tr><td><textarea class=\"resizable\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sAdditionalStuff</td><td>{$sValidationField}</td></tr></table>";
break;
case 'CaseLog':
@@ -1550,7 +1553,7 @@ EOF
}
$sHeader = '<div class="caselog_input_header">&nbsp;'.Dict::S('UI:CaseLogTypeYourTextHere').'</div>';
$sEditValue = $oAttDef->GetEditValue($value);
$sPreviousLog = is_object($value) ? $value->GetAsHTML() : '';
$sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, array('AttributeText', 'RenderWikiHtml')) : '';
$iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0;
$sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains
$sHTMLValue = "<div class=\"caselog\" $sStyle><table style=\"width:100%;\"><tr><td>$sHeader<textarea style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, ENT_QUOTES, 'UTF-8')."</textarea>$sPreviousLog</td><td>{$sValidationField}</td></tr></table>$sHidden</div>";
@@ -1565,8 +1568,9 @@ EOF
case 'LinkedSet':
$aEventsList[] ='validate';
$aEventsList[] ='change';
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed());
$sHTMLValue = $oWidget->Display($oPage, $value);
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed(), $aArgs);
$oObj = isset($aArgs['this']) ? $aArgs['this'] : null;
$sHTMLValue = $oWidget->Display($oPage, $value, array(), $sFormPrefix, $oObj);
break;
case 'Document':
@@ -1585,6 +1589,10 @@ EOF
$sHTMLValue .= "<input title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[fcontents]\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/>&nbsp;{$sValidationField}\n";
break;
case 'StopWatch':
$sHTMLValue = "The edition of a stopwatch is not allowed!!!";
break;
case 'List':
// Not editable for now...
$sHTMLValue = '';
@@ -1606,7 +1614,7 @@ EOF
$aExtKeyParams = $aArgs;
$aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize();
$aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars();
$sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs);
$sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams);
$sHTMLValue .= "<!-- iFlags: $iFlags bMandatory: $bMandatory -->\n";
break;
@@ -1654,7 +1662,7 @@ EOF
}
else
{
$sHTMLValue = "<input title=\"$sHelpText\" type=\"text\" size=\"30\" maxlength=\"$iFieldSize\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
$sHTMLValue = "<input title=\"$sHelpText\" type=\"text\" size=\"30\" maxlength=\"$iFieldSize\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/>&nbsp;{$sValidationField}";
$aEventsList[] ='keyup';
$aEventsList[] ='change';
}
@@ -1733,7 +1741,7 @@ EOF
if ($iKey > 0)
{
// The object already exists in the database, it's a modification
$sButtons = "<input type=\"hidden\" name=\"id\" value=\"$iKey\">\n";
$sButtons = "<input id=\"{$sPrefix}_id\" type=\"hidden\" name=\"id\" value=\"$iKey\">\n";
$sButtons .= "<input type=\"hidden\" name=\"operation\" value=\"{$sOperation}\">\n";
$sButtons .= "<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n";
$sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n";
@@ -1819,6 +1827,10 @@ EOF
$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
$aFieldsMap = $this->DisplayBareProperties($oPage, true, $sPrefix, $aExtraParams);
if ($iKey > 0)
{
$aFieldsMap['id'] = $sPrefix.'_id';
}
// Now display the relations, one tab per relation
if (!isset($aExtraParams['noRelations']))
{
@@ -2146,7 +2158,7 @@ EOF
$current = HILIGHT_CLASS_NONE; // Not hilighted by default
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$new = $oExtensionInstance->GetHilightClass($this);
@$current = self::$m_highlightComparison[$current][$new];
@@ -2431,7 +2443,7 @@ EOF
$this->UpdateObjectFromArray($aFinalValues);
// Invoke extensions after the update of the object from the form
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnFormSubmit($this, $sFormPrefix);
}

View File

@@ -0,0 +1,699 @@
<?php
// Copyright (C) 2012 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
require_once(APPROOT.'application/dashboardlayout.class.inc.php');
require_once(APPROOT.'application/dashlet.class.inc.php');
/**
* A user editable dashboard page
*
*/
abstract class Dashboard
{
protected $sTitle;
protected $sLayoutClass;
protected $aWidgetsData;
protected $oDOMNode;
protected $sId;
protected $aCells;
public function __construct($sId)
{
$this->sLayoutClass = null;
$this->aCells = array();
$this->oDOMNode = null;
$this->sId = $sId;
}
public function FromXml($sXml)
{
$this->aCells = array(); // reset the content of the dashboard
set_error_handler(array('Dashboard', 'ErrorHandler'));
$oDoc = new DOMDocument();
$oDoc->loadXML($sXml);
restore_error_handler();
$this->oDOMNode = $oDoc->getElementsByTagName('dashboard')->item(0);
$oLayoutNode = $this->oDOMNode->getElementsByTagName('layout')->item(0);
$this->sLayoutClass = $oLayoutNode->textContent;
$oTitleNode = $this->oDOMNode->getElementsByTagName('title')->item(0);
$this->sTitle = $oTitleNode->textContent;
$oCellsNode = $this->oDOMNode->getElementsByTagName('cells')->item(0);
$oCellsList = $oCellsNode->getElementsByTagName('cell');
foreach($oCellsList as $oCellNode)
{
$aDashletList = array();
$oDashletList = $oCellNode->getElementsByTagName('dashlet');
foreach($oDashletList as $oDomNode)
{
$sDashletClass = $oDomNode->getAttribute('xsi:type');
$sId = $oDomNode->getAttribute('id');
$oNewDashlet = new $sDashletClass($sId);
$oNewDashlet->FromDOMNode($oDomNode);
$aDashletList[] = $oNewDashlet;
}
$this->aCells[] = $aDashletList;
}
}
/**
* Error handler to turn XML loading warnings into exceptions
*/
public static function ErrorHandler($errno, $errstr, $errfile, $errline)
{
if ($errno == E_WARNING && (substr_count($errstr,"DOMDocument::loadXML()")>0))
{
throw new DOMException($errstr);
}
else
{
return false;
}
}
public function ToXml()
{
$oDoc = new DOMDocument();
$oDoc->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
$oDoc->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
$oMainNode = $oDoc->createElement('dashboard');
$oMainNode->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
$oDoc->appendChild($oMainNode);
$oNode = $oDoc->createElement('layout', $this->sLayoutClass);
$oMainNode->appendChild($oNode);
$oNode = $oDoc->createElement('title', $this->sTitle);
$oMainNode->appendChild($oNode);
$oCellsNode = $oDoc->createElement('cells');
$oMainNode->appendChild($oCellsNode);
foreach ($this->aCells as $aCell)
{
$oCellNode = $oDoc->createElement('cell');
$oCellsNode->appendChild($oCellNode);
foreach ($aCell as $oDashlet)
{
$oNode = $oDoc->createElement('dashlet');
$oCellNode->appendChild($oNode);
$oNode->setAttribute('id', $oDashlet->GetID());
$oNode->setAttribute('xsi:type', get_class($oDashlet));
$oDashlet->ToDOMNode($oNode);
}
}
$sXml = $oDoc->saveXML();
return $sXml;
}
public function FromParams($aParams)
{
$this->sLayoutClass = $aParams['layout_class'];
$this->sTitle = $aParams['title'];
foreach($aParams['cells'] as $aCell)
{
$aCellDashlets = array();
foreach($aCell as $aDashletParams)
{
$sDashletClass = $aDashletParams['dashlet_class'];
$sId = $aDashletParams['dashlet_id'];
$oNewDashlet = new $sDashletClass($sId);
$oForm = $oNewDashlet->GetForm();
$oForm->SetParamsContainer($sId);
$oForm->SetPrefix('');
$aValues = $oForm->ReadParams();
$oNewDashlet->FromParams($aValues);
$aCellDashlets[] = $oNewDashlet;
}
$this->aCells[] = $aCellDashlets;
}
}
public function Save()
{
}
public function GetLayout()
{
return $this->sLayoutClass;
}
public function SetLayout($sLayoutClass)
{
$this->sLayoutClass = $sLayoutClass;
}
public function GetTitle()
{
return $this->sTitle;
}
public function SetTitle($sTitle)
{
$this->sTitle = $sTitle;
}
public function AddDashlet($oDashlet)
{
$sId = $this->GetNewDashletId();
$oDashlet->SetId($sId);
$this->aCells[] = array($oDashlet);
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$oPage->add('<h1>'.Dict::S($this->sTitle).'</h1>');
$oLayout = new $this->sLayoutClass;
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
if (!$bEditMode)
{
$oPage->add_linked_script('../js/dashlet.js');
$oPage->add_linked_script('../js/dashboard.js');
}
}
public function RenderProperties($oPage)
{
// menu to pick a layout and edit other properties of the dashboard
$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:Properties').'</div>');
$sUrl = utils::GetAbsoluteUrlAppRoot();
$oPage->add('<div style="text-align:center">'.Dict::S('UI:DashboardEdit:Layout').'</div>');
$oPage->add('<div id="select_layout" style="text-align:center">');
foreach( get_declared_classes() as $sLayoutClass)
{
if (is_subclass_of($sLayoutClass, 'DashboardLayout'))
{
$oReflection = new ReflectionClass($sLayoutClass);
if (!$oReflection->isAbstract())
{
$aCallSpec = array($sLayoutClass, 'GetInfo');
$aInfo = call_user_func($aCallSpec);
$sChecked = ($this->sLayoutClass == $sLayoutClass) ? 'checked' : '';
$oPage->add('<input type="radio" name="layout_class" '.$sChecked.' value="'.$sLayoutClass.'" id="layout_'.$sLayoutClass.'"><label for="layout_'.$sLayoutClass.'"><img src="'.$sUrl.$aInfo['icon'].'" /></label>'); // title="" on either the img or the label does nothing !
}
}
}
$oPage->add('</div>');
$oForm = new DesignerForm();
$oField = new DesignerLongTextField('dashboard_title', Dict::S('UI:DashboardEdit:DashboardTitle'), $this->sTitle);
$oForm->AddField($oField);
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, ':itop-dashboard');
$oPage->add('</div>');
$oPage->add_ready_script(
<<<EOF
$('#select_layout').buttonset();
$('#select_layout input').click( function() {
var sLayoutClass = $(this).val();
$(':itop-dashboard').dashboard('option', {layout_class: sLayoutClass});
} );
$('#row_attr_dashboard_title').property_field('option', {parent_selector: ':itop-dashboard', auto_apply: false, 'do_apply': function() {
var sTitle = $('#attr_dashboard_title').val();
$(':itop-dashboard').dashboard('option', {title: sTitle});
return true;
}
});
EOF
);
}
public function RenderDashletsSelection($oPage)
{
// Toolbox/palette to drag and drop dashlets
$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:Dashlets').'</div>');
$sUrl = utils::GetAbsoluteUrlAppRoot();
$oPage->add('<div id="select_dashlet" style="text-align:center">');
foreach( get_declared_classes() as $sDashletClass)
{
if (is_subclass_of($sDashletClass, 'Dashlet'))
{
$oReflection = new ReflectionClass($sDashletClass);
if (!$oReflection->isAbstract())
{
$aCallSpec = array($sDashletClass, 'IsVisible');
$bVisible = call_user_func($aCallSpec);
if ($bVisible)
{
$aCallSpec = array($sDashletClass, 'GetInfo');
$aInfo = call_user_func($aCallSpec);
$oPage->add('<span dashlet_class="'.$sDashletClass.'" class="dashlet_icon ui-widget-content ui-corner-all" id="dashlet_'.$sDashletClass.'" title="'.$aInfo['label'].'" style="width:34px; height:34px; display:inline-block; margin:2px;"><img src="'.$sUrl.$aInfo['icon'].'" /></span>');
}
}
}
}
$oPage->add('</div>');
$oPage->add('</div>');
$oPage->add_ready_script("$('.dashlet_icon').draggable({helper: 'clone', appendTo: 'body', zIndex: 10000, revert:'invalid'});");
$oPage->add_ready_script("$('.layout_cell').droppable({accept:'.dashlet_icon', hoverClass:'dragHover'});");
}
public function RenderDashletsProperties($oPage)
{
// Toolbox/palette to edit the properties of each dashlet
$oPage->add('<div class="ui-widget-content ui-corner-all"><div class="ui-widget-header ui-corner-all" style="text-align:center; padding: 2px;">'.Dict::S('UI:DashboardEdit:DashletProperties').'</div>');
$oPage->add('<div id="dashlet_properties" style="text-align:center">');
foreach($this->aCells as $aCell)
{
foreach($aCell as $oDashlet)
{
$sId = $oDashlet->GetID();
$sClass = get_class($oDashlet);
if ($oDashlet->IsVisible())
{
$oPage->add('<div class="dashlet_properties" id="dashlet_properties_'.$sId.'" style="display:none">');
$oForm = $oDashlet->GetForm();
$this->SetFormParams($oForm);
$oForm->RenderAsPropertySheet($oPage, false, ':itop-dashboard');
$oPage->add('</div>');
}
}
}
$oPage->add('</div>');
$oPage->add('</div>');
}
protected function GetNewDashletId()
{
$iNewId = 0;
foreach($this->aCells as $aDashlets)
{
foreach($aDashlets as $oDashlet)
{
$iNewId = max($iNewId, (int)$oDashlet->GetID());
}
}
return $iNewId + 1;
}
abstract protected function SetFormParams($oForm);
}
class RuntimeDashboard extends Dashboard
{
protected $bCustomized;
public function __construct($sId)
{
parent::__construct($sId);
$this->bCustomized = false;
}
public function SetCustomFlag($bCustomized)
{
$this->bCustomized = $bCustomized;
}
protected function SetFormParams($oForm)
{
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));
}
public function Save()
{
$sXml = $this->ToXml();
$oUDSearch = new DBObjectSearch('UserDashboard');
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oUDSearch->AddCondition('menu_code', $this->sId, '=');
$oUDSet = new DBObjectSet($oUDSearch);
if ($oUDSet->Count() > 0)
{
// Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch();
$oUserDashboard->Set('contents', $sXml);
$oUserDashboard->DBUpdate();
}
else
{
// No such customized dasboard for the current user, let's create a new record
$oUserDashboard = new UserDashboard();
$oUserDashboard->Set('user_id', UserRights::GetUserId());
$oUserDashboard->Set('menu_code', $this->sId);
$oUserDashboard->Set('contents', $sXml);
$oUserDashboard->DBInsert();
}
}
public function Revert()
{
$oUDSearch = new DBObjectSearch('UserDashboard');
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oUDSearch->AddCondition('menu_code', $this->sId, '=');
$oUDSet = new DBObjectSet($oUDSearch);
if ($oUDSet->Count() > 0)
{
// Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch();
$oUserDashboard->DBDelete();
}
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
parent::Render($oPage, $bEditMode, $aExtraParams);
if (!$bEditMode)
{
$sEditMenu = "<td><span id=\"DashboardMenu\"><ul><li><img src=\"../images/edit.png\"><ul>";
$aActions = array();
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}')");
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
if ($this->bCustomized)
{
$oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:Revert'),
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}'); else return false");
$aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem();
}
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
$sEditMenu .= $oPage->RenderPopupMenuItems($aActions);
$sEditMenu = addslashes($sEditMenu);
//$sEditBtn = addslashes('<div style="display: inline-block; height: 55px; width:200px;vertical-align:center;line-height:60px;text-align:left;"><button onclick="EditDashboard(\''.$this->sId.'\');">Edit This Page</button></div>');
$oPage->add_ready_script(
<<<EOF
$('#logOffBtn').parent().before('$sEditMenu');
$('#DashboardMenu>ul').popupmenu();
EOF
);
$oPage->add_script(
<<<EOF
function EditDashboard(sId)
{
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'dashboard_editor', id: sId},
function(data)
{
$('body').append(data);
}
);
return false;
}
function RevertDashboard(sId)
{
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'revert_dashboard', dashboard_id: sId},
function(data)
{
$('body').append(data);
}
);
return false;
}
EOF
);
}
}
public function RenderEditor($oPage)
{
$oPage->add('<div id="dashboard_editor">');
$oPage->add('<div class="ui-layout-center">');
$this->Render($oPage, true);
$oPage->add('</div>');
$oPage->add('<div class="ui-layout-east">');
$this->RenderProperties($oPage);
$this->RenderDashletsSelection($oPage);
$this->RenderDashletsProperties($oPage);
$oPage->add('</div>');
$oPage->add('<div id="event_bus"/>'); // For exchanging messages between the panes, same as in the designer
$oPage->add('</div>');
$sDialogTitle = Dict::S('UI:DashboardEdit:Title');
$sOkButtonLabel = Dict::S('UI:Button:Save');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$sId = addslashes($this->sId);
$sLayoutClass = addslashes($this->sLayoutClass);
$sTitle = addslashes($this->sTitle);
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sExitConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
$sCancelConfirmationMessage = addslashes(Dict::S('UI:CancelConfirmationMessage'));
$sAutoApplyConfirmationMessage = addslashes(Dict::S('UI:AutoApplyConfirmationMessage'));
$oPage->add_ready_script(
<<<EOF
window.bLeavingOnUserAction = false;
$('#dashboard_editor').dialog({
height: $('body').height() - 50,
width: $('body').width() - 50,
modal: true,
title: '$sDialogTitle',
buttons: [
{ text: "$sOkButtonLabel", click: function() {
var oDashboard = $(':itop-dashboard').data('dashboard');
if (oDashboard.is_dirty())
{
if (!confirm('$sAutoApplyConfirmationMessage'))
{
return;
}
else
{
oDashboard.apply_changes();
}
}
window.bLeavingOnUserAction = true;
oDashboard.save();
} },
{ text: "$sCancelButtonLabel", click: function() {
var oDashboard = $(':itop-dashboard').data('dashboard');
if (oDashboard.is_modified())
{
if (!confirm('$sCancelConfirmationMessage'))
{
return;
}
}
window.bLeavingOnUserAction = true;
$(this).dialog( "close" );
$(this).remove();
} },
],
close: function() { $(this).remove(); }
});
$('#dashboard_editor .ui-layout-center').dashboard({
dashboard_id: '$sId', layout_class: '$sLayoutClass', title: '$sTitle',
submit_to: '$sUrl', submit_parameters: {operation: 'save_dashboard'},
render_to: '$sUrl', render_parameters: {operation: 'render_dashboard'},
new_dashlet_parameters: {operation: 'new_dashlet'}
});
$('#select_dashlet').droppable({
accept: '.dashlet',
drop: function(event, ui) {
$( this ).find( ".placeholder" ).remove();
var oDashlet = ui.draggable;
oDashlet.remove();
},
});
$('#event_bus').bind('dashlet-selected', function(event, data){
var sDashletId = data.dashlet_id;
var sPropId = 'dashlet_properties_'+sDashletId;
$('.dashlet_properties').each(function() {
var sId = $(this).attr('id');
var bShow = (sId == sPropId);
if (bShow)
{
$(this).show();
}
else
{
$(this).hide();
}
});
});
dashboard_prop_size = GetUserPreference('dashboard_prop_size', 350);
$('#dashboard_editor').layout({
east: {
minSize: 200,
size: dashboard_prop_size,
togglerLength_open: 0,
togglerLength_closed: 0,
onresize_end: function(name, elt, state, options, layout)
{
if (state.isSliding == false)
{
SetUserPreference('dashboard_prop_size', state.size, true);
}
},
}
});
window.onbeforeunload = function() {
if (!window.bLeavingOnUserAction)
{
var oDashboard = $(':itop-dashboard').data('dashboard');
if (oDashboard)
{
if (oDashboard.is_dirty())
{
return '$sExitConfirmationMessage';
}
if (oDashboard.is_modified())
{
return '$sExitConfirmationMessage';
}
}
}
// return nothing ! safer for IE
};
EOF
);
$oPage->add_ready_script("");
}
public static function GetDashletCreationForm($sOQL = null)
{
$oForm = new DesignerForm();
// Get the list of all 'dashboard' menus in which we can insert a dashlet
$aAllMenus = ApplicationMenu::ReflectionMenuNodes();
$aAllowedDashboards = array();
foreach($aAllMenus as $idx => $aMenu)
{
$oMenu = $aMenu['node'];
$sParentId = $aMenu['parent'];
if ($oMenu instanceof DashboardMenuNode)
{
$sMenuLabel = $oMenu->GetTitle();
$sParentLabel = Dict::S('Menu:'.$sParentId);
if ($sParentLabel != $sMenuLabel)
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sParentLabel.' - '.$sMenuLabel;
}
else
{
$aAllowedDashboards[$oMenu->GetMenuId()] = $sMenuLabel;
}
}
}
asort($aAllowedDashboards);
$aKeys = array_keys($aAllowedDashboards); // Select the first one by default
$sDefaultDashboard = $aKeys[0];
$oField = new DesignerComboField('menu_id', Dict::S('UI:DashletCreation:Dashboard'), $sDefaultDashboard);
$oField->SetAllowedValues($aAllowedDashboards);
$oField->SetMandatory(true);
$oForm->AddField($oField);
// Get the list of possible dashlets that support a creation from
// an OQL
$aDashlets = array();
foreach(get_declared_classes() as $sDashletClass)
{
if (is_subclass_of($sDashletClass, 'Dashlet'))
{
$oReflection = new ReflectionClass($sDashletClass);
if (!$oReflection->isAbstract())
{
$aCallSpec = array($sDashletClass, 'CanCreateFromOQL');
$bShorcutMode = call_user_func($aCallSpec);
if ($bShorcutMode)
{
$aCallSpec = array($sDashletClass, 'GetInfo');
$aInfo = call_user_func($aCallSpec);
$aDashlets[$sDashletClass] = array('label' => $aInfo['label'], 'class' => $sDashletClass, 'icon' => $aInfo['icon']);
}
}
}
}
$oSelectorField = new DesignerFormSelectorField('dashlet_class', Dict::S('UI:DashletCreation:DashletType'), '');
$oForm->AddField($oSelectorField);
foreach($aDashlets as $sDashletClass => $aDashletInfo)
{
$oSubForm = new DesignerForm();
$oDashlet = new $sDashletClass(0);
$oDashlet->GetPropertiesFieldsFromOQL($oSubForm, $sOQL);
$oSelectorField->AddSubForm($oSubForm, $aDashletInfo['label'], $aDashletInfo['class']);
}
$oField = new DesignerBooleanField('open_editor', Dict::S('UI:DashletCreation:EditNow'), true);
$oForm->AddField($oField);
return $oForm;
}
public static function GetDashletCreationDlgFromOQL($oPage, $sOQL)
{
$oPage->add('<div id="dashlet_creation_dlg">');
$oForm = self::GetDashletCreationForm($sOQL);
$oForm->Render($oPage);
$oPage->add('</div>');
$sDialogTitle = Dict::S('UI:DashletCreation:Title');
$sOkButtonLabel = Dict::S('UI:Button:Ok');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$oPage->add_ready_script(
<<<EOF
$('#dashlet_creation_dlg').dialog({
width: 400,
modal: true,
title: '$sDialogTitle',
buttons: [
{ text: "$sOkButtonLabel", click: function() {
var oForm = $(this).find('form');
var sFormId = oForm.attr('id');
var oParams = null;
var aErrors = ValidateForm(sFormId, false);
if (aErrors.length == 0)
{
oParams = ReadFormParams(sFormId);
}
oParams.operation = 'add_dashlet';
var me = $(this);
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) {
me.dialog( "close" );
me.remove();
$('body').append(data);
});
} },
{ text: "$sCancelButtonLabel", click: function() {
$(this).dialog( "close" ); $(this).remove();
} },
],
close: function() { $(this).remove(); }
});
EOF
);
}
}

View File

@@ -0,0 +1,185 @@
<?php
abstract class DashboardLayout
{
public function __construct()
{
}
abstract public function Render($oPage, $aDashlets, $bEditMode = false);
static public function GetInfo()
{
return array(
'label' => '',
'icon' => '',
'description' => '',
);
}
}
abstract class DashboardLayoutMultiCol extends DashboardLayout
{
protected $iNbCols;
public function __construct()
{
$this->iNbCols = 1;
}
protected function TrimCell($aDashlets)
{
$aKeys = array_reverse(array_keys($aDashlets));
$idx = 0;
$bNoVisibleFound = true;
while($idx < count($aKeys) && $bNoVisibleFound)
{
$oDashlet = $aDashlets[$aKeys[$idx]];
if ($oDashlet->IsVisible())
{
$bNoVisibleFound = false;
}
else
{
unset($aDashlets[$aKeys[$idx]]);
}
$idx++;
}
return $aDashlets;
}
protected function TrimCellsArray($aCells)
{
foreach($aCells as $key => $aDashlets)
{
$aCells[$key] = $this->TrimCell($aDashlets);
}
$aKeys = array_reverse(array_keys($aCells));
$idx = 0;
$bNoVisibleFound = true;
while($idx < count($aKeys) && $bNoVisibleFound)
{
$aDashlets = $aCells[$aKeys[$idx]];
if (count($aDashlets) > 0)
{
$bNoVisibleFound = false;
}
else
{
unset($aCells[$aKeys[$idx]]);
}
$idx++;
}
return $aCells;
}
public function Render($oPage, $aCells, $bEditMode = false, $aExtraParams = array())
{
// Trim the list of cells to remove the invisible/empty ones at the end of the array
$aCells = $this->TrimCellsArray($aCells);
$oPage->add('<table style="width:100%"><tbody>');
$iCellIdx = 0;
$fColSize = 100 / $this->iNbCols;
$sStyle = $bEditMode ? 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode"' : 'style="width: '.$fColSize.'%;" class="dashboard"';
$iNbRows = ceil(count($aCells) / $this->iNbCols);
for($iRows = 0; $iRows < $iNbRows; $iRows++)
{
$oPage->add('<tr>');
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$oPage->add("<td $sStyle>");
if (array_key_exists($iCellIdx, $aCells))
{
$aDashlets = $aCells[$iCellIdx];
if (count($aDashlets) > 0)
{
foreach($aDashlets as $oDashlet)
{
if ($oDashlet->IsVisible())
{
$oDashlet->DoRender($oPage, $bEditMode, true /* bEnclosingDiv */, $aExtraParams);
}
}
}
else
{
$oPage->add('&nbsp;');
}
}
else
{
$oPage->add('&nbsp;');
}
$oPage->add('</td>');
$iCellIdx++;
}
$oPage->add('</tr>');
}
if ($bEditMode) // Add one row for extensibility
{
$sStyle = 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode layout_extension"';
$oPage->add('<tr>');
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$oPage->add("<td $sStyle>");
$oPage->add('&nbsp;');
$oPage->add('</td>');
}
$oPage->add('</tr>');
}
$oPage->add('</tbody></table>');
}
}
class DashboardLayoutOneCol extends DashboardLayoutMultiCol
{
public function __construct()
{
parent::__construct();
$this->iNbCols = 1;
}
static public function GetInfo()
{
return array(
'label' => 'One Column',
'icon' => 'images/layout_1col.png',
'description' => '',
);
}
}
class DashboardLayoutTwoCols extends DashboardLayoutMultiCol
{
public function __construct()
{
parent::__construct();
$this->iNbCols = 2;
}
static public function GetInfo()
{
return array(
'label' => 'Two Columns',
'icon' => 'images/layout_2col.png',
'description' => '',
);
}
}
class DashboardLayoutThreeCols extends DashboardLayoutMultiCol
{
public function __construct()
{
parent::__construct();
$this->iNbCols = 3;
}
static public function GetInfo()
{
return array(
'label' => 'Two Columns',
'icon' => 'images/layout_3col.png',
'description' => '',
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,833 @@
<?php
// Copyright (C) 2012 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
/**
* Data Table to display a set of objects in a tabular manner in HTML
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html GPL
*/
class DataTable
{
protected $iListId; // Unique ID inside the web page
protected $sTableId; // identifier for saving the settings (combined with the class aliases)
protected $oSet; // The set of objects to display
protected $aClassAliases; // The aliases (alias => class) inside the set
protected $iNbObjects; // Total number of objects inthe set
protected $bUseCustomSettings; // Whether or not the current display uses custom settings
protected $oDefaultSettings; // the default settings for displaying such a list
/**
* @param $iListId mixed Unique ID for this div/table in the page
* @param $oSet DBObjectSet The set of data to display
* @param $aClassAliases Hash The list of classes/aliases to be displayed in this set $sAlias => $sClassName
* @param $sTableId mixed A string (or null) identifying this table in order to persist its settings
*/
public function __construct($iListId, $oSet, $aClassAliases, $sTableId = null)
{
$this->iListId = $iListId;
$this->oSet = $oSet;
$this->aClassAliases = $aClassAliases;
$this->sTableId = $sTableId;
$this->iNbObjects = $oSet->Count();
$this->bUseCustomSettings = false;
$this->oDefaultSettings = null;
}
public function Display(WebPage $oPage, DataTableSettings $oSettings, $bActionsMenu, $sSelectMode, $bViewLink, $aExtraParams)
{
$this->oDefaultSettings = $oSettings;
// Identified tables can have their own specific settings
$oCustomSettings = DataTableSettings::GetTableSettings($this->aClassAliases, $this->sTableId);
if ($oCustomSettings != null)
{
// Custom settings overload the default ones
$this->bUseCustomSettings = true;
}
else
{
$oCustomSettings = $oSettings;
}
if ($oCustomSettings->iDefaultPageSize > 0)
{
$this->oSet->SetLimit($oCustomSettings->iDefaultPageSize);
}
$this->oSet->SetOrderBy($oCustomSettings->GetSortOrder());
$bToolkitMenu = true;
if (UserRights::IsPortalUser())
{
// Portal users have a limited access to data, for now they can only see what's configured for them
$bToolkitMenu = false;
}
return $this->GetAsHTML($oPage, $oCustomSettings->iDefaultPageSize, $oCustomSettings->iDefaultPageSize, 0, $oCustomSettings->aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
{
$sObjectsCount = $this->GetObjectCount($oPage, $sSelectMode);
$sPager = $this->GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex);
$sActionsMenu = '';
$sToolkitMenu = '';
if ($bActionsMenu)
{
$sActionsMenu = $this->GetActionsMenu($oPage, $aExtraParams);
}
if ($bToolkitMenu)
{
$sToolkitMenu = $this->GetToolkitMenu($oPage, $aExtraParams);
}
$sDataTable = $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
$sConfigDlg = $this->GetTableConfigDlg($oPage, $aColumns, $bViewLink, $iDefaultPageSize);
$sHtml = "<table id=\"datatable_{$this->iListId}\" class=\"datatable\">";
$sHtml .= "<tr><td>";
$sHtml .= "<table style=\"width:100%;\">";
$sHtml .= "<tr><td class=\"pagination_container\">$sObjectsCount</td><td class=\"menucontainer\">$sToolkitMenu $sActionsMenu</td></tr>";
$sHtml .= "<tr>$sPager</tr>";
$sHtml .= "</table>";
$sHtml .= "</td></tr>";
$sHtml .= "<tr><td class=\"datacontents\">$sDataTable</td></tr>";
$sHtml .= "</table>\n";
$oPage->add_at_the_end($sConfigDlg);
$aOptions = array(
'sPersistentId' => '',
'sFilter' => $this->oSet->GetFilter()->serialize(),
'oColumns' => $aColumns,
'sSelectMode' => $sSelectMode,
'sViewLink' => ($bViewLink ? 'true' : 'false'),
'iNbObjects' => $this->iNbObjects,
'iDefaultPageSize' => $iDefaultPageSize,
'iPageSize' => $iPageSize,
'iPageIndex' => $iPageIndex,
'oClassAliases' => $this->aClassAliases,
'sTableId' => $this->sTableId,
'oExtraParams' => $aExtraParams,
'sRenderUrl' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
'oRenderParameters' => array('str' => ''), // Forces JSON to encode this as a object...
'oDefaultSettings' => array('str' => ''), // Forces JSON to encode this as a object...
'oLabels' => array('moveup' => Dict::S('UI:Button:MoveUp'), 'movedown' => Dict::S('UI:Button:MoveDown')),
);
if($this->oDefaultSettings != null)
{
$aOptions['oDefaultSettings'] = $this->GetAsHash($this->oDefaultSettings);
}
$sJSOptions = json_encode($aOptions);
$oPage->add_ready_script("$('#datatable_{$this->iListId}').datatable($sJSOptions);");
return $sHtml;
}
/**
* When refreshing the body of a paginated table, get the rows of the table (inside the TBODY)
* return string The HTML rows to insert inside the <tbody> node
*/
public function GetAsHTMLTableRows(WebPage $oPage, $iPageSize, $aColumns, $sSelectMode, $bViewLink, $aExtraParams)
{
$aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
$aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
$sHtml = '';
foreach($aValues as $aRow)
{
$sHtml .= $oPage->GetTableRow($aRow, $aAttribs);
}
return $sHtml;
}
protected function GetObjectCount(WebPage $oPage, $sSelectMode)
{
if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
{
$sHtml = '<div class="pagination_objcount">'.Dict::Format('UI:Pagination:HeaderSelection', '<span id="total">'.$this->iNbObjects.'</span>', '<span class="selectedCount">0</span>').'</div>';
}
else
{
$sHtml = '<div class="pagination_objcount">'.Dict::Format('UI:Pagination:HeaderNoSelection', '<span id="total">'.$this->iNbObjects.'</span>').'</div>';
}
return $sHtml;
}
protected function GetPager(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex)
{
$sHtml = '';
if ($iPageSize < 1) // Display all
{
$sPagerStyle = 'style="display:none"'; // no limit: display the full table, so hide the "pager" UI
}
else
{
$sPagerStyle = '';
}
$sCombo = '<select class="pagesize">';
for($iPage = 1; $iPage < 5; $iPage++)
{
$iNbItems = $iPage * $iDefaultPageSize;
$sSelected = ($iNbItems == $iPageSize) ? 'selected="selected"' : '';
$sCombo .= "<option $sSelected value=\"$iNbItems\">$iNbItems</option>";
}
$sSelected = ($iPageSize < 1) ? 'selected="selected"' : '';
$sCombo .= "<option $sSelected value=\"-1\">".Dict::S('UI:Pagination:All')."</option>";
$sCombo .= '</select>';
$sPages = Dict::S('UI:Pagination:PagesLabel');
$sPageSizeCombo = Dict::Format('UI:Pagination:PageSize', $sCombo);
$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
if ($iNbPages == 1)
{
// No need to display the pager
$sPagerStyle = 'style="display:none"';
}
$aPagesToDisplay = array();
for($idx = 0; $idx <= min(4, $iNbPages-1); $idx++)
{
if ($idx == 0)
{
$aPagesToDisplay[$idx] = '<span page="0" class="curr_page">1</span>';
}
else
{
$aPagesToDisplay[$idx] = "<span id=\"gotopage_$idx\" class=\"gotopage\" page=\"$idx\">".(1+$idx)."</span>";
}
}
$iLastPageIdx = $iNbPages - 1;
if (!isset($aPagesToDisplay[$iLastPageIdx]))
{
unset($aPagesToDisplay[$idx - 1]); // remove the last page added to make room for the very last page
$aPagesToDisplay[$iLastPageIdx] = "<span id=\"gotopage_$iLastPageIdx\" class=\"gotopage\" page=\"$iLastPageIdx\">... $iNbPages</span>";
}
$sPagesLinks = implode('', $aPagesToDisplay);
$sPagesList = '['.implode(',', array_keys($aPagesToDisplay)).']';
$sSelectionMode = ($iNbPages == 1) ? '' : 'positive';
$sHtml =
<<<EOF
<td $sPagerStyle colspan="2">
<table id="pager{$this->iListId}" class="pager"><tr>
<td>$sPages</td>
<td><img src="../images/first.png" class="first"/></td>
<td><img src="../images/prev.png" class="prev"/></td>
<td><span id="index">$sPagesLinks</span></td>
<td><img src="../images/next.png" class="next"/></td>
<td><img src="../images/last.png" class="last"/></td>
<td>$sPageSizeCombo</td>
<td><span id="loading">&nbsp;</span><input type="hidden" name="selectionMode" value="$sSelectionMode"></input>
</td>
</tr>
</table>
</td>
EOF;
return $sHtml;
}
protected function GetActionsMenu(WebPage $oPage, $aExtraParams)
{
$oMenuBlock = new MenuBlock($this->oSet->GetFilter(), 'list');
$sHtml = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId);
return $sHtml;
}
protected function GetToolkitMenu(WebPage $oPage, $aExtraParams)
{
$sMenuTitle = Dict::S('UI:ConfigureThisList');
$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
$aActions = array(
$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
);
$this->oSet->Rewind();
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions);
$this->oSet->Rewind();
$sHtml .= $oPage->RenderPopupMenuItems($aActions);
return $sHtml;
}
protected function GetTableConfigDlg(WebPage $oPage, $aColumns, $bViewLink, $iDefaultPageSize)
{
$sHtml = "<div id=\"datatable_dlg_{$this->iListId}\" style=\"display: none;\">";
$sHtml .= "<form onsubmit=\"return false\">";
$sChecked = ($this->bUseCustomSettings) ? '' : 'checked';
$sHtml .= "<p><input id=\"dtbl_dlg_settings_{$this->iListId}\" type=\"radio\" name=\"settings\" $sChecked value=\"defaults\"><label for=\"dtbl_dlg_settings_{$this->iListId}\">&nbsp;".Dict::S('UI:UseDefaultSettings').'</label></p>';
$sHtml .= "<fieldset>";
$sChecked = ($this->bUseCustomSettings) ? 'checked': '';
$sHtml .= "<legend class=\"transparent\"><input id=\"dtbl_dlg_specific_{$this->iListId}\" type=\"radio\" class=\"specific_settings\" name=\"settings\" $sChecked value=\"specific\"><label for=\"dtbl_dlg_specific_{$this->iListId}\">&nbsp;".Dict::S('UI:UseSpecificSettings')."</label></legend>";
$sHtml .= Dict::S('UI:ColumnsAndSortOrder').'<br/><ul class="sortable_field_list" id="sfl_'.$this->iListId.'"></ul>';
$sHtml .= '<p>'.Dict::Format('UI:Display_X_ItemsPerPage', '<input type="text" size="4" name="page_size" value="'.$iDefaultPageSize.'">').'</p>';
$sHtml .= "</fieldset>";
$sHtml .= "<fieldset>";
$sSaveChecked = ($this->sTableId != null) ? 'checked' : '';
$sCustomDisabled = ($this->sTableId == null) ? 'disabled="disabled" stay-disabled="true" ' : '';
$sCustomChecked = ($this->sTableId != null) ? 'checked' : '';
$sGenericChecked = ($this->sTableId == null) ? 'checked' : '';
$sHtml .= "<legend class=\"transparent\"><input id=\"dtbl_dlg_save_{$this->iListId}\" type=\"checkbox\" $sSaveChecked name=\"save_settings\"><label for=\"dtbl_dlg_save_{$this->iListId}\">&nbsp;".Dict::S('UI:UseSavetheSettings')."</label></legend>";
$sHtml .= "<p><input id=\"dtbl_dlg_this_list_{$this->iListId}\" type=\"radio\" name=\"scope\" $sCustomChecked $sCustomDisabled value=\"this_list\"><label for=\"dtbl_dlg_this_list_{$this->iListId}\">&nbsp;".Dict::S('UI:OnlyForThisList').'</label>&nbsp;&nbsp;&nbsp;&nbsp;';
$sHtml .= "<input id=\"dtbl_dlg_all_{$this->iListId}\" type=\"radio\" name=\"scope\" $sGenericChecked value=\"defaults\"><label for=\"dtbl_dlg_all_{$this->iListId}\">&nbsp;".Dict::S('UI:ForAllLists').'</label></p>';
$sHtml .= "</fieldset>";
$sHtml .= '<table style="width:100%"><tr><td style="text-align:center;">';
$sHtml .= '<button type="button" onclick="$(\'#datatable_'.$this->iListId.'\').datatable(\'onDlgCancel\'); $(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\')">'.Dict::S('UI:Button:Cancel').'</button>';
$sHtml .= '</td><td style="text-align:center;">';
$sHtml .= '<button type="submit" onclick="$(\'#datatable_'.$this->iListId.'\').datatable(\'onDlgOk\');$(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\');">'.Dict::S('UI:Button:Ok').'</button>';
$sHtml .= '</td></tr></table>';
$sHtml .= "</form>";
$sHtml .= "</div>";
$sDlgTitle = addslashes(Dict::S('UI:ListConfigurationTitle'));
$oPage->add_ready_script("$('#datatable_dlg_{$this->iListId}').dialog({autoOpen: false, title: '$sDlgTitle', width: 500, close: function() { $('#datatable_{$this->iListId}').datatable('onDlgCancel'); } });");
return $sHtml;
}
public function GetAsHash($oSetting)
{
$aSettings = array('iDefaultPageSize' => $oSetting->iDefaultPageSize, 'oColumns' => $oSetting->aColumns);
return $aSettings;
}
protected function GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink)
{
$aAttribs = array();
if ($sSelectMode == 'multiple')
{
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->iListId}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
}
else if ($sSelectMode == 'single')
{
$aAttribs['form::select'] = array('label' => "", 'description' => '');
}
foreach($this->aClassAliases as $sAlias => $sClassName)
{
foreach($aColumns[$sAlias] as $sAttCode => $aData)
{
if ($aData['checked'])
{
if ($sAttCode == '_key_')
{
$aAttribs['key_'.$sAlias] = array('label' => MetaModel::GetName($sClassName), 'description' => '');
}
else
{
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
$aAttribs[$sAttCode.'_'.$sAlias] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => $oAttDef->GetOrderByHint());
}
}
}
}
return $aAttribs;
}
protected function GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
{
$aValues = array();
$this->oSet->Seek(0);
$iMaxObjects = $iPageSize;
while (($aObjects = $this->oSet->FetchAssoc()) && ($iMaxObjects != 0))
{
$bFirstObject = true;
$aRow = array();
foreach($this->aClassAliases as $sAlias => $sClassName)
{
$sHilightClass = $aObjects[$sAlias]->GetHilightClass();
if ($sHilightClass != '')
{
$aRow['@class'] = $sHilightClass;
}
if ((($sSelectMode == 'single') || ($sSelectMode == 'multiple')) && $bFirstObject)
{
if (array_key_exists('selection_enabled', $aExtraParams) && isset($aExtraParams['selection_enabled'][$aObjects[$sAlias]->GetKey()]))
{
$sDisabled = ($aExtraParams['selection_enabled'][$aObjects[$sAlias]->GetKey()]) ? '' : ' disabled="disabled"';
}
else
{
$sDisabled = '';
}
if ($sSelectMode == 'single')
{
$aRow['form::select'] = "<input type=\"radio\" $sDisabled class=\"selectList{$this->iListId}\" name=\"selectObject\" value=\"".$aObjects[$sAlias]->GetKey()."\"></input>";
}
else
{
$aRow['form::select'] = "<input type=\"checkBox\" $sDisabled class=\"selectList{$this->iListId}\" name=\"selectObject[]\" value=\"".$aObjects[$sAlias]->GetKey()."\"></input>";
}
}
foreach($aColumns[$sAlias] as $sAttCode => $aData)
{
if ($aData['checked'])
{
if ($sAttCode == '_key_')
{
$aRow['key_'.$sAlias] = $aObjects[$sAlias]->GetHyperLink();
}
else
{
$aRow[$sAttCode.'_'.$sAlias] = $aObjects[$sAlias]->GetAsHTML($sAttCode);
}
}
}
$bFirstObject = false;
}
$aValues[] = $aRow;
$iMaxObjects--;
}
return $aValues;
}
public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
{
$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
if ($iPageSize < 1)
{
$iPageSize = -1; // convention: no pagination
}
$aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
$aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
$sHtml = '<table class="listContainer">';
foreach($this->oSet->GetFilter()->GetInternalParams() as $sName => $sValue)
{
$aExtraParams['query_params'][$sName] = $sValue;
}
$sHtml .= "<tr><td>";
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
$sHtml .= '</td></tr>';
$sHtml .= '</table>';
$iCount = $this->iNbObjects;
$aArgs = $this->oSet->GetArgs();
$sExtraParams = addslashes(str_replace('"', "'", json_encode(array_merge($aExtraParams, $aArgs)))); // JSON encode, change the style of the quotes and escape them
$sSelectModeJS = '';
$sHeaders = '';
if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
{
$sSelectModeJS = $sSelectMode;
$sHeaders = 'headers: { 0: {sorter: false}},';
}
$sDisplayKey = ($bViewLink) ? 'true' : 'false';
// Protect against duplicate elements in the Zlist
$aUniqueOrderedList = array();
foreach($this->aClassAliases as $sAlias => $sClassName)
{
foreach($aColumns[$sAlias] as $sAttCode => $aData)
{
if ($aData['checked'])
{
$aUniqueOrderedList[$sAttCode] = true;
}
}
}
$aUniqueOrderedList = array_keys($aUniqueOrderedList);
$sJSColumns = json_encode($aColumns);
$sJSClassAliases = json_encode($this->aClassAliases);
$sCssCount = isset($aExtraParams['cssCount']) ? ", cssCount: '{$aExtraParams['cssCount']}'" : '';
$this->oSet->ApplyParameters();
// Display the actual sort order of the table
$aRealSortOrder = $this->oSet->GetRealSortOrder();
$aDefaultSort = array();
$iColOffset = 0;
if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
{
$iColOffset += 1;
}
if ($bViewLink)
{
// $iColOffset += 1;
}
foreach($aRealSortOrder as $sColCode => $bAscending)
{
$iPos = array_search($sColCode, $aUniqueOrderedList);
if ($iPos !== false)
{
$aDefaultSort[] = "[".($iColOffset+$iPos).",".($bAscending ? '0' : '1')."]";
}
else if (($iPos = array_search(preg_replace('/_friendlyname$/', '', $sColCode), $aUniqueOrderedList)) !== false)
{
// if sorted on the friendly name of an external key, then consider it sorted on the column that shows the links
$aDefaultSort[] = "[".($iColOffset+$iPos).",".($bAscending ? '0' : '1')."]";
}
else if($sColCode == 'friendlyname' && $bViewLink)
{
$aDefaultSort[] = "[".($iColOffset).",".($bAscending ? '0' : '1')."]";
}
}
$sSortList = '';
if (count($aDefaultSort) > 0)
{
$sSortList = ', sortList: ['.implode(',', $aDefaultSort).']';
}
$sOQL = addslashes($this->oSet->GetFilter()->serialize());
$oPage->add_ready_script(
<<<EOF
var oTable = $('#{$this->iListId} table.listResults');
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList'] $sSortList} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
EOF
);
//if ($iNbPages == 1)
if (false)
{
if (isset($aExtraParams['cssCount']))
{
$sCssCount = $aExtraParams['cssCount'];
if ($sSelectMode == 'single')
{
$sSelectSelector = ":radio[name^=selectObj]";
}
else if ($sSelectMode == 'multiple')
{
$sSelectSelector = ":checkbox[name^=selectObj]";
}
$oPage->add_ready_script(
<<<EOF
$('#{$this->iListId} table.listResults $sSelectSelector').change(function() {
var c = $('{$sCssCount}');
var v = $('#{$this->iListId} table.listResults $sSelectSelector:checked').length;
c.val(v);
$('#{$this->iListId} .selectedCount').text(v);
c.trigger('change');
});
EOF
);
}
}
return $sHtml;
}
public function UpdatePager(WebPage $oPage, $iDefaultPageSize, $iStart)
{
$iPageSize = ($iDefaultPageSize < 1) ? 1 : $iDefaultPageSize;
$iPageIndex = 1 + floor($iStart / $iPageSize);
$sHtml = $this->GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex);
$oPage->add_ready_script("$('#pager{$this->iListId}').html('".str_replace("\n", ' ', addslashes($sHtml))."');");
if ($iDefaultPageSize < 1)
{
$oPage->add_ready_script("$('#pager{$this->iListId}').parent().hide()");
}
else
{
$oPage->add_ready_script("$('#pager{$this->iListId}').parent().show()");
}
}
}
class DataTableSettings implements Serializable
{
public $aClassAliases;
public $sTableId;
public $iDefaultPageSize;
public $aColumns;
public function __construct($aClassAliases, $sTableId = null)
{
$this->aClassAliases = $aClassAliases;
$this->sTableId = $sTableId;
$this->iDefaultPageSize = 10;
$this->aColumns = array();
}
protected function Init($iDefaultPageSize, $aSortOrder, $aColumns)
{
$this->iDefaultPageSize = $iDefaultPageSize;
$this->aColumns = $aColumns;
$this->FixVisibleColumns();
}
public function serialize()
{
// Save only the 'visible' columns
$aColumns = array();
foreach($this->aClassAliases as $sAlias => $sClass)
{
$aColumns[$sAlias] = array();
foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
{
unset($aData['label']); // Don't save the display name
unset($aData['alias']); // Don't save the alias (redundant)
unset($aData['code']); // Don't save the code (redundant)
if ($aData['checked'])
{
$aColumns[$sAlias][$sAttCode] = $aData;
}
}
}
return serialize(
array(
'iDefaultPageSize' => $this->iDefaultPageSize,
'aColumns' => $aColumns,
)
);
}
public function unserialize($sData)
{
$aData = unserialize($sData);
$this->iDefaultPageSize = $aData['iDefaultPageSize'];
$this->aColumns = $aData['aColumns'];
foreach($this->aClassAliases as $sAlias => $sClass)
{
foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
{
$aFieldData = false;
if ($sAttCode == '_key_')
{
$aFieldData = $this->GetFieldData($sAlias, $sAttCode, null, true /* bChecked */, $aData['sort']);
}
else if (MetaModel::isValidAttCode($sClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $aData['sort']);
}
if ($aFieldData)
{
$this->aColumns[$sAlias][$sAttCode] = $aFieldData;
}
else
{
unset($this->aColumns[$sAlias][$sAttCode]);
}
}
}
$this->FixVisibleColumns();
}
static public function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists)
{
$oSettings = new DataTableSettings($aClassAliases);
// Retrieve the class specific settings for each class/alias based on the 'list' ZList
//TODO let the caller pass some other default settings (another Zlist, extre fields...)
$aColumns = array();
foreach($aClassAliases as $sAlias => $sClass)
{
if ($aDefaultLists == null)
{
$aList = cmdbAbstract::FlattenZList(MetaModel::GetZListItems($sClass, 'list'));
}
else
{
$aList = $aDefaultLists[$sAlias];
}
$aSortOrder = MetaModel::GetOrderByDefault($sClass);
if ($bViewLink)
{
$sSort = 'none';
if(array_key_exists('friendlyname', $aSortOrder))
{
$sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc';
}
$aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort);
}
foreach($aList as $sAttCode)
{
$sSort = 'none';
if(array_key_exists($sAttCode, $aSortOrder))
{
$sSort = $aSortOrder[$sAttCode] ? 'asc' : 'desc';
}
$oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode);
$aFieldData = $oSettings->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $sSort);
if ($aFieldData) $aColumns[$sAlias][$sAttCode] = $aFieldData;
}
}
$iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oSettings->Init($iDefaultPageSize, $aSortOrder, $aColumns);
return $oSettings;
}
protected function FixVisibleColumns()
{
foreach($this->aClassAliases as $sAlias => $sClass)
{
foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
{
// Remove non-existent columns
// TODO: check if the existing ones are still valid (in case their type changed)
if (($sAttCode != '_key_') && (!MetaModel::IsValidAttCode($sClass, $sAttCode)))
{
unset($this->aColumns[$sAlias][$sAttCode]);
}
}
$aList = MetaModel::ListAttributeDefs($sClass);
// Add the other (non visible ones), sorted in alphabetical order
$aTempData = array();
foreach($aList as $sAttCode => $oAttDef)
{
if ( (!array_key_exists($sAttCode, $this->aColumns[$sAlias])) && (!$oAttDef instanceof AttributeLinkSet))
{
$aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, false /* bChecked */, 'none');
if ($aFieldData) $aTempData[$aFieldData['label']] = $aFieldData;
}
}
ksort($aTempData);
foreach($aTempData as $sLabel => $aFieldData)
{
$this->aColumns[$sAlias][$aFieldData['code']] = $aFieldData;
}
}
}
static public function GetTableSettings($aClassAliases, $sTableId = null)
{
$pref = null;
$oSettings = new DataTableSettings($aClassAliases, $sTableId);
if ($sTableId != null)
{
// An identified table, let's fetch its own settings (if any)
$pref = appUserPreferences::GetPref($oSettings->GetPrefsKey($sTableId), null);
}
if ($pref == null)
{
// Try the global preferred values for this class / set of classes
$pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null);
if ($pref == null)
{
// no such settings, use the default values provided by the data model
return null;
}
}
$oSettings->unserialize($pref);
return $oSettings;
}
public function GetSortOrder()
{
$aSortOrder = array();
foreach($this->aColumns as $sAlias => $aColumns)
{
foreach($aColumns as $aColumn)
{
if ($aColumn['sort'] != 'none')
{
$sCode = ($aColumn['code'] == '_key_') ? 'friendlyname' : $aColumn['code'];
$aSortOrder[$sCode] = ($aColumn['sort']=='asc'); // true for ascending, false for descending
}
}
break; // TODO: For now the Set object supports only sorting on the first class of the set
}
return $aSortOrder;
}
public function Save()
{
if ($this->sTableId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead
$sSettings = $this->serialize();
appUserPreferences::SetPref($this->GetPrefsKey($this->sTableId), $sSettings);
return true;
}
public function SaveAsDefault()
{
$sSettings = $this->serialize();
appUserPreferences::SetPref($this->GetPrefsKey(null), $sSettings);
return true;
}
/**
* Clear the preferences for this particular table
* @param $bResetAll boolean If true,the settings for all tables of the same class(es)/alias(es) are reset
*/
public function ResetToDefault($bResetAll)
{
if (($this->sTableId == null) && (!$bResetAll)) return false; // Cannot reset, the table is not identified, use force $bResetAll instead
if ($bResetAll)
{
// Turn the key into a suitable PCRE pattern
$sKey = $this->GetPrefsKey(null);
$sPattern = str_replace(array('|'), array('\\|'), $sKey); // escape the | character
$sPattern = '#^'.str_replace(array('*'), array('.*'), $sPattern).'$#'; // Don't use slash as the delimiter since it's used in our key to delimit aliases
appUserPreferences::UnsetPref($sPattern, true);
}
else
{
appUserPreferences::UnsetPref($this->GetPrefsKey($this->sTableId), false);
}
return true;
}
protected function GetPrefsKey($sTableId = null)
{
if ($sTableId == null) $sTableId = '*';
$aKeys = array();
foreach($this->aClassAliases as $sAlias => $sClass)
{
$aKeys[] = $sAlias.'-'.$sClass;
}
return implode('/', $aKeys).'|'.$sTableId;
}
protected function GetFieldData($sAlias, $sAttCode, $oAttDef, $bChecked, $sSort)
{
$ret = false;
if ($sAttCode == '_key_')
{
$sLabel = Dict::Format('UI:ExtKey_AsLink', MetaModel::GetName($this->aClassAliases[$sAlias]));
$ret = array(
'label' => $sLabel,
'checked' => true,
'disabled' => true,
'alias' => $sAlias,
'code' => $sAttCode,
'sort' => $sSort,
);
}
else if (!$oAttDef->IsLinkSet())
{
$sLabel = $oAttDef->GetLabel();
if ($oAttDef->IsExternalKey())
{
$sLabel = Dict::Format('UI:ExtKey_AsLink', $oAttDef->GetLabel());
}
else if ($oAttDef->IsExternalField())
{
$oExtAttDef = $oAttDef->GetExtAttDef();
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
}
elseif ($oAttDef instanceof AttributeFriendlyName)
{
$sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
}
$ret = array(
'label' => $sLabel,
'checked' => $bChecked,
'disabled' => false,
'alias' => $sAlias,
'code' => $sAttCode,
'sort' => $sSort,
);
}
return $ret;
}
}

View File

@@ -44,6 +44,7 @@ class DisplayBlock
{
const TAG_BLOCK = 'itopblock';
protected $m_oFilter;
protected $m_aConditions; // Conditions added to the filter -> avoid duplicate conditions
protected $m_sStyle;
protected $m_bAsynchronous;
protected $m_aParams;
@@ -51,7 +52,8 @@ class DisplayBlock
public function __construct(DBObjectSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null)
{
$this->m_oFilter = $oFilter;
$this->m_oFilter = clone $oFilter;
$this->m_aConditions = array();
$this->m_sStyle = $sStyle;
$this->m_bAsynchronous = $bAsynchronous;
$this->m_aParams = $aParams;
@@ -252,7 +254,7 @@ class DisplayBlock
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
if (empty($aExtraParams['currentId']))
if (!isset($aExtraParams['currentId']))
{
$sId = $oPage->GetUniqueId(); // Works only if the page is not an Ajax one !
}
@@ -281,15 +283,18 @@ class DisplayBlock
$sClass = $this->m_oFilter->GetClass();
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($sClass));
$aCallSpec = array($sClass, 'MapContextParam');
foreach($oAppContext->GetNames() as $sContextParam)
if (is_callable($aCallSpec))
{
$sParamCode = call_user_func($aCallSpec, $sContextParam); //Map context parameter to the value/filter code depending on the class
if (!is_null($sParamCode))
foreach($oAppContext->GetNames() as $sContextParam)
{
$sParamValue = $oAppContext->GetCurrentValue($sContextParam, null);
if (!is_null($sParamValue))
$sParamCode = call_user_func($aCallSpec, $sContextParam); //Map context parameter to the value/filter code depending on the class
if (!is_null($sParamCode))
{
$aExtraParams[$sParamCode] = $sParamValue;
$sParamValue = $oAppContext->GetCurrentValue($sContextParam, null);
if (!is_null($sParamValue))
{
$aExtraParams[$sParamCode] = $sParamValue;
}
}
}
}
@@ -314,6 +319,15 @@ class DisplayBlock
$this->AddCondition($sFilterCode, $condition);
}
}
if ($bDoSearch)
{
// Keep the table_id identifying this table if we're performing a search
$sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
if ($sTableId != null)
{
$aExtraParams['table_id'] = $sTableId;
}
}
}
$aOrderBy = array();
if (isset($aExtraParams['order_by']))
@@ -344,34 +358,55 @@ class DisplayBlock
case 'count':
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
$sLabels = array();
$iTotalCount = $this->m_oSet->Count();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$sLabels[$sValue] = $oObj->GetAsHtml($sGroupByField);
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
else
{
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$aLabels = array();
$aValues = array();
$iTotalCount = 0;
foreach ($aRes as $iRow => $aRow)
{
$sValue = $aRow['grouped_by_1'];
$aValues[$iRow] = $sValue;
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$aLabels[$iRow] = $sHtmlValue;
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
foreach($aGroupBy as $sValue => $iCount)
foreach($aGroupBy as $iRow => $iCount)
{
$aData[] = array ( 'group' => $sLabels[$sValue],
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter&$sGroupByField=".urlencode($sValue)."\">$iCount</a>"); // TO DO: add the context information
// Build the search for this subset
$oSubsetSearch = clone $this->m_oFilter;
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($aValues[$iRow]));
$oSubsetSearch->AddConditionExpression($oCondition);
$sFilter = urlencode($oSubsetSearch->serialize());
$aData[] = array ( 'group' => $aLabels[$iRow],
'value' => "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter\">$iCount</a>"); // TO DO: add the context information
}
$aAttribs =array(
'group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''),
'group' => array('label' => $sGroupByLabel, 'description' => ''),
'value' => array('label'=> Dict::S('UI:GroupBy:Count'), 'description' => Dict::S('UI:GroupBy:Count+'))
);
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
@@ -621,9 +656,10 @@ class DisplayBlock
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
}
$iCount = $this->m_oSet->Count();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$this->m_oFilter->serialize();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize());
$sHtml .= '<p><a class="actions" href="'.$sHyperlink.'">';
$sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;');
// Note: border set to 0 due to various browser interpretations (IE9 adding a 2px border)
$sHtml .= MetaModel::GetClassIcon($sClass, true, 'float;left;margin-right:10px;border:0;');
$sHtml .= MetaModel::GetName($sClass).': '.$iCount.'</a></p>';
$sParams = $oAppContext->GetForLink();
$sHtml .= '<p>';
@@ -682,7 +718,7 @@ class DisplayBlock
}
else
{
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$oFilter->serialize();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($oFilter->serialize());
$aCounts[$sStateValue] = "<a href=\"$sHyperlink\">{$aCounts[$sStateValue]}</a>";
}
}
@@ -691,7 +727,7 @@ class DisplayBlock
$sHtml .= '<tr><td>'.implode('</td><td>', $aCounts).'</td></tr></table></div>';
// Title & summary
$iCount = $this->m_oSet->Count();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.$this->m_oFilter->serialize();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.urlencode($this->m_oFilter->serialize());
$sHtml .= '<h1>'.Dict::S(str_replace('_', ':', $sTitle)).'</h1>';
$sHtml .= '<a class="summary" href="'.$sHyperlink.'">'.Dict::Format(str_replace('_', ':', $sLabel), $iCount).'</a>';
break;
@@ -743,43 +779,70 @@ EOF
$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
$sGroupByExpr = isset($aExtraParams['group_by_expr']) ? '&params[group_by_expr]='.$aExtraParams['group_by_expr'] : '';
$sFilter = $this->m_oFilter->serialize();
$sHtml .= "<div id=\"my_chart_{$iChartCounter}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\n";
$sHtml .= "<div id=\"my_chart_$sId{$iChartCounter}\">If the chart does not display, <a href=\"http://get.adobe.com/flash/\" target=\"_blank\">install Flash</a></div>\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(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&id=$sId&filter=".$sFilter)."\"}, {wmode: 'transparent'} );\n");
if (isset($aExtraParams['group_by_label']))
{
$sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[group_by_label]={$aExtraParams['group_by_label']}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId&id=$sId&filter=".urlencode($sFilter));
}
else
{
$sUrl = urlencode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[chart_title]=$sTitle&params[currentId]=$sId&id=$sId&filter=".urlencode($sFilter));
}
$oPage->add_ready_script("swfobject.embedSWF(\"../images/open-flash-chart.swf\", \"my_chart_$sId{$iChartCounter}\", \"100%\", \"300\",\"9.0.0\", \"expressInstall.swf\",
{\"data-file\":\"".$sUrl."\"}, {wmode: 'transparent'} );\n");
$iChartCounter++;
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
$aData = array();
else
{
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$aLabels = array();
$aValues = array();
$iTotalCount = 0;
foreach ($aRes as $iRow => $aRow)
{
$sValue = $aRow['grouped_by_1'];
$aValues[$iRow] = $sValue;
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$aLabels[$iRow] = $sHtmlValue;
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
$idx = 0;
$aURLs = array();
foreach($aGroupBy as $sValue => $iValue)
foreach($aGroupBy as $iRow => $iCount)
{
$oDrillDownFilter = clone $this->m_oFilter;
$oDrillDownFilter->AddCondition($sGroupByField, $sValue, '=');
$aURLs[$idx] = $oDrillDownFilter->serialize();
// Build the search for this subset
$oSubsetSearch = clone $this->m_oFilter;
$oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($aValues[$iRow]));
$oSubsetSearch->AddConditionExpression($oCondition);
$aURLs[$idx] = $oSubsetSearch->serialize();
$idx++;
}
$sURLList = '';
foreach($aURLs as $index => $sURL)
{
$sURLList .= "\taURLs[$index] = '".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".addslashes($sURL)."';\n";
$sURLList .= "\taURLs[$index] = '".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".urlencode($sURL)."';\n";
}
$oPage->add_script(
<<<EOF
@@ -807,31 +870,46 @@ EOF
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
$aData = array();
$aLabels = array();
$maxValue = 0;
foreach($aGroupBy as $sValue => $iValue)
else
{
$oBarValue = new bar_value($iValue);
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$aLabels = array();
$iTotalCount = 0;
foreach ($aRes as $iRow => $aRow)
{
$sValue = $aRow['grouped_by_1'];
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$aLabels[$iRow] = strip_tags($sHtmlValue);
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
$aChartLabels = array();
$maxValue = 0;
foreach($aGroupBy as $iRow => $iCount)
{
$oBarValue = new bar_value($iCount);
$oBarValue->on_click("ofc_drill_down_$sId");
$aData[] = $oBarValue;
if ($iValue > $maxValue) $maxValue = $iValue;
$aLabels[] = $sValue;
if ($iCount > $maxValue) $maxValue = $iCount;
$aChartLabels[] = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
}
$oYAxis = new y_axis();
$aMagicValues = array(1,2,5,10);
@@ -857,7 +935,7 @@ EOF
// set them vertical
$oXLabels->set_vertical();
// set the label text
$oXLabels->set_labels($aLabels);
$oXLabels->set_labels($aChartLabels);
// Add the X Axis Labels to the X Axis
$oXAxis->set_labels( $oXLabels );
$oChart->set_x_axis( $oXAxis );
@@ -873,37 +951,64 @@ EOF
$oChartElement->set_colours( array('#FF8A00', '#909980', '#2C2B33', '#CCC08D', '#596664') );
if (isset($aExtraParams['group_by']))
{
$sGroupByField = $aExtraParams['group_by'];
$aGroupBy = array();
while($oObj = $this->m_oSet->Fetch())
if (isset($aExtraParams['group_by_label']))
{
if (isset($aExtraParams['group_by_expr']))
{
eval("\$sValue = ".sprintf($aExtraParams['group_by_expr'], $oObj->Get($sGroupByField)).';');
}
else
{
$sValue = $oObj->Get($sGroupByField);
}
$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
$sFilter = urlencode($this->m_oFilter->serialize());
$aData = array();
foreach($aGroupBy as $sValue => $iValue)
else
{
$PieValue = new pie_value($iValue, $sValue); //@@ BUG: not passed via ajax !!!
// Backward compatibility: group_by is simply a field id
$sAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
$aGroupBy = array();
$aLabels = array();
$iTotalCount = 0;
foreach ($aRes as $iRow => $aRow)
{
$sValue = $aRow['grouped_by_1'];
$sHtmlValue = $oGroupByExp->MakeValueLabel($this->m_oFilter, $sValue, $sValue);
$aLabels[$iRow] = strip_tags($sHtmlValue);
$aGroupBy[$iRow] = (int) $aRow['_itop_count_'];
$iTotalCount += $aRow['_itop_count_'];
}
$aData = array();
foreach($aGroupBy as $iRow => $iCount)
{
$sFlashLabel = html_entity_decode($aLabels[$iRow], ENT_QUOTES, 'UTF-8');
$PieValue = new pie_value($iCount, $sFlashLabel); //@@ BUG: not passed via ajax !!!
$PieValue->on_click("ofc_drill_down_$sId");
$aData[] = $PieValue;
}
$oChartElement->set_values( $aData );
$oChart->x_axis = null;
}
}
if (isset($aExtraParams['chart_title']))
{
$oTitle = new title( Dict::S($aExtraParams['chart_title']) );
// The title has been given in an url, and urlencoded...
// and urlencode transforms utf-8 into something similar to ISO-8859-1
// Example: é (C3A9 becomes %E9)
// As a consequence, json_encode (called within open-flash-chart.php)
// was returning 'null' and the graph was not displayed at all
// To make sure that the graph is displayed AND to get a correct title
// (at least for european characters) let's transform back into utf-8 !
$sTitle = iconv("ISO-8859-1", "UTF-8//IGNORE", $aExtraParams['chart_title']);
// If the title is a dictionnary entry, fetch it
$sTitle = Dict::S($sTitle);
$oTitle = new title($sTitle);
$oChart->set_title( $oTitle );
}
$oChart->set_bg_colour('#FFFFFF');
@@ -925,6 +1030,15 @@ EOF
*/
protected function AddCondition($sFilterCode, $condition)
{
// Workaround to an issue revealed whenever a condition on org_id is applied twice (with a hierarchy of organizations)
// Moreover, it keeps the query as simple as possible
if (isset($this->m_aConditions[$sFilterCode]) && $condition == $this->m_aConditions[$sFilterCode])
{
// Skip
return;
}
$this->m_aConditions[$sFilterCode] = $condition;
$sClass = $this->m_oFilter->GetClass();
$bConditionAdded = false;
@@ -1041,8 +1155,13 @@ class HistoryBlock extends DisplayBlock
}
}
/**
* Displays the 'Actions' menu for a given (list of) object(s)
* The 'style' of the list (see constructor of DisplayBlock) can be either 'list' or 'details'
* For backward compatibility 'popup' is equivalent to 'list'...
*/
class MenuBlock extends DisplayBlock
{
{
/**
* Renders the "Actions" popup menu for the given set of objects
*
@@ -1054,6 +1173,10 @@ class MenuBlock extends DisplayBlock
*/
public function GetRenderContent(WebPage $oPage, $aExtraParams = array(), $sId)
{
if ($this->m_sStyle == 'popup') // popup is a synonym of 'list' for backward compatibility
{
$this->m_sStyle = 'list';
}
$sHtml = '';
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
@@ -1064,7 +1187,7 @@ class MenuBlock extends DisplayBlock
$sClass = $this->m_oFilter->GetClass();
$oSet = new CMDBObjectSet($this->m_oFilter);
$sFilter = $this->m_oFilter->serialize();
$sFilterDesc = $this->m_oFilter->ToOql();
$sFilterDesc = $this->m_oFilter->ToOql(true);
$aActions = array();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
@@ -1081,12 +1204,12 @@ class MenuBlock extends DisplayBlock
$sDefault.= "&default[$sKey]=$sValue";
}
}
$bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) == UR_ALLOWED_YES);
switch($oSet->Count())
{
case 0:
// No object in the set, the only possible action is "new"
$bIsModifyAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES);
if ($bIsModifyAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
break;
case 1:
@@ -1100,14 +1223,14 @@ class MenuBlock extends DisplayBlock
if (!isset($aExtraParams['link_attr']))
{
if ($bIsModifyAllowed) { $aActions['UI:Menu:Modify'] = array ('label' => Dict::S('UI:Menu:Modify'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#"); }
if ($bIsModifyAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsDeleteAllowed) { $aActions['UI:Menu:Delete'] = array ('label' => Dict::S('UI:Menu:Delete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}"); }
// Transitions / Stimuli
$aTransitions = $oObj->EnumTransitions();
if (count($aTransitions))
{
$this->AddMenuSeparator($aActions);
$aStimuli = Metamodel::EnumStimuli($sClass);
$aStimuli = Metamodel::EnumStimuli(get_class($oObj));
foreach($aTransitions as $sStimulusCode => $aTransitionDef)
{
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet) : UR_ALLOWED_NO;
@@ -1136,7 +1259,14 @@ class MenuBlock extends DisplayBlock
// Static menus: Email this page & CSV Export
$sUrl = ApplicationContext::MakeObjectUrl($sClass, $id);
$aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl));
$aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=$sFilter&format=csv{$sContext}");
$aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv{$sContext}");
// The style tells us whether the menu is displayed on a list of one object, or on the details of the given object
if ($this->m_sStyle == 'list')
{
// Actions specific to the list
$sOQL = addslashes($sFilterDesc);
$aActions['UI:Menu:AddToDashboard'] = array ('label' => Dict::S('UI:Menu:AddToDashboard'), 'url' => "#", 'onclick' => "return DashletCreationDlg('$sOQL')");
}
}
$this->AddMenuSeparator($aActions);
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
@@ -1169,9 +1299,9 @@ class MenuBlock extends DisplayBlock
else
{
// many objects in the set, possible actions are: new / modify all / delete all
if ($bIsModifyAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:ModifyAll'] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=$sFilter{$sContext}"); }
if ($bIsBulkDeleteAllowed) { $aActions['UI:Menu:BulkDelete'] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=$sFilter{$sContext}"); }
if ($bIsCreationAllowed) { $aActions['UI:Menu:New'] = array ('label' => Dict::S('UI:Menu:New'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}"); }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:ModifyAll'] = array ('label' => Dict::S('UI:Menu:ModifyAll'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)."{$sContext}"); }
if ($bIsBulkDeleteAllowed) { $aActions['UI:Menu:BulkDelete'] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=".urlencode($sFilter)."{$sContext}"); }
// Stimuli
$aStates = MetaModel::EnumStates($sClass);
@@ -1202,7 +1332,7 @@ class MenuBlock extends DisplayBlock
{
case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS:
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=$sFilter{$sContext}");
$aActions[$sStimulusCode] = array('label' => $aStimuli[$sStimulusCode]->GetLabel(), 'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}");
break;
default:
@@ -1214,18 +1344,48 @@ class MenuBlock extends DisplayBlock
}
$this->AddMenuSeparator($aActions);
$sUrl = utils::GetAbsoluteUrlAppRoot();
$aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=$sFilterDesc&body=".urlencode("{$sUrl}pages/$sUIPage?operation=search&filter=$sFilter{$sContext}"));
$aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=$sFilter&format=csv{$sContext}");
$aActions['UI:Menu:EMail'] = array ('label' => Dict::S('UI:Menu:EMail'), 'url' => "mailto:?subject=$sFilterDesc&body=".urlencode("{$sUrl}pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."{$sContext}"));
$aActions['UI:Menu:CSVExport'] = array ('label' => Dict::S('UI:Menu:CSVExport'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=search&filter=".urlencode($sFilter)."&format=csv{$sContext}");
$sOQL = addslashes($sFilterDesc);
$aActions['UI:Menu:AddToDashboard'] = array ('label' => Dict::S('UI:Menu:AddToDashboard'), 'url' => "#", 'onclick' => "return DashletCreationDlg('$sOQL')");
}
$this->AddMenuSeparator($aActions);
foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
{
$oSet->Rewind();
foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $sUrl)
foreach($oExtensionInstance->EnumAllowedActions($oSet) as $sLabel => $data)
{
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $sUrl);
if (is_array($data))
{
// New plugins can provide javascript handlers via the 'onclick' property
//TODO: enable extension of different menus by checking the 'target' property ??
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => isset($data['url']) ? $data['url'] : '#', 'onclick' => isset($data['onclick']) ? $data['onclick'] : '');
}
else
{
// Backward compatibility with old plugins
$aActions[$sLabel] = array ('label' => $sLabel, 'url' => $data);
}
}
}
// New extensions based on iPopupMenuItem interface
switch($this->m_sStyle)
{
case 'list':
$oSet->Rewind();
$param = $oSet;
$iMenuId = iPopupMenuExtension::MENU_OBJLIST_ACTIONS;
break;
case 'details':
$oSet->Rewind();
$param = $oSet->Fetch();
$iMenuId = iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS;
break;
}
utils::GetPopupMenuItems($oPage, $iMenuId, $param, $aActions);
}
$aFavoriteActions = array();
$aCallSpec = array($sClass, 'GetShortcutActions');
@@ -1248,42 +1408,15 @@ class MenuBlock extends DisplayBlock
if (count($aFavoriteActions) > 0)
{
$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."\n<ul>\n";
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."\n<ul>\n";
}
else
{
$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
}
$sPrevUrl = '';
foreach ($aActions as $key => $aAction)
{
if (in_array($key, $aShortcutActions))
{
$aFavoriteActions[] = $aAction;
}
else
{
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
if (empty($aAction['url']))
{
if ($sPrevUrl != '') // Don't output consecutively two separators...
{
$sHtml .= "<li>{$aAction['label']}</li>\n";
}
$sPrevUrl = '';
}
else
{
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n";
$sPrevUrl = $aAction['url'];
}
}
}
$sHtml .= "</ul>\n</li>\n</ul></div>";
foreach(array_reverse($aFavoriteActions) as $aAction)
{
$sHtml .= "<div class=\"actions_button\"><a href='{$aAction['url']}'>{$aAction['label']}</a></div>";
$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
}
$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);
static $bPopupScript = false;
if (!$bPopupScript)
{
@@ -1313,4 +1446,87 @@ class MenuBlock extends DisplayBlock
}
}
}
?>
/**
* Some dummy menus for testing
*/
class ExtraMenus implements iPopupMenuExtension
{
/*
const MENU_OBJLIST_ACTIONS = 1; // $param is a DBObjectSet containing the list of objects
const MENU_OBJLIST_TOOLKIT = 2; // $param is a DBObjectSet containing the list of objects
const MENU_OBJDETAILS_ACTIONS = 3; // $param is a DBObject instance: the object currently displayed
const MENU_DASHBOARD_ACTIONS = 4; // $param is a Dashboard instance: the dashboard currently displayed
const MENU_USER_ACTIONS = 5; // $param is a null ??
*/
/**
* Get the list of items to be added to a menu. The items will be inserted in the menu in the order of the returned array
* @param int $iMenuId The identifier of the type of menu, as listed by the constants MENU_xxx above
* @param mixed $param Depends on $iMenuId see the constants define above
* @return Array An array of ApplicationPopupMenuItem or an empty array if no action is to be added to the menu
*/
public static function EnumItems($iMenuId, $param)
{
switch($iMenuId)
{
/*
case iPopupMenuExtension::MENU_OBJLIST_ACTIONS:
// $param is a DBObjectSet
$aResult = array(
new JSPopupMenuItem('Test::Item1', 'List Test 1', "alert('Test 1')"),
new JSPopupMenuItem('Test::Item2', 'List Test 2', "alert('Test 2')"),
);
break;
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
// $param is a DBObjectSet
$aResult = array(
new JSPopupMenuItem('Test::Item1', 'Toolkit Test 1', "alert('Test 1')"),
new JSPopupMenuItem('Test::Item2', 'Toolkit Test 2', "alert('Test 2')"),
);
break;
case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
// $param is a DBObject
$aResult = array(
new JSPopupMenuItem('Test::Item1', 'Object Test 1', "alert('Test 1')"),
new JSPopupMenuItem('Test::Item2', 'Object Test 2', "alert('Test 2')"),
);
break;
*/
case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS:
// $param is a Dashboard
$oAppContext = new ApplicationContext();
$aParams = $oAppContext->GetAsHash();
$sMenuId = ApplicationMenu::GetActiveNodeId();
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
$aResult = array(
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sMenuId),
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sMenuId', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn' })",
array('../js/ajaxfileupload.js')),
);
break;
/*
case iPopupMenuExtension::MENU_USER_ACTIONS:
// $param is null ??
$aResult = array(
new SeparatorPopupMenuItem(),
new JSPopupMenuItem('Test::Item1', 'Reset preferences...', "alert('Test 1')"),
new JSPopupMenuItem('Test::Item2', 'Do Something Stupid', "alert('Hey Dude !')"),
);
break;
*/
default:
// Unknown type of menu, do nothing
$aResult = array();
}
return $aResult;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,9 @@ require_once(APPROOT."/application/nicewebpage.class.inc.php");
*/
class LoginWebPage extends NiceWebPage
{
{
protected static $m_sLoginFailedMessage = '';
public function __construct()
{
parent::__construct("iTop Login");
@@ -89,6 +91,11 @@ EOF
);
}
public static function SetLoginFailedMessage($sMessage)
{
self::$m_sLoginFailedMessage = $sMessage;
}
public function DisplayLoginForm($sLoginType, $bFailedLogin = false)
{
switch($sLoginType)
@@ -119,7 +126,14 @@ EOF
$this->add("<h1>".Dict::S('UI:Login:Welcome')."</h1>\n");
if ($bFailedLogin)
{
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectLoginPassword')."</p>\n");
if (self::$m_sLoginFailedMessage != '')
{
$this->add("<p class=\"hilite\">".self::$m_sLoginFailedMessage."</p>\n");
}
else
{
$this->add("<p class=\"hilite\">".Dict::S('UI:Login:IncorrectLoginPassword')."</p>\n");
}
}
else
{
@@ -133,6 +147,7 @@ EOF
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"login\" />\n");
$this->add("</form>\n");
$this->add(Dict::S('UI:Login:About'));
$this->add("</div>\n");
break;
}
@@ -191,15 +206,10 @@ EOF
$sPreviousLoginMode = '';
}
// Unset all of the session variables.
$_SESSION = array();
unset($_SESSION['auth_user']);
unset($_SESSION['login_mode']);
// 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();
}
static function SecureConnectionRequired()
@@ -250,77 +260,17 @@ EOF
// check CAS authentication
if (phpCAS::isAuthenticated())
{
// Check is a membership is required
$sCASMemberships = MetaModel::GetConfig()->Get('cas_memberof');
$bFound = false;
if (!empty($sCASMemberships))
{
if (phpCAS::hasAttribute('memberOf'))
{
// A list of groups is specified, the user must a be member of (at least) one of them to pass
$aCASMemberships = array();
$aTmp = explode(';', $sCASMemberships);
setlocale(LC_ALL, "en_US.utf8"); // !!! WARNING: this is needed to have the iconv //TRANSLIT working fine below !!!
foreach($aTmp as $sGroupName)
{
$aCASMemberships[] = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Just in case remove accents and spaces...
}
$aMemberOf = phpCAS::getAttribute('memberOf');
if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array
$aFilteredGroupNames = array();
foreach($aMemberOf as $sGroupName)
{
$sGroupName = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Remove accents and spaces as well
$aFilteredGroupNames[] = $sGroupName;
if (in_array($sGroupName, $aCASMemberships))
{
$bFound = true;
break;
}
}
if(!$bFound)
{
phpCAS :: log("User ".phpCAS::getUser().", none of his/her groups (".implode('; ', $aFilteredGroupNames).") match any of the required groups: ".implode('; ', $aCASMemberships));
}
}
else
{
// Too bad, the user is not part of any of the group => not allowed
phpCAS :: log("No 'memberOf' attribute found for user ".phpCAS::getUser().". Are you using the SAML protocol (S1) ?");
}
}
else
{
// No membership required, anybody will pass
$bFound = true;
}
if ($bFound)
{
$sAuthUser = phpCAS::getUser();
$sAuthPwd = '';
$sLoginMode = 'cas';
$sAuthentication = 'external';
}
else
{
// The user is not part of the allowed groups, => log out
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sCASLogoutUrl = MetaModel::GetConfig()->Get('cas_logout_redirect_service');
if (empty($sCASLogoutUrl))
{
$sCASLogoutUrl = $sUrl;
}
phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page
}
$sAuthUser = phpCAS::getUser();
$sAuthPwd = '';
$sLoginMode = 'cas';
$sAuthentication = 'external';
}
break;
case 'form':
// iTop standard mode: form based authentication
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', '', 'raw_data');
$sAuthUser = utils::ReadPostedParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', '', false, 'raw_data');
if ($sAuthUser != '')
{
$sLoginMode = 'form';
@@ -359,9 +309,9 @@ EOF
case 'url':
// Credentials passed directly in the url
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
if ($sAuthUser != '')
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (($sAuthUser != '') && ($sAuthPwd != null))
{
$sAuthPwd = utils::ReadParam('auth_pwd', '', false, 'raw_data');
$sLoginMode = 'url';
}
break;
@@ -388,7 +338,7 @@ EOF
}
else
{
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sAuthentication))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $sLoginMode, $sAuthentication))
{
//echo "Check Credentials returned false for user $sAuthUser!";
self::ResetSession();
@@ -429,8 +379,6 @@ EOF
{
$sMessage = ''; // In case we need to return a message to the calling web page
$operation = utils::ReadParam('loginop', '');
session_name(MetaModel::GetConfig()->Get('session_name'));
session_start();
if ($operation == 'logoff')
{
@@ -469,8 +417,8 @@ EOF
{
$sAuthUser = $_SESSION['auth_user'];
UserRights::Login($sAuthUser); // Set the user's language
$sOldPwd = utils::ReadPostedParam('old_pwd', 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', 'raw_data');
$sOldPwd = utils::ReadPostedParam('old_pwd', '', false, 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', false, 'raw_data');
if (UserRights::CanChangePassword() && ((!UserRights::CheckCredentials($sAuthUser, $sOldPwd)) || (!UserRights::ChangePassword($sOldPwd, $sNewPwd))))
{
$oPage = new LoginWebPage();
@@ -485,7 +433,7 @@ EOF
if ($bMustBeAdmin && !UserRights::IsAdministrator())
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new SetupWebPage(Dict::S('UI:PageTitle:FatalError'));
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:Login:Error:AccessAdmin')."</h1>\n");
$oP->p("<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/logoff.php\">".Dict::S('UI:LogOffMenu')."</a>");
$oP->output();
@@ -497,7 +445,6 @@ EOF
header('Location: '.utils::GetAbsoluteUrlAppRoot().'portal/index.php');
}
return $sMessage;
}
}
} // End of class
?>

View File

@@ -25,6 +25,7 @@
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/template.class.inc.php');
require_once(APPROOT."/application/user.dashboard.class.inc.php");
/**
@@ -59,10 +60,29 @@ require_once(APPROOT.'/application/template.class.inc.php');
class ApplicationMenu
{
static $bAdditionalMenusLoaded = false;
static $aRootMenus = array();
static $aMenusIndex = array();
static $sFavoriteSiloQuery = 'SELECT Organization';
static public function LoadAdditionalMenus()
{
if (!self::$bAdditionalMenusLoaded)
{
// 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);
}
}
self::$bAdditionalMenusLoaded = true;
}
}
/**
* Set the query used to limit the list of displayed organizations in the drop-down menu
* @param $sOQL string The OQL query returning a list of Organization objects
@@ -87,35 +107,59 @@ class ApplicationMenu
* Main function to add a menu entry into the application, can be called during the definition
* of the data model objects
*/
static public function InsertMenu(MenuNode $oMenuNode, $iParentIndex = -1, $fRank)
static public function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank)
{
$index = self::GetMenuIndexById($oMenuNode->GetMenuId());
if ($index == -1)
{
// The menu does not already exist, insert it
$index = count(self::$aMenusIndex);
self::$aMenusIndex[$index] = array( 'node' => $oMenuNode, 'children' => array());
if ($iParentIndex == -1)
{
$sParentId = '';
self::$aRootMenus[] = array ('rank' => $fRank, 'index' => $index);
}
else
{
$sParentId = self::$aMenusIndex[$iParentIndex]['node']->GetMenuId();
self::$aMenusIndex[$iParentIndex]['children'][] = array ('rank' => $fRank, 'index' => $index);
}
// Note: At the time when 'parent', 'rank' and 'source_file' have been added for the reflection API,
// they were not used to display the menus (redundant or unused)
//
$aBacktrace = debug_backtrace();
$sFile = $aBacktrace[2]["file"];
self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
}
else
{
// the menu already exists, let's combine the conditions that make it visible
self::$aMenusIndex[$index]['node']->AddCondition($oMenuNode);
}
return $index;
}
/**
* Reflection API - Get menu entries
*/
static public function ReflectionMenuNodes()
{
self::LoadAdditionalMenus();
return self::$aMenusIndex;
}
/**
* Entry point to display the whole menu into the web page, used by iTopWebPage
*/
static public function DisplayMenu(iTopWebPage $oPage, $aExtraParams)
{
self::LoadAdditionalMenus();
// Sort the root menu based on the rank
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$iAccordion = 0;
$iActiveMenu = ApplicationMenu::GetActiveNodeId();
$iActiveMenu = self::GetMenuIndexById(self::GetActiveNodeId());
foreach(self::$aRootMenus as $aMenu)
{
$oMenuNode = self::GetMenuNode($aMenu['index']);
@@ -234,22 +278,21 @@ class ApplicationMenu
/**
* Retrieves the currently active menu (if any, otherwise the first menu is the default)
* @return MenuNode or null if there is no menu at all !
* @return string The Id of the currently active menu
*/
static public function GetActiveNodeId()
{
$oAppContext = new ApplicationContext();
$iMenuIndex = $oAppContext->GetCurrentValue('menu', -1);
if ($iMenuIndex == -1)
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
if ($sMenuId === null)
{
// Make sure the root menu is sorted on 'rank'
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$oFirstGroup = self::GetMenuNode(self::$aRootMenus[0]['index']);
$oMenuNode = self::GetMenuNode(self::$aMenusIndex[$oFirstGroup->GetIndex()]['children'][0]['index']);
$iMenuIndex = $oMenuNode->GetIndex();
$sMenuId = $oMenuNode->GetMenuId();
}
return $iMenuIndex;
return $sMenuId;
}
}
@@ -284,25 +327,30 @@ abstract class MenuNode
protected $sMenuId;
protected $index;
/**
* Properties reflecting how the node has been declared
*/
protected $aReflectionProperties;
/**
* Class of objects to check if the menu is enabled, null if none
*/
protected $m_sEnableClass;
protected $m_aEnableClasses;
/**
* User Rights Action code to check if the menu is enabled, null if none
*/
protected $m_iEnableAction;
protected $m_aEnableActions;
/**
* User Rights allowed results (actually a bitmask) to check if the menu is enabled, null if none
*/
protected $m_iEnableActionResults;
protected $m_aEnableActionResults;
/**
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
*/
protected $m_sEnableStimulus;
protected $m_aEnableStimuli;
/**
* Create a menu item, sets the condition to have it displayed and inserts it into the application's main menu
@@ -318,12 +366,25 @@ abstract class MenuNode
public function __construct($sMenuId, $iParentIndex = -1, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
{
$this->sMenuId = $sMenuId;
$this->m_sEnableClass = $sEnableClass;
$this->m_iEnableAction = $iActionCode;
$this->m_iEnableActionResults = $iAllowedResults;
$this->m_sEnableStimulus = $sEnableStimulus;
$this->aReflectionProperties = array();
if (strlen($sEnableClass) > 0)
{
$this->aReflectionProperties['enable_class'] = $sEnableClass;
$this->aReflectionProperties['enable_action'] = $iActionCode;
$this->aReflectionProperties['enable_permission'] = $iAllowedResults;
$this->aReflectionProperties['enable_stimulus'] = $sEnableStimulus;
}
$this->m_aEnableClasses = array($sEnableClass);
$this->m_aEnableActions = array($iActionCode);
$this->m_aEnableActionResults = array($iAllowedResults);
$this->m_aEnableStimuli = array($sEnableStimulus);
$this->index = ApplicationMenu::InsertMenu($this, $iParentIndex, $fRank);
}
public function ReflectionProperties()
{
return $this->aReflectionProperties;
}
public function GetMenuId()
{
@@ -332,12 +393,12 @@ abstract class MenuNode
public function GetTitle()
{
return Dict::S("Menu:$this->sMenuId");
return Dict::S("Menu:$this->sMenuId", str_replace('_', ' ', $this->sMenuId));
}
public function GetLabel()
{
return Dict::S("Menu:$this->sMenuId+");
return Dict::S("Menu:$this->sMenuId+", "");
}
public function GetIndex()
@@ -347,42 +408,58 @@ abstract class MenuNode
public function GetHyperlink($aExtraParams)
{
$aExtraParams['c[menu]'] = $this->GetIndex();
$aExtraParams['c[menu]'] = $this->GetMenuId();
return $this->AddParams(utils::GetAbsoluteUrlAppRoot().'pages/UI.php', $aExtraParams);
}
/**
* Add a limiting display condition for the same menu node. The conditions will be combined with a AND
* @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction
* @return void
*/
public function AddCondition(MenuNode $oMenuNode)
{
foreach($oMenuNode->m_aEnableClasses as $index => $sClass )
{
$this->m_aEnableClasses[] = $sClass;
$this->m_aEnableActions[] = $oMenuNode->m_aEnableActions[$index];
$this->m_aEnableActionResults[] = $oMenuNode->m_aEnableActionResults[$index];
$this->m_aEnableStimuli[] = $oMenuNode->m_aEnableStimuli[$index];
}
}
/**
* Tells whether the menu is enabled (i.e. displayed) for the current user
* @return bool True if enabled, false otherwise
*/
public function IsEnabled()
{
if ($this->m_sEnableClass != null)
foreach($this->m_aEnableClasses as $index => $sClass)
{
if (MetaModel::IsValidClass($this->m_sEnableClass))
if ($sClass != null)
{
if ($this->m_sEnableStimulus != null)
if (MetaModel::IsValidClass($sClass))
{
if (!UserRights::IsStimulusAllowed($this->m_sEnableClass, $this->m_sEnableStimulus))
if ($this->m_aEnableStimuli[$index] != null)
{
return false;
if (!UserRights::IsStimulusAllowed($sClass, $this->m_aEnableStimuli[$index]))
{
return false;
}
}
if ($this->m_aEnableActions[$index] != null)
{
$iResult = UserRights::IsActionAllowed($sClass, $this->m_aEnableActions[$index]);
if (!($iResult & $this->m_aEnableActionResults[$index]))
{
return false;
}
}
}
if ($this->m_iEnableAction != null)
else
{
$iResult = UserRights::IsActionAllowed($this->m_sEnableClass, $this->m_iEnableAction);
if (($iResult & $this->m_iEnableActionResults))
{
return true;
}
else
{
return false;
}
return false;
}
return true;
}
return false;
}
return true;
}
@@ -458,6 +535,7 @@ class TemplateMenuNode extends MenuNode
{
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sTemplateFile = $sTemplateFile;
$this->aReflectionProperties['template_file'] = $sTemplateFile;
}
public function GetHyperlink($aExtraParams)
@@ -471,6 +549,7 @@ class TemplateMenuNode extends MenuNode
$sTemplate = @file_get_contents($this->sTemplateFile);
if ($sTemplate !== false)
{
$aExtraParams['table_id'] = 'Menu_'.$this->GetMenuId();
$oTemplate = new DisplayTemplate($sTemplate);
$oTemplate->Render($oPage, $aExtraParams);
}
@@ -516,6 +595,8 @@ class OQLMenuNode extends MenuNode
$this->sOQL = $sOQL;
$this->bSearch = $bSearch;
$this->m_aParams = array();
$this->aReflectionProperties['oql'] = $sOQL;
$this->aReflectionProperties['do_search'] = $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...
}
@@ -527,6 +608,10 @@ class OQLMenuNode extends MenuNode
public function SetParameters($aParams)
{
$this->m_aParams = $aParams;
foreach($aParams as $sKey => $value)
{
$this->aReflectionProperties[$sKey] = $value;
}
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
@@ -541,31 +626,19 @@ 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 = '';
if ($this->bSearch)
{
$sTemplate .= <<<EOF
<itopblock BlockClass="DisplayBlock" type="search" asynchronous="false" encoding="text/oql">$this->sOQL</itopblock>
EOF;
$aParams = array_merge(array('open' => true, 'table_id' => 'Menu_'.$this->GetMenuId()), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0);
}
$sParams = '';
if (!empty($this->m_aParams))
{
$sParams = 'parameters="';
foreach($this->m_aParams as $sName => $sValue)
{
$sParams .= $sName.':'.$sValue.';';
}
$sParams .= '"';
}
$sTemplate .= <<<EOF
<p class="page-header">$sIcon<itopstring>$this->sPageTitle</itopstring></p>
<itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql" $sParams>$this->sOQL</itopblock>
EOF;
$oTemplate = new DisplayTemplate($sTemplate);
$oTemplate->Render($oPage, $aExtraParams);
$oPage->add("<p class=\"page-header\">$sIcon ".Dict::S($this->sPageTitle)."</p>");
$aParams = array_merge(array('table_id' => 'Menu_'.$this->GetMenuId()), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 1);
}
}
/**
@@ -593,16 +666,15 @@ class SearchMenuNode extends MenuNode
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sPageTitle = "Menu:$sMenuId+";
$this->sClass = $sClass;
$this->aReflectionProperties['class'] = $sClass;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
// The standard template used for all such pages: an open search form at the top
$sTemplate = <<<EOF
<itopblock BlockClass="DisplayBlock" type="search" asynchronous="false" encoding="text/oql" parameters="open:true">SELECT $this->sClass</itopblock>
EOF;
$oTemplate = new DisplayTemplate($sTemplate);
$oTemplate->Render($oPage, $aExtraParams);
$oSearch = new DBObjectSearch($this->sClass);
$aParams = array_merge(array('open' => true, 'table_id' => 'Menu_'.$this->GetMenuId()), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0);
}
}
@@ -632,11 +704,12 @@ class WebPageMenuNode extends MenuNode
{
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sHyperlink = $sHyperlink;
$this->aReflectionProperties['url'] = $sHyperlink;
}
public function GetHyperlink($aExtraParams)
{
$aExtraParams['c[menu]'] = $this->GetIndex();
$aExtraParams['c[menu]'] = $this->GetMenuId();
return $this->AddParams( $this->sHyperlink, $aExtraParams);
}
@@ -669,12 +742,13 @@ class NewObjectMenuNode extends MenuNode
{
parent::__construct($sMenuId, $iParentIndex, $fRank);
$this->sClass = $sClass;
$this->aReflectionProperties['class'] = $sClass;
}
public function GetHyperlink($aExtraParams)
{
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class='.$this->sClass;
$aExtraParams['c[menu]'] = $this->GetIndex();
$aExtraParams['c[menu]'] = $this->GetMenuId();
return $this->AddParams($sHyperlink, $aExtraParams);
}
@@ -705,4 +779,116 @@ class NewObjectMenuNode extends MenuNode
assert(false); // Shall never be called, the external web page will handle the display by itself
}
}
?>
require_once(APPROOT.'application/dashboard.class.inc.php');
/**
* This class defines a menu item which content is based on XML dashboard.
*/
class DashboardMenuNode extends MenuNode
{
protected $sDashboardFile;
/**
* Create a menu item based on a custom template 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 $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, $sDashboardFile, $iParentIndex, $fRank = 0, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
{
parent::__construct($sMenuId, $iParentIndex, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
$this->sDashboardFile = $sDashboardFile;
$this->aReflectionProperties['definition_file'] = $sDashboardFile;
}
public function GetHyperlink($aExtraParams)
{
if ($this->sDashboardFile == '') return '';
return parent::GetHyperlink($aExtraParams);
}
public function GetDashboard()
{
$sDashboardDefinition = @file_get_contents($this->sDashboardFile);
if ($sDashboardDefinition !== false)
{
$bCustomized = false;
// Search for an eventual user defined dashboard, overloading the existing one
$oUDSearch = new DBObjectSearch('UserDashboard');
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$oUDSearch->AddCondition('menu_code', $this->sMenuId, '=');
$oUDSet = new DBObjectSet($oUDSearch);
if ($oUDSet->Count() > 0)
{
// Assuming there is at most one couple {user, menu}!
$oUserDashboard = $oUDSet->Fetch();
$sDashboardDefinition = $oUserDashboard->Get('contents');
$bCustomized = true;
}
$oDashboard = new RuntimeDashboard($this->sMenuId);
$oDashboard->FromXml($sDashboardDefinition);
$oDashboard->SetCustomFlag($bCustomized);
}
else
{
$oDashboard = null;
}
return $oDashboard;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
$oDashboard->Render($oPage, false, $aExtraParams);
$bEdit = utils::ReadParam('edit', false);
if ($bEdit)
{
$sId = addslashes($this->sMenuId);
$oPage->add_ready_script("EditDashboard('$sId');");
}
}
else
{
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
}
}
public function RenderEditor(WebPage $oPage)
{
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
$oDashboard->RenderEditor($oPage);
}
else
{
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
}
}
public function AddDashlet($oDashlet)
{
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
$oDashboard->AddDashlet($oDashlet);
$oDashboard->Save();
}
else
{
$oPage->p("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
}
}
}

View File

@@ -30,30 +30,125 @@ require_once(APPROOT."/application/webpage.class.inc.php");
class NiceWebPage extends WebPage
{
var $m_aReadyScripts;
var $m_sRootUrl;
public function __construct($s_title)
{
parent::__construct($s_title);
$this->m_aReadyScripts = array();
$this->add_linked_script("../js/jquery-1.4.2.min.js");
//$this->add_linked_script("../js/jquery.history_remote.pack.js");
$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.8.2.custom.css');
$this->add_linked_script('../js/jquery-ui-1.8.2.custom.min.js');
//$this->add_linked_script("../js/ui.resizable.js");
// $this->add_linked_script("../js/ui.tabs.js");
$this->add_linked_script("../js/jquery-1.7.1.min.js");
$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.8.17.custom.css');
$this->add_linked_script('../js/jquery-ui-1.8.17.custom.min.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);');
}
// table sorting
$this->add_linked_script("../js/jquery.tablesorter.min.js");
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
$this->add_linked_script("../js/jquery.tablehover.js");
$this->add_ready_script(
<<< EOF
//add new widget called TruncatedList to properly display truncated lists when they are sorted
$.tablesorter.addWidget({
// give the widget a id
id: "truncatedList",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Check if there is a "truncated" line
this.truncatedList = false;
if ($("tr td.truncated",table).length > 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');
// In case we sort again the table, we need to remove the added 'even' classes on odd rows
$("tbody tr:odd",table).removeClass('even');
$("tbody tr.red_even:odd",table).removeClass('even').removeClass('red_even').addClass('red');
$("tbody tr.orange_even:odd",table).removeClass('even').removeClass('orange_even').addClass('orange');
$("tbody tr.green_even:odd",table).removeClass('even').removeClass('green_even').addClass('green');
}
});
$("table.listResults").tableHover(); // hover tables
EOF
);
$this->add_linked_stylesheet("../css/light-grey.css");
$this->m_sRootUrl = $this->GetAbsoluteUrlAppRoot();
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
$sAbsURLModulesRoot = addslashes($this->GetAbsoluteUrlModulesRoot());
$sAppContext = addslashes($this->GetApplicationContext());
$this->add_script(
<<<EOF
function GetAbsoluteUrlAppRoot()
{
return '$sAbsURLAppRoot';
}
function GetAbsoluteUrlModulesRoot()
{
return '$sAbsURLModulesRoot';
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
if (sContext.length > 0)
{
if (sURL.indexOf('?') == -1)
{
return sURL+'?'+sContext;
}
return sURL+'&'+sContext;
}
return sURL;
}
EOF
);
}
public function SetRootUrl($sRootUrl)
{
$this->m_sRootUrl = $sRootUrl;
}
public function small_p($sText)
{
$this->add("<p style=\"font-size:smaller\">$sText</p>\n");
}
}
public function GetAbsoluteUrlAppRoot()
{
return utils::GetAbsoluteUrlAppRoot();
}
public function GetAbsoluteUrlModulesRoot()
{
return utils::GetAbsoluteUrlModulesRoot();
}
function GetApplicationContext()
{
$oAppContext = new ApplicationContext();
return $oAppContext->GetForLink();
}
// By Rom, used by CSVImport and Advanced search
public function MakeClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null)
@@ -100,6 +195,7 @@ class NiceWebPage extends WebPage
*/
public function output()
{
$this->set_base($this->m_sRootUrl.'pages/');
if (count($this->m_aReadyScripts)>0)
{
$this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});");

View File

@@ -60,6 +60,7 @@ class PortalWebPage extends NiceWebPage
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$sAbsURLAppRoot = addslashes(utils::GetAbsoluteUrlAppRoot()); // Pass it to Javascript scripts
$sAbsURLModulesRoot = addslashes(utils::GetAbsoluteUrlModulesRoot()); // Pass it to Javascript scripts
$oAppContext = new ApplicationContext();
$sAppContext = addslashes($oAppContext->GetForLink());
if ($sAlternateStyleSheet != '')
@@ -172,6 +173,11 @@ EOF
return '$sAbsURLAppRoot';
}
function GetAbsoluteUrlModulesRoot()
{
return '$sAbsURLModulesRoot';
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
@@ -279,7 +285,7 @@ EOF
//
$this->add("<div id=\"listOf$sClass\">\n");
cmdbAbstractObject::DisplaySet($this, $oSet, array('currentId' => "listOf$sClass", 'menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList)));
cmdbAbstractObject::DisplaySet($this, $oSet, array('currentId' => $this->GetUniqueId()."$sClass", 'menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList)));
$this->add("</div>\n");
}
else

View File

@@ -0,0 +1,107 @@
<?php
// 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
/**
* Persistent class Event and derived
* Application internal events
* There is also a file log
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
abstract class Query extends cmdbAbstractObject
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui,application",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_query",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"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 AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("fields", array("allowed_values"=>null, "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'fields')); // 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
}
}
class QueryOQL extends Query
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,view_in_gui,application",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_query_oql",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("oql", array("allowed_values"=>null, "sql"=>"oql", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'oql', 'fields')); // 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 DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
parent::DisplayBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams);
if (!$bEditMode)
{
$sUrl = utils::GetAbsoluteUrlAppRoot().'webservices/export.php?format=spreadsheet&login_mode=basic&query='.$this->GetKey();
$sOql = $this->Get('oql');
$oSearch = DBObjectSearch::FromOQL($sOql);
$aParameters = $oSearch->GetQueryParams();
foreach($aParameters as $sParam => $val)
{
$sUrl .= '&arg_'.$sParam.'=["'.$sParam.'"]';
}
$oPage->p(Dict::S('UI:Query:UrlForExcel').':<br/><textarea cols="80" rows="3" READONLY>'.$sUrl.'</textarea>');
}
}
}
?>

View File

@@ -40,13 +40,15 @@ class SqlBlock
protected $m_aColumns;
protected $m_sTitle;
protected $m_sType;
protected $m_aParams;
public function __construct($sQuery, $aColumns, $sTitle, $sType)
public function __construct($sQuery, $aColumns, $sTitle, $sType, $aParams = array())
{
$this->m_sQuery = $sQuery;
$this->m_aColumns = $aColumns;
$this->m_sTitle = $sTitle;
$this->m_sType = $sType;
$this->m_aParams = $aParams;
}
/**
@@ -54,9 +56,14 @@ class SqlBlock
/*
*
* <sqlblock>
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) &lt; start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d')</sql>
* <sql>SELECT date_format(start_date, '%d') AS Date, count(*) AS Count FROM ticket WHERE DATE_SUB(NOW(), INTERVAL 15 DAY) &lt; start_date AND finalclass = 'UserIssue' GROUP BY date_format(start_date, '%d') AND $CONDITION(param1, ticket.org_id)$</sql>
* <type>table</type>
* <title>UserRequest:Overview-Title</title>
* <parameter>
* <name>param1</name>
* <type>context</type>
* <mapping>org_id</mapping>
* </parameter>
* <column>
* <name>Date</name>
* <label>UserRequest:Overview-Date</label>
@@ -73,6 +80,11 @@ class SqlBlock
* - sql: a (My)SQL query. Do not forget to use html entities (e.g. &lt; for <)
* - type: table (default), bars or pie. If bars or pie is selected only the two first columns are taken into account.
* - title: optional title, typed in clear or given as a dictionnary entry
* - parameter: specifies how to map the context parameters (namely org_id) to a given named parameter in the query.
* The expression $CONDITION(<param_name>, <sql_column_name>) will be automatically replaced by:
* either the string "1" if there is no restriction on the organisation in iTop
* or the string "(<sql_column_name>=<value_of_org_id>)" if there is a limitation to one organizations in iTop
* or the string "(<sql_column_name> IN (<values_of_org_id>))" if there is a limitation to a given set of organizations in iTop
* - column: specification of a column (not displayed if omitted)
* - column / name: name of the column in the SQL query (use aliases)
* - column / label: label, typed in clear or given as a dictionnary entry
@@ -144,8 +156,96 @@ class SqlBlock
}
}
}
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType);
$aParams = array();
if (isset($oXml->parameter))
{
foreach ($oXml->parameter AS $oParamData)
{
if (!isset($oParamData->name))
{
throw new Exception("Missing tag 'name' for parameter in sqlblock/column");
}
$sName = (string) $oParamData->name;
if (strlen($sName) == 0)
{
throw new Exception("Empty tag 'name' for parameter in sqlblock/column");
}
if (!isset($oParamData->mapping))
{
throw new Exception("Missing tag 'mapping' for parameter in sqlblock/column");
}
$sMapping = (string) $oParamData->mapping;
if (strlen($sMapping) == 0)
{
throw new Exception("Empty tag 'mapping' for parameter in sqlblock/column");
}
if (isset($oParamData->type))
{
$sParamType = $oParamData->type;
}
else
{
$sParamType = 'context';
}
$aParams[$sName] = array('mapping' => $sMapping, 'type' => $sParamType);
}
}
return new SqlBlock($sQuery, $aColumns, $sTitle, $sType, $aParams);
}
/**
* Applies the defined parameters into the SQL query
* @return string the SQL query to execute
*/
public function BuildQuery()
{
$oAppContext = new ApplicationContext();
$sQuery = $this->m_sQuery;
$sQuery = str_replace('$DB_PREFIX$', MetaModel::GetConfig()->GetDBSubname(), $sQuery); // put the tables DB prefix (if any)
foreach($this->m_aParams as $sName => $aParam)
{
if ($aParam['type'] == 'context')
{
$sSearchPattern = '/\$CONDITION\('.$sName.',([^\)]+)\)\$/';
$value = $oAppContext->GetCurrentValue($aParam['mapping']);
if (empty($value))
{
$sSQLExpr = '(1)';
}
else
{
// Special case for managing the hierarchy of organizations
if (($aParam['mapping'] == 'org_id') && ( MetaModel::IsValidClass('Organization')))
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode != false)
{
// organizations are in hierarchy... gather all the orgs below the given one...
$sOQL = "SELECT Organization AS node JOIN Organization AS root ON node.$sHierarchicalKeyCode BELOW root.id WHERE root.id = :value";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('value' => $value));
$aOrgIds = array();
while($oOrg = $oSet->Fetch())
{
$aOrgIds[]= $oOrg->GetKey();
}
$sSQLExpr = '($1 IN('.implode(',', $aOrgIds).'))';
}
else
{
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
}
}
else
{
$sSQLExpr = '($1 = '.CMDBSource::Quote($value).')';
}
}
$sQuery = preg_replace($sSearchPattern, $sSQLExpr, $sQuery);
}
}
return $sQuery;
}
public function RenderContent(WebPage $oPage, $aExtraParams = array())
@@ -160,7 +260,8 @@ class SqlBlock
}
// $oPage->add($this->GetRenderContent($oPage, $aExtraParams, $sId));
$res = CMDBSource::Query($this->m_sQuery);
$sQuery = $this->BuildQuery();
$res = CMDBSource::Query($sQuery);
$aQueryCols = CMDBSource::GetColumns($res);
// Prepare column definitions (check + give default values)

View File

@@ -25,7 +25,24 @@
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
MetaModel::Startup(ITOP_CONFIG_FILE);
session_name('itop-'.md5(APPROOT));
session_start();
if (isset($_REQUEST['switch_env']))
{
$sEnv = $_REQUEST['switch_env'];
$_SESSION['itop_env'] = $sEnv;
// TODO: reset the credentials as well ??
}
else if (isset($_SESSION['itop_env']))
{
$sEnv = $_SESSION['itop_env'];
}
else
{
$sEnv = ITOP_DEFAULT_ENV;
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile);
?>

View File

@@ -1,15 +1,6 @@
<div style="width:100%;background: #fff url(../images/welcome.jpg) top left no-repeat;">
<style>
.dashboard {
vertical-align:top;
width:50%;
border:0px solid #000;
background-color:#F9F9F1;
padding:10px;
text-align:left;
font-size:10pt;
}
.dashboard2 {
.welcome_popup_cell {
vertical-align:top;
width:50%;
border:0px solid #000;
@@ -17,15 +8,7 @@ border:0px solid #000;
padding:5px;
text-align:left;
}
td.dashboard li {
margin-top: 5px;
display: list-item;
}
td div.display_block {
padding:0;
}
tr td.dashboard2, tr td.dashboard2 ul {
tr td.welcome_popup_cell, tr td.welcome_popup_cell ul {
font-size:10pt;
}
</style>
@@ -35,10 +18,10 @@ font-size:10pt;
<p></p>
<table border="0" style="padding:10px;border-spacing: 10px;width:100%">
<tr>
<td class="dashboard2">
<td class="welcome_popup_cell">
<itopstring>UI:WelcomeMenu:LeftBlock</itopstring>
</td>
<td class="dashboard2">
<td class="welcome_popup_cell">
<itopstring>UI:WelcomeMenu:RightBlock</itopstring>
</td>
</tr>

View File

@@ -65,6 +65,8 @@ class UIExtKeyWidget
{
protected $iId;
protected $sTargetClass;
protected $sAttCode;
protected $bSearchMode;
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
@@ -81,14 +83,16 @@ class UIExtKeyWidget
{
$sDisplayStyle = 'select'; // In search mode, always use a drop-down list
}
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId);
return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, $bSearchMode, $sDisplayStyle);
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle);
}
public function __construct($sTargetClass, $iInputId)
public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false)
{
$this->sTargetClass = $sTargetClass;
$this->iId = $iInputId;
$this->sAttCode = $sAttCode;
$this->bSearchMode = $bSearchMode;
}
/**
@@ -97,28 +101,41 @@ class UIExtKeyWidget
* @param Hash $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = false, $sDisplayStyle = 'select')
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select')
{
if (!is_null($bSearchMode))
{
$this->bSearchMode = $bSearchMode;
}
$sTitle = addslashes($sTitle);
$oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bCreate = (!$this->bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($bSearchMode) ? '' : 'attr_';
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sHTMLValue = "<span style=\"white-space:nowrap\">"; // no wrap
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
if($bSearchMode)
if($this->bSearchMode)
{
$sWizHelper = 'null';
$sWizHelperJSON = "''";
$sJSSearchMode = 'true';
}
else
{
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$sWizHelperJSON = $sWizHelper.'.ToJSON()';
if (isset($aArgs['wizHelper']))
{
$sWizHelper = $aArgs['wizHelper'];
}
else
{
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
}
$sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()';
$sJSSearchMode = 'false';
}
if (is_null($oAllowedValues))
{
@@ -153,7 +170,7 @@ class UIExtKeyWidget
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
if ($bSearchMode)
if ($this->bSearchMode)
{
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";
@@ -182,7 +199,7 @@ class UIExtKeyWidget
$sHTMLValue .= "</select>\n";
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
@@ -213,13 +230,14 @@ EOF
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"$value\" />\n";
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter', json: function() { return $sWizHelperJSON; } }});
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
@@ -262,13 +280,24 @@ EOF
return $sHTMLValue;
}
public function GetSearchDialog(WebPage $oPage, $sTitle)
public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
{
$sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
$oFilter = new DBObjectSearch($this->sTargetClass);
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
$aParams = array('query_params' => array('this' => $oCurrObject));
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
$oFilter = $oSet->GetFilter();
}
else
{
$aParams = array();
$oFilter = new DBObjectSearch($this->sTargetClass);
}
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => true, 'currentId' => $this->iId));
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
@@ -306,8 +335,9 @@ EOF
try
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj)));
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'table_id' => 'select_'.$this->sAttCode)); // Don't display the 'Actions' menu on the results
}
catch(MissingQueryArgument $e)
{
@@ -315,8 +345,9 @@ EOF
// TODO check if we can improve this behavior...
$sOQL = 'SELECT '.$sRemoteClass;
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, $this->iId.'_results', array('cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
//$oBlock = new DisplayBlock($oFilter, 'list', false);
//$oBlock->Display($oP, $this->iId.'_results', array('cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
}
}
@@ -334,6 +365,7 @@ EOF
throw new Exception('Implementation: null value for allowed values definition');
}
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains);
foreach($aValues as $sKey => $sFriendlyName)
{
@@ -346,14 +378,24 @@ EOF
*/
public function GetObjectName($iObjId)
{
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId);
return $oObj->GetName();
$aModifierProps = array();
$aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode;
$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps);
if ($oObj)
{
return $oObj->GetName();
}
else
{
return '';
}
}
/**
* Get the form to create a new object of the 'target' class
*/
public function GetObjectCreationForm(WebPage $oPage)
public function GetObjectCreationForm(WebPage $oPage, $oCurrObject)
{
// Set all the default values in an object and clone this "default" object
$oNewObj = MetaModel::NewObject($this->sTargetClass);
@@ -362,7 +404,24 @@ EOF
$oAppContext = new ApplicationContext();
$oAppContext->InitObjectFromContext($oNewObj);
// 2nd - set values from the page argument 'default'
// 2nd set the default values from the constraint on the external key... if any
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
$aParams = array('this' => $oCurrObject);
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aParams);
$aConsts = $oSet->ListConstantFields();
$sClassAlias = $oSet->GetFilter()->GetClassAlias();
if (isset($aConsts[$sClassAlias]))
{
foreach($aConsts[$sClassAlias] as $sAttCode => $value)
{
$oNewObj->Set($sAttCode, $value);
}
}
}
// 3rd - set values from the page argument 'default'
$oNewObj->UpdateObjectFromArg('default');
$sDialogTitle = addslashes($this->sTitle);
@@ -391,6 +450,7 @@ EOF
try
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj));
}
catch(MissingQueryArgument $e)
@@ -399,6 +459,7 @@ EOF
// TODO check if we can improve this behavior...
$sOQL = 'SELECT '.$this->m_sTargetClass;
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter);
}

View File

@@ -34,6 +34,7 @@ class UILinksWidget
protected $m_iInputId;
protected $m_aAttributes;
protected $m_sExtKeyToRemote;
protected $m_sExtKeyToMe;
protected $m_sLinkedClass;
protected $m_sRemoteClass;
protected $m_bDuplicatesAllowed;
@@ -50,6 +51,7 @@ class UILinksWidget
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
$this->m_sLinkedClass = $oAttDef->GetLinkedClass();
$this->m_sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$this->m_sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
@@ -60,8 +62,9 @@ class UILinksWidget
$this->m_aTableConfig = array();
$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->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)
foreach(MetaModel::FlattenZList(MetaModel::GetZListItems($this->m_sLinkedClass, 'list')) as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sAttCode);
if ($sStateAttCode == $sAttCode)
{
// State attribute is always hidden from the UI
@@ -97,43 +100,98 @@ class UILinksWidget
* @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() )
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId = null, $aArgs = array(), $oCurrentObj )
{
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
$aRow = array();
$aFieldsMap = array();
if(is_object($linkObjOrId))
{
$key = $linkObjOrId->GetKey();
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
$sPrefix .= "[$key][";
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$key}";
$aArgs['this'] = $linkObjOrId;
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$key\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"$key\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
$sSafeId = str_replace(array('[',']','-'), '_', $sFieldId);
$sSafeId = self::MakeID($sFieldId);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $linkObjOrId->Get($sFieldCode), '' /* DisplayValue */, $sSafeId, $sNameSuffix, 0, $aArgs);
$aFieldsMap[$sFieldCode] = $sSafeId;
}
$sState = $linkObjOrId->GetState();
}
else
{
// form for creating a new record
$sPrefix .= "[$linkObjOrId][";
$iRemoteObjKey = -$linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, -$linkObjOrId);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".(-$linkObjOrId);
$aArgs['this'] = $oNewLinkObj;
$aRow['form::checkbox'] = "<input class=\"selection\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$linkObjOrId\">";
$aRow['form::checkbox'] .= "<input type=\"hidden\" name=\"attr_{$sPrefix}id{$sNameSuffix}\" value=\"\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId.']';
$sSafeId = str_replace(array('[',']','-'), '_', $sFieldId);
$sSafeId = self::MakeID($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 */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs);
$aRow[$sFieldCode] = cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $oNewLinkObj->Get($sFieldCode) /* TO DO/ call GetDefaultValue($oObject->ToArgs()) */, '' /* DisplayValue */, $sSafeId /* id */, $sNameSuffix, 0, $aArgs);
$aFieldsMap[$sFieldCode] = $sSafeId;
}
$sState = '';
$oP->add_script(
<<<EOF
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd',
constrainInput: false,
changeMonth: true,
changeYear: true
});
$(".datetime-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
buttonImageOnly: true,
dateFormat: 'yy-mm-dd 00:00:00',
constrainInput: false,
changeMonth: true,
changeYear: true
});
EOF
);
}
$sExtKeyToMeId = self::MakeID($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = self::MakeID($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oP->add_script(
<<<EOF
var {$aArgs['wizHelper']} = new WizardHelper('{$this->m_sLinkedClass}', '', '$sState');
{$aArgs['wizHelper']}.SetFieldsMap($sJsonFieldsMap);
{$aArgs['wizHelper']}.SetFieldsCount($iFieldsCount);
EOF
);
$aRow['static::key'] = $oLinkedObj->GetHyperLink();
foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
{
@@ -141,6 +199,11 @@ class UILinksWidget
}
return $aRow;
}
protected function MakeID($sName)
{
return str_replace(array('[', ']', '-'), '_', $sName);
}
/**
* Display one row of the whole form
@@ -207,9 +270,11 @@ class UILinksWidget
* @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
* @param string $sFormPrefix prefix of the fields in the current form
* @param DBObject $oCurrentObj the current object to which the linkset is related
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array())
public function Display(WebPage $oPage, DBObjectSet $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj)
{
$sHtmlValue = '';
$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
@@ -223,27 +288,28 @@ class UILinksWidget
if ($oCurrentLink->IsNew())
{
$key = -$oLinkedObj->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $key, $aArgs);
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $key, $aArgs, $oCurrentObj);
}
else
{
$key = $oCurrentLink->GetKey();
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs);
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj);
}
}
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script(<<<EOF
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} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper);
oWidget{$this->m_iInputId}.Init();
EOF
);
$sHtmlValue .= "<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
$sHtmlValue .= "&nbsp;&nbsp;&nbsp;<input id=\"{$this->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();\"></span>\n";
$sHtmlValue .= "&nbsp;&nbsp;&nbsp;<input id=\"{$this->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();\"><span id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_indicatorAdd\"></span></span>\n";
$sHtmlValue .= "<span style=\"clear:both;\"><p>&nbsp;</p></span>\n";
$sHtmlValue .= "</div>\n";
$oPage->add_at_the_end($this->GetObjectPickerDialog($oPage), "dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}"); // To prevent adding forms inside the main form
$oPage->add_at_the_end("<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\"></div>"); // To prevent adding forms inside the main form
return $sHtmlValue;
}
@@ -266,12 +332,11 @@ EOF
return $sTargetClass;
}
protected function GetObjectPickerDialog($oPage)
public function GetObjectPickerDialog($oPage, $oCurrentObj)
{
$sHtml = "<div id=\"dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}\">";
$sHtml .= "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
$oSet = new CMDBObjectSet($oFilter);
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}", array('open' => true));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\" OnSubmit=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\">\n";
@@ -282,12 +347,11 @@ EOF
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$sHtml .= "</div>\n";
$oPage->add($sHtml);
$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{$this->m_iInputId}.SearchObjectsToAdd);");
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget{$this->m_iInputId}.UpdateSizes);");
return $sHtml;
}
/**
@@ -341,10 +405,10 @@ EOF
}
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true)); // Don't display the 'Actions' menu on the results
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'table_id' => 'add_'.$this->m_sAttCode)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $oFullSetFilter)
public function DoAddObjects(WebPage $oP, $oFullSetFilter, $oCurrentObj)
{
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
@@ -353,7 +417,7 @@ EOF
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId);
if (is_object($oLinkedObj))
{
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId ); // Not yet created link get negative Ids
$aRow = $this->GetFormRow($oP, $oLinkedObj, -$iObjectId, array(), $oCurrentObj ); // Not yet created link get negative Ids
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iObjectId));
}
else
@@ -362,5 +426,47 @@ EOF
}
}
}
/**
* Initializes the default search parameters based on 1) a 'current' object and 2) the silos defined by the context
* @param DBObject $oSourceObj
* @param DBObjectSearch $oSearch
*/
protected function SetSearchDefaultFromContext($oSourceObj, &$oSearch)
{
$oAppContext = new ApplicationContext();
$sSrcClass = get_class($oSourceObj);
$sDestClass = $oSearch->GetClass();
foreach($oAppContext->GetNames() as $key)
{
// Find the value of the object corresponding to each 'context' parameter
$aCallSpec = array($sSrcClass, 'MapContextParam');
$sAttCode = '';
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sSrcClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sSrcClass, $sAttCode);
$defaultValue = $oSourceObj->Get($sAttCode);
// Find the attcode for the same 'context' parameter in the destination class
// and sets its value as the default value for the search condition
$aCallSpec = array($sDestClass, 'MapContextParam');
$sAttCode = '';
if (is_callable($aCallSpec))
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
{
$oSearch->AddCondition($sAttCode, $defaultValue);
}
}
}
}
}
?>

View File

@@ -104,7 +104,7 @@ class UILinksWizard
var nbChecked = $('.selection:checked').length;
if (nbChecked > 0)
{
$('#btnRemove').attr('disabled','');
$('#btnRemove').removeAttr('disabled');
}
else
{

View File

@@ -0,0 +1,61 @@
<?php
// 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
/**
* Store and retrieve user custom dashboards
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
require_once(APPROOT.'/core/dbobject.class.php');
/**
* This class is used to store, in a persistent manner, a dashboard edited by a user
*/
class UserDashboard extends DBObject
{
public static function Init()
{
$aParams = array
(
"category" => "gui",
"key_type" => "autoincrement",
"name_attcode" => "user_id",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_app_dashboards",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("menu_code", array("allowed_values"=>null, "sql"=>"menu_code", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("contents", array("allowed_values"=>null, "sql"=>"contents", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
}
/**
* Overloading this function here to secure a fix done right before the release
* The real fix should be to implement this verb in DBObject
*/
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
$this->DBDelete($oDeletionPlan);
}
}
?>

View File

@@ -77,6 +77,42 @@ class appUserPreferences extends DBObject
self::Save();
}
/**
* Clears the value for a given preference (or list of preferences that matches a pattern), and updates the database
* @param string $sPattern Code/Pattern of the properties/preferences to reset
* @param boolean $bPattern Whether or not the supplied code is a PCRE pattern
*/
static function UnsetPref($sCodeOrPattern, $bPattern = false)
{
if (self::$oUserPrefs == null)
{
self::Load();
}
$aPrefs = self::$oUserPrefs->Get('preferences');
if ($bPattern)
{
// the supplied code is a pattern, clear all preferences that match
foreach($aPrefs as $sKey => $void)
{
if (preg_match($sCodeOrPattern, $sKey))
{
unset($aPrefs[$sKey]);
}
}
self::$oUserPrefs->Set('preferences', $aPrefs);
}
else
{
unset($aPrefs[$sCode]);
self::$oUserPrefs->Set('preferences', $aPrefs);
}
// Save only if needed
if (self::$oUserPrefs->IsModified())
{
self::Save();
}
}
/**
* Call this function to get all the preferences for the user, packed as a JSON object
* @return string JSON representation of the preferences

View File

@@ -26,7 +26,9 @@
require_once(APPROOT.'/core/config.class.inc.php');
require_once(APPROOT.'/application/transaction.class.inc.php');
define('ITOP_CONFIG_FILE', APPROOT.'/config-itop.php');
define('ITOP_CONFIG_FILE', 'config-itop.php');
define('ITOP_DEFAULT_CONFIG_FILE', APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
define('SERVER_NAME_PLACEHOLDER', '$SERVER_NAME$');
class FileUploadException extends Exception
@@ -40,12 +42,12 @@ class FileUploadException extends Exception
*/
class utils
{
private static $m_sConfigFile = ITOP_CONFIG_FILE;
private static $m_oConfig = null;
private static $oConfig = null;
private static $m_bCASClient = false;
// Parameters loaded from a file, parameters of the page/command line still have precedence
private static $m_aParamsFromFile = null;
private static $m_aParamSource = array();
protected static function LoadParamFile($sParamFile)
{
@@ -82,6 +84,7 @@ class utils
$sParam = $aMatches[1];
$value = trim($aMatches[2]);
self::$m_aParamsFromFile[$sParam] = $value;
self::$m_aParamSource[$sParam] = $sParamFile;
}
}
}
@@ -99,6 +102,25 @@ class utils
}
}
/**
* Return the source file from which the parameter has been found,
* usefull when it comes to pass user credential to a process executed
* in the background
* @param $sName Parameter name
* @return The file name if any, or null
*/
public static function GetParamSourceFile($sName)
{
if (array_key_exists($sName, self::$m_aParamSource))
{
return self::$m_aParamSource[$sName];
}
else
{
return null;
}
}
public static function IsModeCLI()
{
$sSAPIName = php_sapi_name();
@@ -152,10 +174,18 @@ class utils
public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
{
$retValue = self::Sanitize_Internal($value, $sSanitizationFilter);
if ($retValue === false)
if ($value === $defaultValue)
{
$retValue = $defaultValue;
// Preserve the real default value (can be used to detect missing mandatory parameters)
$retValue = $value;
}
else
{
$retValue = self::Sanitize_Internal($value, $sSanitizationFilter);
if ($retValue === false)
{
$retValue = $defaultValue;
}
}
return $retValue;
}
@@ -180,6 +210,7 @@ class utils
$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
break;
case 'context_param':
case 'parameter':
case 'field_name':
if (is_array($value))
@@ -206,12 +237,15 @@ class utils
case 'field_name':
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
break;
case 'context_param':
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options"=>array("regexp"=>'/^[ A-Za-z0-9_=%:+-]*$/')));
break;
}
}
break;
break;
default:
case 'raw_data':
$retValue = $value;
@@ -429,15 +463,30 @@ class utils
// http://www.spaweditor.com/scripts/regex/index.php
}
static public function GetConfig()
{
if (self::$oConfig == null)
{
$sConfigFile = self::GetConfigFilePath();
if (file_exists($sConfigFile))
{
self::$oConfig = new Config($sConfigFile);
}
else
{
// When executing the setup, the config file may be still missing
self::$oConfig = new Config();
}
}
return self::$oConfig;
}
/**
* Returns the absolute URL to the server's root path
* @param $sCurrentRelativePath string NO MORE USED, kept for backward compatibility only !
* @param $bForceHTTPS bool True to force HTTPS, false otherwise
* @return string The absolute URL to the server's root, without the first slash
* Returns the absolute URL to the application root path
* @return string The absolute URL to the application root, without the first slash
*/
static public function GetAbsoluteUrlAppRoot()
{
$sUrl = MetaModel::GetConfig()->Get('app_root_url');
$sUrl = self::GetConfig()->Get('app_root_url');
if (strpos($sUrl, SERVER_NAME_PLACEHOLDER) > -1)
{
if (isset($_SERVER['SERVER_NAME']))
@@ -557,10 +606,10 @@ class utils
*/
static function InitCASClient()
{
$sCASIncludePath = MetaModel::GetConfig()->Get('cas_include_path');
$sCASIncludePath = self::GetConfig()->Get('cas_include_path');
include_once($sCASIncludePath.'/CAS.php');
$bCASDebug = MetaModel::GetConfig()->Get('cas_debug');
$bCASDebug = self::GetConfig()->Get('cas_debug');
if ($bCASDebug)
{
phpCAS::setDebug(APPROOT.'/error.log');
@@ -569,13 +618,13 @@ class utils
if (!self::$m_bCASClient)
{
// Initialize phpCAS
$sCASVersion = MetaModel::GetConfig()->Get('cas_version');
$sCASHost = MetaModel::GetConfig()->Get('cas_host');
$iCASPort = MetaModel::GetConfig()->Get('cas_port');
$sCASContext = MetaModel::GetConfig()->Get('cas_context');
$sCASVersion = self::GetConfig()->Get('cas_version');
$sCASHost = self::GetConfig()->Get('cas_host');
$iCASPort = self::GetConfig()->Get('cas_port');
$sCASContext = self::GetConfig()->Get('cas_context');
phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */);
self::$m_bCASClient = true;
$sCASCACertPath = MetaModel::GetConfig()->Get('cas_server_ca_cert_path');
$sCASCACertPath = self::GetConfig()->Get('cas_server_ca_cert_path');
if (empty($sCASCACertPath))
{
// If no certificate authority is provided, do not attempt to validate
@@ -601,5 +650,165 @@ class utils
}
echo "<p><pre>".print_r($aLightTrace, true)."</pre></p>\n";
}
/**
* Execute the given iTop PHP script, passing it the current credentials
* Only CLI mode is supported, because of the need to hand the credentials over to the next process
* Throws an exception if the execution fails or could not be attempted (config issue)
* @param string $sScript Name and relative path to the file (relative to the iTop root dir)
* @param hash $aArguments Associative array of 'arg' => 'value'
* @return array(iCode, array(output lines))
*/
/**
*/
static function ExecITopScript($sScriptName, $aArguments)
{
$aDisabled = explode(', ', ini_get('disable_functions'));
if (in_array('exec', $aDisabled))
{
throw new Exception("The PHP exec() function has been disabled on this server");
}
$sPHPExec = trim(self::GetConfig()->Get('php_path'));
if (strlen($sPHPExec) == 0)
{
throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file.");
}
$sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
$sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
$sParamFile = self::GetParamSourceFile('auth_user');
if (is_null($sParamFile))
{
$aArguments['auth_user'] = $sAuthUser;
$aArguments['auth_pwd'] = $sAuthPwd;
}
else
{
$aArguments['param_file'] = $sParamFile;
}
$aArgs = array();
foreach($aArguments as $sName => $value)
{
// Note: See comment from the 23-Apr-2004 03:30 in the PHP documentation
// It suggests to rely on pctnl_* function instead of using escapeshellargs
$aArgs[] = "--$sName=".escapeshellarg($value);
}
$sArgs = implode(' ', $aArgs);
$sScript = realpath(APPROOT.$sScriptName);
if (!file_exists($sScript))
{
throw new Exception("Could not find the script file '$sScriptName' from the directory '".APPROOT."'");
}
$sCommand = '"'.$sPHPExec.'" '.escapeshellarg($sScript).' -- '.$sArgs;
if (version_compare(phpversion(), '5.3.0', '<'))
{
if (substr(PHP_OS,0,3) == 'WIN')
{
// Under Windows, and for PHP 5.2.x, the whole command has to be quoted
// Cf PHP doc: http://php.net/manual/fr/function.exec.php, comment from the 27-Dec-2010
$sCommand = '"'.$sCommand.'"';
}
}
$sLastLine = exec($sCommand, $aOutput, $iRes);
if ($iRes == 1)
{
throw new Exception(Dict::S('Core:ExecProcess:Code1')." - ".$sCommand);
}
elseif ($iRes == 255)
{
$sErrors = implode("\n", $aOutput);
throw new Exception(Dict::S('Core:ExecProcess:Code255')." - ".$sCommand.":\n".$sErrors);
}
//$aOutput[] = $sCommand;
return array($iRes, $aOutput);
}
/**
* Get the current environment
*/
public static function GetCurrentEnvironment()
{
if (isset($_SESSION['itop_env']))
{
return $_SESSION['itop_env'];
}
else
{
return ITOP_DEFAULT_ENV;
}
}
/**
* Get the "Back" button to go out of the current environment
*/
public static function GetEnvironmentBackButton()
{
if (isset($_SESSION['itop_return_env']))
{
if (isset($_SESSION['itop_return_url']))
{
$sReturnUrl = $_SESSION['itop_return_url'];
}
else
{
$sReturnUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?switch_env='.$_SESSION['itop_return_env'];
}
return '&nbsp;<button onclick="window;location.href=\''.addslashes($sReturnUrl).'\'">'.Dict::S('UI:Button:Back').'</button>';
}
else
{
return '';
}
}
/**
* Get the "Back" button to go out of the current environment
*/
public static function GetPopupMenuItems($oPage, $iMenuId, $param, &$aActions)
{
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
{
foreach($oExtensionInstance->EnumItems($iMenuId, $param) as $oMenuItem)
{
if (is_object($oMenuItem))
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
{
$oPage->add_linked_script($sLinkedScript);
}
}
}
}
}
/**
* Get target configuration file name (including full path)
*/
public static function GetConfigFilePath($sEnvironment = null)
{
if (is_null($sEnvironment))
{
$sEnvironment = self::GetCurrentEnvironment();
}
return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
}
/**
* Returns the absolute URL to the modules root path
* @return string ...
*/
static public function GetAbsoluteUrlModulesRoot()
{
$sUrl = self::GetAbsoluteUrlAppRoot().'env-'.self::GetCurrentEnvironment().'/';
return $sUrl;
}
}
?>
?>

View File

@@ -23,6 +23,22 @@
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
/**
* Generic interface common to CLI and Web pages
*/
Interface Page
{
public function output();
public function add($sText);
public function p($sText);
public function pre($sText);
public function add_comment($sText);
public function table($aConfig, $aData, $aParams = array());
}
/**
* Simple helper class to ease the production of HTML pages
*
@@ -34,7 +50,7 @@
* $oPage->p("Hello World !");
* $oPage->output();
*/
class WebPage
class WebPage implements Page
{
protected $s_title;
protected $s_content;
@@ -50,6 +66,8 @@ class WebPage
protected $sContentType;
protected $sContentDisposition;
protected $sContentFileName;
protected $s_sOutputFormat;
protected $a_OutputOptions;
public function __construct($s_title)
{
@@ -67,6 +85,8 @@ class WebPage
$this->sContentType = '';
$this->sContentDisposition = '';
$this->sContentFileName = '';
$this->s_OutputFormat = utils::ReadParam('output_format', 'html');
$this->a_OutputOptions = array();
ob_start(); // Start capturing the output
}
@@ -121,6 +141,13 @@ class WebPage
$this->add('<pre>'.$s_html.'</pre>');
}
/**
* Add a comment
*/
public function add_comment($sText)
{
$this->add('<!--'.$sText.'-->');
}
/**
* Add a paragraph to the body of the page
*/
@@ -372,13 +399,13 @@ class WebPage
{
// Make sure that the URL to the script contains the application's version number
// so that the new script do NOT get reloaded from the cache when the application is upgraded
if (strpos('?', $s_script) === false)
if (strpos($s_script, '?') === false)
{
$s_script .= "?version=".ITOP_VERSION;
$s_script .= "?itopversion=".ITOP_VERSION;
}
else
{
$s_script .= "&version=".ITOP_VERSION;
$s_script .= "&itopversion=".ITOP_VERSION;
}
echo "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
}
@@ -413,6 +440,10 @@ class WebPage
}
echo "</style>\n";
}
if (class_exists('MetaModel') && MetaModel::GetConfig())
{
echo "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico\" />\n";
}
echo "</head>\n";
echo "<body>\n";
echo self::FilterXSS($this->s_content);
@@ -511,5 +542,98 @@ class WebPage
{
return str_ireplace('<script', '&lt;script', $sHTML);
}
/**
* What is the currently selected output format
* @return string The selected output format: html, pdf...
*/
public function GetOutputFormat()
{
return $this->s_OutputFormat;
}
/**
* Check whether the desired output format is possible or not
* @param string $sOutputFormat The desired output format: html, pdf...
* @return bool True if the format is Ok, false otherwise
*/
function IsOutputFormatAvailable($sOutputFormat)
{
$bResult = false;
switch($sOutputFormat)
{
case 'html':
$bResult = true; // Always supported
break;
case 'pdf':
$bResult = @is_readable(APPROOT.'lib/MPDF/mpdf.php');
break;
}
return $bResult;
}
/**
* Retrieves the value of a named output option for the given format
* @param string $sFormat The format: html or pdf
* @param string $sOptionName The name of the option
* @return mixed false if the option was never set or the options's value
*/
public function GetOutputOption($sFormat, $sOptionName)
{
if (isset($this->a_OutputOptions[$sFormat][$sOptionName]))
{
return $this->a_OutputOptions[$sFormat][$sOptionName];
}
return false;
}
/**
* Sets a named output option for the given format
* @param string $sFormat The format for which to set the option: html or pdf
* @param string $sOptionName the name of the option
* @param mixed $sValue The value of the option
*/
public function SetOutputOption($sFormat, $sOptionName, $sValue)
{
if (!isset($this->a_OutputOptions[$sFormat]))
{
$this->a_OutputOptions[$sFormat] = array($sOptionName => $sValue);
}
else
{
$this->a_OutputOptions[$sFormat][$sOptionName] = $sValue;
}
}
public function RenderPopupMenuItems($aActions, $aFavoriteActions = array())
{
$sPrevUrl = '';
$sHtml = '';
foreach ($aActions as $aAction)
{
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
$sOnClick = isset($aAction['onclick']) ? " onclick=\"{$aAction['onclick']}\"" : "";
if (empty($aAction['url']))
{
if ($sPrevUrl != '') // Don't output consecutively two separators...
{
$sHtml .= "<li>{$aAction['label']}</li>";
}
$sPrevUrl = '';
}
else
{
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
$sPrevUrl = $aAction['url'];
}
}
$sHtml .= "</ul></li></ul></div>";
foreach(array_reverse($aFavoriteActions) as $aAction)
{
$sHtml .= "<div class=\"actions_button\"><a href='{$aAction['url']}'>{$aAction['label']}</a></div>";
}
return $sHtml;
}
}
?>

View File

@@ -76,7 +76,7 @@ class WizardHelper
if ( isset($aLinkedObject[$sLinkedAttCode]) && ($aLinkedObject[$sLinkedAttCode] !== null) )
{
$sLinkedAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sLinkedAttCode);
if (($sLinkedAttDef->IsExternalKey()) && ($aLinkedObject[$sLinkedAttCode] != '') && ($aLinkedObject[$sLinkedAttCode] != 0) )
if (($sLinkedAttDef->IsExternalKey()) && ($aLinkedObject[$sLinkedAttCode] != '') && ($aLinkedObject[$sLinkedAttCode] > 0) )
{
// For external keys: load the target object so that external fields
// get filled too
@@ -108,7 +108,7 @@ class WizardHelper
$oObj->Set($sAttCode, $oDocument);
}
}
else if (($oAttDef->IsExternalKey()) && (!empty($value)) )
else if (($oAttDef->IsExternalKey()) && (!empty($value)) && ($value > 0) )
{
// For external keys: load the target object so that external fields
// get filled too
@@ -260,7 +260,7 @@ class WizardHelper
foreach($aLinkObj as $sAttCode => $value)
{
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
if (($oAttDef->IsExternalKey()) && ($value != '') )
if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0))
{
// For external keys: load the target object so that external fields
// get filled too

View File

@@ -1,5 +1,9 @@
<?php
define('APPROOT', dirname(__FILE__).'/');
define('APPCONF', APPROOT.'conf/');
define('ITOP_DEFAULT_ENV', 'production');
if (function_exists('microtime'))
{
$fItopStarted = microtime(true);

View File

@@ -42,7 +42,7 @@ abstract class Action extends cmdbAbstractObject
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('name'),
"db_table" => "priv_action",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
@@ -106,7 +106,7 @@ abstract class ActionNotification extends Action
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('name'),
"db_table" => "priv_action_notification",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -139,7 +139,7 @@ class ActionEmail extends ActionNotification
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('name'),
"db_table" => "priv_action_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -183,6 +183,7 @@ class ActionEmail extends ActionNotification
try
{
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSearch->AllowAllData();
}
catch (OQLException $e)
{
@@ -294,14 +295,16 @@ class ActionEmail extends ActionNotification
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
$oObj = $aContextArgs['this->object()'];
$sReference = '<iTop/'.get_class($oObj).'/'.$oObj->GetKey().'>';
$sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), MetaModel::GetEnvironmentId());
$sReference = '<'.$sMessageId.'>';
}
catch(Exception $e)
{
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
throw $e;
}
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
if (!is_null($oLog))
{
// Note: we have to secure this because those values are calculated
@@ -338,6 +341,7 @@ class ActionEmail extends ActionNotification
$oEmail->SetRecipientTO($this->Get('test_recipient'));
$oEmail->SetRecipientFrom($this->Get('test_recipient'));
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
}
else
{
@@ -349,6 +353,7 @@ class ActionEmail extends ActionNotification
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetRecipientReplyTo($sReplyTo);
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
}
if (empty($this->m_aMailErrors))

View File

@@ -140,8 +140,7 @@ class AsyncSendEmail extends AsyncTask
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("header", array("allowed_values"=>null, "sql"=>"header", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "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', 'trigger_list')); // Attributes to be displayed for the complete details
@@ -151,31 +150,26 @@ class AsyncSendEmail extends AsyncTask
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
static public function AddToQueue($sTo, $sSubject, $sBody, $aHeaders, $oLog)
static public function AddToQueue(EMail $oEMail, $oLog)
{
$oNew = MetaModel::NewObject(__class__);
if ($oLog)
{
$oNew->Set('event_id', $oLog->GetKey());
}
$oNew->Set('to', $sTo);
$oNew->Set('subject', $sSubject);
$oNew->Set('body', $sBody);
$sHeaders = serialize($aHeaders);
$oNew->Set('header', $sHeaders);
$oNew->Set('to', $oEMail->GetRecipientTO(true /* string */));
$oNew->Set('subject', $oEMail->GetSubject());
$sMessage = serialize($oEMail);
$oNew->Set('message', $sMessage);
$oNew->DBInsert();
}
public function DoProcess()
{
$sTo = $this->Get('to');
$sSubject = $this->Get('subject');
$sBody = $this->Get('body');
$sHeaders = $this->Get('header');
$aHeaders = unserialize($sHeaders);
$oEmail = new EMail($sTo, $sSubject, $sBody, $aHeaders);
$iRes = $oEmail->Send($aIssues, true /* force synchro !!!!! */);
$sMessage = $this->Get('message');
$oEMail = unserialize($sMessage);
$iRes = $oEMail->Send($aIssues, true /* force synchro !!!!! */);
switch ($iRes)
{
case EMAIL_SEND_OK:
@@ -189,4 +183,4 @@ class AsyncSendEmail extends AsyncTask
}
}
}
?>
?>

File diff suppressed because it is too large Load Diff

View File

@@ -401,7 +401,13 @@ class BulkChange
if ($sAttCode == 'id') continue;
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
$aReasons = array();
$iFlags = $oTargetObj->GetAttributeFlags($sAttCode, $aReasons);
if ( (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY) && ( $oTargetObj->Get($sAttCode) != $aRowData[$iCol]) )
{
$aErrors[$sAttCode] = "the attribute '$sAttCode' is read-only and cannot be modified (current value: ".$oTargetObj->Get($sAttCode).", proposed value: {$aRowData[$iCol]}).";
}
else if ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
{
try
{
@@ -908,7 +914,7 @@ class BulkChange
$bLimitExceeded = true;
if (!$bShowAll)
{
$iMaxObjects = MetaModel::GetConfig()->GetMinDisplayLimit();
$iMaxObjects = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oBulkChanges->SetLimit($iMaxObjects);
}
}
@@ -917,7 +923,7 @@ class BulkChange
$aDetails = array();
while ($oChange = $oBulkChanges->Fetch())
{
$sDate = '<a href="?step=10&changeid='.$oChange->GetKey().'&'.$oAppContext->GetForLink().'">'.$oChange->Get('date').'</a>';
$sDate = '<a href="csvimport.php?step=10&changeid='.$oChange->GetKey().'&'.$oAppContext->GetForLink().'">'.$oChange->Get('date').'</a>';
$sUser = $oChange->GetUserName();
if (preg_match('/^(.*)\\(CSV\\)$/i', $oChange->Get('userinfo'), $aMatches))
{

View File

@@ -200,9 +200,6 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
*/
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');
@@ -218,76 +215,7 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
$sAttName = $oAttDef->GetLabel();
$sNewValue = $this->Get('newvalue');
$sOldValue = $this->Get('oldvalue');
if ($oAttDef instanceof AttributeEnum)
{
// translate the enum values
$sOldValue = $oAttDef->GetAsHTML($sOldValue);
$sNewValue = $oAttDef->GetAsHTML($sNewValue);
if (strlen($sOldValue) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sAttName, $sNewValue);
}
else
{
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue);
}
}
elseif ( (($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 = 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 = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sAttName);
}
else
{
if (strlen($sOldValue) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sAttName, $sNewValue);
}
else
{
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $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)";
if (strlen($sFrom) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sAttName, $sTo);
}
else
{
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sTo, $sFrom);
}
}
elseif ($oAttDef instanceOf AttributeBlob)
{
$sResult = "#@# Issue... found an attribute for which other type of tracking should be made";
}
else
{
if (strlen($sOldValue) == 0)
{
$sResult = Dict::Format('Change:AttName_SetTo', $sAttName, $sNewValue);
}
else
{
$sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue);
}
}
$sResult = $oAttDef->GetAsHTMLForHistory($sOldValue, $sNewValue);
}
return $sResult;
}

View File

@@ -76,6 +76,7 @@ require_once('cmdbchangeop.class.inc.php');
// Romain: temporary moved into application.inc.php (see explanations there)
//require_once('event.class.inc.php');
require_once('templatestring.class.inc.php');
require_once('csvparser.class.inc.php');
require_once('bulkchange.class.inc.php');
@@ -92,6 +93,23 @@ abstract class CMDBObject extends DBObject
protected static $m_oCurrChange = null;
public static function SetCurrentChange(CMDBChange $oChange)
{
self::$m_oCurrChange = $oChange;
}
//
// Todo: simplify the APIs and do not pass the current change as an argument anymore
// SetCurrentChange to be invoked in very few cases (UI.php, CSV import, Data synchro)
// GetCurrentChange to be called ONCE (!) by CMDBChangeOp::OnInsert ($this->Set('change', ..GetCurrentChange())
// GetCurrentChange to create a default change if not already done in the current context
//
public static function GetCurrentChange()
{
return self::$m_oCurrChange;
}
private function RecordObjCreation(CMDBChange $oChange)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate");
@@ -115,8 +133,18 @@ abstract class CMDBObject extends DBObject
foreach ($aValues as $sAttCode=> $value)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if ($oAttDef->IsExternalField()) continue; // #@# temporary
if ($oAttDef->IsLinkSet()) continue; // #@# temporary
if (array_key_exists($sAttCode, $aOrigValues))
{
$original = $aOrigValues[$sAttCode];
}
else
{
$original = null;
}
if ($oAttDef instanceOf AttributeOneWayPassword)
{
// One Way encrypted passwords' history is stored -one way- encrypted
@@ -126,11 +154,7 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (array_key_exists($sAttCode, $aOrigValues))
{
$original = $aOrigValues[$sAttCode];
}
else
if (is_null($original))
{
$original = '';
}
@@ -146,11 +170,7 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (array_key_exists($sAttCode, $aOrigValues))
{
$original = $aOrigValues[$sAttCode];
}
else
if (is_null($original))
{
$original = '';
}
@@ -166,17 +186,40 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (array_key_exists($sAttCode, $aOrigValues))
{
$original = $aOrigValues[$sAttCode];
}
else
if (is_null($original))
{
$original = new ormDocument();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
}
elseif ($oAttDef instanceOf AttributeStopWatch)
{
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
//
if (is_null($original))
{
$original = new OrmStopWatch();
}
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
{
$item_value = $oSubItemAttDef->GetValue($value);
$item_original = $oSubItemAttDef->GetValue($original);
if ($item_value != $item_original)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeScalar");
$oMyChangeOp->Set("change", $oChange->GetKey());
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sSubItemAttCode);
$oMyChangeOp->Set("oldvalue", $item_original);
$oMyChangeOp->Set("newvalue", $item_value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
}
}
elseif ($oAttDef instanceOf AttributeCaseLog)
{
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeCaseLog");
@@ -197,17 +240,9 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (array_key_exists($sAttCode, $aOrigValues))
if (!is_null($original) && ($original instanceof ormCaseLog))
{
$original = $aOrigValues[$sAttCode];
if ($original instanceof ormCaseLog)
{
$original = $original->GetText();
}
}
else
{
$original = null;
$original = $original->GetText();
}
$oMyChangeOp->Set("prevdata", $original);
$iId = $oMyChangeOp->DBInsertNoReload();
@@ -221,16 +256,7 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
if (array_key_exists($sAttCode, $aOrigValues))
{
$sOriginalValue = $aOrigValues[$sAttCode];
}
else
{
$sOriginalValue = 'undefined';
}
$oMyChangeOp->Set("oldvalue", $sOriginalValue);
$oMyChangeOp->Set("oldvalue", $original);
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
@@ -279,9 +305,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$ret = $this->DBInsertTracked_Internal();
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
return $ret;
}
@@ -289,9 +316,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$ret = $this->DBInsertTracked_Internal(true);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
return $ret;
}
@@ -320,9 +348,10 @@ abstract class CMDBObject extends DBObject
public function DBCloneTracked(CMDBChange $oChange, $newKey = null)
{
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->DBCloneTracked_Internal($newKey);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected function DBCloneTracked_Internal($newKey = null)
@@ -347,9 +376,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->DBUpdateTracked_Internal();
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected function DBUpdateTracked_Internal()
@@ -382,9 +412,10 @@ abstract class CMDBObject extends DBObject
{
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_DELETE);
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->DBDeleteTracked_Internal($oDeletionPlan);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
@@ -406,9 +437,10 @@ abstract class CMDBObject extends DBObject
public static function BulkDeleteTracked(CMDBChange $oChange, DBObjectSearch $oFilter)
{
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->BulkDeleteTracked_Internal($oFilter);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
@@ -445,9 +477,10 @@ abstract class CMDBObject extends DBObject
public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues)
{
$oPreviousChange = self::$m_oCurrChange;
self::$m_oCurrChange = $oChange;
$this->BulkUpdateTracked_Internal($oFilter, $aValues);
self::$m_oCurrChange = null;
self::$m_oCurrChange = $oPreviousChange;
}
protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)

View File

@@ -24,13 +24,14 @@
*/
require_once('MyHelpers.class.inc.php');
require_once(APPROOT.'core/kpi.class.inc.php');
class MySQLException extends CoreException
{
public function __construct($sIssue, $aContext)
{
$aContext['mysql_error'] = mysql_error();
$aContext['mysql_errno'] = mysql_errno();
$aContext['mysql_error'] = CMDBSource::GetError();
$aContext['mysql_errno'] = CMDBSource::GetErrNo();;
parent::__construct($sIssue, $aContext);
}
}
@@ -56,13 +57,13 @@ class CMDBSource
self::$m_sDBUser = $sUser;
self::$m_sDBPwd = $sPwd;
self::$m_sDBName = $sSource;
if (!self::$m_resDBLink = mysql_connect($sServer, $sUser, $sPwd))
if (!self::$m_resDBLink = @mysqli_connect($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))
if (!((bool)mysqli_query(self::$m_resDBLink, "USE $sSource")))
{
throw new MySQLException('Could not select DB', array('host'=>$sServer, 'user'=>$sUser, 'db_name'=>$sSource));
}
@@ -118,7 +119,7 @@ class CMDBSource
{
// 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);
return @((bool)mysqli_query(self::$m_resDBLink, "USE $sSource"));
}
}
@@ -131,7 +132,7 @@ class CMDBSource
public static function SelectDB($sSource)
{
if (!mysql_select_db($sSource, self::$m_resDBLink))
if (!((bool)mysqli_query(self::$m_resDBLink, "USE $sSource")))
{
throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
}
@@ -171,6 +172,30 @@ class CMDBSource
return $res;
}
public static function GetErrNo()
{
if (self::$m_resDBLink)
{
return mysqli_errno(self::$m_resDBLink);
}
else
{
return mysqli_connect_errno();
}
}
public static function GetError()
{
if (self::$m_resDBLink)
{
return mysqli_error(self::$m_resDBLink);
}
else
{
return mysqli_connect_error();
}
}
public static function DBHost() {return self::$m_sDBHost;}
public static function DBUser() {return self::$m_sDBUser;}
public static function DBPwd() {return self::$m_sDBPwd;}
@@ -208,7 +233,7 @@ class CMDBSource
// Quote if not a number or a numeric string
if ($bAlways || is_string($value))
{
$value = $cQuoteStyle . mysql_real_escape_string($value, self::$m_resDBLink) . $cQuoteStyle;
$value = $cQuoteStyle . mysqli_real_escape_string(self::$m_resDBLink, $value) . $cQuoteStyle;
}
return $value;
}
@@ -222,7 +247,7 @@ class CMDBSource
// $sSQLQuery .= MyHelpers::MakeSQLComment($aTraceInf);
$oKPI = new ExecutionKPI();
$result = mysql_query($sSQLQuery, self::$m_resDBLink);
$result = mysqli_query(self::$m_resDBLink, $sSQLQuery);
if (!$result)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery));
@@ -236,15 +261,21 @@ class CMDBSource
{
$sSQL = "SHOW TABLE STATUS LIKE '$sTable'";
$result = self::Query($sSQL);
$aRow = mysql_fetch_assoc($result);
$aRow = mysqli_fetch_assoc($result);
$iNextInsertId = $aRow['Auto_increment'];
return $iNextInsertId;
}
public static function GetInsertId()
{
return mysql_insert_id(self::$m_resDBLink);
$iRes = mysqli_insert_id(self::$m_resDBLink);
if (is_null($iRes))
{
return 0;
}
return $iRes;
}
public static function InsertInto($sSQLQuery)
{
if (self::Query($sSQLQuery))
@@ -261,37 +292,37 @@ class CMDBSource
public static function QueryToScalar($sSql)
{
$result = mysql_query($sSql, self::$m_resDBLink);
$result = mysqli_query(self::$m_resDBLink, $sSql);
if (!$result)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
if ($aRow = mysql_fetch_array($result, MYSQL_BOTH))
if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH))
{
$res = $aRow[0];
}
else
{
mysql_free_result($result);
mysqli_free_result($result);
throw new MySQLException('Found no result for query', array('query' => $sSql));
}
mysql_free_result($result);
mysqli_free_result($result);
return $res;
}
public static function QueryToArray($sSql)
{
$aData = array();
$result = mysql_query($sSql, self::$m_resDBLink);
$result = mysqli_query(self::$m_resDBLink, $sSql);
if (!$result)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
while ($aRow = mysql_fetch_array($result, MYSQL_BOTH))
while ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH))
{
$aData[] = $aRow;
}
mysql_free_result($result);
mysqli_free_result($result);
return $aData;
}
@@ -309,7 +340,7 @@ class CMDBSource
public static function ExplainQuery($sSql)
{
$aData = array();
$result = mysql_query("EXPLAIN $sSql", self::$m_resDBLink);
$result = mysqli_query(self::$m_resDBLink, "EXPLAIN $sSql");
if (!$result)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
@@ -318,62 +349,62 @@ class CMDBSource
$aNames = self::GetColumns($result);
$aData[] = $aNames;
while ($aRow = mysql_fetch_array($result, MYSQL_ASSOC))
while ($aRow = mysqli_fetch_array($result, MYSQLI_ASSOC))
{
$aData[] = $aRow;
}
mysql_free_result($result);
mysqli_free_result($result);
return $aData;
}
public static function TestQuery($sSql)
{
$result = mysql_query("EXPLAIN $sSql", self::$m_resDBLink);
$result = mysqli_query(self::$m_resDBLink, "EXPLAIN $sSql");
if (!$result)
{
return mysql_error();
return self::GetError();
}
mysql_free_result($result);
mysqli_free_result($result);
return '';
}
public static function NbRows($result)
{
return mysql_num_rows($result);
return mysqli_num_rows($result);
}
public static function FetchArray($result)
{
return mysql_fetch_array($result, MYSQL_ASSOC);
return mysqli_fetch_array($result, MYSQLI_ASSOC);
}
public static function GetColumns($result)
{
$aNames = array();
for ($i = 0; $i < mysql_num_fields($result) ; $i++)
for ($i = 0; $i < (($___mysqli_tmp = mysqli_num_fields($result)) ? $___mysqli_tmp : 0) ; $i++)
{
$meta = mysql_fetch_field($result, $i);
if (!$meta)
{
$meta = mysqli_fetch_field_direct($result, $i);
if (!$meta)
{
throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i));
}
else
{
$aNames[] = $meta->name;
}
}
else
{
$aNames[] = $meta->name;
}
}
return $aNames;
}
public static function Seek($result, $iRow)
{
return mysql_data_seek($result, $iRow);
return mysqli_data_seek($result, $iRow);
}
public static function FreeResult($result)
{
return mysql_free_result($result);
return ((mysqli_free_result($result) || (is_object($result) && (get_class($result) == "mysqli_result"))) ? true : false);
}
public static function IsTable($sTable)
@@ -511,18 +542,18 @@ class CMDBSource
public static function DumpTable($sTable)
{
$sSql = "SELECT * FROM `$sTable`";
$result = mysql_query($sSql, self::$m_resDBLink);
$result = mysqli_query(self::$m_resDBLink, $sSql);
if (!$result)
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
$aRows = array();
while ($aRow = mysql_fetch_array($result, MYSQL_ASSOC))
while ($aRow = mysqli_fetch_array($result, MYSQLI_ASSOC))
{
$aRows[] = $aRow;
}
mysql_free_result($result);
mysqli_free_result($result);
return $aRows;
}
@@ -560,12 +591,12 @@ class CMDBSource
}
$aRes = array();
while ($aRow = mysql_fetch_array($result, MYSQL_NUM))
while ($aRow = mysqli_fetch_array($result, MYSQLI_NUM))
{
// so far, only one column...
$aRes[] = implode('/', $aRow);
}
mysql_free_result($result);
mysqli_free_result($result);
// so far, only one line...
return implode(', ', $aRes);
}
@@ -585,14 +616,14 @@ class CMDBSource
throw new CoreException("Current user not allowed to check the status", array('mysql_error' => $e->getMessage()));
}
if (mysql_num_rows($result) == 0)
if (mysqli_num_rows($result) == 0)
{
return false;
}
// Returns one single row anytime
$aRow = mysql_fetch_array($result, MYSQL_ASSOC);
mysql_free_result($result);
$aRow = mysqli_fetch_array($result, MYSQLI_ASSOC);
mysqli_free_result($result);
if (!isset($aRow['Slave_IO_Running']))
{

121
core/computing.inc.php Normal file
View File

@@ -0,0 +1,121 @@
<?php
// 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
/**
* Any extension to compute things like a stop watch deadline or working hours
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
/**
* Metric computing for stop watches
*/
interface iMetricComputer
{
public static function GetDescription();
public function ComputeMetric($oObject);
}
/**
* Working time computing for stop watches
*/
interface iWorkingTimeComputer
{
public static function GetDescription();
/**
* Get the date/time corresponding to a given delay in the future from the present
* considering only the valid (open) hours for a specified object
* @param $oObject DBObject The object for which to compute the deadline
* @param $iDuration integer The duration (in seconds) in the future
* @param $oStartDate DateTime The starting point for the computation
* @return DateTime The date/time for the deadline
*/
public function GetDeadline($oObject, $iDuration, DateTime $oStartDate);
/**
* Get duration (considering only open hours) elapsed bewteen two given DateTimes
* @param $oObject DBObject The object for which to compute the duration
* @param $oStartDate DateTime The starting point for the computation (default = now)
* @param $oEndDate DateTime The ending point for the computation (default = now)
* @return integer The duration (number of seconds) of open hours elapsed between the two dates
*/
public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate);
}
/**
* Default implementation oof deadline computing: NO deadline
*/
class DefaultMetricComputer implements iMetricComputer
{
public static function GetDescription()
{
return "Null";
}
public function ComputeMetric($oObject)
{
return null;
}
}
/**
* Default implementation of working time computing
*/
class DefaultWorkingTimeComputer implements iWorkingTimeComputer
{
public static function GetDescription()
{
return "24x7, no holidays";
}
/**
* Get the date/time corresponding to a given delay in the future from the present
* considering only the valid (open) hours for a specified object
* @param $oObject DBObject The object for which to compute the deadline
* @param $iDuration integer The duration (in seconds) in the future
* @param $oStartDate DateTime The starting point for the computation
* @return DateTime The date/time for the deadline
*/
public function GetDeadline($oObject, $iDuration, DateTime $oStartDate)
{
//echo "GetDeadline - default: ".$oStartDate->format('Y-m-d H:i:s')." + $iDuration<br/>\n";
// Default implementation: 24x7, no holidays: to compute the deadline, just add
// the specified duration to the given date/time
$oResult = clone $oStartDate;
$oResult->modify('+'.$iDuration.' seconds');
return $oResult;
}
/**
* Get duration (considering only open hours) elapsed bewteen two given DateTimes
* @param $oObject DBObject The object for which to compute the duration
* @param $oStartDate DateTime The starting point for the computation (default = now)
* @param $oEndDate DateTime The ending point for the computation (default = now)
* @return integer The duration (number of seconds) of open hours elapsed between the two dates
*/
public function GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
{
//echo "GetOpenDuration - default: ".$oStartDate->format('Y-m-d H:i:s')." to ".$oEndDate->format('Y-m-d H:i:s')."<br/>\n";
return abs($oEndDate->format('U') - $oStartDate->format('U'));
}
}
?>

View File

@@ -84,6 +84,14 @@ class Config
// New way to store the settings !
//
protected $m_aSettings = array(
'app_env_label' => array(
'type' => 'string',
'description' => 'Label displayed to describe the current application environnment, defaults to the environment name (e.g. "production")',
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'app_root_url' => array(
'type' => 'string',
'description' => 'Root URL used for navigating within the application, or from an email to the application (you can put $SERVER_NAME$ as a placeholder for the server\'s name)',
@@ -124,6 +132,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'php_path' => array(
'type' => 'string',
'description' => 'Path to the php executable in CLI mode',
'default' => 'php',
'value' => 'php',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'session_name' => array(
'type' => 'string',
'description' => 'The name of the cookie used to store the PHP session id',
@@ -285,6 +301,54 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_transport' => array(
'type' => 'string',
'description' => 'Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocole)',
'default' => "PHPMail",
'value' => "PHPMail",
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_transport_smtp.host' => array(
'type' => 'string',
'description' => 'host name or IP address (optional)',
'default' => "localhost",
'value' => "localhost",
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.port' => array(
'type' => 'integer',
'description' => 'port number (optional)',
'default' => 25,
'value' => 25,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.encryption' => array(
'type' => 'string',
'description' => 'tls or ssl (optional)',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.username' => array(
'type' => 'string',
'description' => 'Authentication user (optional)',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.password' => array(
'type' => 'string',
'description' => 'Authentication password (optional)',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'apc_cache.enabled' => array(
'type' => 'bool',
'description' => 'If set, the APC cache is allowed (the PHP extension must also be active)',
@@ -383,6 +447,42 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_user_synchro' => array(
'type' => 'bool',
'description' => 'Whether or not to synchronize users with CAS/LDAP',
// examples... not used (nor 'description')
'default' => 0,
'value' => 0,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_update_profiles' => array(
'type' => 'bool',
'description' => 'Whether or not to update the profiles of an existing user from the CAS information',
// examples... not used (nor 'description')
'default' => 0,
'value' => 0,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_profile_pattern' => array(
'type' => 'string',
'description' => 'A regular expression pattern to extract the name of the iTop profile from the name of an LDAP/CAS group',
// examples... not used (nor 'description')
'default' => '/^cn=([^,]+),/',
'value' => '/^cn=([^,]+),/',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_default_profiles' => array(
'type' => 'string',
'description' => 'A semi-colon separated list of iTop Profiles to use when creating a new user if no profile is retrieved from CAS',
// examples... not used (nor 'description')
'default' => 'Portal user',
'value' => 'Portal user',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cas_debug' => array(
'type' => 'bool',
'description' => 'Activate the CAS debug',
@@ -419,6 +519,24 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'synchro_prevent_delete_all' => array(
'type' => 'bool',
'description' => 'Stop the synchro if all the replicas of a data source become obsolete at the same time.',
// examples... not used
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'source_dir' => array(
'type' => 'string',
'description' => 'Source directory for the datamodel files. (which gets compiled to env-production).',
// examples... not used
'default' => 'datamodels/latest',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
);
public function IsProperty($sPropCode)
@@ -477,8 +595,8 @@ class Config
protected $m_bLogNotification;
protected $m_bLogIssue;
protected $m_bLogWebService;
protected $m_bLogKpiDuration; // private setting
protected $m_bLogKpiMemory; // private setting
protected $m_bLogKPIDuration; // private setting
protected $m_bLogKPIMemory; // private setting
protected $m_bDebugQueries; // private setting
protected $m_bQueryCacheEnabled; // private setting
@@ -535,15 +653,22 @@ class Config
*/
protected $m_aCharsets;
public function __construct($sConfigFile, $bLoadConfig = true)
public function __construct($sConfigFile = null, $bLoadConfig = true)
{
$this->m_sFile = $sConfigFile;
if (is_null($sConfigFile))
{
$bLoadConfig = false;
}
$this->m_aAppModules = array(
// Some default modules, always present can be move to an official iTop Module later if needed
'application/transaction.class.inc.php',
'application/menunode.class.inc.php',
'application/user.preferences.class.inc.php',
'application/user.dashboard.class.inc.php',
'application/audit.rule.class.inc.php',
'application/query.class.inc.php',
// Romain - That's dirty, because those 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
@@ -601,10 +726,12 @@ class Config
$this->Verify();
}
// Application root url: set a default value, then normalize it
$sAppRootUrl = trim($this->Get('app_root_url'));
// Application root url: set a default value, then normalize it
/*
* Does not work in CLI/unattended mode
$sAppRootUrl = trim($this->Get('app_root_url'));
if (strlen($sAppRootUrl) == 0)
{
{
$sAppRootUrl = utils::GetDefaultUrlAppRoot();
}
if (substr($sAppRootUrl, -1, 1) != '/')
@@ -612,6 +739,7 @@ class Config
$sAppRootUrl .= '/';
}
$this->Set('app_root_url', $sAppRootUrl);
*/
}
protected function CheckFile($sPurpose, $sFileName)
@@ -1033,13 +1161,17 @@ class Config
{
$this->m_aCharsets[$sIconvCode] = $sDisplayName;
}
public function FileIsWritable()
{
return is_writable($this->m_sFile);
}
public function GetLoadedFile()
{
return $this->m_sFile;
if (is_null($this->m_sFile))
{
return '';
}
else
{
return $this->m_sFile;
}
}
/**
@@ -1122,64 +1254,98 @@ class Config
fwrite($hFile, "<?php\n");
fwrite($hFile, "\n/**\n");
fwrite($hFile, " *\n");
fwrite($hFile, " * phpMyORM configuration file, generated by the iTop configuration wizard\n");
fwrite($hFile, " * Configuration file, generated by the ".ITOP_APPLICATION." configuration wizard\n");
fwrite($hFile, " *\n");
fwrite($hFile, " * The file is used in MetaModel::LoadConfig() which does all the necessary initialization job\n");
fwrite($hFile, " *\n");
fwrite($hFile, " */\n");
fwrite($hFile, "\n");
$aConfigSettings = $this->m_aSettings;
// Old fashioned boolean settings
$aBoolValues = array(
'log_global' => $this->m_bLogGlobal,
'log_notification' => $this->m_bLogNotification,
'log_issue' => $this->m_bLogIssue,
'log_web_service' => $this->m_bLogWebService,
'secure_connection_required' => $this->m_bSecureConnectionRequired,
);
foreach($aBoolValues as $sKey => $bValue)
{
$aConfigSettings[$sKey] = array(
'show_in_conf_sample' => true,
'type' => 'bool',
'value' => $bValue,
);
}
// Old fashioned non boolean values
$aOtherValues = array(
'db_host' => $this->m_sDBHost,
'db_user' => $this->m_sDBUser,
'db_pwd' => $this->m_sDBPwd,
'db_name' => $this->m_sDBName,
'db_subname' => $this->m_sDBSubname,
'db_character_set' => $this->m_sDBCharacterSet,
'db_collation' => $this->m_sDBCollation,
'default_language' => $this->m_sDefaultLanguage,
'allowed_login_types' => $this->m_sAllowedLoginTypes,
'encryption_key' => $this->m_sEncryptionKey,
'csv_import_charsets' => $this->m_aCharsets,
);
foreach($aOtherValues as $sKey => $value)
{
$aConfigSettings[$sKey] = array(
'show_in_conf_sample' => true,
'type' => is_string($value) ? 'string' : 'mixed',
'value' => $value,
);
}
ksort($aConfigSettings);
fwrite($hFile, "\$MySettings = array(\n");
foreach($this->m_aSettings as $sPropCode => $aSettingInfo)
foreach($aConfigSettings as $sPropCode => $aSettingInfo)
{
if ($aSettingInfo['show_in_conf_sample'])
{
$sType = $this->m_aSettings[$sPropCode]['type'];
$sType = $aSettingInfo['type'];
switch($sType)
{
case 'bool':
$sSeenAs = $aSettingInfo['value'] ? '1' : '0';
$sSeenAs = $aSettingInfo['value'] ? 'true' : 'false';
break;
default:
$sSeenAs = "'".addslashes($aSettingInfo['value'])."'";
$sSeenAs = self::PrettyVarExport($aSettingInfo['value'], "\t");
}
fwrite($hFile, "\n");
if (isset($aSettingInfo['description']))
{
fwrite($hFile, "\t// $sPropCode: {$aSettingInfo['description']}\n");
}
if (isset($aSettingInfo['default']))
{
$default = $aSettingInfo['default'];
if ($aSettingInfo['type'] == 'bool')
{
$default = $default ? 'true' : 'false';
}
fwrite($hFile, "\t//\tdefault: ".self::PrettyVarExport($aSettingInfo['default'],"\t//\t\t", true)."\n");
}
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");
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");
fwrite($hFile, "\t'log_issue' => {$this->m_bLogIssue},\n");
fwrite($hFile, "\t'log_web_service' => {$this->m_bLogWebService},\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, "\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, "\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");
fwrite($hFile, "/**\n *\n * Modules specific settings\n *\n */\n");
fwrite($hFile, "\$MyModuleSettings = array(\n");
foreach ($this->m_aModuleSettings as $sModule => $aProperties)
{
fwrite($hFile, "\t'$sModule' => array (\n");
foreach ($aProperties as $sProperty => $value)
{
$sExport = var_export($value, true);
fwrite($hFile, "\t\t'$sProperty' => $sExport,\n");
$sNiceExport = self::PrettyVarExport($value, "\t\t");
fwrite($hFile, "\t\t'$sProperty' => $sNiceExport,\n");
}
fwrite($hFile, "\t),\n");
}
@@ -1250,5 +1416,158 @@ class Config
}
return $aResult;
}
/**
* Helper function to initialize a configuration from the page arguments
*/
public function UpdateFromParams($aParamValues, $sModulesDir = null)
{
if (isset($aParamValues['application_path']))
{
$this->Set('app_root_url', $aParamValues['application_path']);
}
if (isset($aParamValues['mode']) && isset($aParamValues['language']))
{
if (($aParamValues['mode'] == 'install') || $this->GetDefaultLanguage() == '')
{
$this->SetDefaultLanguage($aParamValues['language']);
}
}
if (isset($aParamValues['db_server']))
{
$this->SetDBHost($aParamValues['db_server']);
$this->SetDBUser($aParamValues['db_user']);
$this->SetDBPwd($aParamValues['db_pwd']);
$sDBName = $aParamValues['db_name'];
if ($sDBName == '')
{
// Todo - obsolete after the transition to the new setup (2.0) is complete (WARNING: used by the designer)
$sDBName = $aParamValues['new_db_name'];
}
$this->SetDBName($sDBName);
$this->SetDBSubname($aParamValues['db_prefix']);
}
if (!is_null($sModulesDir))
{
if (isset($aParamValues['selected_modules']))
{
$aSelectedModules = explode(',', $aParamValues['selected_modules']);
}
else
{
$aSelectedModules = null;
}
// Initialize the arrays below with default values for the application...
$oEmptyConfig = new Config('dummy_file', false); // Do NOT load any config file, just set the default values
$aAddOns = $oEmptyConfig->GetAddOns();
$aAppModules = $oEmptyConfig->GetAppModules();
$aDataModels = $oEmptyConfig->GetDataModels();
$aWebServiceCategories = $oEmptyConfig->GetWebServiceCategories();
$aDictionaries = $oEmptyConfig->GetDictionaries();
// Merge the values with the ones provided by the modules
// Make sure when don't load the same file twice...
$aModules = ModuleDiscovery::GetAvailableModules(APPROOT, $sModulesDir);
foreach($aModules as $sModuleId => $aModuleInfo)
{
list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId);
if (is_null($aSelectedModules) || in_array($sModuleName, $aSelectedModules))
{
if (isset($aModuleInfo['datamodel']))
{
$aDataModels = array_unique(array_merge($aDataModels, $aModuleInfo['datamodel']));
}
if (isset($aModuleInfo['webservice']))
{
$aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aModuleInfo['webservice']));
}
if (isset($aModuleInfo['dictionary']))
{
$aDictionaries = array_unique(array_merge($aDictionaries, $aModuleInfo['dictionary']));
}
if (isset($aModuleInfo['settings']))
{
foreach($aModuleInfo['settings'] as $sProperty => $value)
{
list($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId);
$this->SetModuleSetting($sName, $sProperty, $value);
}
}
if (isset($aModuleInfo['installer']))
{
$sModuleInstallerClass = $aModuleInfo['installer'];
if (!class_exists($sModuleInstallerClass))
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aModuleInfo['label']);
}
if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI'))
{
throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aModuleInfo['label']);
}
$aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig');
call_user_func_array($aCallSpec, array($this));
}
}
}
$this->SetAddOns($aAddOns);
$this->SetAppModules($aAppModules);
$this->SetDataModels($aDataModels);
$this->SetWebServiceCategories($aWebServiceCategories);
$this->SetDictionaries($aDictionaries);
}
}
/**
* Helper: for an array of string, change the prefix when found
*/
protected static function ChangePrefix(&$aStrings, $sSearchPrefix, $sNewPrefix)
{
foreach ($aStrings as &$sFile)
{
if (substr($sFile, 0, strlen($sSearchPrefix)) == $sSearchPrefix)
{
$sFile = $sNewPrefix.substr($sFile, strlen($sSearchPrefix));
}
}
}
/**
* Quick an dirty way to clone a config file into another environment
*/
public function ChangeModulesPath($sSourceEnv, $sTargetEnv)
{
$sSearchPrefix = 'env-'.$sSourceEnv.'/';
$sNewPrefix = 'env-'.$sTargetEnv.'/';
self::ChangePrefix($this->m_aDataModels, $sSearchPrefix, $sNewPrefix);
self::ChangePrefix($this->m_aWebServiceCategories, $sSearchPrefix, $sNewPrefix);
self::ChangePrefix($this->m_aDictionaries, $sSearchPrefix, $sNewPrefix);
}
/**
* Pretty format a var_export'ed value so that (if possible) the identation is preserved on every line
* @param mixed $value The value to export
* @param string $sIdentation The string to use to indent the text
* @param bool $bForceIndentation Forces the identation (enven if it breaks/changes an eval, for example to ouput a value inside a comment)
* @return string The indented export string
*/
protected static function PrettyVarExport($value, $sIdentation, $bForceIndentation = false)
{
$sExport = var_export($value, true);
$sNiceExport = trim(preg_replace("/^/m", "\t\t\t", $sExport));
if (!$bForceIndentation)
{
eval('$aImported='.$sNiceExport.';');
// Check if adding the identations at the beginning of each line
// did not modify the values (in case of a string containing a line break)
if($aImported != $value)
{
$sNiceExport = $sExport;
}
}
return $sNiceExport;
}
}
?>

View File

@@ -80,7 +80,7 @@ abstract class DBObject
{
$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
$this->m_aOrigValues[$sAttCode] = null;
if ($oAttDef->IsExternalField())
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
{
// This field has to be read from the DB
// Leave the flag unset (optimization)
@@ -145,8 +145,8 @@ abstract class DBObject
{
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
{
@$bIsLoaded = $this->m_aLoadedAtt[$sAttCode];
if ($bIsLoaded !== true)
if (!$oAttDef->LoadInObject()) continue;
if (!isset($this->m_aLoadedAtt[$sAttCode]) || !$this->m_aLoadedAtt[$sAttCode])
{
return false;
}
@@ -154,7 +154,7 @@ abstract class DBObject
return true;
}
protected function Reload()
public function Reload()
{
assert($this->m_bIsInDB);
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey, false/*, $this->m_bAllowAllData*/);
@@ -249,6 +249,8 @@ abstract class DBObject
// Skip links (could not be loaded by the mean of this query)
if ($oAttDef->IsLinkSet()) continue;
if (!$oAttDef->LoadInObject()) continue;
// 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
// Take care: the function isset will return false in case the value is null,
@@ -260,7 +262,14 @@ abstract class DBObject
$value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
$this->m_aCurrValues[$sAttCode] = $value;
$this->m_aOrigValues[$sAttCode] = $value;
if (is_object($value))
{
$this->m_aOrigValues[$sAttCode] = clone $value;
}
else
{
$this->m_aOrigValues[$sAttCode] = $value;
}
$this->m_aLoadedAtt[$sAttCode] = true;
}
else
@@ -302,31 +311,39 @@ abstract class DBObject
$this->Reload();
}
if ($oAttDef->IsExternalKey() && is_object($value))
if ($oAttDef->IsExternalKey())
{
// 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())))
if (is_object($value))
{
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
}
else
{
// The object has changed, reset caches
$this->m_bCheckStatus = null;
$this->m_aAsArgs = null;
// 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 CoreUnexpectedValue("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] = $value->GetKey();
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
{
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
}
}
}
return;
else
{
// Setting an external key, but no any other information is available...
// Invalidate the corresponding fields so that they get reloaded in case they are needed (See Get())
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
{
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue();
unset($this->m_aLoadedAtt[$sCode]);
}
}
}
}
if(!$oAttDef->IsScalar() && !is_object($value))
{
@@ -350,6 +367,7 @@ abstract class DBObject
}
$realvalue = $oAttDef->MakeRealValue($value, $this);
$this->m_aCurrValues[$sAttCode] = $realvalue;
// The object has changed, reset caches
@@ -368,16 +386,101 @@ abstract class DBObject
public function Get($sAttCode)
{
if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
if (($iPos = strpos($sAttCode, '->')) === false)
{
throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
return $this->GetStrict($sAttCode);
}
if ($this->m_bIsInDB && !isset($this->m_aLoadedAtt[$sAttCode]) && !$this->m_bDirty)
else
{
// #@# non-scalar attributes.... handle that differently
$this->Reload();
$sExtKeyAttCode = substr($sAttCode, 0, $iPos);
$sRemoteAttCode = substr($sAttCode, $iPos + 2);
if (!MetaModel::IsValidAttCode(get_class($this), $sExtKeyAttCode))
{
throw new CoreException("Unknown external key '$sExtKeyAttCode' for the class ".get_class($this));
}
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
$sRemoteClass = $oKeyAttDef->GetTargetClass();
$oRemoteObj = MetaModel::GetObject($sRemoteClass, $this->GetStrict($sExtKeyAttCode), false);
if (is_null($oRemoteObj))
{
return '';
}
else
{
return $oRemoteObj->Get($sRemoteAttCode);
}
}
$value = $this->m_aCurrValues[$sAttCode];
}
public function GetStrict($sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if (!$oAttDef->LoadInObject())
{
$sParentAttCode = $oAttDef->GetParentAttCode();
$parentValue = $this->GetStrict($sParentAttCode);
$value = $oAttDef->GetValue($parentValue, $this);
}
else
{
if (isset($this->m_aLoadedAtt[$sAttCode]))
{
// Standard case... we have the information directly
}
elseif ($this->m_bIsInDB && !$this->m_bDirty)
{
// Lazy load (polymorphism): complete by reloading the entire object
// #@# non-scalar attributes.... handle that differently?
$this->Reload();
}
elseif ($sAttCode == 'friendlyname')
{
// The friendly name is not computed and the object is dirty
// Todo: implement the computation of the friendly name based on sprintf()
//
$this->m_aCurrValues[$sAttCode] = '';
}
else
{
// Not loaded... is it related to an external key?
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
{
// Let's get the object and compute all of the corresponding attributes
// (i.e not only the requested attribute)
//
$sExtKeyAttCode = $oAttDef->GetKeyAttCode();
if (($iRemote = $this->Get($sExtKeyAttCode)) && ($iRemote > 0)) // Objects in memory have negative IDs
{
$oExtKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
$oRemote = MetaModel::GetObject($oExtKeyAttDef->GetTargetClass(), $iRemote);
}
else
{
$oRemote = null;
}
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sExtKeyAttCode))
{
if ($oRemote)
{
$this->m_aCurrValues[$sCode] = $oRemote->Get($oDef->GetExtAttCode());
}
else
{
$this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue();
}
$this->m_aLoadedAtt[$sCode] = true;
}
}
}
}
$value = $this->m_aCurrValues[$sAttCode];
}
if ($value instanceof DBObjectSet)
{
$value->Rewind();
@@ -455,12 +558,20 @@ abstract class DBObject
//return $this->Get($sAttCode.'_friendlyname');
$sTargetClass = $oAtt->GetTargetClass(EXTKEY_ABSOLUTE);
$iTargetKey = $this->Get($sAttCode);
$sLabel = $this->Get($sAttCode.'_friendlyname');
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sLabel);
if ($iTargetKey < 0)
{
// the key points to an object that exists only in memory... no hyperlink points to it yet
return '';
}
else
{
$sLabel = $this->Get($sAttCode.'_friendlyname');
return $this->MakeHyperLink($sTargetClass, $iTargetKey, $sLabel);
}
}
// That's a standard attribute (might be an ext field or a direct field, etc.)
return $oAtt->GetAsHTML($this->Get($sAttCode));
return $oAtt->GetAsHTML($this->Get($sAttCode), $this);
}
public function GetEditValue($sAttCode)
@@ -493,7 +604,7 @@ abstract class DBObject
}
else
{
$sEditValue = $oAtt->GetEditValue($this->Get($sAttCode));
$sEditValue = $oAtt->GetEditValue($this->Get($sAttCode), $this);
}
return $sEditValue;
}
@@ -528,7 +639,7 @@ abstract class DBObject
return $oAtt->GetAsCSV($this->GetOriginal($sAttCode), $sSeparator, $sTextQualifier, $this);
}
protected static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
public static function MakeHyperLink($sObjClass, $sObjKey, $sLabel = '', $sUrlMakerClass = null, $bWithNavigationContext = true)
{
if ($sObjKey <= 0) return '<em>'.Dict::S('UI:UndefinedObject').'</em>'; // Objects built in memory have negative IDs
@@ -683,6 +794,29 @@ abstract class DBObject
return MetaModel::GetStateDescription(get_class($this), $sStateValue);
}
}
/**
* Overridable - Define attributes read-only from the end-user perspective
*
* @return array List of attcodes
*/
public static function GetReadOnlyAttributes()
{
return null;
}
/**
* Overridable - Get predefined objects (could be hardcoded)
* The predefined objects will be synchronized with the DB at each install/upgrade
* As soon as a class has predefined objects, then nobody can create nor delete objects
* @return array An array of id => array of attcode => php value(so-called "real value": integer, string, ormDocument, DBObjectSet, etc.)
*/
public static function GetPredefinedObjects()
{
return null;
}
/**
* 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
@@ -694,6 +828,16 @@ abstract class DBObject
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
{
$iFlags = 0; // By default (if no life cycle) no flag at all
$aReadOnlyAtts = $this->GetReadOnlyAttributes();
if ($aReadOnlyAtts != null)
{
if (in_array($sAttCode, $aReadOnlyAtts))
{
return OPT_ATT_READONLY;
}
}
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
if (!empty($sStateAttCode))
{
@@ -904,7 +1048,7 @@ abstract class DBObject
$oDeletionPlan->AddToDelete($oReplica, DEL_SILENT);
if ($oDataSource->GetKey() == SynchroDataSource::GetCurrentTaskId())
if ($oDataSource->GetKey() == SynchroExecution::GetCurrentTaskId())
{
// The current task has the right to delete the object
continue;
@@ -1226,6 +1370,26 @@ abstract class DBObject
throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
}
// Stop watches
$sState = $this->GetState();
if ($sState != '')
{
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
{
if (in_array($sState, $oAttDef->GetStates()))
{
// Start the stop watch and compute the deadlines
$oSW = $this->Get($sAttCode);
$oSW->Start($this, $oAttDef);
$oSW->ComputeDeadlines($this, $oAttDef);
$this->Set($sAttCode, $oSW);
}
}
}
}
// First query built upon on the root class, because the ID must be created first
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
@@ -1275,6 +1439,11 @@ abstract class DBObject
return $this->DBInsert();
}
public function DBInsertTrackedNoReload(CMDBChange $oVoid)
{
return $this->DBInsertNoReload();
}
// Creates a copy of the current object into the database
// Returns the id of the newly created object
public function DBClone($iNewKey = null)
@@ -1304,6 +1473,25 @@ abstract class DBObject
throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
}
// Stop watches
$sState = $this->GetState();
if ($sState != '')
{
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
{
if (in_array($sState, $oAttDef->GetStates()))
{
// Compute or recompute the deadlines
$oSW = $this->Get($sAttCode);
$oSW->ComputeDeadlines($this, $oAttDef);
$this->Set($sAttCode, $oSW);
}
}
}
}
$this->DoComputeValues();
$this->OnUpdate();
@@ -1545,6 +1733,10 @@ abstract class DBObject
return MetaModel::EnumTransitions(get_class($this), $sState);
}
/**
* Designed as an action to be called when a stop watch threshold times out
* or from within the framework
*/
public function ApplyStimulus($sStimulusCode)
{
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
@@ -1579,25 +1771,60 @@ abstract class DBObject
// if one call fails, the whole is considered as failed
if (!$bRet) $bSuccess = false;
}
// Change state triggers...
$sClass = get_class($this);
$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())
if ($bSuccess)
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
// Change state triggers...
$sClass = get_class($this);
$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 IN ('$sClassList') AND t.state='$sNewState'"));
while ($oTrigger = $oSet->Fetch())
{
$oTrigger->DoActivate($this->ToArgs('this'));
}
$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'));
// Stop watches
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
{
$oSW = $this->Get($sAttCode);
if (in_array($sNewState, $oAttDef->GetStates()))
{
$oSW->Start($this, $oAttDef);
}
else
{
$oSW->Stop($this, $oAttDef);
}
$this->Set($sAttCode, $oSW);
}
}
}
return $bSuccess;
}
/**
* Designed as an action to be called when a stop watch threshold times out
*/
public function ResetStopWatch($sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if (!$oAttDef instanceof AttributeStopWatch)
{
throw new CoreException("Invalid stop watch id: '$sAttCode'");
}
$oSW = $this->Get($sAttCode);
$oSW->Reset($this, $oAttDef);
$this->Set($sAttCode, $oSW);
}
// 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:
@@ -1615,7 +1842,7 @@ abstract class DBObject
$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink('iTopStandardURLMaker', false);
$aScalarArgs[$sArgName.'->hyperlink(portal)'] = $this->GetHyperlink('PortalURLMaker', false);
$aScalarArgs[$sArgName.'->name()'] = $this->GetName();
$sClass = get_class($this);
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
@@ -1625,7 +1852,7 @@ abstract class DBObject
// #@# 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);
$aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = $this->GetEditValue($sAttCode); // "Nice" display value, but without HTML tags and entities
}
// Do something for case logs... quick N' dirty...
if ($aScalarArgs[$sArgName.'->'.$sAttCode] instanceof ormCaseLog)
@@ -1635,6 +1862,7 @@ abstract class DBObject
$aScalarArgs[$sArgName.'->head('.$sAttCode.')'] = $oCaseLog->GetLatestEntry();
}
}
$this->m_aAsArgs = $aScalarArgs;
$oKPI->ComputeStats('ToArgs', get_class($this));
}
@@ -1831,7 +2059,7 @@ abstract class DBObject
$oSet = $this->GetMasterReplica();
while($aData = $oSet->FetchAssoc())
{
if ($aData['datasource']->GetKey() == SynchroDataSource::GetCurrentTaskId())
if ($aData['datasource']->GetKey() == SynchroExecution::GetCurrentTaskId())
{
// Ignore the current task (check to write => ok)
continue;

View File

@@ -66,6 +66,8 @@ class DBObjectSearch
$this->m_aRelatedTo = array();
$this->m_bDataFiltered = false;
$this->m_aParentConditions = array();
$this->m_aModifierProperties = array();
}
public function AllowAllData() {$this->m_bAllowAllData = true;}
@@ -73,7 +75,18 @@ class DBObjectSearch
public function IsDataFiltered() {return $this->m_bDataFiltered; }
public function SetDataFiltered() {$this->m_bDataFiltered = true;}
public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];}
public function GetClassName($sAlias)
{
if (array_key_exists($sAlias, $this->m_aClasses))
{
return $this->m_aClasses[$sAlias];
}
else
{
throw new CoreException("Invalid class alias '$sAlias'");
}
}
public function GetJoinedClasses() {return $this->m_aClasses;}
public function GetClass()
@@ -115,6 +128,23 @@ class DBObjectSearch
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
}
public function GetModifierProperties($sPluginClass)
{
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
{
return $this->m_aModifierProperties[$sPluginClass];
}
else
{
return array();
}
}
public function IsAny()
{
// #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false;
@@ -518,24 +548,38 @@ class DBObjectSearch
{
if ($bTranslateMainAlias)
{
$sOrigAlias = $this->GetClassAlias();
$sOrigAlias = $this->GetFirstJoinedClassAlias();
if (array_key_exists($sOrigAlias, $aClassAliases))
{
$sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetClass());
//echo "<p>Generating a new alias for $sOrigAlias (already used). It is now: $sNewAlias</p>\n";
$this->m_aSelectedClasses[$sNewAlias] = $this->GetClass();
unset($this->m_aSelectedClasses[$sOrigAlias]);
$this->m_aClasses[$sNewAlias] = $this->GetClass();
unset($this->m_aClasses[$sOrigAlias]);
$sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetFirstJoinedClass());
if (isset($this->m_aSelectedClasses[$sOrigAlias]))
{
$this->m_aSelectedClasses[$sNewAlias] = $this->GetFirstJoinedClass();
unset($this->m_aSelectedClasses[$sOrigAlias]);
}
// TEMPORARY ALGORITHM (m_aClasses is not correctly updated, it is not possible to add a subtree onto a subnode)
// Replace the element at the same position (unset + set is not enough because the hash array is ordered)
$aPrevList = $this->m_aClasses;
$this->m_aClasses = array();
foreach ($aPrevList as $sSomeAlias => $sSomeClass)
{
if ($sSomeAlias == $sOrigAlias)
{
$this->m_aClasses[$sNewAlias] = $sSomeClass; // note: GetFirstJoinedClass now returns '' !!!
}
else
{
$this->m_aClasses[$sSomeAlias] = $sSomeClass;
}
}
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias;
}
//echo "<p>Adding the alias ".$this->GetClass()." as ".$this->GetClassAlias()."</p>\n";
// add the alias into the filter aliases list
$aClassAliases[$this->GetClassAlias()] = $this->GetClass();
$aClassAliases[$this->GetFirstJoinedClassAlias()] = $this->GetFirstJoinedClass();
}
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$aPointingTo)
@@ -568,7 +612,6 @@ class DBObjectSearch
protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
{
//echo "<p style=\"color:green\">Calling: AddCondition_PointingTo_InNameSpace([<pre>".print_r($aClassAliases, true)."</pre></br>], [<pre>".print_r($aAliasTranslation, true)."</pre>]);</p>";
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");
@@ -580,7 +623,7 @@ class DBObjectSearch
}
if(($iOperatorCode != TREE_OPERATOR_EQUALS) && !($oAttExtKey instanceof AttributeHierarchicalKey))
{
throw new CoreException("The specified tree operator $isOperatorCode is not applicable to the key '{$this->GetClass()}::$sExtKeyAttCode', which is not a HierarchicalKey");
throw new CoreException("The specified tree operator $iOperatorCode is not applicable to the key '{$this->GetClass()}::$sExtKeyAttCode', which is not a HierarchicalKey");
}
$bSamePointingTo = false;
@@ -590,27 +633,19 @@ class DBObjectSearch
{
if (array_key_exists($oFilter->GetClassAlias(), $this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode]))
{
//echo "<p style=\"color:red\">[".__LINE__."]this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][".$oFilter->GetFirstJoinedClassAlias()."]:<pre>\n".print_r($this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode], true)."</pre>;</p>";
$bSamePointingTo = true;
}
}
}
//echo "<p style=\"color:red\">[".__LINE__."]Calling: AddToNameSpace([".implode(',', $aClassAliases)."], [".implode(',', $aAliasTranslation)."]);</p>";
if ($bSamePointingTo)
{
//echo "<p style=\"color:red\">[".__LINE__."]AddPointingTo: Merging filters for [$sExtKeyAttCode][$iOperatorCode][".$oFilter->GetClassAlias()."]</p>";
// Same ext key, alias and same operator, merge the filters together
// $sAlias = $oFilter->GetClassAlias();
//echo "<p style=\"color:red\">[".__LINE__."]before: AddToNameSpace(aClassAliases[<pre>\n".print_r($aClassAliases, true)."</pre>], aAliasTranslation[<pre>\n".print_r($aAliasTranslation, true)."</pre>]);</p>";
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation, true /* Don't translate the main alias */);
//echo "<p style=\"color:blue\">[".__LINE__."]after: AddToNameSpace(aClassAliases[<pre>\n".print_r($aClassAliases, true)."</pre>], aAliasTranslation[<pre>\n".print_r($aAliasTranslation, true)."</pre>]);</p>";
// $this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][$sAlias]->MergeWith($oFilter, $aClassAliases, $aAliasTranslation);
$this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][$oFilter->GetClassAlias()] = $oFilter;
}
else
{
//echo "<p style=\"color:red\">[".__LINE__."]AddPointingTo: Adding a new PointingTo filter for [$sExtKeyAttCode][$iOperatorCode][".$oFilter->GetClassAlias()."]</p>";
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
$this->m_aPointingTo[$sExtKeyAttCode][$iOperatorCode][$oFilter->GetClassAlias()] = $oFilter;
}
@@ -737,20 +772,47 @@ class DBObjectSearch
{
return $this->m_aRelatedTo;
}
public function SetInternalParams($aParams)
{
return $this->m_aParams = $aParams;
}
public function GetInternalParams()
{
return $this->m_aParams;
}
public function GetQueryParams()
{
$aParams = array();
$this->m_oSearchCondition->Render($aParams, true);
return $aParams;
}
public function ListConstantFields()
{
return $this->m_oSearchCondition->ListConstantFields();
}
public function RenderCondition()
{
return $this->m_oSearchCondition->Render($this->m_aParams, false);
}
/**
* Turn the parameters (:xxx) into scalar values in order to easily
* serialize a search
*/
public function ApplyParameters($aArgs)
{
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
}
public function serialize($bDevelopParams = false, $aContextParams = null)
{
$sOql = $this->ToOql($bDevelopParams, $aContextParams);
return base64_encode(serialize(array($sOql, $this->m_aParams)));
return base64_encode(serialize(array($sOql, $this->m_aParams, $this->m_aModifierProperties)));
}
static public function unserialize($sValue)
@@ -761,7 +823,9 @@ class DBObjectSearch
// 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, $aParams);
$oRetFilter = self::FromOQL($sOql, $aParams);
$oRetFilter->m_aModifierProperties = $aData[2];
return $oRetFilter;
}
// SImple BUt Structured Query Languag - SubuSQL
@@ -857,6 +921,7 @@ class DBObjectSearch
{
$aParams = array_merge($aContextParams, $this->m_aParams);
}
$aParams = MetaModel::PrepareQueryArguments($aParams);
}
else
{
@@ -867,7 +932,7 @@ class DBObjectSearch
$sSelectedClasses = implode(', ', array_keys($this->m_aSelectedClasses));
$sRes = 'SELECT '.$sSelectedClasses.' FROM';
$sRes .= ' '.$this->GetClass().' AS '.$this->GetClassAlias();
$sRes .= ' '.$this->GetFirstJoinedClass().' AS '.$this->GetFirstJoinedClassAlias();
$sRes .= $this->ToOQL_Joins();
$sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams);
@@ -927,7 +992,7 @@ class DBObjectSearch
break;
}
$sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.$sOperator.$oFilter->GetClassAlias().'.id';
$sRes .= ' JOIN '.$oFilter->GetFirstJoinedClass().' AS '.$oFilter->GetFirstJoinedClassAlias().' ON '.$this->GetFirstJoinedClassAlias().'.'.$sExtKey.$sOperator.$oFilter->GetFirstJoinedClassAlias().'.id';
$sRes .= $oFilter->ToOQL_Joins();
}
}
@@ -936,7 +1001,7 @@ class DBObjectSearch
{
foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
{
$sRes .= ' JOIN '.$oForeignFilter->GetClass().' AS '.$oForeignFilter->GetClassAlias().' ON '.$oForeignFilter->GetClassAlias().'.'.$sForeignExtKeyAttCode.' = '.$this->GetClassAlias().'.id';
$sRes .= ' JOIN '.$oForeignFilter->GetFirstJoinedClass().' AS '.$oForeignFilter->GetFirstJoinedClassAlias().' ON '.$oForeignFilter->GetFirstJoinedClassAlias().'.'.$sForeignExtKeyAttCode.' = '.$this->GetFirstJoinedClassAlias().'.id';
$sRes .= $oForeignFilter->ToOQL_Joins();
}
}
@@ -1072,7 +1137,7 @@ class DBObjectSearch
if (!MetaModel::IsValidClass($sClass))
{
throw new OqlNormalizeException('Unknown class', $sQuery, $oOqlQuery->GetClassDetails(), MetaModel::GetClasses());
throw new UnknownClassOqlException($sQuery, $oOqlQuery->GetClassDetails(), MetaModel::GetClasses());
}
$oResultFilter = new DBObjectSearch($sClass, $sClassAlias);
@@ -1092,7 +1157,7 @@ class DBObjectSearch
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
if (!MetaModel::IsValidClass($sJoinClass))
{
throw new OqlNormalizeException('Unknown class', $sQuery, $oJoinSpec->GetClassDetails(), MetaModel::GetClasses());
throw new UnknownClassOqlException($sQuery, $oJoinSpec->GetClassDetails(), MetaModel::GetClasses());
}
if (array_key_exists($sJoinClassAlias, $aAliases))
{
@@ -1223,4 +1288,4 @@ class DBObjectSearch
}
?>
?>

View File

@@ -290,6 +290,11 @@ class DBObjectSet
return $this->m_oFilter->GetClass();
}
public function GetClassAlias()
{
return $this->m_oFilter->GetClassAlias();
}
public function GetSelectedClasses()
{
return $this->m_oFilter->GetSelectedClasses();
@@ -311,6 +316,19 @@ class DBObjectSet
$this->m_iLimitStart = $iLimitStart;
}
public function SetOrderBy($aOrderBy)
{
if ($this->m_aOrderBy != $aOrderBy)
{
$this->m_aOrderBy = $aOrderBy;
if ($this->m_bLoaded)
{
$this->m_bLoaded = false;
$this->Load();
}
}
}
public function GetLimitCount()
{
return $this->m_iLimitCount;
@@ -321,6 +339,20 @@ class DBObjectSet
return $this->m_iLimitStart;
}
public function GetRealSortOrder()
{
// Get the class default sort order if not specified with the API
//
if (empty($this->m_aOrderBy))
{
return MetaModel::GetOrderByDefault($this->m_oFilter->GetClass());
}
else
{
return $this->m_aOrderBy;
}
}
public function Load()
{
if ($this->m_bLoaded) return;
@@ -329,11 +361,11 @@ class DBObjectSet
if ($this->m_iLimitCount > 0)
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec, $this->m_iLimitCount, $this->m_iLimitStart);
}
else
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->GetRealSortOrder(), $this->m_aArgs, $this->m_aAttToLoad, $this->m_aExtendedDataSpec);
}
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return;
@@ -370,7 +402,7 @@ class DBObjectSet
{
if (is_null($this->m_iCount))
{
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy, $this->m_aArgs, null, null, 0, 0, true);
$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, array(), $this->m_aArgs, null, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return 0;
@@ -687,6 +719,56 @@ class DBObjectSet
$this->Rewind();
return $oCommonObj;
}
/**
* List the constant fields (and their value) in the given query
* @return Hash [Alias][AttCode] => value
*/
public function ListConstantFields()
{
$aScalarArgs = $this->ExpandArgs();
$aConst = $this->m_oFilter->ListConstantFields();
foreach($aConst as $sClassAlias => $aVals)
{
foreach($aVals as $sCode => $oExpr)
{
if ($oExpr instanceof ScalarExpression)
{
$aConst[$sClassAlias][$sCode] = $oExpr->GetValue();
}
else //Variable
{
$aConst[$sClassAlias][$sCode] = $aScalarArgs[$oExpr->GetName()];
}
}
}
return $aConst;
}
protected function ExpandArgs()
{
$aScalarArgs = $this->m_oFilter->GetInternalParams();
foreach($this->m_aArgs as $sArgName => $value)
{
if (MetaModel::IsValidObject($value))
{
$aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName));
}
else
{
$aScalarArgs[$sArgName] = (string) $value;
}
}
$aScalarArgs['current_contact_id'] = UserRights::GetContactId();
return $aScalarArgs;
}
public function ApplyParameters()
{
$aScalarArgs = $this->ExpandArgs();
$this->m_oFilter->ApplyParameters($aScalarArgs);
}
}
/**

View File

@@ -97,7 +97,7 @@ class Dict
}
public static function GetCurrentLanguage()
public static function GetUserLanguage()
{
if (self::$m_sCurrentLanguage == null) // May happen when no user is logged in (i.e login screen, non authentifed page)
{
@@ -124,12 +124,12 @@ class Dict
{
// Attempt to find the string in the user language
//
if (!array_key_exists(self::GetCurrentLanguage(), self::$m_aData))
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
{
// It may happen, when something happens before the dictionnaries get loaded
return $sStringCode;
}
$aCurrentDictionary = self::$m_aData[self::GetCurrentLanguage()];
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
if (array_key_exists($sStringCode, $aCurrentDictionary))
{
return $aCurrentDictionary[$sStringCode];
@@ -220,6 +220,20 @@ class Dict
}
}
/**
* Clone a string in every language (if it exists in that language)
*/
public static function CloneString($sSourceCode, $sDestCode)
{
foreach(self::$m_aLanguages as $sLanguageCode => $foo)
{
if (isset(self::$m_aData[$sLanguageCode][$sSourceCode]))
{
self::$m_aData[$sLanguageCode][$sDestCode] = self::$m_aData[$sLanguageCode][$sSourceCode];
}
}
}
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{
$aMissing = array(); // Strings missing for the target language

View File

@@ -15,8 +15,7 @@
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Send an mail (for notification, testing,... purposes)
* #@# TODO - replace by a more sophisticated mean (and update the prototype)
* Send an email (abstraction for synchronous/asynchronous modes)
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
@@ -24,6 +23,11 @@
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
require_once(APPROOT.'/lib/swiftmailer/lib/swift_required.php');
Swift_Preferences::getInstance()->setCharset('UTF-8');
define ('EMAIL_SEND_OK', 0);
define ('EMAIL_SEND_PENDING', 1);
define ('EMAIL_SEND_ERROR', 2);
@@ -31,34 +35,32 @@ define ('EMAIL_SEND_ERROR', 2);
class EMail
{
protected $m_sBody;
protected $m_sSubject;
protected $m_sTo;
protected $m_aHeaders; // array of key=>value
protected static $m_oConfig = null;
public function __construct($sTo = '', $sSubject = '', $sBody = '', $aHeaders = array())
public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE)
{
$this->m_sTo = $sTo;
$this->m_sSubject = $sSubject;
$this->m_sBody = $sBody;
$this->m_aHeaders = $aHeaders;
if (is_null(self::$m_oConfig))
{
self::$m_oConfig = new Config($sConfigFile);
}
}
// 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)
protected $m_oMessage;
public function __construct()
{
$sCleanMessage= str_replace("mail() [<a href='function.mail'>function.mail</a>]: ", "", $errstr);
$this->m_aMailErrors[] = $sCleanMessage;
$this->m_oMessage = Swift_Message::newInstance();
$oEncoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
$this->m_oMessage->setEncoder($oEncoder);
}
protected function SendAsynchronous(&$aIssues, $oLog = null)
{
try
{
AsyncSendEmail::AddToQueue($this->m_sTo, $this->m_sSubject, $this->m_sBody, $this->m_aHeaders, $oLog);
AsyncSendEmail::AddToQueue($this, $oLog);
}
catch(Exception $e)
{
@@ -71,34 +73,37 @@ class EMail
protected function SendSynchronous(&$aIssues, $oLog = null)
{
$sHeaders = 'MIME-Version: 1.0' . "\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)
$this->LoadConfig();
$sTransport = self::$m_oConfig->Get('email_transport');
switch ($sTransport)
{
$sHeaders .= "$sKey: $sValue\r\n";
case 'SMTP':
$sHost = self::$m_oConfig->Get('email_transport_smtp.host');
$sPort = self::$m_oConfig->Get('email_transport_smtp.port');
$sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption');
$sUserName = self::$m_oConfig->Get('email_transport_smtp.username');
$sPassword = self::$m_oConfig->Get('email_transport_smtp.password');
$oTransport = Swift_SmtpTransport::newInstance($sHost, $sPort, $sEncryption);
if (strlen($sUserName) > 0)
{
$oTransport->setUsername($sUserName);
$oTransport->setPassword($sPassword);
}
break;
case 'PHPMail':
default:
$oTransport = Swift_MailTransport::newInstance();
}
// 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
(
str_replace(array("\n", "\r"), ' ', $this->m_sTo), // Prevent header injection
str_replace(array("\n", "\r"), ' ', $this->m_sSubject), // Prevent header injection
$this->m_sBody,
$sHeaders
);
restore_error_handler();
if (!$bRes && empty($this->m_aMailErrors))
$oMailer = Swift_Mailer::newInstance($oTransport);
$iSent = $oMailer->send($this->m_oMessage);
if ($iSent === false)
{
$this->m_aMailErrors[] = 'Unknown reason';
}
if (count($this->m_aMailErrors) > 0)
{
$aIssues = $this->m_aMailErrors;
$aIssues = 'une erreur s\'est produite... mais quoi !!!';
return EMAIL_SEND_ERROR;
}
else
@@ -128,32 +133,104 @@ class EMail
}
}
protected function AddToHeader($sKey, $sValue)
public function AddToHeader($sKey, $sValue)
{
if (strlen($sValue) > 0)
{
$this->m_aHeaders[$sKey] = $sValue;
$oHeaders = $this->m_oMessage->getHeaders();
switch(strtolower($sKey))
{
case 'from':
case 'cc':
case 'bcc':
$aMatches = array();
// Header may be in the form: John Doe <jd@company.com>
if (preg_match('/^([^<]+) <([^>]+)>$/', $sValue, $aMatches))
{
$aHeader = array($aMatches[2] => $aMatches[1]);
}
else
{
$aHeader = array($sValue);
}
$oHeaders->addMailboxHeader($sKey, $aHeader);
break;
default:
$oHeaders->addTextHeader($sKey, $sValue);
}
}
}
public function SetMessageId($sId)
{
// Note: Swift will add the angle brackets for you
// so let's remove the angle brackets if present, for historical reasons
$sId = str_replace(array('<', '>'), '', $sId);
$oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID');
$oMsgId->SetId($sId);
}
public function SetReferences($sReferences)
{
$this->AddToHeader('References', $sReferences);
}
public function SetBody($sBody)
public function SetBody($sBody, $sMimeType = 'text/html')
{
$this->m_sBody = $sBody;
$this->m_oMessage->setBody($sBody, $sMimeType);
}
public function AddPart($sText, $sMimeType = 'text/html')
{
$this->m_oMessage->addPart($sText, $sMimeType);
}
public function AddAttachment($data, $sFileName, $sMimeType)
{
$this->m_oMessage->attach(Swift_Attachment::newInstance($data, $sFileName, $sMimeType));
}
public function SetSubject($aSubject)
{
$this->m_sSubject = $aSubject;
$this->m_oMessage->setSubject($aSubject);
}
public function GetSubject()
{
return $this->m_oMessage->getSubject();
}
public function SetRecipientTO($sAddress)
{
$this->m_sTo = $sAddress;
$this->m_oMessage->setTo($sAddress);
}
public function GetRecipientTO($bAsString = false)
{
$aRes = $this->m_oMessage->getTo();
if ($bAsString)
{
$aStrings = array();
foreach ($aRes as $sEmail => $sName)
{
if (is_null($sName))
{
$aStrings[] = $sEmail;
}
else
{
$sName = str_replace(array('<', '>'), '', $sName);
$aStrings[] = "$sName <$sEmail>";
}
}
return implode(', ', $aStrings);
}
else
{
return $aRes;
}
}
public function SetRecipientCC($sAddress)
@@ -166,14 +243,16 @@ class EMail
$this->AddToHeader('Bcc', $sAddress);
}
public function SetRecipientFrom($sAddress)
public function SetRecipientFrom($sAddress, $sLabel = '')
{
$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);
if ($sLabel != '')
{
$this->m_oMessage->setFrom(array($sAddress => $sLabel));
}
else
{
$this->m_oMessage->setFrom($sAddress);
}
}
public function SetRecipientReplyTo($sAddress)

View File

@@ -103,7 +103,7 @@ class Event extends DBObject implements iDisplay
$this->DisplayBareProperties($oPage, $bEditMode);
}
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $aExtraParams = array())
function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array())
{
if ($bEditMode) return; // Not editable
@@ -174,7 +174,7 @@ class EventNotificationEmail extends EventNotification
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_AddAttribute(new AttributeHTML("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// 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
@@ -340,14 +340,22 @@ class EventLoginUsage extends Event
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"user_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"contactid", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList = array('date', 'user_id');
if (MetaModel::IsValidAttCode('Contact', 'name'))
{
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"contactid", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList[] = 'contact_name';
}
if (MetaModel::IsValidAttCode('Contact', 'email'))
{
MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("allowed_values"=>null, "extkey_attcode"=>"user_id", "target_attcode"=>"email", "is_null_allowed"=>true, "depends_on"=>array())));
$aZList[] = 'contact_email';
}
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'user_id', 'contact_name', 'contact_email', 'userinfo', 'message')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'user_id', 'contact_name', 'contact_email', 'userinfo')); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('details', array_merge($aZList, array('userinfo', 'message'))); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array_merge($aZList, array('userinfo'))); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('date', 'user_id', 'contact_name', 'contact_email')); // Criteria of the std search form
MetaModel::Init_SetZListItems('standard_search', $aZList); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}

View File

@@ -36,11 +36,16 @@ abstract class Expression
// 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);
abstract public function ApplyParameters($aArgs);
// recursively builds an array of class => fieldname
abstract public function ListRequiredFields();
abstract public function IsTrue();
// recursively builds an array of [classAlias][fieldName] => value
abstract public function ListConstantFields();
public function RequiresField($sClass, $sFieldName)
{
// #@# todo - optimize : this is called quite often when building a single query !
@@ -86,6 +91,19 @@ abstract class Expression
}
abstract public function RenameParam($sOldName, $sNewName);
/**
* Make the most relevant label, given the value of the expression
*
* @param DBObjectSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
* @return The label
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
return $sDefault;
}
}
class SQLExpression extends Expression
@@ -108,6 +126,10 @@ class SQLExpression extends Expression
return $this->m_sSQL;
}
public function ApplyParameters($aArgs)
{
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -122,6 +144,11 @@ class SQLExpression extends Expression
return array();
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
// Do nothing, since there is nothing to rename
@@ -164,11 +191,11 @@ class BinaryExpression extends Expression
// 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;
if ($this->m_oLeftExpr->IsTrue() && $this->m_oRightExpr->IsTrue()) return true;
}
return false;
}
public function GetLeftExpr()
{
return $this->m_oLeftExpr;
@@ -192,7 +219,27 @@ class BinaryExpression extends Expression
$sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams);
return "($sLeft $sOperator $sRight)";
}
public function ApplyParameters($aArgs)
{
if ($this->m_oLeftExpr instanceof VariableExpression)
{
$this->m_oLeftExpr = $this->m_oLeftExpr->GetAsScalar($aArgs);
}
else //if ($this->m_oLeftExpr instanceof Expression)
{
$this->m_oLeftExpr->ApplyParameters($aArgs);
}
if ($this->m_oRightExpr instanceof VariableExpression)
{
$this->m_oRightExpr = $this->m_oRightExpr->GetAsScalar($aArgs);
}
else //if ($this->m_oRightExpr instanceof Expression)
{
$this->m_oRightExpr->ApplyParameters($aArgs);
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->GetLeftExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -213,6 +260,44 @@ class BinaryExpression extends Expression
return array_merge($aLeft, $aRight);
}
/**
* List all constant expression of the form <field> = <scalar> or <field> = :<variable>
* Could be extended to support <field> = <function><constant_expression>
*/
public function ListConstantFields()
{
$aResult = array();
if ($this->m_sOperator == '=')
{
if (($this->m_oLeftExpr instanceof FieldExpression) && ($this->m_oRightExpr instanceof ScalarExpression))
{
$aResult[$this->m_oLeftExpr->GetParent()][$this->m_oLeftExpr->GetName()] = $this->m_oRightExpr;
}
else if (($this->m_oRightExpr instanceof FieldExpression) && ($this->m_oLeftExpr instanceof ScalarExpression))
{
$aResult[$this->m_oRightExpr->GetParent()][$this->m_oRightExpr->GetName()] = $this->m_oLeftExpr;
}
else if (($this->m_oLeftExpr instanceof FieldExpression) && ($this->m_oRightExpr instanceof VariableExpression))
{
$aResult[$this->m_oLeftExpr->GetParent()][$this->m_oLeftExpr->GetName()] = $this->m_oRightExpr;
}
else if (($this->m_oRightExpr instanceof FieldExpression) && ($this->m_oLeftExpr instanceof VariableExpression))
{
$aResult[$this->m_oRightExpr->GetParent()][$this->m_oRightExpr->GetName()] = $this->m_oLeftExpr;
}
else
{
$aResult = array_merge($this->m_oRightExpr->ListConstantFields(), $this->m_oLeftExpr->ListConstantFields()) ;
}
}
else
{
$aResult = array_merge($this->m_oRightExpr->ListConstantFields(), $this->m_oLeftExpr->ListConstantFields()) ;
}
return $aResult;
}
public function RenameParam($sOldName, $sNewName)
{
$this->GetLeftExpr()->RenameParam($sOldName, $sNewName);
@@ -244,18 +329,13 @@ class UnaryExpression extends Expression
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
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);
}
return CMDBSource::Quote($this->m_value);
}
public function ApplyParameters($aArgs)
{
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -269,6 +349,11 @@ class UnaryExpression extends Expression
{
return array();
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
@@ -281,12 +366,26 @@ class ScalarExpression extends UnaryExpression
{
public function __construct($value)
{
if (!is_scalar($value))
if (!is_scalar($value) && !is_null($value))
{
throw new CoreException('Attempt to create a scalar expression from a non scalar', array('var_type'=>gettype($value)));
}
parent::__construct($value);
}
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
if (is_null($this->m_value))
{
$sRet = 'NULL';
}
else
{
$sRet = CMDBSource::Quote($this->m_value);
}
return $sRet;
}
}
class TrueExpression extends ScalarExpression
@@ -359,6 +458,12 @@ class FieldExpression extends UnaryExpression
// Add a reference to the field
$aUnresolved[$this->m_sName] = $this;
}
elseif ($sAlias == '')
{
// An empty alias means "any alias"
// In such a case, the results are indexed differently
$aUnresolved[$this->m_sParent][$this->m_sName] = $this;
}
}
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
@@ -393,6 +498,52 @@ class FieldExpression extends UnaryExpression
}
return $oRet;
}
/**
* Make the most relevant label, given the value of the expression
*
* @param DBObjectSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
* @return The label
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
$sAttCode = $this->GetName();
$sParentAlias = $this->GetParent();
$aSelectedClasses = $oFilter->GetSelectedClasses();
$sClass = $aSelectedClasses[$sParentAlias];
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
// Set a default value for the general case
$sRes = $oAttDef->GetAsHtml($sValue);
// Exceptions...
if ($oAttDef->IsExternalKey())
{
$sObjClass = $oAttDef->GetTargetClass();
$iObjKey = (int)$sValue;
if ($iObjKey > 0)
{
$oObject = MetaModel::GetObject($sObjClass, $iObjKey);
$sRes = $oObject->GetHyperlink();
}
else
{
// Undefined
$sRes = DBObject::MakeHyperLink($sObjClass, 0);
}
}
elseif ($oAttDef->IsExternalField())
{
if (is_null($sValue))
{
$sRes = Dict::S('UI:UndefinedObject');
}
}
return $sRes;
}
}
// Has been resolved into an SQL expression
@@ -440,7 +591,7 @@ class VariableExpression extends UnaryExpression
}
elseif ($bRetrofitParams)
{
//$aArgs[$this->m_sName] = null;
$aArgs[$this->m_sName] = null;
return ':'.$this->m_sName;
}
else
@@ -456,6 +607,20 @@ class VariableExpression extends UnaryExpression
$this->m_sName = $sNewName;
}
}
public function GetAsScalar($aArgs)
{
$value = '';
if (array_key_exists($this->m_sName, $aArgs))
{
$value = $aArgs[$this->m_sName];
}
else
{
throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>array_keys($aArgs)));
}
return new ScalarExpression($value);
}
}
// Temporary, until we implement functions and expression casting!
@@ -501,6 +666,22 @@ class ListExpression extends Expression
return '('.implode(', ', $aRes).')';
}
public function ApplyParameters($aArgs)
{
$aRes = array();
foreach ($this->m_aExpressions as $idx => $oExpr)
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar();
}
else
{
$oExpr->ApplyParameters($aArgs);
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -529,6 +710,16 @@ class ListExpression extends Expression
return $aRes;
}
public function ListConstantFields()
{
$aRes = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aRes = array_merge($aRes, $oExpr->ListConstantFields());
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
$aRes = array();
@@ -578,6 +769,22 @@ class FunctionExpression extends Expression
return $this->m_sVerb.'('.implode(', ', $aRes).')';
}
public function ApplyParameters($aArgs)
{
$aRes = array();
foreach ($this->m_aArgs as $idx => $oExpr)
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aArgs[$idx] = $oExpr->GetAsScalar($aArgs);
}
else
{
$oExpr->ApplyParameters($aArgs);
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aArgs as $oExpr)
@@ -606,13 +813,62 @@ class FunctionExpression extends Expression
return $aRes;
}
public function ListConstantFields()
{
$aRes = array();
foreach ($this->m_aArgs as $oExpr)
{
$aRes = array_merge($aRes, $oExpr->ListConstantFields());
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
foreach ($this->m_aArgs as $key => $oExpr)
{
$this->m_aArgs[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
}
/**
* Make the most relevant label, given the value of the expression
*
* @param DBObjectSearch oFilter The context in which this expression has been used
* @param string sValue The value returned by the query, for this expression
* @param string sDefault The default value if no relevant label could be computed
* @return The label
*/
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
$sRes = $sDefault;
if (strtolower($this->m_sVerb) == 'date_format')
{
$oFormatExpr = $this->m_aArgs[1];
if ($oFormatExpr->Render() == "'%w'")
{
static $aWeekDayToString = null;
if (is_null($aWeekDayToString))
{
// Init the correspondance table
$aWeekDayToString = array(
0 => Dict::S('DayOfWeek-Sunday'),
1 => Dict::S('DayOfWeek-Monday'),
2 => Dict::S('DayOfWeek-Tuesday'),
3 => Dict::S('DayOfWeek-Wednesday'),
4 => Dict::S('DayOfWeek-Thursday'),
5 => Dict::S('DayOfWeek-Friday'),
6 => Dict::S('DayOfWeek-Saturday')
);
}
if (isset($aWeekDayToString[(int)$sValue]))
{
$sRes = $aWeekDayToString[(int)$sValue];
}
}
}
return $sRes;
}
}
class IntervalExpression extends Expression
@@ -648,6 +904,18 @@ class IntervalExpression extends Expression
return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit;
}
public function ApplyParameters($aArgs)
{
if ($this->m_oValue instanceof VariableExpression)
{
$this->m_oValue = $this->m_oValue->GetAsScalar($aArgs);
}
else
{
$this->m_oValue->ApplyParameters($aArgs);
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->m_oValue->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -662,6 +930,11 @@ class IntervalExpression extends Expression
{
return array();
}
public function ListConstantFields()
{
return array();
}
public function RenameParam($sOldName, $sNewName)
{
@@ -702,6 +975,22 @@ class CharConcatExpression extends Expression
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
}
public function ApplyParameters($aArgs)
{
$aRes = array();
foreach ($this->m_aExpressions as $idx => $oExpr)
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar();
}
else
{
$this->m_aExpressions->ApplyParameters($aArgs);
}
}
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -730,6 +1019,16 @@ class CharConcatExpression extends Expression
return $aRes;
}
public function ListConstantFields()
{
$aRes = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aRes = array_merge($aRes, $oExpr->ListConstantFields());
}
return $aRes;
}
public function RenameParam($sOldName, $sNewName)
{
foreach ($this->m_aExpressions as $key => $oExpr)
@@ -770,12 +1069,14 @@ class QueryBuilderExpressions
{
protected $m_oConditionExpr;
protected $m_aSelectExpr;
protected $m_aGroupByExpr;
protected $m_aJoinFields;
public function __construct($aSelect, $oCondition)
public function __construct($oCondition, $aGroupByExpr = null)
{
$this->m_oConditionExpr = $oCondition;
$this->m_aSelectExpr = $aSelect;
$this->m_aSelectExpr = array();
$this->m_aGroupByExpr = $aGroupByExpr;
$this->m_aJoinFields = array();
}
@@ -784,6 +1085,11 @@ class QueryBuilderExpressions
return $this->m_aSelectExpr;
}
public function GetGroupBy()
{
return $this->m_aGroupByExpr;
}
public function GetCondition()
{
return $this->m_oConditionExpr;
@@ -817,6 +1123,13 @@ class QueryBuilderExpressions
{
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$oExpr->GetUnresolvedFields($sAlias, $aUnresolved);
}
}
foreach($this->m_aJoinFields as $oExpression)
{
$oExpression->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -830,6 +1143,13 @@ class QueryBuilderExpressions
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$this->m_aGroupByExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
@@ -843,6 +1163,13 @@ class QueryBuilderExpressions
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
if ($this->m_aGroupByExpr)
{
foreach($this->m_aGroupByExpr as $sColAlias => $oExpr)
{
$this->m_aGroupByExpr[$sColAlias] = $oExpr->RenameParam($sOldName, $sNewName);
}
}
foreach($this->m_aJoinFields as $index => $oExpression)
{
$this->m_aJoinFields[$index] = $oExpression->RenameParam($sOldName, $sNewName);

View File

@@ -50,21 +50,6 @@ abstract class FilterDefinition
$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()
{
@@ -170,12 +155,13 @@ class FilterFromAttribute extends FilterDefinition
return array_merge(parent::ListExpectedParams(), array("refattribute"));
}
public function __construct($oRefAttribute, $aParam = array())
public function __construct($oRefAttribute, $sSuffix = '')
{
// In this very specific case, the code is the one of the attribute
// (this to get a very very simple syntax upon declaration)
$aParam = array();
$aParam["refattribute"] = $oRefAttribute;
parent::__construct($oRefAttribute->GetCode(), $aParam);
parent::__construct($oRefAttribute->GetCode().$sSuffix, $aParam);
}
public function GetType() {return "Basic";}

View File

@@ -191,6 +191,16 @@ class ExecutionKPI
return $output[1] * 1024;
}
}
static public function memory_get_peak_usage($bRealUsage = false)
{
if (function_exists('memory_get_peak_usage'))
{
return memory_get_peak_usage($bRealUsage);
}
// PHP > 5.2.1 - this verb depends on a compilation option
return 0;
}
}
class ApplicationStartupKPI extends ExecutionKPI

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
/**
* Persistent classes for a CMDB
* Any extension to hook the initialization of the metamodel
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
@@ -23,4 +23,9 @@
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
interface iOnClassInitialization
{
public function OnAfterClassInitialization($sClass);
}
?>

View File

@@ -168,7 +168,7 @@ class OQLLexerRaw
'/\GABOVE STRICT/ ',
'/\GNOT ABOVE/ ',
'/\GNOT ABOVE STRICT/ ',
'/\G[0-9]+|0x[0-9a-fA-F]+/ ',
'/\G(0x[0-9a-fA-F]+|[0-9]+)/ ',
'/\G\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/ ',
'/\G([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/ ',
'/\G:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/ ',

View File

@@ -140,7 +140,23 @@ above = "ABOVE"
above_strict = "ABOVE STRICT"
not_above = "NOT ABOVE"
not_above_strict = "NOT ABOVE STRICT"
numval = /[0-9]+|0x[0-9a-fA-F]+/
//
// WARNING: there seems to be a bug in the Lexer about matching the longest pattern
// when there are alternates in the regexp.
//
// For instance:
// numval = /[0-9]+|0x[0-9a-fA-F]+/
// Does not work: SELECT Toto WHERE name = 'Text0xCTest' => Fails because 0xC is recongnized as a numval (inside the string) instead of a strval !!
//
// Inserting a ^ after the alternate (see comment at the top of this file) does not work either
// numval = /[0-9]+|'.chr(94).'0x[0-9a-fA-F]+/
// SELECT Toto WHERE name = 'Text0xCTest' => works but
// SELECT Toto WHERE id = 0xC => does not work, 'xC' is found as a name (apparently 0 is recognized as a numval and the remaining is a name !)
//
// numval = /([0-9]+|0x[0-9a-fA-F]+)/
// Does not work either, the hexadecimal numbers are not matched properly
// The following seems to work...
numval = /(0x[0-9a-fA-F]+|[0-9]+)/
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]*)/

View File

@@ -50,7 +50,13 @@ class OQLException extends CoreException
parent::__construct($sMessage, 0);
}
public function getHtmlDesc($sHighlightHtmlBegin = '<b>', $sHighlightHtmlEnd = '</b>')
public function GetUserFriendlyDescription()
{
// Todo - translate all errors!
return $this->getMessage();
}
public function getHtmlDesc($sHighlightHtmlBegin = '<span style="font-weight: bolder">', $sHighlightHtmlEnd = '</span>')
{
$sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: ", ENT_QUOTES, 'UTF-8');
$sRet .= htmlentities(substr($this->m_sInput, 0, $this->m_iCol), ENT_QUOTES, 'UTF-8');
@@ -71,7 +77,27 @@ class OQLException extends CoreException
return $sRet;
}
static protected function FindClosestString($sInput, $aDictionary)
public function GetIssue()
{
return $this->m_MyIssue;
}
public function GetSuggestions()
{
return $this->m_aExpecting;
}
public function GetWrongWord()
{
return $this->m_sUnexpected;
}
public function GetColumn()
{
return $this->m_iCol;
}
static public function FindClosestString($sInput, $aDictionary)
{
// no shortest distance found, yet
$fShortest = -1;
@@ -88,7 +114,7 @@ class OQLException extends CoreException
return $sSuggestion;
}
if ($fShortest < 0 || ($fDist < 4 && $fDist <= $fShortest))
if (($fDist <= 3) && ($fShortest < 0 || $fDist <= $fShortest))
{
// set the closest match, and shortest distance
$sRet = $sSuggestion;

View File

@@ -31,6 +31,28 @@ class OqlNormalizeException extends OQLException
parent::__construct($sIssue, $sInput, 0, $oName->GetPos(), $oName->GetValue(), $aExpecting);
}
}
class UnknownClassOqlException extends OqlNormalizeException
{
public function __construct($sInput, OqlName $oName, $aExpecting = null)
{
parent::__construct('Unknown class', $sInput, $oName, $aExpecting);
}
public function GetUserFriendlyDescription()
{
$sWrongClass = $this->GetWrongWord();
$sSuggest = self::FindClosestString($sWrongClass, $this->GetSuggestions());
if ($sSuggest != '')
{
return Dict::Format('UI:OQL:UnknownClassAndFix', $sWrongClass, $sSuggest);
}
else
{
return Dict::Format('UI:OQL:UnknownClassNoFix', $sWrongClass);
}
}
}
class OqlInterpreterException extends OQLException
{

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2011 Combodo SARL
// Copyright (C) 2011-2012 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
@@ -26,6 +26,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
class ormCaseLog {
protected $m_sLog;
protected $m_aIndex;
protected $m_bModified;
/**
* Initializes the log with the first (initial) entry
@@ -36,6 +37,7 @@ class ormCaseLog {
{
$this->m_sLog = $sLog;
$this->m_aIndex = $aIndex;
$this->m_bModified = false;
}
public function GetText()
@@ -53,13 +55,25 @@ class ormCaseLog {
return $this->m_sLog;
}
public function ClearModifiedFlag()
{
$this->m_bModified = false;
}
public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
{
$sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
$iPos = 0;
for($index=count($this->m_aIndex)-1 ; $index >= 0 ; $index--)
$aIndex = $this->m_aIndex;
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
{
if ($index < count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS)
// Don't display the first element, that is still considered as editable
$iPos = $aIndex[0]['separator_length'] + $aIndex[0]['text_length'];
array_shift($aIndex);
}
for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
{
if ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS)
{
$sOpen = '';
$sDisplay = 'style="display:none;"';
@@ -69,31 +83,31 @@ class ormCaseLog {
$sOpen = ' open';
$sDisplay = '';
}
$iPos += $this->m_aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
$iPos += $aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
{
$sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
}
$iPos += $this->m_aIndex[$index]['text_length'];
$iPos += $aIndex[$index]['text_length'];
$sEntry = '<div class="caselog_header'.$sOpen.'">';
// Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
// therefore we have changed the format. To preserve the compatibility with existing
// installations of iTop, both format are allowed:
// the 'date' item is either a DateTime object, or a unix timestamp
if (is_int($this->m_aIndex[$index]['date']))
if (is_int($aIndex[$index]['date']))
{
// Unix timestamp
$sDate = date(Dict::S('UI:CaseLog:DateFormat'), $this->m_aIndex[$index]['date']);
$sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
}
elseif (is_object($this->m_aIndex[$index]['date']))
elseif (is_object($aIndex[$index]['date']))
{
if (version_compare(phpversion(), '5.3.0', '>='))
{
// DateTime
$sDate = $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
$sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
}
else
{
@@ -101,7 +115,7 @@ class ormCaseLog {
$sDate = '';
}
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $this->m_aIndex[$index]['user_name']);
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
$sEntry .= '</div>';
$sEntry .= '<div class="caselog_entry"'.$sDisplay.'>';
$sEntry .= $sTextEntry;
@@ -150,23 +164,78 @@ class ormCaseLog {
}
/**
* Add a new entry to the log and updates the internal index
* Add a new entry to the log or merge the given text into the currently modified entry
* and updates the internal index
* @param $sText string The text of the new entry
*/
public function AddLogEntry($sText)
public function AddLogEntry($sText, $sOnBehalfOf = '')
{
$bMergeEntries = false;
$sDate = date(Dict::S('UI:CaseLog:DateFormat'));
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, UserRights::GetUserFriendlyName(), UserRights::GetUserId());
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => UserRights::GetUserFriendlyName(),
'user_id' => UserRights::GetUserId(),
'date' => time(),
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
);
if ($sOnBehalfOf == '')
{
$sOnBehalfOf = UserRights::GetUserFriendlyName();
$iUserId = UserRights::GetUserId();
}
else
{
$iUserId = null;
}
if ($this->m_bModified)
{
$aLatestEntry = end($this->m_aIndex);
if ($aLatestEntry['user_name'] != $sOnBehalfOf)
{
$bMergeEntries = false;
}
else
{
$bMergeEntries = true;
}
}
if ($bMergeEntries)
{
$aLatestEntry = end($this->m_aIndex);
$this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length']);
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText."\n");
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => $aLatestEntry['text_length'] + $iTextlength,
'separator_length' => $iSepLength,
);
}
else
{
$sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
$iSepLength = strlen($sSeparator);
$iTextlength = strlen($sText);
$this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
$this->m_aIndex[] = array(
'user_name' => $sOnBehalfOf,
'user_id' => $iUserId,
'date' => time(),
'text_length' => $iTextlength,
'separator_length' => $iSepLength,
);
}
$this->m_bModified = true;
}
public function GetModifiedEntry()
{
$sModifiedEntry = '';
if ($this->m_bModified)
{
$sModifiedEntry = $this->GetLatestEntry();
}
return $sModifiedEntry;
}
/**
@@ -191,4 +260,4 @@ class ormCaseLog {
return $iLast;
}
}
?>
?>

View File

@@ -0,0 +1,465 @@
<?php
// Copyright (C) 2010-2012 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
require_once('backgroundprocess.inc.php');
/**
* ormStopWatch
* encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
/**
* ormStopWatch
* encapsulate the behavior of a stop watch that will be stored as an attribute of class AttributeStopWatch
*
* @package itopORM
*/
class ormStopWatch
{
protected $iTimeSpent; // seconds
protected $iStarted; // unix time (seconds)
protected $iLastStart; // unix time (seconds)
protected $iStopped; // unix time (seconds)
protected $aThresholds;
/**
* Constructor
*/
public function __construct($iTimeSpent = 0, $iStarted = null, $iLastStart = null, $iStopped = null)
{
$this->iTimeSpent = (int) $iTimeSpent;
$this->iStarted = $iStarted;
$this->iLastStart = $iLastStart;
$this->iStopped = $iStopped;
$this->aThresholds = array();
}
/**
* Necessary for the triggers
*/
public function __toString()
{
return (string) $this->iTimeSpent;
}
public function DefineThreshold($iPercent, $tDeadline = null, $bPassed = false, $bTriggered = false, $iOverrun = null)
{
$this->aThresholds[$iPercent] = array(
'deadline' => $tDeadline, // unix time (seconds)
'passed' => $bPassed,
'triggered' => $bTriggered,
'overrun' => $iOverrun
);
}
public function MarkThresholdAsTriggered($iPercent)
{
$this->aThresholds[$iPercent]['triggered'] = true;
}
public function GetTimeSpent()
{
return $this->iTimeSpent;
}
public function GetStartDate()
{
return $this->iStarted;
}
public function GetLastStartDate()
{
return $this->iLastStart;
}
public function GetStopDate()
{
return $this->iStopped;
}
public function GetThresholdDate($iPercent)
{
if (array_key_exists($iPercent, $this->aThresholds))
{
return $this->aThresholds[$iPercent]['deadline'];
}
else
{
return null;
}
}
public function GetOverrun($iPercent)
{
if (array_key_exists($iPercent, $this->aThresholds))
{
return $this->aThresholds[$iPercent]['overrun'];
}
else
{
return null;
}
}
public function IsThresholdPassed($iPercent)
{
if (array_key_exists($iPercent, $this->aThresholds))
{
return $this->aThresholds[$iPercent]['passed'];
}
else
{
return false;
}
}
public function IsThresholdTriggered($iPercent)
{
if (array_key_exists($iPercent, $this->aThresholds))
{
return $this->aThresholds[$iPercent]['triggered'];
}
else
{
return false;
}
}
public function GetAsHTML($oAttDef, $oHostObject = null)
{
$aProperties = array();
$aProperties['States'] = implode(', ', $oAttDef->GetStates());
if (is_null($this->iLastStart))
{
if (is_null($this->iStarted))
{
$aProperties['Elapsed'] = 'never started';
}
else
{
$aProperties['Elapsed'] = $this->iTimeSpent.' s';
}
}
else
{
$iElapsedTemp = ''; //$this->ComputeDuration($oHostObject, $oAttDef, $this->iLastStart, time());
$aProperties['Elapsed'] = $this->iTimeSpent.' + '.$iElapsedTemp.' s + <img src="../images/indicator.gif">';
}
$aProperties['Started'] = $oAttDef->SecondsToDate($this->iStarted);
$aProperties['LastStart'] = $oAttDef->SecondsToDate($this->iLastStart);
$aProperties['Stopped'] = $oAttDef->SecondsToDate($this->iStopped);
foreach ($this->aThresholds as $iPercent => $aThresholdData)
{
$sThresholdDesc = $oAttDef->SecondsToDate($aThresholdData['deadline']);
if ($aThresholdData['triggered'])
{
$sThresholdDesc .= " <b>TRIGGERED</b>";
}
if ($aThresholdData['overrun'])
{
$sThresholdDesc .= " Overrun:".(int) $aThresholdData['overrun']." sec.";
}
$aProperties[$iPercent.'%'] = $sThresholdDesc;
}
$sRes = "<TABLE class=\"listResults\">";
$sRes .= "<TBODY>";
foreach ($aProperties as $sProperty => $sValue)
{
$sRes .= "<TR>";
$sCell = str_replace("\n", "<br>\n", $sValue);
$sRes .= "<TD class=\"label\">$sProperty</TD><TD>$sCell</TD>";
$sRes .= "</TR>";
}
$sRes .= "</TBODY>";
$sRes .= "</TABLE>";
return $sRes;
}
protected function ComputeGoal($oObject, $oAttDef)
{
$sMetricComputer = $oAttDef->Get('goal_computing');
$oComputer = new $sMetricComputer();
$aCallSpec = array($oComputer, 'ComputeMetric');
if (!is_callable($aCallSpec))
{
throw new CoreException("Unknown class/verb '$sMetricComputer/ComputeMetric'");
}
$iRet = call_user_func($aCallSpec, $oObject);
return $iRet;
}
protected function ComputeDeadline($oObject, $oAttDef, $iStartTime, $iDurationSec)
{
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
$aCallSpec = array($sWorkingTimeComputer, '__construct');
if (!is_callable($aCallSpec))
{
//throw new CoreException("Pas de constructeur pour $sWorkingTimeComputer!");
}
$oComputer = new $sWorkingTimeComputer();
$aCallSpec = array($oComputer, 'GetDeadline');
if (!is_callable($aCallSpec))
{
throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetDeadline'");
}
// GetDeadline($oObject, $iDuration, DateTime $oStartDate)
$oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
$oDeadline = call_user_func($aCallSpec, $oObject, $iDurationSec, $oStartDate);
$iRet = $oDeadline->format('U');
return $iRet;
}
protected function ComputeDuration($oObject, $oAttDef, $iStartTime, $iEndTime)
{
$sWorkingTimeComputer = $oAttDef->Get('working_time_computing');
$oComputer = new $sWorkingTimeComputer();
$aCallSpec = array($oComputer, 'GetOpenDuration');
if (!is_callable($aCallSpec))
{
throw new CoreException("Unknown class/verb '$sWorkingTimeComputer/GetOpenDuration'");
}
// GetOpenDuration($oObject, DateTime $oStartDate, DateTime $oEndDate)
$oStartDate = new DateTime('@'.$iStartTime); // setTimestamp not available in PHP 5.2
$oEndDate = new DateTime('@'.$iEndTime);
$iRet = call_user_func($aCallSpec, $oObject, $oStartDate, $oEndDate);
return $iRet;
}
public function Reset($oObject, $oAttDef)
{
$this->iTimeSpent = 0;
$this->iStarted = null;
$this->iLastStart = null;
$this->iStopped = null;
foreach ($this->aThresholds as $iPercent => &$aThresholdData)
{
$aThresholdData['passed'] = false;
$aThresholdData['triggered'] = false;
$aThresholdData['deadline'] = null;
$aThresholdData['overrun'] = null;
}
}
/**
* Start or continue
* It is the responsibility of the caller to compute the deadlines
* (to avoid computing twice for the same result)
*/
public function Start($oObject, $oAttDef)
{
if (!is_null($this->iLastStart))
{
// Already started
return false;
}
if (is_null($this->iStarted))
{
$this->iStarted = time();
}
$this->iLastStart = time();
$this->iStopped = null;
return true;
}
/**
* Compute or recompute the goal and threshold deadlines
*/
public function ComputeDeadlines($oObject, $oAttDef)
{
if (is_null($this->iLastStart))
{
// Currently stopped - do nothing
return false;
}
$iDurationGoal = $this->ComputeGoal($oObject, $oAttDef);
foreach ($this->aThresholds as $iPercent => &$aThresholdData)
{
if (is_null($iDurationGoal))
{
// No limit: leave null thresholds
$aThresholdData['deadline'] = null;
}
else
{
$iThresholdDuration = round($iPercent * $iDurationGoal / 100);
$aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iLastStart, $iThresholdDuration - $this->iTimeSpent);
// OR $aThresholdData['deadline'] = $this->ComputeDeadline($oObject, $oAttDef, $this->iStarted, $iThresholdDuration);
}
if (is_null($aThresholdData['deadline']) || ($aThresholdData['deadline'] > time()))
{
// The threshold is in the future, reset
$aThresholdData['passed'] = false;
$aThresholdData['triggered'] = false;
$aThresholdData['overrun'] = null;
}
else
{
// The new threshold is in the past
$aThresholdData['passed'] = true;
// Note: the overrun can be wrong, but the correct algorithm to compute
// the overrun of a deadline in the past requires that the ormStopWatch keeps track of all its history!!!
}
}
return true;
}
/**
* Stop counting if not already done
*/
public function Stop($oObject, $oAttDef)
{
if (is_null($this->iLastStart))
{
// Already stopped
return false;
}
$iElapsed = $this->ComputeDuration($oObject, $oAttDef, $this->iLastStart, time());
$this->iTimeSpent = $this->iTimeSpent + $iElapsed;
foreach ($this->aThresholds as $iPercent => &$aThresholdData)
{
if (!is_null($aThresholdData['deadline']) && (time() > $aThresholdData['deadline']))
{
if ($aThresholdData['overrun'] > 0)
{
// Accumulate from last start
$aThresholdData['overrun'] += $iElapsed;
}
else
{
// First stop after the deadline has been passed
$iOverrun = $this->ComputeDuration($oObject, $oAttDef, $aThresholdData['deadline'], time());
$aThresholdData['overrun'] = $iOverrun;
}
$aThresholdData['passed'] = true;
}
$aThresholdData['deadline'] = null;
}
$this->iLastStart = null;
$this->iStopped = time();
return true;
}
}
/**
* CheckStopWatchThresholds
* Implements the automatic actions
*
* @package itopORM
*/
class CheckStopWatchThresholds implements iBackgroundProcess
{
public function GetPeriodicity()
{
return 10; // seconds
}
public function Process($iTimeLimit)
{
foreach (MetaModel::GetClasses() as $sClass)
{
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
{
foreach ($oAttDef->ListThresholds() as $iThreshold => $aThresholdData)
{
$iPercent = $aThresholdData['percent']; // could be different than the index !
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < NOW()";
//echo $sExpression."<br/>\n";
$oFilter = DBObjectSearch::FromOQL($sExpression);
$aList = array();
$oSet = new DBObjectSet($oFilter);
while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
{
$sClass = get_class($oObj);
$aList[] = $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold;
//echo $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold."\n";
// Execute planned actions
//
foreach ($aThresholdData['actions'] as $aActionData)
{
$sVerb = $aActionData['verb'];
$aParams = $aActionData['params'];
$sParams = implode(', ', $aParams);
//echo "Calling: $sVerb($sParams)<br/>\n";
$aCallSpec = array($oObj, $sVerb);
call_user_func_array($aCallSpec, $aParams);
}
// Mark the threshold as "triggered"
//
$oSW = $oObj->Get($sAttCode);
$oSW->MarkThresholdAsTriggered($iThreshold);
$oObj->Set($sAttCode, $oSW);
if($oObj->IsModified())
{
// Todo - factorize so that only one single change will be instantiated
$oMyChange = new CMDBChange();
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", "Automatic - threshold triggered");
$iChangeId = $oMyChange->DBInsertNoReload();
$oObj->DBUpdateTracked($oMyChange, true /*skip security*/);
}
// Activate any existing trigger
//
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(
DBObjectSearch::FromOQL("SELECT TriggerOnThresholdReached AS t WHERE t.target_class IN ('$sClassList') AND stop_watch_code=:stop_watch_code AND threshold_index = :threshold_index"),
array(), // order by
array('stop_watch_code' => $sAttCode, 'threshold_index' => $iThreshold)
);
while ($oTrigger = $oSet->Fetch())
{
$oTrigger->DoActivate($oObj->ToArgs('this'));
}
}
}
}
}
}
$iProcessed = count($aList);
return "Triggered $iProcessed threshold(s)";
}
}
?>

View File

@@ -0,0 +1,74 @@
<?php
// 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
/**
* Associated with the metamodel -> MakeQuery/MakeQuerySingleTable
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
class QueryBuilderContext
{
protected $m_oRootFilter;
protected $m_aClassAliases;
protected $m_aTableAliases;
protected $m_aModifierProperties;
public $m_oQBExpressions;
public function __construct($oFilter, $aModifierProperties, $aGroupByExpr = null)
{
$this->m_oRootFilter = $oFilter;
$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter->GetCriteria(), $aGroupByExpr);
$this->m_aClassAliases = $oFilter->GetJoinedClasses();
$this->m_aTableAliases = array();
$this->m_aModifierProperties = $aModifierProperties;
}
public function GetRootFilter()
{
return $this->m_oRootFilter;
}
public function GenerateTableAlias($sNewName, $sRealName)
{
return MetaModel::GenerateUniqueAlias($this->m_aTableAliases, $sNewName, $sRealName);
}
public function GenerateClassAlias($sNewName, $sRealName)
{
return MetaModel::GenerateUniqueAlias($this->m_aClassAliases, $sNewName, $sRealName);
}
public function GetModifierProperties($sPluginClass)
{
if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
{
return $this->m_aModifierProperties[$sPluginClass];
}
else
{
return array();
}
}
}
?>

View File

@@ -1,51 +1,33 @@
<?php
// 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
/**
* Localized data
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
// Dictionnay conventions
// Class:<class_name>
// Class:<class_name>+
// Class:<class_name>/Attribute:<attribute_code>
// Class:<class_name>/Attribute:<attribute_code>+
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
// Class:<class_name>/Stimulus:<stimulus_code>
// Class:<class_name>/Stimulus:<stimulus_code>+
//////////////////////////////////////////////////////////////////////
// Classes in 'bizmodel'
//////////////////////////////////////////////////////////////////////
//
// Dictionnay conventions
// Class:<class_name>
// Class:<class_name>+
// Class:<class_name>/Attribute:<attribute_code>
// Class:<class_name>/Attribute:<attribute_code>+
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
// Class:<class_name>/Stimulus:<stimulus_code>
// Class:<class_name>/Stimulus:<stimulus_code>+
?>
<?php
// 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
/**
* Interface iQueryModifier
* Defines the API to tweak queries (e.g. translate data on the fly)
*
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
interface iQueryModifier
{
public function __construct();
public function GetFieldExpression(QueryBuilderContext &$oBuild, $sClass, $sAttCode, $sColId, Expression $oFieldSQLExp, SQLQuery &$oSelect);
}
?>

View File

@@ -41,6 +41,7 @@ class SQLQuery
private $m_sTable = '';
private $m_sTableAlias = '';
private $m_aFields = array();
private $m_aGroupBy = array();
private $m_oConditionExpr = null;
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
@@ -62,12 +63,18 @@ class SQLQuery
$this->m_sTable = $sTable;
$this->m_sTableAlias = $sTableAlias;
$this->m_aFields = $aFields;
$this->m_aGroupBy = null;
$this->m_oConditionExpr = null;
$this->m_bToDelete = $bToDelete;
$this->m_aValues = $aValues;
$this->m_oSelectedIdField = $oSelectedIdField;
}
public function GetTableAlias()
{
return $this->m_sTableAlias;
}
public function SetSourceOQL($sOQL)
{
$this->m_SourceOQL = $sOQL;
@@ -101,21 +108,31 @@ class SQLQuery
{
$sJoinType = $aJoinInfo["jointype"];
$oSQLQuery = $aJoinInfo["select"];
$sLeftField = $aJoinInfo["leftfield"];
$sRightField = $aJoinInfo["rightfield"];
$sRightTableAlias = $aJoinInfo["righttablealias"];
if (isset($aJoinInfo["on_expression"]))
{
$sOnCondition = $aJoinInfo["on_expression"]->Render();
echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
echo "<li>Join '$sJoinType', ON ($sOnCondition)".$oSQLQuery->DisplayHtml()."</li>\n";
}
else
{
$sLeftField = $aJoinInfo["leftfield"];
$sRightField = $aJoinInfo["rightfield"];
$sRightTableAlias = $aJoinInfo["righttablealias"];
echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
}
}
echo "</ul>";
}
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
echo "From ...<br/>\n";
echo "<pre style=\"font-size: smaller;\">\n";
print_r($aFrom);
@@ -127,6 +144,11 @@ class SQLQuery
$this->m_aFields = $aExpressions;
}
public function SetGroupBy($aExpressions)
{
$this->m_aGroupBy = $aExpressions;
}
public function SetCondition($oConditionExpr)
{
$this->m_oConditionExpr = $oConditionExpr;
@@ -196,6 +218,24 @@ class SQLQuery
{
return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField);
}
public function AddInnerJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression)
{
$this->m_aJoinSelects[] = array(
"jointype" => 'inner',
"select" => $oSQLQuery,
"on_expression" => $oOnExpression
);
}
public function AddLeftJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression)
{
$this->m_aJoinSelects[] = array(
"jointype" => 'left',
"select" => $oSQLQuery,
"on_expression" => $oOnExpression
);
}
// Interface, build the SQL query
public function RenderDelete($aArgs = array())
@@ -203,11 +243,12 @@ class SQLQuery
// The goal will be to complete the list as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
// Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ...
@@ -238,11 +279,12 @@ class SQLQuery
// The goal will be to complete the list as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sValues = self::ClauseValues($aSetValues);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -255,11 +297,12 @@ class SQLQuery
// The goal will be to complete the lists as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sFrom = self::ClauseFrom($aFrom);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
@@ -296,6 +339,27 @@ class SQLQuery
return $sSQL;
}
// Interface, build the SQL query
public function RenderGroupBy($aArgs = array())
{
// The goal will be to complete the lists as we build the Joins
$aFrom = array();
$aFields = array();
$aGroupBy = array();
$oCondition = null;
$aDelTables = array();
$aSetValues = array();
$aSelectedIdFields = array();
$this->privRender($aFrom, $aFields, $aGroupBy, $oCondition, $aDelTables, $aSetValues, $aSelectedIdFields);
$sSelect = self::ClauseSelect($aFields);
$sFrom = self::ClauseFrom($aFrom);
$sWhere = self::ClauseWhere($oCondition, $aArgs);
$sGroupBy = self::ClauseGroupBy($aGroupBy);
$sSQL = "SELECT $sSelect, COUNT(*) AS _itop_count_ FROM $sFrom WHERE $sWhere GROUP BY $sGroupBy";
return $sSQL;
}
private static function ClauseSelect($aFields)
{
$aSelect = array();
@@ -307,6 +371,12 @@ class SQLQuery
return $sSelect;
}
private static function ClauseGroupBy($aGroupBy)
{
$sRes = implode(', ', $aGroupBy);
return $sRes;
}
private static function ClauseDelete($aDelTableAliases)
{
$aDelTables = array();
@@ -375,21 +445,22 @@ class SQLQuery
$aOrderBySpec = array();
foreach($aOrderBy as $sFieldAlias => $bAscending)
{
$aOrderBySpec[] = '`'.$sFieldAlias.'`'.($bAscending ? " ASC" : " DESC");
// Note: sFieldAlias must have backticks around column aliases
$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, &$aSelectedIdFields)
private function privRender(&$aFrom, &$aFields, &$aGroupBy, &$oCondition, &$aDelTables, &$aSetValues, &$aSelectedIdFields)
{
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, '', array('jointype' => 'first'));
$oCondition = $this->m_oConditionExpr;
return $sTableAlias;
}
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
private function privRenderSingleTable(&$aFrom, &$aFields, &$aGroupBy, &$aDelTables, &$aSetValues, &$aSelectedIdFields, $sCallerAlias = '', $aJoinData)
{
$aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable);
@@ -412,8 +483,14 @@ class SQLQuery
break;
case "inner":
case "left":
// table or tablealias ???
$sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`";
if (isset($aJoinData["on_expression"]))
{
$sJoinCond = $aJoinData["on_expression"]->Render();
}
else
{
$sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`";
}
$aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
break;
case "inner_tree":
@@ -467,6 +544,13 @@ class SQLQuery
{
$aFields["`$sAlias`"] = $oExpression->Render();
}
if ($this->m_aGroupBy)
{
foreach($this->m_aGroupBy as $sAlias => $oExpression)
{
$aGroupBy["`$sAlias`"] = $oExpression->Render();
}
}
if ($this->m_bToDelete)
{
$aDelTables[] = "`{$this->m_sTableAlias}`";
@@ -488,12 +572,8 @@ class SQLQuery
foreach ($this->m_aJoinSelects as $aJoinData)
{
$oRightSelect = $aJoinData["select"];
// $sJoinType = $aJoinData["jointype"];
// $sLeftField = $aJoinData["leftfield"];
// $sRightField = $aJoinData["rightfield"];
// $sRightTableAlias = $aJoinData["righttablealias"];
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aGroupBy, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
}
$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;

View File

@@ -0,0 +1,177 @@
<?php
// 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
/**
* Simple helper class to interpret and transform a template string
*
* Usage:
* $oString = new TemplateString("Blah $this->friendlyname$ is in location $this->location_id->name$ ('$this->location_id->org_id->name$)");
* echo $oString->Render(array('this' => $oContact));
* @author Erwan Taloc <erwan.taloc@combodo.com>
* @author Romain Quetiez <romain.quetiez@combodo.com>
* @author Denis Flaven <denis.flaven@combodo.com>
* @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
*/
/**
* Helper class
*/
class TemplateStringPlaceholder
{
public $sToken;
public $sAttCode;
public $sFunction;
public $sParamName;
public $bIsValid;
public function __construct($sToken)
{
$this->sToken = $sToken;
$this->sAttcode = '';
$this->sFunction = '';
$this->sParamName = '';
$this->bIsValid = false; // Validity may be false in general, but it can work anyway (thanks to specialization) when rendering
}
}
/**
* Class TemplateString
*/
class TemplateString
{
protected $m_sRaw;
protected $m_aPlaceholders;
public function __construct($sRaw)
{
$this->m_sRaw = $sRaw;
$this->m_aPlaceholders = null;
}
/**
* Split the string into placholders
* @param Hash $aParamTypes Class of the expected parameters: hash array of '<param_id>' => '<class_name>'
* @return void
*/
protected function Analyze($aParamTypes = array())
{
if (!is_null($this->m_aPlaceholders)) return;
$this->m_aPlaceholders = array();
if (preg_match_all('/\\$([a-z0-9_]+(->[a-z0-9_]+)*)\\$/', $this->m_sRaw, $aMatches))
{
foreach($aMatches[1] as $sPlaceholder)
{
$oPlaceholder = new TemplateStringPlaceholder($sPlaceholder);
$oPlaceholder->bIsValid = false;
foreach ($aParamTypes as $sParamName => $sClass)
{
$sParamPrefix = $sParamName.'->';
if (substr($sPlaceholder, 0, strlen($sParamPrefix)) == $sParamPrefix)
{
// Todo - detect functions (label...)
$oPlaceholder->sFunction = '';
$oPlaceholder->sParamName = $sParamName;
$sAttcode = substr($sPlaceholder, strlen($sParamPrefix));
$oPlaceholder->sAttcode = $sAttcode;
$oPlaceholder->bIsValid = MetaModel::IsValidAttCode($sClass, $sAttcode, true /* extended */);
}
}
$this->m_aPlaceholders[] = $oPlaceholder;
}
}
}
/**
* Return the placeholders (for reporting purposes)
* @return void
*/
public function GetPlaceholders()
{
return $this->m_aPlaceholders;
}
/**
* Check the format when possible
* @param Hash $aParamTypes Class of the expected parameters: hash array of '<param_id>' => '<class_name>'
* @return void
*/
public function IsValid($aParamTypes = array())
{
$this->Analyze($aParamTypes);
foreach($this->m_aPlaceholders as $oPlaceholder)
{
if (!$oPlaceholder->bIsValid)
{
if (count($aParamTypes) == 0)
{
return false;
}
if (array_key_exists($oPlaceholder->sParamName, $aParamTypes))
{
return false;
}
}
}
return true;
}
/**
* Apply the given parameters to replace the placeholders
* @param Hash $aParamValues Value of the expected parameters: hash array of '<param_id>' => '<value>'
* @return void
*/
public function Render($aParamValues = array())
{
$aParamTypes = array();
foreach($aParamValues as $sParamName => $value)
{
$aParamTypes[$sParamName] = get_class($value);
}
$this->Analyze($aParamTypes);
$aSearch = array();
$aReplace = array();
foreach($this->m_aPlaceholders as $oPlaceholder)
{
if (array_key_exists($oPlaceholder->sParamName, $aParamValues))
{
$oRef = $aParamValues[$oPlaceholder->sParamName];
try
{
$value = $oRef->Get($oPlaceholder->sAttcode);
$aSearch[] = '$'.$oPlaceholder->sToken.'$';
$aReplace[] = $value;
$oPlaceholder->bIsValid = true;
}
catch(Exception $e)
{
$oPlaceholder->bIsValid = false;
}
}
else
{
$oPlaceholder->bIsValid = false;
}
}
return str_replace($aSearch, $aReplace, $this->m_sRaw);
}
}
?>

View File

@@ -41,7 +41,7 @@ abstract class Trigger extends cmdbAbstractObject
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
@@ -87,7 +87,7 @@ abstract class TriggerOnObject extends Trigger
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onobject",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -114,11 +114,11 @@ class TriggerOnPortalUpdate extends TriggerOnObject
{
$aParams = array
(
"category" => "core/cmdb",
"category" => "core/cmdb,bizmodel",
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onportalupdate",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -144,7 +144,7 @@ abstract class TriggerOnStateChange extends TriggerOnObject
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onstatechange",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -173,7 +173,7 @@ class TriggerOnStateEnter extends TriggerOnStateChange
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onstateenter",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -201,7 +201,7 @@ class TriggerOnStateLeave extends TriggerOnStateChange
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onstateleave",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -229,7 +229,7 @@ class TriggerOnObjectCreate extends TriggerOnObject
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onobjcreate",
"db_key_field" => "id",
"db_finalclass_field" => "",
@@ -257,7 +257,7 @@ class lnkTriggerAction extends cmdbAbstractObject
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(""),
"reconc_keys" => array('action_id', 'trigger_id'),
"db_table" => "priv_link_action_trigger",
"db_key_field" => "link_id",
"db_finalclass_field" => "",
@@ -278,4 +278,35 @@ class lnkTriggerAction extends cmdbAbstractObject
MetaModel::Init_SetZListItems('advanced_search', array('action_id', 'trigger_id', 'order')); // Criteria of the advanced search form
}
}
class TriggerOnThresholdReached extends TriggerOnObject
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb,bizmodel",
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_threshold",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("stop_watch_code", array("allowed_values"=>null, "sql"=>"stop_watch_code", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("threshold_index", array("allowed_values"=>null, "sql"=>"threshold_index", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'stop_watch_code', 'threshold_index', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('target_class', 'threshold_index', 'threshold_index')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
?>

View File

@@ -41,6 +41,8 @@ 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_CREATE', 7); // Instantiate an object
define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions (CSV import, View schema...)
/**
@@ -55,7 +57,7 @@ abstract class UserRightsAddOnAPI
abstract public function Init(); // loads data (possible optimizations)
// 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 GetSelectFilter($sLogin, $sClass, $aSettings = array()); // returns a filter object
abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null);
abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null);
@@ -295,6 +297,35 @@ abstract class UserInternal extends User
}
}
/**
* Self register extension
*
* @package iTopORM
*/
interface iSelfRegister
{
/**
* Called when no user is found in iTop for the corresponding 'name'. This method
* can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly
* @param string $sName The typed-in user name
* @param string $sPassword The typed-in password
* @param string $sLoginMode The login method used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used (any|internal|external)
* @return bool true if the user is a valid one, false otherwise
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
/**
* Called after the user has been authenticated and found in iTop. This method can
* Update the user's definition on the fly (profiles...) to keep it in sync with an external source
* @param User $oUser The user to update/synchronize
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return void
*/
public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication);
}
/**
* User management core API
*
@@ -305,6 +336,7 @@ class UserRights
protected static $m_oAddOn;
protected static $m_oUser;
protected static $m_oRealUser;
protected static $m_sSelfRegisterAddOn = null;
public static function SelectModule($sModuleName)
{
@@ -324,6 +356,15 @@ class UserRights
self::$m_oRealUser = null;
}
public static function SelectSelfRegister($sModuleName)
{
if (!class_exists($sModuleName))
{
throw new CoreException("Could not select the class, '$sModuleName' for self register, is not a valid class name");
}
self::$m_sSelfRegisterAddOn = $sModuleName;
}
public static function GetModuleInstance()
{
return self::$m_oAddOn;
@@ -361,21 +402,38 @@ class UserRights
return true;
}
public static function CheckCredentials($sName, $sPassword, $sAuthentication = 'any')
public static function CheckCredentials($sName, $sPassword, $sLoginMode = 'form', $sAuthentication = 'any')
{
$oUser = self::FindUser($sName, $sAuthentication);
if (is_null($oUser))
{
return false;
return self::CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
}
if (!$oUser->CheckCredentials($sPassword))
{
return false;
}
self::UpdateUser($oUser, $sLoginMode, $sAuthentication);
return true;
}
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
{
return call_user_func(array(self::$m_sSelfRegisterAddOn, 'CheckCredentialsAndCreateUser'), $sName, $sPassword, $sLoginMode, $sAuthentication);
}
}
public static function UpdateUser($oUser, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
{
call_user_func(array(self::$m_sSelfRegisterAddOn, 'UpdateUser'), $oUser, $sLoginMode, $sAuthentication);
}
}
public static function TrustWebServerContext()
{
if (!is_null(self::$m_oUser))
@@ -591,7 +649,7 @@ class UserRights
return true;
}
public static function GetSelectFilter($sClass)
public static function GetSelectFilter($sClass, $aSettings = array())
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) return true;
@@ -600,7 +658,7 @@ class UserRights
if (MetaModel::HasCategory($sClass, 'bizmodel'))
{
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass);
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
}
else
{
@@ -615,9 +673,21 @@ class UserRights
if (MetaModel::DBIsReadOnly())
{
if ($iActionCode == UR_ACTION_CREATE) return false;
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_DELETE) return false;
if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
}
$aPredefinedObjects = call_user_func(array($sClass, 'GetPredefinedObjects'));
if ($aPredefinedObjects != null)
{
// As opposed to the read-only DB, modifying an object is allowed
// (the constant columns will be marked as read-only)
//
if ($iActionCode == UR_ACTION_CREATE) return false;
if ($iActionCode == UR_ACTION_DELETE) return false;
if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
}
@@ -625,14 +695,16 @@ class UserRights
if (MetaModel::HasCategory($sClass, 'bizmodel'))
{
// #@# Temporary?????
// The read access is controlled in MetaModel::MakeSelectQuery()
if ($iActionCode == UR_ACTION_READ) return true;
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
if ($iActionCode == UR_ACTION_CREATE)
{
// The addons currently DO NOT handle the case "CREATE"
// Therefore it is considered to be equivalent to "MODIFY"
$iActionCode = UR_ACTION_MODIFY;
}
return self::$m_oAddOn->IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet);
}
elseif(($iActionCode == UR_ACTION_READ) && MetaModel::HasCategory($sClass, 'view_in_gui'))
@@ -969,4 +1041,339 @@ class StimulusChecker extends ActionChecker
return $this->iState;
}
}
/**
* Self-register extension to allow the automatic creation & update of CAS users
*
* @package iTopORM
*
*/
class CAS_SelfRegister implements iSelfRegister
{
/**
* Called when no user is found in iTop for the corresponding 'name'. This method
* can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly
* @param string $sName The CAS authenticated user name
* @param string $sPassword Ignored
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return bool true if the user is a valid one, false otherwise
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
{
$bOk = true;
if ($sLoginMode != 'cas') return false; // Must be authenticated via CAS
$sCASMemberships = MetaModel::GetConfig()->Get('cas_memberof');
$bFound = false;
if (!empty($sCASMemberships))
{
if (phpCAS::hasAttribute('memberOf'))
{
// A list of groups is specified, the user must a be member of (at least) one of them to pass
$aCASMemberships = array();
$aTmp = explode(';', $sCASMemberships);
setlocale(LC_ALL, "en_US.utf8"); // !!! WARNING: this is needed to have the iconv //TRANSLIT working fine below !!!
foreach($aTmp as $sGroupName)
{
$aCASMemberships[] = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Just in case remove accents and spaces...
}
$aMemberOf = phpCAS::getAttribute('memberOf');
if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array
$aFilteredGroupNames = array();
foreach($aMemberOf as $sGroupName)
{
phpCAS::log("Info: user if a member of the group: ".$sGroupName);
$sGroupName = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Remove accents and spaces as well
$aFilteredGroupNames[] = $sGroupName;
$bIsMember = false;
foreach($aCASMemberships as $sCASPattern)
{
if (self::IsPattern($sCASPattern))
{
if (preg_match($sCASPattern, $sGroupName))
{
$bIsMember = true;
break;
}
}
else if ($sPattern == $sGroupName)
{
$bIsMember = true;
break;
}
}
if ($bIsMember)
{
$bCASUserSynchro = MetaModel::GetConfig()->Get('cas_user_synchro');
if ($bCASUserSynchro)
{
// If needed create a new user for this email/profile
phpCAS::log('Info: cas_user_synchro is ON');
$bOk = self::CreateCASUser(phpCAS::getUser(), $aMemberOf);
if($bOk)
{
$bFound = true;
}
else
{
phpCAS::log("User ".phpCAS::getUser()." cannot be created in iTop. Logging off...");
}
}
else
{
phpCAS::log('Info: cas_user_synchro is OFF');
$bFound = true;
}
break;
}
}
if($bOk && !$bFound)
{
phpCAS::log("User ".phpCAS::getUser().", none of his/her groups (".implode('; ', $aFilteredGroupNames).") match any of the required groups: ".implode('; ', $aCASMemberships));
}
}
else
{
// Too bad, the user is not part of any of the group => not allowed
phpCAS::log("No 'memberOf' attribute found for user ".phpCAS::getUser().". Are you using the SAML protocol (S1) ?");
}
}
else
{
// No membership required, anybody will pass
$bFound = true;
}
if (!$bFound)
{
// The user is not part of the allowed groups, => log out
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sCASLogoutUrl = MetaModel::GetConfig()->Get('cas_logout_redirect_service');
if (empty($sCASLogoutUrl))
{
$sCASLogoutUrl = $sUrl;
}
phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page
// Will never return !
}
return $bFound;
}
/**
* Called after the user has been authenticated and found in iTop. This method can
* Update the user's definition (profiles...) on the fly to keep it in sync with an external source
* @param User $oUser The user to update/synchronize
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return void
*/
public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication)
{
$bCASUpdateProfiles = MetaModel::GetConfig()->Get('cas_update_profiles');
if (($sLoginMode == 'cas') && $bCASUpdateProfiles && (phpCAS::hasAttribute('memberOf')))
{
$aMemberOf = phpCAS::getAttribute('memberOf');
if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array
return self::SetProfilesFromCAS($oUser, $aMemberOf);
}
// No groups defined in CAS or not CAS at all: do nothing...
return true;
}
/**
* Helper method to create a CAS based user
* @param string $sEmail
* @param array $aGroups
* @return bool true on success, false otherwise
*/
protected static function CreateCASUser($sEmail, $aGroups)
{
if (!MetaModel::IsValidClass('URP_Profiles'))
{
phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry.");
return false;
}
$oUser = MetaModel::GetObjectByName('UserExternal', $sEmail, false);
if ($oUser == null)
{
// Create the user, link it to a contact
phpCAS::log("Info: the user '$sEmail' does not exist. A new UserExternal will be created.");
$oSearch = new DBObjectSearch('Person');
$oSearch->AddCondition('email', $sEmail);
$oSet = new DBObjectSet($oSearch);
$iContactId = 0;
switch($oSet->Count())
{
case 0:
phpCAS::log("Error: found no contact with the email: '$sEmail'. Cannot create the user in iTop.");
return false;
case 1:
$oContact = $oSet->Fetch();
$iContactId = $oContact->GetKey();
phpCAS::log("Info: Found 1 contact '".$oContact->GetName()."' (id=$iContactId) corresponding to the email '$sEmail'.");
break;
default:
phpCAS::log("Error: ".$oSet->Count()." contacts have the same email: '$sEmail'. Cannot create a user for this email.");
return false;
}
$oUser = new UserExternal();
$oUser->Set('login', $sEmail);
$oUser->Set('contactid', $iContactId);
$oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage());
}
else
{
phpCAS::log("Info: the user '$sEmail' already exists (id=".$oUser->GetKey().").");
}
// Now synchronize the profiles
if (!self::SetProfilesFromCAS($oUser, $aGroups))
{
return false;
}
else
{
if ($oUser->IsNew() || $oUser->IsModified())
{
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", 'CAS/LDAP Synchro');
$oMyChange->DBInsert();
if ($oUser->IsNew())
{
$oUser->DBInsertTracked($oMyChange);
}
else
{
$oUser->DBUpdateTracked($oMyChange);
}
}
return true;
}
}
protected static function SetProfilesFromCAS($oUser, $aGroups)
{
if (!MetaModel::IsValidClass('URP_Profiles'))
{
phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry.");
return false;
}
// read all the existing profiles
$oProfilesSearch = new DBObjectSearch('URP_Profiles');
$oProfilesSet = new DBObjectSet($oProfilesSearch);
$aAllProfiles = array();
while($oProfile = $oProfilesSet->Fetch())
{
$aAllProfiles[strtolower($oProfile->GetName())] = $oProfile->GetKey();
}
// Translate the CAS/LDAP group names into iTop profile names
$aProfiles = array();
$sPattern = MetaModel::GetConfig()->Get('cas_profile_pattern');
foreach($aGroups as $sGroupName)
{
if (preg_match($sPattern, $sGroupName, $aMatches))
{
if (array_key_exists(strtolower($aMatches[1]), $aAllProfiles))
{
$aProfiles[] = $aAllProfiles[strtolower($aMatches[1])];
phpCAS::log("Info: Adding the profile '{$aMatches[1]}' from CAS.");
}
else
{
phpCAS::log("Warning: {$aMatches[1]} is not a valid iTop profile (extracted from group name: '$sGroupName'). Ignored.");
}
}
else
{
phpCAS::log("Info: The CAS group '$sGroupName' does not seem to match an iTop pattern. Ignored.");
}
}
if (count($aProfiles) == 0)
{
phpCAS::log("Info: The user '".$oUser->GetName()."' has no profiles retrieved from CAS. Default profile(s) will be used.");
// Second attempt: check if there is/are valid default profile(s)
$sCASDefaultProfiles = MetaModel::GetConfig()->Get('cas_default_profiles');
$aCASDefaultProfiles = explode(';', $sCASDefaultProfiles);
foreach($aCASDefaultProfiles as $sDefaultProfileName)
{
if (array_key_exists(strtolower($sDefaultProfileName), $aAllProfiles))
{
$aProfiles[] = $aAllProfiles[strtolower($sDefaultProfileName)];
phpCAS::log("Info: Adding the default profile '".$aAllProfiles[strtolower($sDefaultProfileName)]."' from CAS.");
}
else
{
phpCAS::log("Warning: the default profile {$sDefaultProfileName} is not a valid iTop profile. Ignored.");
}
}
if (count($aProfiles) == 0)
{
phpCAS::log("Error: The user '".$oUser->GetName()."' has no profiles in iTop, and therefore cannot be created.");
return false;
}
}
// Now synchronize the profiles
$oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile');
foreach($aProfiles as $iProfileId)
{
$oLink = new URP_UserProfile();
$oLink->Set('profileid', $iProfileId);
$oLink->Set('reason', 'CAS/LDAP Synchro');
$oProfilesSet->AddObject($oLink);
}
$oUser->Set('profile_list', $oProfilesSet);
phpCAS::log("Info: the user '".$oUser->GetName()."' (id=".$oUser->GetKey().") now has the following profiles: '".implode("', '", $aProfiles)."'.");
if ($oUser->IsModified())
{
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", 'CAS/LDAP Synchro');
$oMyChange->DBInsert();
if ($oUser->IsNew())
{
$oUser->DBInsertTracked($oMyChange);
}
else
{
$oUser->DBUpdateTracked($oMyChange);
}
}
return true;
}
/**
* Helper function to check if the supplied string is a litteral string or a regular expression pattern
* @param string $sCASPattern
* @return bool True if it's a regular expression pattern, false otherwise
*/
protected static function IsPattern($sCASPattern)
{
if ((substr($sCASPattern, 0, 1) == '/') && (substr($sCASPattern, -1) == '/'))
{
// the string is enclosed by slashes, let's assume it's a pattern
return true;
}
else
{
return false;
}
}
}
// By default enable the 'CAS_SelfRegister' defined above
UserRights::SelectSelfRegister('CAS_SelfRegister');
?>

View File

@@ -97,17 +97,24 @@ class ValueSetObjects extends ValueSetDefinition
protected $m_aOrderBy;
protected $m_aExtraConditions;
private $m_bAllowAllData;
private $m_aModifierProperties;
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false)
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
{
$this->m_sContains = '';
$this->m_sFilterExpr = $sFilterExp;
$this->m_sValueAttCode = $sValueAttCode;
$this->m_aOrderBy = $aOrderBy;
$this->m_bAllowAllData = $bAllowAllData;
$this->m_aModifierProperties = $aModifierProperties;
$this->m_aExtraConditions = array();
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
{
$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
}
public function AddCondition(DBObjectSearch $oFilter)
{
$this->m_aExtraConditions[] = $oFilter;
@@ -127,6 +134,13 @@ class ValueSetObjects extends ValueSetDefinition
{
$oFilter->MergeWith($oExtraFilter);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
}
@@ -162,6 +176,13 @@ class ValueSetObjects extends ValueSetDefinition
{
$oFilter->MergeWith($oExtraFilter);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
@@ -187,6 +208,11 @@ class ValueSetObjects extends ValueSetDefinition
{
return 'Filter: '.$this->m_sFilterExpr;
}
public function GetFilterExpression()
{
return $this->m_sFilterExpr;
}
}
@@ -278,6 +304,13 @@ class ValueSetEnum extends ValueSetDefinition
$this->m_values = $Values;
}
// Helper to export the datat model
public function GetValueList()
{
$this->LoadValues($aArgs = array());
return $this->m_aValues;
}
protected function LoadValues($aArgs)
{
if (is_array($this->m_values))

115
css/fg.menu.css Executable file
View File

@@ -0,0 +1,115 @@
/* Styles for jQuery menu widget
Author: Maggie Wachs, maggie@filamentgroup.com
Date: September 2008
*/
/* REQUIRED STYLES - the menus will only render correctly with these rules */
.positionHelper { z-index: 2999; }
.fg-menu-container { position: absolute; top:0; left:-999px; padding: .4em; overflow: hidden; }
.fg-menu-container.fg-menu-flyout { overflow: visible; }
.fg-menu, .fg-menu ul { list-style-type:none; padding: 0; margin:0; }
.fg-menu { position:relative; }
.fg-menu-flyout .fg-menu { position:static; }
.fg-menu ul { position:absolute; top:0; }
.fg-menu ul ul { top:-1px; }
.fg-menu-container.fg-menu-ipod .fg-menu-content,
.fg-menu-container.fg-menu-ipod .fg-menu-content ul { background: none !important; }
.fg-menu.fg-menu-scroll,
.fg-menu ul.fg-menu-scroll { overflow: scroll; overflow-x: hidden; }
.fg-menu li { clear:both; float:left; width:100%; margin: 0; padding:0; border: 0; }
.fg-menu li li { font-size:1em; } /* inner li font size must be reset so that they don't blow up */
.fg-menu-flyout ul ul { padding: .4em; }
.fg-menu-flyout li { position:relative; }
.fg-menu-scroll { overflow: scroll; overflow-x: hidden; }
.fg-menu-breadcrumb { margin: 0; padding: 0; }
.fg-menu-footer { margin-top: .4em; padding: .4em; }
.fg-menu-header { margin-bottom: .4em; padding: .4em; }
.fg-menu-breadcrumb li { float: left; list-style: none; margin: 0; padding: 0 .2em; font-size: .9em; opacity: .7; }
.fg-menu-breadcrumb li.fg-menu-prev-list,
.fg-menu-breadcrumb li.fg-menu-current-crumb { clear: left; float: none; opacity: 1; }
.fg-menu-breadcrumb li.fg-menu-current-crumb { padding-top: .2em; }
.fg-menu-breadcrumb a,
.fg-menu-breadcrumb span { float: left; }
.fg-menu-footer a:link,
.fg-menu-footer a:visited { float:left; width:100%; text-decoration: none; }
.fg-menu-footer a:hover,
.fg-menu-footer a:active { }
.fg-menu-footer a span { float:left; cursor: pointer; }
.fg-menu-breadcrumb .fg-menu-prev-list a:link,
.fg-menu-breadcrumb .fg-menu-prev-list a:visited,
.fg-menu-breadcrumb .fg-menu-prev-list a:hover,
.fg-menu-breadcrumb .fg-menu-prev-list a:active { background-image: none; text-decoration:none; }
.fg-menu-breadcrumb .fg-menu-prev-list a { float: left; padding-right: .4em; }
.fg-menu-breadcrumb .fg-menu-prev-list a .ui-icon { float: left; }
.fg-menu-breadcrumb .fg-menu-current-crumb a:link,
.fg-menu-breadcrumb .fg-menu-current-crumb a:visited,
.fg-menu-breadcrumb .fg-menu-current-crumb a:hover,
.fg-menu-breadcrumb .fg-menu-current-crumb a:active { display:block; background-image:none; font-size:1.3em; text-decoration:none; }
/* REQUIRED LINK STYLES: links are "display:block" by default; if the menu options are split into
selectable node links and 'next' links, the script floats the node links left and floats the 'next' links to the right */
.fg-menu a:link,
.fg-menu a:visited,
.fg-menu a:hover,
.fg-menu a:active { float:left; width:92%; padding:.3em 3%; text-decoration:none; outline: 0 !important; }
.fg-menu a { border: 1px dashed transparent; }
.fg-menu a.ui-state-default:link,
.fg-menu a.ui-state-default:visited,
.fg-menu a.ui-state-default:hover,
.fg-menu a.ui-state-default:active,
.fg-menu a.ui-state-hover:link,
.fg-menu a.ui-state-hover:visited,
.fg-menu a.ui-state-hover:hover,
.fg-menu a.ui-state-hover:active,
.fg-menu a.ui-state-active:link,
.fg-menu a.ui-state-active:visited,
.fg-menu a.ui-state-active:hover,
.fg-menu a.ui-state-active:active { border-style: solid; font-weight: normal; }
.fg-menu a span { display:block; cursor:pointer; }
/* SUGGESTED STYLES - for use with jQuery UI Themeroller CSS */
.fg-menu-indicator span { float:left; }
.fg-menu-indicator span.ui-icon { float:right; }
.fg-menu-content.ui-widget-content,
.fg-menu-content ul.ui-widget-content { border:0; }
/* ICONS AND DIVIDERS */
.fg-menu.fg-menu-has-icons a:link,
.fg-menu.fg-menu-has-icons a:visited,
.fg-menu.fg-menu-has-icons a:hover,
.fg-menu.fg-menu-has-icons a:active { padding-left:20px; }
.fg-menu .horizontal-divider hr, .fg-menu .horizontal-divider span { padding:0; margin:5px .6em; }
.fg-menu .horizontal-divider hr { border:0; height:1px; }
.fg-menu .horizontal-divider span { font-size:.9em; text-transform: uppercase; padding-left:.2em; }

View File

@@ -2,7 +2,6 @@
body {
font-family: Tahoma, Verdana, Arial, Helvetica;
font-size: 10pt;
background-color: #fff;
color:#000000;
margin: 0; /* Remove body margin/padding */
padding: 0;
@@ -41,6 +40,14 @@ h3 {
color: #EB8F00;
text-decoration: none;
}
table.datatable {
width: 100%;
border: 0;
padding: 0;
}
td.menucontainer {
text-align: right;
}
table.listResults {
padding: 0px;
border-top: 3px solid #f6f6f1;
@@ -59,7 +66,8 @@ table.listContainer {
border: 0;
padding: 0;
margin:0;
width: 97%;
width: 100%;
clear: both;
}
tr.containerHeader, tr.containerHeader td {
@@ -176,6 +184,17 @@ legend {
-webkit-border-radius: 6px;
border-radius: 6px;
}
legend.transparent {
background: transparent;
color: #333333;
font-size: 1em;
font-weight: normal;
padding: 0;
}
.ui-widget-content td legend a, .ui-widget-content td legend a:hover, .ui-widget-content td legend a:visited {
color: #fff;
}
.ui-widget-content td a, p a, p a:visited, td a, td a:visited {
text-decoration:none;
color: #1C94C4;
@@ -381,6 +400,10 @@ a.CollapsibleLabel.open, td a.CollapsibleLabel.open {
background-color:#f6f6f1;
padding:5px;
}
/* move up a header immediately following a display block (i.e. "actions" menu) */
.display_block + .page_header {
margin-top: -8px;
}
.notreeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; }
.notreeview .collapsable { background-image: url(../images/tv-collapsable.gif); }
@@ -399,8 +422,12 @@ div.itop_popup {
padding: 0;
float:right;
}
div.itop_popup > ul > li {
list-style: none;
cursor: pointer;
}
div.itop_popup > ul {
div.actions_menu > ul {
height:19px;
line-height: 17px;
vertical-align: middle;
@@ -409,9 +436,10 @@ div.itop_popup > ul {
padding-left: 5px;
background: url(../images/actions_left.png) no-repeat top left;
cursor: pointer;
margin: 0;
}
div.itop_popup > ul > li {
div.actions_menu > ul > li {
float: left;
list-style: none;
font-size: 11px;
@@ -423,12 +451,14 @@ div.itop_popup > ul > li {
font-weight: bold;
color: #fff;
vertical-align: middle;
margin: 0;
}
#logOffBtn > ul > li {
list-style: none;
vertical-align: middle;
margin: 0;
padding: 0;
cursor: pointer;
}
#logOffBtn > ul {
list-style: none;
@@ -459,6 +489,14 @@ div.itop_popup > ul > li {
white-space: nowrap;
background: #fff;
}
.itop_popup ul {
padding-left: 0;
}
.menucontainer div.toolkit_menu {
margin-left: 10px;
}
.itop_popup li a:hover, #logOffBtn li a:hover {
background: #1A4473;
}
@@ -493,6 +531,9 @@ div.itop_popup > ul > li {
color: #fff;
font-weight: bold;
}
.itop_popup > ul {
margin: 0;
}
hr.menu-separator {
border: none 0;
border-top: 1px solid #ccc;;
@@ -769,9 +810,10 @@ p.page-header {
}
td.dashboard {
vertical-align:top;
text-align: center;
border: 1px solid #ccc;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
padding: 10px;
width: 50%;
}
@@ -782,7 +824,6 @@ td.dashboard {
/*** New Lacanau layout ***/
.ui-layout-pane { /* all 'panes' */
background: #FFF;
overflow: auto;
}
@@ -811,6 +852,7 @@ div#right {
}
div#menu {
width: 100%;
overflow: auto;
}
.header-menu {
padding-left: 24px;
@@ -1057,8 +1099,11 @@ table.pagination {
table.pagination tr td {
padding: 3px;
}
.pagination_container {
padding-left: 3px;
}
.pager {
float:left;
display:inline-block;
}
.pager p {
margin-top: 0;
@@ -1066,6 +1111,8 @@ table.pagination tr td {
}
.pager td span {
min-width: 20px;
padding-left: 2px;
padding-right: 2px;
display:inline-block;
text-align: center;
cursor: pointer;
@@ -1085,7 +1132,7 @@ div.actions_button {
float:right;
background: url("../images/actions_left.png") no-repeat scroll left top transparent;
padding-left: 5px;
margin-top: 13px;
margin-top: 0;
margin-right: 10px;
height:17px;
vertical-align: middle;
@@ -1104,4 +1151,221 @@ div.actions_button a, .actions_button a:hover, .actions_button a:visited {
height:17px;
line-height: 17px;
display: block;
}
select#org_id {
max-width: 90%;
}
/*********** Dashboards ***********/
.itop-dashboard {
background-color: #fff;
}
.dragHover {
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png);
}
.edit_mode .dashlet {
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png);
padding: 5px;
margin:0;
position:relative;
}
.edit_mode .dashlet-selected {
background: #EB8F00 !important;
padding: 5px;
margin:0;
}
td.layout_cell {
height: 50px; /* min-height does not work */
vertical-align: top;
}
.dashlet-content {
background: #fff;
margin:0;
}
table.prop_table {
border-bottom: 2px solid #F9F9F1;
padding: 1px;
width: 100%;
}
.close-box {
margin: 5px;
width: 20px;
height: 20px;
position: absolute;
top: 0;
right: 0;
z-index: 10;
background: transparent url(../images/delete.png) no-repeat center;
}
td.prop_value {
text-align: left;
}
tr.itop-property-field-modified td {
background: #fbb;
}
tr.itop-property-field-modified td.hover {
background: #f99;
}
td.prop_value textarea, td.prop_value input[type=text]{
width: 98%;
}
td.prop_icon {
width: 20px;
}
.dashlet {
text-align:left;
}
.dashlet-inline {
display: inline-block;
}
.dashlet-badge a.actions {
background: none repeat scroll 0 0 transparent;
color: #666666;
font-size: 16px;
text-decoration: none;
}
.dashlet-content .display_block {
text-align:left;
}
.prop_apply .ui-icon-alert {
display: none;
}
.prop_apply .ui-state-error .ui-icon-alert {
display: block;
}
.ui-state-error .ui-icon-circle-check {
display: none;
}
.summary-details {
float: right;
margin-top: 5px;
}
.summary-details th {
background: none repeat scroll 0 0 #CCCCCC;
color: #EEEEEE;
padding: 5px;
text-align: center;
}
.main_header {
background-color: #F1F1F6;
min-height: 60px;
width: 100%;
}
.main_header h1 {
color: #1C94C4;
line-height: 16px;
margin-bottom: 0;
margin-top: 0;
padding-bottom: 10px;
padding-top: 10px;
}
.main_header img {
margin-top: 10px;
margin-right: 10px;
float:left;
}
a.summary, a.summary:hover {
background: none repeat scroll 0 0 transparent;
color: #666666;
text-decoration: none;
padding-left: 0;
}
.summary-details td {
background: none repeat scroll 0 0 transparent;
padding: 5px;
text-align: center;
}
#DashboardMenu > ul > li {
list-style: none;
vertical-align: middle;
margin: 0;
padding: 0;
cursor: pointer;
}
#DashboardMenu > ul {
list-style: none;
vertical-align: middle;
margin: 0;
padding: 0;
height: 25px;
}
#DashboardMenu li a {
display: block;
padding: 5px 12px;
text-decoration: none;
color: #000;
font-weight: bold;
text-align: left;
white-space: nowrap;
background: #fff;
}
#DashboardMenu li span {
display: block;
padding: 5px 12px;
text-decoration: none;
color: #000;
white-space: nowrap;
background: #fff;
}
#DashboardMenu li {
list-style: none;
}
#DashboardMenu li a:hover {
background: #1A4473;
}
#DashboardMenu ul > li > ul
{
border: 1px solid black;
background: #fff;
}
#DashboardMenu li > ul
{ margin: 0;
padding: 0;
position: absolute;
display: none;
border-top: 1px solid white;
z-index: 999;
}
#DashboardMenu li ul li a:hover{
background: #D81515;
color: #fff;
font-weight: bold;
list-style: none;
}
.sortable_field_list {
display: inline-block;
width: 250px;
height: 150px;
border: 1px #333 solid;
overflow: auto;
padding-left: 0;
margin: 0;
}
.sortable_field_list li {
list-style: none;
font-size: 11px;
}
.sort_order {
display: inline-block;
width: 16px;
height: 12px;
}
.sort_none {
background: url(../images/bg.gif) no-repeat center;
}
.sort_asc {
background: url(../images/desc.gif) no-repeat center;
}
.sort_desc {
background: url(../images/asc.gif) no-repeat center;
}
.sort_hidden {
display: none;
}
.sortable_field_list > li.selected {
background: #F6A828;
}

15
css/print.css Normal file
View File

@@ -0,0 +1,15 @@
@CHARSET "UTF-8";
#left-pane { display: none; }
span.ui-layout-resizer { display: none; }
#header-logo { display: none; }
#logo { display: none; }
div.header-menu { display:none; }
div.footer { display:none; }
#top-bar { display: none; }
#menu { display: none; }
div.actions_button { display:none; }
div.itop_popup { display:none; }
div.HRDrawer { display:none; }
div.DrawerHandle { display:none; }
a.tab { display:none; }
div.itop-tab { border: #ccc 1px solid; margin-top: 1em; padding-bottom:1em; }

View File

@@ -0,0 +1,565 @@
/*
* jQuery UI CSS Framework 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*/
/* Layout helpers
----------------------------------*/
.ui-helper-hidden { display: none; }
.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
.ui-helper-clearfix:after { clear: both; }
.ui-helper-clearfix { zoom: 1; }
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
/*
* jQuery UI CSS Framework 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
*/
/* Component containers
----------------------------------*/
.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
.ui-widget-content a { color: #333333; }
.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
.ui-widget-header a { color: #ffffff; }
/* Interaction states
----------------------------------*/
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
.ui-widget :active { outline: none; }
/* Interaction Cues
----------------------------------*/
.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
/* positioning */
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
.ui-icon-carat-1-se { background-position: -48px 0; }
.ui-icon-carat-1-s { background-position: -64px 0; }
.ui-icon-carat-1-sw { background-position: -80px 0; }
.ui-icon-carat-1-w { background-position: -96px 0; }
.ui-icon-carat-1-nw { background-position: -112px 0; }
.ui-icon-carat-2-n-s { background-position: -128px 0; }
.ui-icon-carat-2-e-w { background-position: -144px 0; }
.ui-icon-triangle-1-n { background-position: 0 -16px; }
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
.ui-icon-triangle-1-e { background-position: -32px -16px; }
.ui-icon-triangle-1-se { background-position: -48px -16px; }
.ui-icon-triangle-1-s { background-position: -64px -16px; }
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
.ui-icon-triangle-1-w { background-position: -96px -16px; }
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
.ui-icon-arrow-1-n { background-position: 0 -32px; }
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
.ui-icon-arrow-1-e { background-position: -32px -32px; }
.ui-icon-arrow-1-se { background-position: -48px -32px; }
.ui-icon-arrow-1-s { background-position: -64px -32px; }
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
.ui-icon-arrow-1-w { background-position: -96px -32px; }
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
.ui-icon-arrow-4 { background-position: 0 -80px; }
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
.ui-icon-extlink { background-position: -32px -80px; }
.ui-icon-newwin { background-position: -48px -80px; }
.ui-icon-refresh { background-position: -64px -80px; }
.ui-icon-shuffle { background-position: -80px -80px; }
.ui-icon-transfer-e-w { background-position: -96px -80px; }
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
.ui-icon-folder-collapsed { background-position: 0 -96px; }
.ui-icon-folder-open { background-position: -16px -96px; }
.ui-icon-document { background-position: -32px -96px; }
.ui-icon-document-b { background-position: -48px -96px; }
.ui-icon-note { background-position: -64px -96px; }
.ui-icon-mail-closed { background-position: -80px -96px; }
.ui-icon-mail-open { background-position: -96px -96px; }
.ui-icon-suitcase { background-position: -112px -96px; }
.ui-icon-comment { background-position: -128px -96px; }
.ui-icon-person { background-position: -144px -96px; }
.ui-icon-print { background-position: -160px -96px; }
.ui-icon-trash { background-position: -176px -96px; }
.ui-icon-locked { background-position: -192px -96px; }
.ui-icon-unlocked { background-position: -208px -96px; }
.ui-icon-bookmark { background-position: -224px -96px; }
.ui-icon-tag { background-position: -240px -96px; }
.ui-icon-home { background-position: 0 -112px; }
.ui-icon-flag { background-position: -16px -112px; }
.ui-icon-calendar { background-position: -32px -112px; }
.ui-icon-cart { background-position: -48px -112px; }
.ui-icon-pencil { background-position: -64px -112px; }
.ui-icon-clock { background-position: -80px -112px; }
.ui-icon-disk { background-position: -96px -112px; }
.ui-icon-calculator { background-position: -112px -112px; }
.ui-icon-zoomin { background-position: -128px -112px; }
.ui-icon-zoomout { background-position: -144px -112px; }
.ui-icon-search { background-position: -160px -112px; }
.ui-icon-wrench { background-position: -176px -112px; }
.ui-icon-gear { background-position: -192px -112px; }
.ui-icon-heart { background-position: -208px -112px; }
.ui-icon-star { background-position: -224px -112px; }
.ui-icon-link { background-position: -240px -112px; }
.ui-icon-cancel { background-position: 0 -128px; }
.ui-icon-plus { background-position: -16px -128px; }
.ui-icon-plusthick { background-position: -32px -128px; }
.ui-icon-minus { background-position: -48px -128px; }
.ui-icon-minusthick { background-position: -64px -128px; }
.ui-icon-close { background-position: -80px -128px; }
.ui-icon-closethick { background-position: -96px -128px; }
.ui-icon-key { background-position: -112px -128px; }
.ui-icon-lightbulb { background-position: -128px -128px; }
.ui-icon-scissors { background-position: -144px -128px; }
.ui-icon-clipboard { background-position: -160px -128px; }
.ui-icon-copy { background-position: -176px -128px; }
.ui-icon-contact { background-position: -192px -128px; }
.ui-icon-image { background-position: -208px -128px; }
.ui-icon-video { background-position: -224px -128px; }
.ui-icon-script { background-position: -240px -128px; }
.ui-icon-alert { background-position: 0 -144px; }
.ui-icon-info { background-position: -16px -144px; }
.ui-icon-notice { background-position: -32px -144px; }
.ui-icon-help { background-position: -48px -144px; }
.ui-icon-check { background-position: -64px -144px; }
.ui-icon-bullet { background-position: -80px -144px; }
.ui-icon-radio-off { background-position: -96px -144px; }
.ui-icon-radio-on { background-position: -112px -144px; }
.ui-icon-pin-w { background-position: -128px -144px; }
.ui-icon-pin-s { background-position: -144px -144px; }
.ui-icon-play { background-position: 0 -160px; }
.ui-icon-pause { background-position: -16px -160px; }
.ui-icon-seek-next { background-position: -32px -160px; }
.ui-icon-seek-prev { background-position: -48px -160px; }
.ui-icon-seek-end { background-position: -64px -160px; }
.ui-icon-seek-start { background-position: -80px -160px; }
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
.ui-icon-seek-first { background-position: -80px -160px; }
.ui-icon-stop { background-position: -96px -160px; }
.ui-icon-eject { background-position: -112px -160px; }
.ui-icon-volume-off { background-position: -128px -160px; }
.ui-icon-volume-on { background-position: -144px -160px; }
.ui-icon-power { background-position: 0 -176px; }
.ui-icon-signal-diag { background-position: -16px -176px; }
.ui-icon-signal { background-position: -32px -176px; }
.ui-icon-battery-0 { background-position: -48px -176px; }
.ui-icon-battery-1 { background-position: -64px -176px; }
.ui-icon-battery-2 { background-position: -80px -176px; }
.ui-icon-battery-3 { background-position: -96px -176px; }
.ui-icon-circle-plus { background-position: 0 -192px; }
.ui-icon-circle-minus { background-position: -16px -192px; }
.ui-icon-circle-close { background-position: -32px -192px; }
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
.ui-icon-circle-zoomin { background-position: -176px -192px; }
.ui-icon-circle-zoomout { background-position: -192px -192px; }
.ui-icon-circle-check { background-position: -208px -192px; }
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
.ui-icon-circlesmall-close { background-position: -32px -208px; }
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
.ui-icon-squaresmall-close { background-position: -80px -208px; }
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
/* Misc visuals
----------------------------------*/
/* Corner radius */
.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
/* Overlays */
.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*
* jQuery UI Resizable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Resizable#theming
*/
.ui-resizable { position: relative;}
.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; }
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
* jQuery UI Selectable 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Selectable#theming
*/
.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
/*
* jQuery UI Accordion 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Accordion#theming
*/
/* IE/Win - Fix animation bug - #4615 */
.ui-accordion { width: 100%; }
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
.ui-accordion .ui-accordion-li-fix { display: inline; }
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
.ui-accordion .ui-accordion-content-active { display: block; }
/*
* jQuery UI Autocomplete 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Autocomplete#theming
*/
.ui-autocomplete { position: absolute; cursor: default; }
/* workarounds */
* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
/*
* jQuery UI Menu 1.8.17
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Menu#theming
*/
.ui-menu {
list-style:none;
padding: 2px;
margin: 0;
display:block;
float: left;
}
.ui-menu .ui-menu {
margin-top: -3px;
}
.ui-menu .ui-menu-item {
margin:0;
padding: 0;
zoom: 1;
float: left;
clear: left;
width: 100%;
}
.ui-menu .ui-menu-item a {
text-decoration:none;
display:block;
padding:.2em .4em;
line-height:1.5;
zoom:1;
}
.ui-menu .ui-menu-item a.ui-state-hover,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
}
/*
* jQuery UI Button 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Button#theming
*/
.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
.ui-button-icons-only { width: 3.4em; }
button.ui-button-icons-only { width: 3.7em; }
/*button text element */
.ui-button .ui-button-text { display: block; line-height: 1.4; }
.ui-button-text-only .ui-button-text { padding: .4em 1em; }
.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
/* no icon support for input elements, provide padding by default */
input.ui-button { padding: .4em 1em; }
/*button icon element(s) */
.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
/*button sets*/
.ui-buttonset { margin-right: 7px; }
.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
/* workarounds */
button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
/*
* jQuery UI Dialog 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Dialog#theming
*/
.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
.ui-draggable .ui-dialog-titlebar { cursor: move; }
/*
* jQuery UI Slider 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Slider#theming
*/
.ui-slider { position: relative; text-align: left; }
.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
.ui-slider-horizontal { height: .8em; }
.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
.ui-slider-horizontal .ui-slider-range-min { left: 0; }
.ui-slider-horizontal .ui-slider-range-max { right: 0; }
.ui-slider-vertical { width: .8em; height: 100px; }
.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
.ui-slider-vertical .ui-slider-range-max { top: 0; }/*
* jQuery UI Tabs 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Tabs#theming
*/
.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
.ui-tabs .ui-tabs-hide { display: none !important; }
/*
* jQuery UI Datepicker 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Datepicker#theming
*/
.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
.ui-datepicker .ui-datepicker-prev { left:2px; }
.ui-datepicker .ui-datepicker-next { right:2px; }
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
.ui-datepicker td { border: 0; padding: 1px; }
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi { width:auto; }
.ui-datepicker-multi .ui-datepicker-group { float:left; }
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
/* RTL support */
.ui-datepicker-rtl { direction: rtl; }
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
.ui-datepicker-cover {
display: none; /*sorry for IE5*/
display/**/: block; /*sorry for IE5*/
position: absolute; /*must have*/
z-index: -1; /*must have*/
filter: mask(); /*must have*/
top: -4px; /*must have*/
left: -4px; /*must have*/
width: 200px; /*must have*/
height: 200px; /*must have*/
}/*
* jQuery UI Progressbar 1.8.17
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Progressbar#theming
*/
.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }

View File

@@ -120,7 +120,7 @@ class UserLDAP extends UserInternal
$aEntry = ldap_get_entries($hDS, $hSearchResult);
$sUserDN = $aEntry[0]['dn'];
$bUserBind = @ldap_bind($hDS, $sUserDN, $sPassword);
if ($bUserBind !== false)
if (($bUserBind !== false) && !empty($sPassword))
{
ldap_unbind($hDS);
return true; // Password Ok

Some files were not shown because too many files have changed in this diff Show More