Compare commits

..

203 Commits

Author SHA1 Message Date
Molkobain
a37698a9de Fix regression introduced in commit 10b7fa6 (Web queries have issue with line breaks in cell value) 2018-10-17 13:04:57 +02:00
steffunky
11ba459d1b N°1590: Advanced search: fixed a bug when selecting foreign keys would not add items (selectionMode is mandatory) 2018-10-12 10:48:32 +02:00
Molkobain
215786d740 Update readme.txt for iTop 2.5.1 2018-10-10 14:40:08 +02:00
Molkobain
fe9ca2b9cc Update readme.txt for iTop 2.5.1 2018-10-10 14:38:18 +02:00
steffunky
41a479b48d N°1555: fixed a case where AttributeExternal field was pointing anything else than an ExternalKey and its label wouldn't show up on search criteria 2018-10-09 18:30:12 +02:00
Pierre Goiffon
eb630efdf5 N°1658 update advanced search german translation (UI:Search:NoAutoSubmit:ExplainText) 2018-10-09 18:12:37 +02:00
Pierre Goiffon
91b7130671 N°1556 Advanced Search : fix removing only criteria in first row (was generating "AND 1") 2018-10-09 18:01:31 +02:00
Pierre Goiffon
80be4b4371 idea files improvements
- use Combodo preset
- JS code style (use tab for indentation, newlines in if/else statements)
2018-10-09 18:01:31 +02:00
Denis Flaven
21d7de7d8d Use a longer timeout to let the IFRAME load itself and inform iTop Hub
of the updated configuration. (Bug N°1672)
2018-10-09 17:36:30 +02:00
Molkobain
4a431be0a9 (Cherry picked from commit 5099060) N°1203 : Correctly use 'from' field for test email notifications 2018-10-08 17:43:55 +02:00
Molkobain
b84ac80aaa (Cherry pick from develop ab1715e) N°1576 Portal: Security hardening (missed one file in previous commit). 2018-10-08 13:05:45 +02:00
Eric
b6d0843e55 Fix dashboard edition when a bad OQL is present in dashlet 'Group By' 2018-10-04 14:33:29 +02:00
Molkobain
396fc2cdcd N°1658 Fix hard-coded translation in search page when the form has not been automatically submitted. 2018-10-03 17:43:06 +02:00
Molkobain
11308dc76a N°1059 Fix new empty caselog entry on bulk modification. 2018-10-03 17:24:08 +02:00
Molkobain
7fddd6acdc N°1624 Fix bulk transition integrity exception when "org_id" was not checked. 2018-10-03 15:08:54 +02:00
Molkobain
10b7fa6014 N°1647 Fix Excel web queries import. (JS script error popups) 2018-10-03 10:16:15 +02:00
Molkobain
cbe749af13 N°1645 Fix reset password dictionary entries (hyperlink had wrongly escaped characters) 2018-10-02 16:48:58 +02:00
Molkobain
c5f3598f4e N°1652 Fix broken search form when user has no read right on objects. 2018-10-02 16:40:26 +02:00
Molkobain
12e9e453d8 N°1644 Fix PHP 7.2 compatibility issue (count() on none array) 2018-10-02 14:57:51 +02:00
Stephen Abello
6e50a1c4be (Retrofit from develop 7d37b065) Form prefill: add possibility to change attributes flag on the fly 2018-10-02 11:11:46 +02:00
Guillaume Lajarige
e3e416b467 (Retrofit from deveop ab1715ed) N°1576 Portal: Security hardening. 2018-10-02 11:08:49 +02:00
Guillaume Lajarige
fcb6a4069a (Retrofit from develop 526d4c4d) N°1575 Portal: Security hardening. 2018-10-02 11:06:27 +02:00
Stephen Abello
02f83c4f52 (Retrofit from develop 9db47428) N°1559: Fixed external attributes selection on export form 2018-10-02 10:57:49 +02:00
Guillaume Lajarige
452c6c3df6 (Retrofit from develop 3d1ccf20) N°1566 Fix security message in the browser console ("Unsafe attempt to load URL data:image/svg+xml;utf8") 2018-10-02 10:56:19 +02:00
Guillaume Lajarige
2966efcfde (Retrofit from develop af43e22f) N°1578 Fix "Run Query" page hotkeys behavior in some configurations due to a wrong url. 2018-10-02 10:55:04 +02:00
Guillaume Lajarige
1cf36a5d01 (Retrofit from develop 5e1452f9) N°1580 Portal: Default image of image attributes not correctly displayed in object forms. 2018-10-02 10:52:22 +02:00
Molkobain
1973f7526e Update .gitignore to match the one from develop 2018-10-02 10:48:32 +02:00
Guillaume Lajarige
866dfe4531 (Retrofit from develop 07056613) N°1611 Fix "UTF-8 Characters Malformed" error due to wrong file encoding. 2018-10-02 10:38:27 +02:00
Molkobain
f617cb556b Merge branch 'support/2.5' of https://github.com/Combodo/iTop into support/2.5 2018-10-02 10:21:13 +02:00
bruno DA SILVA
a7000b2582 [WIP] jenkinsfile integration
cherry picked from commits :
79157c465a
e473c46dc3
148309245b
5dc39fe068
47c7a3c5e3
85b7e86e58
3fca465f1d
7955dd86f4
bef4ac83a4
bde83fc705
2018-09-26 09:00:25 +02:00
Eric
27332931d0 N°1626 - Error 500 when returning from a move to test 2018-09-17 11:42:32 +02:00
Eric
36e32b23e2 N°1585: Fix request uri too long (search form) 2018-09-11 09:36:10 +02:00
Eric
b9708c8d37 N°1585: Fix ajax request uri too long on auto-complete 2018-09-05 14:47:20 +02:00
Eric
10683d943f N°1620: Fix 'forgot your password?' link 2018-09-03 16:50:30 +02:00
Molkobain
9ad8c6a96d Add .idea project files 2018-09-03 15:53:00 +02:00
Molkobain
c74d9bbafb Add .gitignore 2018-09-03 15:51:43 +02:00
Eric Espié
22df5d09fb Retrofit from trunk
N°1595 - Setup : Blocking error on backup failure [from revision 6010]

SVN:2.5[6023]
2018-08-28 12:14:56 +00:00
Pierre Goiffon
931593a59e (Retrofit from trunk) N°1572 Add composer.json for PHP language level (r5967)
SVN:2.5[6007]
2018-08-17 08:42:41 +00:00
Stephen Abello
7092dc6f86 (Retrofit from trunk) German Translation: typos (UserRequest #18704)
SVN:2.5[6002]
2018-08-08 10:12:43 +00:00
Pierre Goiffon
c3f80a5876 (Retrofit from trunk) N°1582 Fix audit when a current organization is set and there is an audit rule with valid=true
Was generating an OQL missing query argument error (r5991)

SVN:2.5[5992]
2018-08-01 09:13:15 +00:00
Pierre Goiffon
821eb4df8c (Retrofit from trunk) PHP 7.2 compatibility: fix another count(null) (r5988)
SVN:2.5[5989]
2018-07-30 10:31:22 +00:00
Pierre Goiffon
e276587fdc N°931 change iTop 2.6 MySQL requirements from 5.5.3 to 5.6 (to have fulltext on InnoDB)
SVN:2.5[5978]
2018-07-25 07:57:31 +00:00
Eric Espié
a454a43111 Retrofit from trunk
N°1556 - Search: Fix removing last criterion on a 'or' line resulted in 'OR 1'.
The empty OR condition is now removed completely from the screen and the criterion list.
[from revision 5951]

SVN:2.5[5956]
2018-07-19 08:17:46 +00:00
Eric Espié
045f58144e Retrofit from trunk
N°1553 - Search: Fix operator on indexed attributes.
It was previously always forced to '=', now it's only defaulted to '='
[from revision 5950]

SVN:2.5[5955]
2018-07-19 08:14:26 +00:00
Eric Espié
8ffea22f0e Retrofit from trunk
N°1561 - Fix auto-complete error when the friendlyname depends on other classes
[from revision 5948]

SVN:2.5[5954]
2018-07-19 08:10:32 +00:00
Eric Espié
c630676792 Retrofit from trunk
N°1555 - Search: ExternalField label not displayed.
The search is not restricted for external fields anymore.
[from revision 5945]

SVN:2.5[5953]
2018-07-19 08:06:39 +00:00
Eric Espié
0bbb586094 Retrofit from trunk
Advanced search: Fix an error when using search form from an union.
[from revision 5940]

SVN:2.5[5944]
2018-07-17 12:23:27 +00:00
Eric Espié
efa1f4ee43 Retrofit from trunk
N°1551 - Search: selected org & default criteria
Display the default search criteria also when an org is selected.
The org restriction criterion is read only.
[from revision 5939]

SVN:2.5[5943]
2018-07-17 12:22:09 +00:00
Eric Espié
e184eb6aae Retrofit from trunk
DBObject->GetOriginal() hardening (now support attributes not set: for example sla_tto_passed for UserRequest until it is closed)
[from revision 5932]

SVN:2.5[5942]
2018-07-17 12:19:26 +00:00
Pierre Goiffon
6e9fcb81f0 (Retrofit from trunk) Fix setup for PHP 5.5 (cannot use expression as default field value) (r5933)
SVN:2.5[5934]
2018-07-12 07:46:36 +00:00
Denis Flaven
774ecb4003 (retrofit from trunk) Do not check if the organizations are allowed if there is no user logged in (use case: automatic synchro of users at connection time)
SVN:2.5[5931]
2018-07-05 13:02:10 +00:00
Guillaume Lajarige
c555d1274b Creating SVN branch for iTop 2.5
SVN:2.5[5920]
2018-06-28 08:22:50 +00:00
Eric Espié
261bc83811 N°1479 : Fixed a bug where impact analysis zoom would affect other tabs' size #jfb :)
SVN:trunk[5918]
2018-06-27 15:42:32 +00:00
Vincent Dumas
2dbaf4dfa1 german translation for Graphique in Portal
SVN:trunk[5917]
2018-06-27 15:38:45 +00:00
Pierre Goiffon
5df9f38391 German translations update (many thanks Lars Hippler / Itomig !)
SVN:trunk[5916]
2018-06-27 15:14:19 +00:00
Eric Espié
93763c5932 N°1401 - External dashlet edition in the designer #jfb :)
SVN:trunk[5915]
2018-06-27 14:42:28 +00:00
Eric Espié
242caff990 N°1401 - External dashlet edition in the designer
SVN:trunk[5914]
2018-06-27 13:58:36 +00:00
Romain Quetiez
e97e9907c7 Releasing 2.5.0
SVN:trunk[5913]
2018-06-27 12:47:15 +00:00
Guillaume Lajarige
ac330b6665 Fix dashlet container CSS class for better positioning
SVN:trunk[5912]
2018-06-27 08:23:59 +00:00
Guillaume Lajarige
a4a70a1287 German translation placeholders for iTop 2.5
SVN:trunk[5911]
2018-06-26 15:36:55 +00:00
Vincent Dumas
dda8651ba2 Add default search criterion on SynchroDataSource
SVN:trunk[5910]
2018-06-25 15:57:06 +00:00
Guillaume Lajarige
83e2d48f4d N°1401 Fix none draggable third-party dashlets in a dashboard.
SVN:trunk[5909]
2018-06-25 14:17:41 +00:00
Vincent Dumas
a903711a7a Add default search criterion on Action and Profile classes
SVN:trunk[5908]
2018-06-25 13:23:35 +00:00
Vincent Dumas
9e17a611d2 Add 'name' and 'description' as default search criteria on QueryPhrase, Audit Category and Profile. Enable search bar on Profile and Audit Category menus.
SVN:trunk[5907]
2018-06-25 11:06:52 +00:00
Eric Espié
1e11ed3041 N°1381 - Search on string with index -> operator = (Fixed for derived classes)
SVN:trunk[5906]
2018-06-25 10:51:15 +00:00
Vincent Dumas
0449470cdf Add 'name' as default search criteria on QueryPhrase
SVN:trunk[5905]
2018-06-25 10:11:39 +00:00
Eric Espié
21a5a2d4ef N°1522 - Dashlet group by on stopwatch
SVN:trunk[5904]
2018-06-25 09:59:36 +00:00
Eric Espié
a848cb28f1 N°1436 - Access control updated for grant_by_profile categories of classes -
Fix access to internal classes form the core engine

SVN:trunk[5903]
2018-06-22 16:07:35 +00:00
Denis Flaven
b7ae6b143e Impact analysis performance enhancement: much better (and faster) processing of graphs containing loops.
SVN:trunk[5902]
2018-06-22 15:56:17 +00:00
Stephen Abello
ba0c18eec1 N°1479 : Fixed a bug where impact analysis zoom would affect other tabs' size
SVN:trunk[5901]
2018-06-22 13:51:21 +00:00
Guillaume Lajarige
61366b347d Code cleanup
SVN:trunk[5900]
2018-06-22 12:46:30 +00:00
Guillaume Lajarige
edab6643f6 Portal: Add default width and decoration class to FilterBrick.
SVN:trunk[5899]
2018-06-22 12:34:02 +00:00
Stephen Abello
ce36ef3aad N°1479 : Fixed a bug where impact analysis zoom would reduce div size
SVN:trunk[5898]
2018-06-22 07:58:48 +00:00
Stephen Abello
fdb439f054 N°1520 : URP_Profiles now has a default search criteria
SVN:trunk[5897]
2018-06-21 13:22:01 +00:00
Stephen Abello
2229f3f015 N°1395 : New Portuguese BR translations (thanks to Anderson Cardoso!)
SVN:trunk[5896]
2018-06-21 08:22:23 +00:00
Pierre Goiffon
18a5df1ce7 modify all in views : fix header checkbox not checked after check_all event fired
SVN:trunk[5895]
2018-06-19 10:05:57 +00:00
Eric Espié
f5cb29fadd N°1513 - Datamodel menu access control
SVN:trunk[5894]
2018-06-19 08:49:06 +00:00
Eric Espié
d929732fb6 N°1495 - Regression: Search on hierarchical key
SVN:trunk[5893]
2018-06-19 08:42:58 +00:00
Eric Espié
6c36f3bc7c N°1495 - Regression: Search on hierarchical key
SVN:trunk[5892]
2018-06-19 08:32:54 +00:00
Vincent Dumas
c6a59a5309 Suppression of obsolesence condition on Ticket (was creating performance issue)
SVN:trunk[5891]
2018-06-18 16:26:03 +00:00
Pierre Goiffon
b02c30a525 Translation keys available client side (Dict.S()) : format + JS/PHPDoc
SVN:trunk[5890]
2018-06-18 15:55:10 +00:00
Guillaume Lajarige
46a647ae66 N°1514 Add missing translations for advanced search.
SVN:trunk[5889]
2018-06-18 14:11:04 +00:00
Denis Flaven
8943a67f85 Officially support PHP 7.2.0.
SVN:trunk[5888]
2018-06-15 16:37:11 +00:00
Guillaume Lajarige
f132d751f5 N°1303 Portal: ManageBrick lists are now ordered regarding the datamodel definition (//class/properties/order).
SVN:trunk[5887]
2018-06-15 14:00:28 +00:00
Guillaume Lajarige
4ba8c9ff9e Fix default classes order by on DBObjectSet.
SVN:trunk[5886]
2018-06-15 13:42:00 +00:00
Guillaume Lajarige
955ae6c392 Portal: Remove unused folder.
SVN:trunk[5885]
2018-06-15 12:46:39 +00:00
Guillaume Lajarige
ed33b327fb Portal: Add XML comments to standard portal configuration.
SVN:trunk[5884]
2018-06-15 12:46:06 +00:00
Guillaume Lajarige
f8f7486be2 N°1244 Authorize "colspan" & "rowspan" attributes on "th" / "td" / "tr" tags in HTML fields.
SVN:trunk[5883]
2018-06-15 10:09:35 +00:00
Bruno Da Silva
3215875e5f N°462 : Added an information about file max size on portal forms
SVN:trunk[5882]
2018-06-15 09:57:02 +00:00
Guillaume Lajarige
62da90418a Portal: Default object forms are now more like in the administration console instead of just having their fields one after another. (BETA!)
SVN:trunk[5881]
2018-06-15 09:16:36 +00:00
Bruno Da Silva
7c620fae78 N°607 : Fixed an issue where a file on a transition form wouldn't be uploaded
SVN:trunk[5880]
2018-06-15 09:02:22 +00:00
Bruno Da Silva
daef0c3a8f N°1430 Fixed an issue on n-n Date attributes
SVN:trunk[5879]
2018-06-15 08:25:55 +00:00
Guillaume Lajarige
52b6d399a0 Portal: Remove copyright from page footer.
SVN:trunk[5878]
2018-06-15 08:10:11 +00:00
Guillaume Lajarige
f210f63ec4 Portal: Change ManageBrick XML definition for tile & page display modes.
SVN:trunk[5877]
2018-06-15 08:09:12 +00:00
Bruno Da Silva
41694050ea N°1498 Fixed an issue on Advanced search external key autocomplete where homonyms weren't displayed
SVN:trunk[5876]
2018-06-15 07:34:11 +00:00
Pierre Goiffon
3f612cfc90 some PHPDoc
SVN:trunk[5875]
2018-06-14 15:19:30 +00:00
Guillaume Lajarige
272acdd8d3 Portal: Fix OQL exception in ManageBrick when grouping tabs on an attribute (instead of sub OQLs).
SVN:trunk[5874]
2018-06-14 15:10:14 +00:00
Eric Espié
5a4375cb71 N°1485 - Search: "Undefined" on hierarchical key not working
SVN:trunk[5873]
2018-06-14 14:56:42 +00:00
Guillaume Lajarige
5b3d7e2354 Portal: Fix missing parameter in new ScopeValidatorHelper::AddScopeToQuery() method. "Read" scope was always applied!
SVN:trunk[5872]
2018-06-14 14:38:48 +00:00
Guillaume Lajarige
189cefac1b Portal: Forgot to commit generated CSS file.
SVN:trunk[5871]
2018-06-14 13:51:58 +00:00
Guillaume Lajarige
87d36914c4 Portal: Enhancements on ManageBrick badges UI.
SVN:trunk[5870]
2018-06-14 12:32:58 +00:00
Guillaume Lajarige
72e14805b1 Portal: Fix HTML tooltips in ManageBrick tiles.
SVN:trunk[5869]
2018-06-14 10:29:34 +00:00
Guillaume Lajarige
87e2f76793 Portal: Improvements on ManageBrick badge tiles UI.
SVN:trunk[5868]
2018-06-14 10:28:06 +00:00
Bruno Da Silva
8fbd53d72d N°1500 Advanced search on external keys performance
- bugfix: do not run both queries if nb result is >= 150

SVN:trunk[5864]
2018-06-14 08:14:53 +00:00
Guillaume Lajarige
586cc1f003 Portal: Fix regression introduced in r5862.
SVN:trunk[5863]
2018-06-14 07:12:38 +00:00
Guillaume Lajarige
a77753cfef Portal: Rework on ManageBrick to put D3/C3 in the portal core instead of using an external API.
SVN:trunk[5862]
2018-06-13 16:03:59 +00:00
Eric Espié
5e464ef3a2 N°1484 - Advanced search: empty/not empty not working
SVN:trunk[5861]
2018-06-13 15:22:43 +00:00
Guillaume Lajarige
0727c9774b Portal: Fixes on ManageBrick routes and display modes.
SVN:trunk[5860]
2018-06-13 15:21:45 +00:00
Bruno Da Silva
ee43a365dc N°1500 Advanced search on external keys performance
- first search using "equals" or "start with"
- then using "contains"
- search is triggered at two first chars

SVN:trunk[5859]
2018-06-13 13:36:31 +00:00
Eric Espié
d5ba0d9ed5 N°1401 - External dashlet edition in the designer
SVN:trunk[5858]
2018-06-13 11:46:37 +00:00
Stephen Abello
7031a52a43 N°1499 : Edge case where picking a datetime and pressing enter would not close the criterion
SVN:trunk[5857]
2018-06-13 09:49:49 +00:00
Stephen Abello
12af164dcc N°1482 : Retrocompatibility hack for non "standard" extensions' search form
SVN:trunk[5856]
2018-06-12 14:01:35 +00:00
Eric Espié
362cd72e87 N°1325 Dashboards: Unknown dashlets: keep the original type of the unknown dashlet
SVN:trunk[5855]
2018-06-12 08:56:41 +00:00
Guillaume Lajarige
cb19520b6b N°1453 Portal: Fix regression introduced by previous change.
SVN:trunk[5854]
2018-06-11 15:22:12 +00:00
Eric Espié
c74972488d Don't automatically launch the search results when coming from an object detail
SVN:trunk[5853]
2018-06-11 15:01:39 +00:00
Eric Espié
b78e40153f N°858 - Archive mode: WRITE write access menus disappear in archive mode
SVN:trunk[5852]
2018-06-11 14:32:17 +00:00
Eric Espié
f7212662b9 N°1420 - Performances enhancement (consider search auto-submit parameter)
SVN:trunk[5851]
2018-06-11 13:17:15 +00:00
Eric Espié
97c8e1f7a9 N° 1436 - Allowed orgs on Users not managed
SVN:trunk[5850]
2018-06-11 10:02:20 +00:00
Guillaume Lajarige
2afc6d1c62 N°1453 Portal: Fix ajax filter on ManageBrick that was looking on "standard_search" zlist attributes instead of the brick fields.
SVN:trunk[5849]
2018-06-11 07:55:40 +00:00
Guillaume Lajarige
708858da39 Portal: Fix regression in FilterBrick that crashes when pointing on a ManageBrick.
SVN:trunk[5848]
2018-06-08 16:33:55 +00:00
Stephen Abello
41dccb468e N°1236 : Added "approved" state to the tto active states
SVN:trunk[5847]
2018-06-08 14:17:44 +00:00
Eric Espié
36cfe9e5c2 N°1445 - Regression: User cannot change language
SVN:trunk[5846]
2018-06-08 13:18:06 +00:00
Eric Espié
1c7fd57f2e N°1431 - Dashlet: Tri sur le champ ou la fonction (Fix error on Pie chart)
SVN:trunk[5845]
2018-06-08 13:15:39 +00:00
Eric Espié
f920851420 N°1420 - Performances enhancement (enhance counts)
SVN:trunk[5844]
2018-06-08 13:13:33 +00:00
Stephen Abello
04b8fe3326 N°1496 : Fixed datamodel viewer for IE11 : IE doesn't support backtick character in JS
SVN:trunk[5843]
2018-06-08 13:02:19 +00:00
Eric Espié
9fb4374efa N°1420 - Performances enhancement (fix indexes generation)
SVN:trunk[5841]
2018-06-08 12:58:29 +00:00
Stephen Abello
885cabd6ef N°1479 : Fixed a regression where on impact analysis display, every tabs would be resized to 15px. Added css for advanced search for impact analysis & normal search
SVN:trunk[5840]
2018-06-08 10:17:58 +00:00
Eric Espié
e00f9c2a83 N°1420 - Performances enhancement (fix regression on yesterday commit)
SVN:trunk[5836]
2018-06-08 09:15:19 +00:00
Guillaume Lajarige
fe24eda4b4 N°1494 Portal: Fix images size on mosaic view. They could be too height sometimes.
SVN:trunk[5835]
2018-06-08 08:42:38 +00:00
Eric Espié
483d80b576 N°1420 - Performances enhancement (fix indexes generation)
SVN:trunk[5834]
2018-06-08 08:18:57 +00:00
Eric Espié
70a0a3c52e N°1420 - Performances enhancement
SVN:trunk[5833]
2018-06-07 15:06:38 +00:00
Denis Flaven
5b2f32c08a Enhancement of the data collection for iTop Hub: better detection of the web server version.
SVN:trunk[5831]
2018-06-07 11:46:45 +00:00
Stephen Abello
5bad1e1c88 N°1476 : Fixed a regression introduced in 2.5 where on changing final class on a n-n object selection form, checkboxes were lost.
SVN:trunk[5830]
2018-06-07 07:41:49 +00:00
Eric Espié
2706ebf638 Merged from 2.4
N°1488 - restore failed on production-modules [from revision 5827]

SVN:trunk[5828]
2018-06-05 14:14:37 +00:00
Denis Flaven
9fe3261424 Linked JS scripts can now be used in ajax pages. This is useful for IPopupMenu extensions which depend on a JS script and are loaded asynchronously when a list of objects changes (for example when changing the target class for a search)
SVN:trunk[5826]
2018-06-05 13:04:50 +00:00
Denis Flaven
9df087984f (Internal) declare the member variables with the correct names: declare what is actually used !!
SVN:trunk[5825]
2018-06-05 13:02:33 +00:00
Denis Flaven
cbb9bcd93d Proper use of the "304" (Not modified) HTTP header for InlineImages. Seems that FastCGI is more sensitive to incorrect HTTP headers than MPM...
SVN:trunk[5824]
2018-06-05 12:59:17 +00:00
Bruno Da Silva
ad8f7576e0 N°1381 Advanced search did not correctly compute indexes for "other" list of fields
SVN:trunk[5823]
2018-05-31 07:41:03 +00:00
Pierre Goiffon
e205d85728 Portal : new helper method to add scope to a DbSearch (moved from existing one in ManageBrick)
SVN:trunk[5822]
2018-05-30 16:44:23 +00:00
Guillaume Lajarige
a01d5c2760 Fix regression introduced in r5806. Making the ITSM Designer unstable when adding a new attribute to a class.
SVN:trunk[5821]
2018-05-30 16:03:33 +00:00
Stephen Abello
b57423386c N°1477 : fixed an issue where datamodel viewer couldn't load attributes' filters with backspaces in it
SVN:trunk[5818]
2018-05-30 07:46:27 +00:00
Vincent Dumas
22e525452a Rename the id of the Portal contact scope for administrator, as there was two scopes with id="all"
SVN:trunk[5817]
2018-05-29 16:12:08 +00:00
Vincent Dumas
6c84074b02 Enable CSV export of on-going and closed tickets from the User Portal
SVN:trunk[5816]
2018-05-28 15:57:20 +00:00
Denis Flaven
6cc4a6e1be PHP 7.2 compatibility: count(null) now generates a PHP warning !!
SVN:trunk[5815]
2018-05-28 14:08:31 +00:00
Eric Espié
04e41ab676 N°1431 - Sort on attribute or aggregation function
SVN:trunk[5814]
2018-05-25 15:38:36 +00:00
Guillaume Lajarige
3ad64d9823 N°1472 Portal: OQL optimization in ManageBrick when several UNIONs are used.
SVN:trunk[5812]
2018-05-23 15:00:41 +00:00
Eric Espié
8de7ff5470 Fix broken menus on search from dashlet header statistics
SVN:trunk[5811]
2018-05-23 14:50:17 +00:00
Stephen Abello
0a44f34c2c N°1444 : fixed regression introduced in [r5724] & [r5773]
SVN:trunk[5809]
2018-05-23 10:11:23 +00:00
Bruno Da Silva
4f900e36c1 Advanced search:
- deduplicate pre-existing criterion N°1454
- search's 'breadcrumb' and 'history.replaceState' now preserve the org_id parameter

SVN:trunk[5808]
2018-05-22 16:03:38 +00:00
Eric Espié
571d90618e cleanup code
SVN:trunk[5807]
2018-05-22 15:26:08 +00:00
Eric Espié
fe8436f2ad cleanup code
SVN:trunk[5806]
2018-05-22 15:14:51 +00:00
Eric Espié
a4459901e8 N°1429: Dashlet header statistics fix corresponding search criteria
SVN:trunk[5805]
2018-05-22 14:57:05 +00:00
Bruno Da Silva
bef8fd566f Advanced search: fix a side effect of the new behaviour "validate the draft" when the criteria is closed by a click outside of it's box
- when a criteria is being edited and the user click on search/refresh button, the search handler now update the criteria BEFORE making the ajax call

SVN:trunk[5804]
2018-05-22 13:20:07 +00:00
Bruno Da Silva
0f9994ac74 Advanced search: several improvements
- remove a side effect on script listening to the body (by removing an overly agressive top propagation on body listener)
- Submit on click outside of the criteria N°1381
- Do not auto submit when values do not changes N°1381	
- Open criteria on click on the bold part of the title was broken

SVN:trunk[5803]
2018-05-22 08:06:45 +00:00
Bruno Da Silva
8691ccc013 test: reduce the verbosity of test cases
SVN:trunk[5802]
2018-05-18 14:57:41 +00:00
Eric Espié
bac7b50090 N°1429 - Fix regression due to "Display results wrt obsolescence display choices"
SVN:trunk[5801]
2018-05-18 12:20:22 +00:00
Stephen Abello
39ff1e318c N°1319 & N°1203: Refactored [r5750]
SVN:trunk[5800]
2018-05-17 14:43:46 +00:00
Stephen Abello
1f8110573c N°1226 : When global searching with needles smaller than 'full_text_needle_min', exclude these needles from the search instead of stopping it
SVN:trunk[5799]
2018-05-17 14:16:18 +00:00
Eric Espié
7c128e0f6e DBSearch: test with only functions (no group by)
SVN:trunk[5798]
2018-05-17 12:00:28 +00:00
Romain Quetiez
11b50b4917 #815 Cosmetics on the documentation shown upon setup completion (Completing the iTop installation for workflow management): the file cron.params has been renamed into cron.distrib
SVN:trunk[5797]
2018-05-17 10:01:56 +00:00
Bruno Da Silva
9d771be8b2 N°955: fix the bugfix [r5766]
- handling of forbidden char code in the error message (happen for example on host error using a french windows), in this case a less verbose message is written in the table, and a issue log is written into the fs 
- apply for both synchronous and asynchronous

SVN:trunk[5796]
2018-05-17 09:55:58 +00:00
Bruno Da Silva
02315b8aa1 N°880: fix the bugfix [r5737]
- previous bugfix altered the cron frequency, this is not the desired behaviour, now the conf alter the lifetime of drafts's attachments
- apply for both inline images and attachments

SVN:trunk[5795]
2018-05-16 13:03:15 +00:00
Denis Flaven
7fb3d133e3 Typo in the CSS class name !!
SVN:trunk[5794]
2018-05-16 09:44:58 +00:00
Bruno Da Silva
65fb29a1d4 N°1226: code refactoring of the previously coded bugfix.
SVN:trunk[5793]
2018-05-16 08:07:00 +00:00
Bruno Da Silva
703a432f7b N°1173: code refactoring of the previously coded bugfix.
SVN:trunk[5792]
2018-05-16 08:01:59 +00:00
Eric Espié
4d37942717 cleanup
SVN:trunk[5791]
2018-05-15 15:53:59 +00:00
Eric Espié
137067ea43 cleanup and fix Group by queries
SVN:trunk[5790]
2018-05-15 15:12:57 +00:00
Stephen Abello
67e12dcc74 Type in revision nb
SVN:trunk[5789]
2018-05-15 14:48:05 +00:00
Eric Espié
911c0d2c1b N°1429 - Display results wrt obsolescence display choices
SVN:trunk[5788]
2018-05-15 13:39:22 +00:00
Eric Espié
4b9648affa N°1429 - fix value list display when editing a dynamic header dashlet in french
SVN:trunk[5787]
2018-05-15 13:20:42 +00:00
Eric Espié
804afa65f2 DBSearch: test with only functions (no group by)
SVN:trunk[5786]
2018-05-15 12:35:44 +00:00
Eric Espié
c56dc6cade code cleanup
SVN:trunk[5785]
2018-05-15 12:30:52 +00:00
Eric Espié
c4f7055e1a N°1330 - Fix broken sql requests due to the use of class instead of alias
SVN:trunk[5784]
2018-05-15 12:17:00 +00:00
Eric Espié
44b6dfab1d DBSearch: add test with only functions (no group by)
SVN:trunk[5782]
2018-05-15 08:59:49 +00:00
Eric Espié
50e79b8c97 Advanced search: fix date time i18n and 1 second/day add/remove on > and <
SVN:trunk[5781]
2018-05-15 08:33:54 +00:00
Bruno Da Silva
185825f83c advanced search: dates i18n tests
SVN:trunk[5780]
2018-05-15 08:17:02 +00:00
Eric Espié
c6be331f14 Code cleanup
SVN:trunk[5779]
2018-05-14 07:41:51 +00:00
Eric Espié
c0dd418992 N°478 - Access rights on admin menus and support for some classes not in XML (in OQL, dashlets and groups)
SVN:trunk[5778]
2018-05-11 13:58:27 +00:00
Pierre Goiffon
c03d5167f6 N°1418 audit : fix regression, error with union queries in AuditRule
SVN:trunk[5777]
2018-05-09 16:47:54 +00:00
Bruno Da Silva
56d625b7b9 PHPunit is now integrated through composer (inside the directory /test)
SVN:trunk[5776]
2018-05-04 16:13:26 +00:00
Bruno Da Silva
e74b23f305 PHPunit is now integrated through composer (inside the directory /test)
SVN:trunk[5775]
2018-05-04 15:59:34 +00:00
Pierre Goiffon
6e7d2abc9a N°1418 fix audit with valid_flag=true that were always failing
SVN:trunk[5773]
2018-05-04 15:10:25 +00:00
Pierre Goiffon
3bebb9bf0f convert inline comment to PHPDoc
SVN:trunk[5772]
2018-05-04 15:08:48 +00:00
Bruno Da Silva
1063697e85 N°865: exports (csv, xslx, pdf) LocalizeOutput option lost
- it happens when the export has more thant one chunk, stating the 2d chunk.
- this option is now persisted and restored on each chunk

SVN:trunk[5771]
2018-05-04 12:49:27 +00:00
Bruno Da Silva
7bdad90564 bugfix: sanitization filter "parameter" => Since the filter parameter is now url-encoded, it now may contains %3D, %2B and %2F (respectively =, + and /).
a migration note was written : https://wiki.combodo.com/doku.php?id=latest:install:240_to_250_migration_notes#param_filter

SVN:trunk[5770]
2018-05-04 10:13:29 +00:00
Stephen Abello
1dccc54814 N°1385 : Bare relation tabs on FunctionCI now correctly display item count according to user obsolescence preferences.
SVN:trunk[5769]
2018-05-04 09:52:47 +00:00
Stephen Abello
1c255213e1 N°974 : MySQL strict mode compatibility (null replaced 0000-00-00 00:00:00 for DateTime).
SVN:trunk[5768]
2018-05-04 08:29:32 +00:00
Stephen Abello
cb2c172483 N°1173 : mysqldump now correctly use 'mysql_bindir' parameter on setup & move to prod's backups
SVN:trunk[5767]
2018-05-03 15:00:13 +00:00
Stephen Abello
b43063a6d2 N°995 : Update notification status when its async task fail
SVN:trunk[5766]
2018-05-02 14:40:22 +00:00
Stephen Abello
3974406f1b N°1175 : Fixed missing params error occurring when resetting password from a notification linking to portal.
SVN:trunk[5763]
2018-05-02 08:19:27 +00:00
Eric Espié
21aed2d2e1 Advanced Search: Fix missing query internal params
SVN:trunk[5762]
2018-05-02 08:11:28 +00:00
Pierre Goiffon
820c257e96 N°1427 New method to fix timezone where datamodel is not yet loaded
SVN:trunk[5761]
2018-05-02 06:36:57 +00:00
Pierre Goiffon
56e5616080 N°1370 Portal badge : improve tile on low resolutions 2 (wooops pushed to soon)
SVN:trunk[5759]
2018-04-27 15:59:24 +00:00
Pierre Goiffon
6f7351ecc3 @@N°1370 Portal badge : improve tile on low resolutions
SVN:trunk[5758]
2018-04-27 15:53:34 +00:00
Eric Espié
8f56032d49 Fix Bug delete when the serialized filter contains %
SVN:trunk[5757]
2018-04-27 15:53:08 +00:00
Guillaume Lajarige
1b6ca2ed14 N°1425 Fix regression introduced in 2.4. Creation of an object in a specific state could result in a fatal error due to bad ormlinkSet initialization.
SVN:trunk[5755]
2018-04-27 14:17:55 +00:00
Stephen Abello
d956682b9f N°1285 & N°1278: Updated swiftmailer to v5.4.9
SVN:trunk[5754]
2018-04-27 14:06:51 +00:00
Stephen Abello
76759f1847 N°1424 : Sharing base compatibility fix
SVN:trunk[5751]
2018-04-27 12:14:39 +00:00
Stephen Abello
d441595ee6 N°1319 & N°1203: Added a conf params 'email_default_sender_address' and 'email_default_sender_label' that will be used if a mail has no sender set. (forgot password, test mail, test notification mail, data source fail notification)
SVN:trunk[5750]
2018-04-27 08:29:48 +00:00
Pierre Goiffon
2be0250aee N°1370 Portal badge : wooops forgot the .css as always :/
SVN:trunk[5744]
2018-04-26 13:08:43 +00:00
Pierre Goiffon
b2a3b10065 N°1370 Portal badge : tooltips for descriptions
SVN:trunk[5743]
2018-04-26 12:57:29 +00:00
Pierre Goiffon
22b181a8f7 N°1370 Portal badge : change some aligns + handle description correctly
SVN:trunk[5742]
2018-04-26 12:56:54 +00:00
Pierre Goiffon
71d07be646 N°1370 portal badges : number on top of the description and wider
SVN:trunk[5741]
2018-04-26 09:18:12 +00:00
363 changed files with 15667 additions and 10572 deletions

122
.gitignore vendored Normal file
View File

@@ -0,0 +1,122 @@
/toolkit/
/conf/*
/env-*/*
# composer reserver directory, from sources, populate/update using "composer install"
vendor/*
test/vendor/*
# all datas but listing prevention
data/*
!data/.htaccess
!data/index.php
!data/web.config
# iTop extensions
extensions/*
!extensions/readme.txt
# all logs but listing prevention
log/*
!log/.htaccess
!log/index.php
!log/web.config
# Jetbrains
.idea/**
!.idea/encodings.xml
!.idea/codeStyles
!.idea/codeStyles/*
!.idea/inspectionProfiles
!.idea/inspectionProfiles/*
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
### Eclipse template
.metadata
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet

45
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,45 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<PHPCodeStyleSettings>
<option name="CONCAT_SPACES" value="false" />
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
<option name="PHPDOC_BLANK_LINES_AROUND_PARAMETERS" value="true" />
<option name="PHPDOC_WRAP_LONG_LINES" value="true" />
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
<option name="LOWER_CASE_NULL_CONST" value="true" />
<option name="BLANK_LINES_BEFORE_RETURN_STATEMENT" value="1" />
<option name="KEEP_RPAREN_AND_LBRACE_ON_ONE_LINE" value="true" />
<option name="PHPDOC_USE_FQCN" value="true" />
</PHPCodeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="PHP">
<option name="BLANK_LINES_AFTER_PACKAGE" value="1" />
<option name="BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="SPACE_BEFORE_FOR_PARENTHESES" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<indentOptions>
<option name="USE_TAB_CHARACTER" value="true" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Combodo" />
</state>
</component>

6
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@@ -0,0 +1,154 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="BladeControlDirectives" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckEmptyScriptTag" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckImageSize" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckNodeTest" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckTagEmptyBody" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CheckValidXmlInScriptTagBody" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptArgumentsOutsideFunction" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptFunctionSignatures" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptInfiniteLoop" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptLiteralNotFunction" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptSillyAssignment" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptSwitchStatementWithNoDefaultBranch" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CoffeeScriptUnusedLocalSymbols" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ComposeFileStructure" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssFloatPxLength" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidAtRule" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidCharsetRule" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidElement" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidFunction" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidHtmlTagReference" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidImport" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssInvalidMediaFeature" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidPropertyValue" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssInvalidPseudoSelector" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssMissingComma" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssNegativeValue" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssNoGenericFontName" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssOptimizeSimilarProperties" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="CssOverwrittenProperties" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssRedundantUnit" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssUnitlessNumber" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CssUnknownProperty" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myCustomPropertiesEnabled" value="false" />
<option name="myIgnoreVendorSpecificProperties" value="false" />
<option name="myCustomPropertiesList">
<value>
<list size="0" />
</value>
</option>
</inspection_tool>
<inspection_tool class="CssUnknownTarget" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnresolvedClass" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnresolvedCustomProperty" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnresolvedCustomPropertySet" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CssUnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CucumberExamplesColon" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CucumberMissedExamples" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="CucumberTableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CucumberUndefinedStep" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DockerFileArgumentCount" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="DuplicateKeyInSection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="DuplicateSectionInFile" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EmptyEventHandler" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="FileHeaderInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="GherkinBrokenTableInspection" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="GherkinMisplacedBackground" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="HamlNestedTagContent" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HardwiredNamespacePrefix" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlDeprecatedTag" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlExtraClosingTag" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlFormInputWithoutLabel" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlMissingClosingTag" enabled="false" level="INFORMATION" enabled_by_default="false" />
<inspection_tool class="HtmlPresentationalElement" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownAnchorTarget" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownAttribute" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myValues">
<value>
<list size="0" />
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownBooleanAttribute" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlUnknownTag" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myValues">
<value>
<list size="6">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="ImplicitTypeConversion" enabled="false" level="WARNING" enabled_by_default="false">
<option name="BITS" value="1720" />
<option name="FLAG_EXPLICIT_CONVERSION" value="true" />
<option name="IGNORE_NODESET_TO_BOOLEAN_VIA_STRING" value="true" />
</inspection_tool>
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSLastCommaInArrayLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSLastCommaInObjectLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="JSUnresolvedFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="JSUnresolvedVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="LossyEncoding" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MissingSinceTagDocInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="MysqlParsingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NonAsciiCharacters" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocMissingReturnTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpIncludeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedClassInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedConstantInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedFieldInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedFunctionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedGotoLabelInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUndefinedNamespaceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpUnusedParameterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="RedundantTypeConversion" enabled="false" level="WARNING" enabled_by_default="false">
<option name="CHECK_ANY" value="false" />
</inspection_tool>
<inspection_tool class="RequiredAttributes" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myAdditionalRequiredHtmlAttributes" value="" />
</inspection_tool>
<inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlCheckUsingColumnsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlConstantConditionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDeprecateTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDerivedTableAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDropIndexedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlNullComparisonInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlPostgresqlSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlStorageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnusedVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="TaskProblemsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="TypeScriptPreferShortImport" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="XmlDefaultAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="XmlUnboundNsPrefix" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="XmlUnusedNamespaceDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="XsltUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="XsltVariableShadowing" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -x
# create target dirs
mkdir -p var
mkdir -p toolkit
# cleanup target dirs
rm -rf toolkit/*
# fill target dirs
curl http://www.combodo.com/documentation/iTopDataModelToolkit-2.3.zip | tar xvz --directory toolkit
cp -r .jenkins/configuration/default-environment/unattended_install/* toolkit

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -x
# on the root dir
composer install
# under the test dir
cd test
composer install

13
.jenkins/bin/init/debug.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -x
whoami
pwd
ls
echo "$BRANCH_NAME:${BRANCH_NAME}"
echo "printenv :"
printenv

8
.jenkins/bin/tests/phpunit.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -x
cd test
export DEBUG_UNIT_TEST="0"
php vendor/bin/phpunit --log-junit ../var/test/phpunit-log.junit.xml --teamcity

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -x
cd toolkit
php unattended_install.php default-params.xml

View File

@@ -0,0 +1,285 @@
<?php
/**
*
* Configuration file, generated by the iTop configuration wizard
*
* The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
*
*/
$MySettings = array(
// access_message: Message displayed to the users when there is any access restriction
// default: 'iTop is temporarily frozen, please wait... (the admin team)'
'access_message' => 'iTop is temporarily frozen, please wait... (the admin team)',
// access_mode: Access mode: ACCESS_READONLY = 0, ACCESS_ADMIN_WRITE = 2, ACCESS_FULL = 3
// default: 3
'access_mode' => 3,
'allowed_login_types' => 'form|basic|external',
// apc_cache.enabled: If set, the APC cache is allowed (the PHP extension must also be active)
// default: true
'apc_cache.enabled' => true,
// apc_cache.query_ttl: Time to live set in APC for the prepared queries (seconds - 0 means no timeout)
// default: 3600
'apc_cache.query_ttl' => 3600,
// app_root_url: 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)
// default: ''
'app_root_url' => 'http://127.0.0.1/itop/svn/trunk/',
// buttons_position: Position of the forms buttons: bottom | top | both
// default: 'both'
'buttons_position' => 'both',
// cas_include_path: The path where to find the phpCAS library
// default: '/usr/share/php'
'cas_include_path' => '/usr/share/php',
// cron_max_execution_time: Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout
// default: 600
'cron_max_execution_time' => 600,
// csv_file_default_charset: Character set used by default for downloading and uploading data as a CSV file. Warning: it is case sensitive (uppercase is preferable).
// default: 'ISO-8859-1'
'csv_file_default_charset' => 'ISO-8859-1',
'csv_import_charsets' => array (
),
// csv_import_history_display: Display the history tab in the import wizard
// default: false
'csv_import_history_display' => false,
// date_and_time_format: Format for date and time display (per language)
// default: array (
// 'default' =>
// array (
// 'date' => 'Y-m-d',
// 'time' => 'H:i:s',
// 'date_time' => '$date $time',
// ),
// )
'date_and_time_format' => array (
'default' =>
array (
'date' => 'Y-m-d',
'time' => 'H:i:s',
'date_time' => '$date $time',
),
'FR FR' =>
array (
'date' => 'd/m/Y',
'time' => 'H:i:s',
'date_time' => '$date $time',
),
),
'db_host' => '',
'db_name' => 'itop_ci_main',
'db_pwd' => 'c8mb0do',
'db_subname' => '',
'db_user' => 'root',
// deadline_format: The format used for displaying "deadline" attributes: any string with the following placeholders: $date$, $difference$
// default: '$difference$'
'deadline_format' => '$difference$',
'default_language' => 'EN US',
// disable_attachments_download_legacy_portal: Disable attachments download from legacy portal
// default: true
'disable_attachments_download_legacy_portal' => true,
// draft_attachments_lifetime: Lifetime (in seconds) of drafts' attachments and inline images: after this duration, the garbage collector will delete them.
// default: 3600
'draft_attachments_lifetime' => 3600,
// email_asynchronous: If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode
// default: false
'email_asynchronous' => false,
// email_default_sender_address: Default address provided in the email from header field.
// default: ''
'email_default_sender_address' => '',
// email_default_sender_label: Default label provided in the email from header field.
// default: ''
'email_default_sender_label' => '',
// email_transport: Mean to send emails: PHPMail (uses the function mail()) or SMTP (implements the client protocole)
// default: 'PHPMail'
'email_transport' => 'SMTP',
// email_transport_smtp.host: host name or IP address (optional)
// default: 'localhost'
'email_transport_smtp.host' => 'smtp.combodo.com',
// email_transport_smtp.password: Authentication password (optional)
// default: ''
'email_transport_smtp.password' => '++combodo++',
// email_transport_smtp.port: port number (optional)
// default: 25
'email_transport_smtp.port' => 25,
// email_transport_smtp.username: Authentication user (optional)
// default: ''
'email_transport_smtp.username' => 'test2@combodo.com',
// email_validation_pattern: Regular expression to validate/detect the format of an eMail address
// default: '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}'
'email_validation_pattern' => '[a-zA-Z0-9._&\'-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z0-9-]{2,}',
'encryption_key' => '@iT0pEncr1pti0n!',
'ext_auth_variable' => '$_SERVER[\'REMOTE_USER\']',
'fast_reload_interval' => '60',
// graphviz_path: Path to the Graphviz "dot" executable for graphing objects lifecycle
// default: '/usr/bin/dot'
'graphviz_path' => '/usr/bin/dot',
// inline_image_max_display_width: The maximum width (in pixels) when displaying images inside an HTML formatted attribute. Images will be displayed using this this maximum width.
// default: '250'
'inline_image_max_display_width' => 250,
// inline_image_max_storage_width: The maximum width (in pixels) when uploading images to be used inside an HTML formatted attribute. Images larger than the given size will be downsampled before storing them in the database.
// default: '1600'
'inline_image_max_storage_width' => 1600,
// link_set_attribute_qualifier: Link set from string: attribute qualifier (encloses both the attcode and the value)
// default: '\''
'link_set_attribute_qualifier' => '\'',
// link_set_attribute_separator: Link set from string: attribute separator
// default: ';'
'link_set_attribute_separator' => ';',
// link_set_item_separator: Link set from string: line separator
// default: '|'
'link_set_item_separator' => '|',
// link_set_value_separator: Link set from string: value separator (between the attcode and the value itself
// default: ':'
'link_set_value_separator' => ':',
'log_global' => true,
'log_issue' => true,
'log_notification' => true,
'log_web_service' => true,
// max_combo_length: The maximum number of elements in a drop-down list. If more then an autocomplete will be used
// default: 50
'max_combo_length' => 50,
'max_display_limit' => '15',
// max_linkset_output: Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.
// default: 100
'max_linkset_output' => 100,
'min_display_limit' => '10',
// online_help: Hyperlink to the online-help web page
// default: 'http://www.combodo.com/itop-help'
'online_help' => 'http://www.combodo.com/itop-help',
// php_path: Path to the php executable in CLI mode
// default: 'php'
'php_path' => 'php',
// portal_tickets: CSV list of classes supported in the portal
// default: 'UserRequest'
'portal_tickets' => 'UserRequest',
'query_cache_enabled' => true,
// search_manual_submit: Force manual submit of search requests (class => true)
// default: false
'search_manual_submit' => array (
'Person' => true,
),
'secure_connection_required' => false,
// session_name: The name of the cookie used to store the PHP session id
// default: 'iTop'
'session_name' => 'iTop',
// shortcut_actions: Actions that are available as direct buttons next to the "Actions" menu
// default: 'UI:Menu:Modify,UI:Menu:New'
'shortcut_actions' => 'UI:Menu:Modify,UI:Menu:New',
// source_dir: Source directory for the datamodel files. (which gets compiled to env-production).
// default: ''
'source_dir' => 'datamodels/2.x/',
'standard_reload_interval' => '300',
// synchro_trace: Synchronization details: none, display, save (includes 'display')
// default: 'none'
'synchro_trace' => 'none',
// timezone: Timezone (reference: http://php.net/manual/en/timezones.php). If empty, it will be left unchanged and MUST be explicitely configured in PHP
// default: 'Europe/Paris'
'timezone' => 'Europe/Paris',
// tracking_level_linked_set_default: Default tracking level if not explicitely set at the attribute level, for AttributeLinkedSet (defaults to NONE in case of a fresh install, LIST otherwise - this to preserve backward compatibility while upgrading from a version older than 2.0.3 - see TRAC #936)
// default: 1
'tracking_level_linked_set_default' => 0,
// url_validation_pattern: Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)
// default: '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?'
'url_validation_pattern' => '(https?|ftp)\\://([a-zA-Z0-9+!*(),;?&=\\$_.-]+(\\:[a-zA-Z0-9+!*(),;?&=\\$_.-]+)?@)?([a-zA-Z0-9-.]{3,})(\\:[0-9]{2,5})?(/([a-zA-Z0-9%+\\$_-]\\.?)+)*/?(\\?[a-zA-Z+&\\$_.-][a-zA-Z0-9;:[\\]@&%=+/\\$_.-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+\\$_.-]*)?',
);
/**
*
* Modules specific settings
*
*/
$MyModuleSettings = array(
'itop-attachments' => array (
'allowed_classes' => array (
0 => 'Ticket',
),
'position' => 'relations',
'preview_max_width' => 290,
),
'itop-backup' => array (
'mysql_bindir' => '',
'week_days' => 'monday, tuesday, wednesday, thursday, friday',
'time' => '23:30',
'retention_count' => 5,
'enabled' => true,
'debug' => false,
),
'molkobain-console-tooltips' => array (
'decoration_class' => 'fas fa-question',
'enabled' => true,
),
);
/**
*
* Data model modules to be loaded. Names are specified as relative paths
*
*/
$MyModules = array(
'addons' => array (
'user rights' => 'addons/userrights/userrightsprofile.class.inc.php',
),
);
?>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<installation>
<mode>upgrade</mode>
<preinstall>
<copies type="array"/>
</preinstall>
<source_dir>datamodels/2.x/</source_dir>
<datamodel_version>2.5.0</datamodel_version>
<previous_configuration_file>/var/lib/jenkins/workspace/iTop-CI/unattended_install/default-config-itop.php</previous_configuration_file>
<extensions_dir>extensions</extensions_dir>
<target_env>production</target_env>
<workspace_dir></workspace_dir>
<database>
<server></server>
<user>root</user>
<pwd>c8mb0do</pwd>
<name>itop_ci</name>
<db_tls_enabled></db_tls_enabled>
<db_tls_ca></db_tls_ca>
<prefix></prefix>
</database>
<url>http://127.0.0.1/itop/svn/trunk/</url>
<graphviz_path>/usr/bin/dot</graphviz_path>
<admin_account>
<user>admin</user>
<pwd>admin</pwd>
<language>EN US</language>
</admin_account>
<language>EN US</language>
<selected_modules type="array">
<item>authent-external</item>
<item>authent-local</item>
<item>itop-backup</item>
<item>itop-config</item>
<item>itop-profiles-itil</item>
<item>itop-sla-computation</item>
<item>itop-tickets</item>
<item>itop-welcome-itil</item>
<item>itop-config-mgmt</item>
<item>itop-attachments</item>
<item>itop-datacenter-mgmt</item>
<item>itop-endusers-devices</item>
<item>itop-storage-mgmt</item>
<item>itop-virtualization-mgmt</item>
<item>itop-bridge-virtualization-storage</item>
<item>itop-service-mgmt</item>
<item>itop-request-mgmt</item>
<item>itop-portal</item>
<item>itop-portal-base</item>
<item>itop-change-mgmt</item>
</selected_modules>
<selected_extensions type="array">
<item>itop-config-mgmt-core</item>
<item>itop-config-mgmt-datacenter</item>
<item>itop-config-mgmt-end-user</item>
<item>itop-config-mgmt-storage</item>
<item>itop-config-mgmt-virtualization</item>
<item>itop-service-mgmt-enterprise</item>
<item>itop-ticket-mgmt-simple-ticket</item>
<item>itop-ticket-mgmt-simple-ticket-enhanced-portal</item>
<item>itop-change-mgmt-simple</item>
</selected_extensions>
<sample_data>1</sample_data>
<old_addon></old_addon>
<options>
<generate_config>1</generate_config>
</options>
<mysql_bindir></mysql_bindir>
</installation>

View File

@@ -0,0 +1,190 @@
<?php
//this scrit will be run under the ./toolkit directory, relatively to the document root
require_once('../approot.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/clipage.class.inc.php');
require_once(APPROOT.'/core/config.class.inc.php');
require_once(APPROOT.'/core/log.class.inc.php');
require_once(APPROOT.'/core/kpi.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
require_once(APPROOT.'/setup/setuppage.class.inc.php');
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
require_once(APPROOT.'/setup/applicationinstaller.class.inc.php');
/////////////////////////////////////////////////
$sParamFile = utils::ReadParam('response_file', 'default-params.xml', true /* CLI allowed */, 'raw_data');
$bCheckConsistency = (utils::ReadParam('check_consistency', '0', true /* CLI allowed */) == '1');
$oParams = new XMLParameters($sParamFile);
$sMode = $oParams->Get('mode');
if ($sMode == 'install')
{
echo "Installation mode detected.\n";
$bClean = utils::ReadParam('clean', false, true /* CLI allowed */);
if ($bClean)
{
echo "Cleanup mode detected.\n";
$sTargetEnvironment = $oParams->Get('target_env', '');
if ($sTargetEnvironment == '')
{
$sTargetEnvironment = 'production';
}
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
// Configuration file
$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE;
if (file_exists($sConfigFile))
{
echo "Trying to delete the configuration file: '$sConfigFile'.\n";
@chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others
unlink($sConfigFile);
}
else
{
echo "No config file to delete ($sConfigFile does not exist).\n";
}
// env-xxx directory
if (file_exists($sTargetDir))
{
if (is_dir($sTargetDir))
{
echo "Emptying the target directory '$sTargetDir'.\n";
SetupUtils::tidydir($sTargetDir);
}
else
{
die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n");
}
}
else
{
echo "No target directory to delete ($sTargetDir does not exist).\n";
}
// Database
$aDBSettings = $oParams->Get('database', array());
$sDBServer = $aDBSettings['server'];
$sDBUser = $aDBSettings['user'];
$sDBPwd = $aDBSettings['pwd'];
$sDBName = $aDBSettings['name'];
$sDBPrefix = $aDBSettings['prefix'];
if ($sDBPrefix != '')
{
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
}
$oMysqli = new mysqli($sDBServer, $sDBUser, $sDBPwd);
if ($oMysqli->connect_errno)
{
die("Cannot connect to the MySQL server (".$mysqli->connect_errno . ") ".$mysqli->connect_error."\nExiting");
}
else
{
if ($oMysqli->select_db($sDBName))
{
echo "Deleting database '$sDBName'\n";
$oMysqli->query("DROP DATABASE `$sDBName`");
}
else
{
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
}
}
}
}
$bHasErrors = false;
$aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should be the backup destination dir
$aSelectedModules = $oParams->Get('selected_modules');
$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest');
$sExtensionDir = $oParams->Get('extensions_dir', 'extensions');
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules));
foreach($aChecks as $oCheckResult)
{
switch($oCheckResult->iSeverity)
{
case CheckResult::ERROR:
$bHasErrors = true;
$sHeader = "Error";
break;
case CheckResult::WARNING:
$sHeader = "Warning";
break;
case CheckResult::INFO:
default:
$sHeader = "Info";
break;
}
echo $sHeader.": ".$oCheckResult->sLabel;
if (strlen($oCheckResult->sDescription))
{
echo ' - '.$oCheckResult->sDescription;
}
echo "\n";
}
if ($bHasErrors)
{
echo "Encountered stopper issues. Aborting...\n";
die;
}
$bFoundIssues = false;
$bInstall = utils::ReadParam('install', true, true /* CLI allowed */);
if ($bInstall)
{
echo "Starting the unattended installation...\n";
$oWizard = new ApplicationInstaller($oParams);
$bRes = $oWizard->ExecuteAllSteps();
if (!$bRes)
{
echo "\nencountered installation issues!";
$bFoundIssues = true;
}
}
else
{
echo "No installation requested.\n";
}
if (!$bFoundIssues && $bCheckConsistency)
{
echo "Checking data model consistency.\n";
ob_start();
$sCheckRes = '';
try
{
MetaModel::CheckDefinitions(false);
$sCheckRes = ob_get_clean();
}
catch(Exception $e)
{
$sCheckRes = ob_get_clean()."\nException: ".$e->getMessage();
}
if (strlen($sCheckRes) > 0)
{
echo $sCheckRes;
echo "\nfound consistency issues!";
$bFoundIssues = true;
}
}
if (!$bFoundIssues)
{
// last line: used to check the install
// the only way to track issues in case of Fatal error or even parsing error!
echo "\ninstalled!";
exit;
}

62
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,62 @@
pipeline {
agent any
stages {
stage('init') {
parallel {
stage('debug') {
steps {
sh './.jenkins/bin/init/debug.sh'
}
}
stage('append files to project') {
steps {
sh './.jenkins/bin/init/append_files.sh'
}
}
stage('composer install') {
steps {
sh './.jenkins/bin/init/composer_install.sh'
}
}
}
}
stage('unattended_install') {
parallel {
stage('unattended_install default env') {
steps {
sh './.jenkins/bin/unattended_install/default_env.sh'
}
}
}
}
stage('test') {
parallel {
stage('phpunit') {
steps {
sh './.jenkins/bin/tests/phpunit.sh'
}
}
}
}
}
post {
always {
junit 'var/test/phpunit-log.junit.xml'
}
failure {
slackSend(channel: "#jenkins-itop", color: '#FF0000', message: "Ho no! Build failed! (${currentBuild.result}), Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
}
}
environment {
DEBUG_UNIT_TEST = '0'
}
options {
timeout(time: 20, unit: 'MINUTES')
}
}

View File

@@ -75,8 +75,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
MetaModel::Init_SetZListItems('standard_search', array('name','description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array ('name','description'));
}
protected static $m_aCacheProfiles = null;
@@ -200,6 +200,12 @@ class URP_Profiles extends UserRightsBaseClassGUI
// preserve DB integrity by deleting links to users
protected function OnDelete()
{
// Don't remove admin profile
if ($this->Get('name') === ADMIN_PROFILE_NAME)
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin'));
}
// Note: this may break the rule that says: "a user must have at least ONE profile" !
$oLnkSet = $this->Get('user_list');
while($oLnk = $oLnkSet->Fetch())
@@ -297,13 +303,38 @@ class URP_UserProfile extends UserRightsBaseClassGUI
$this->CheckIfProfileIsAllowed(UR_ACTION_DELETE);
}
/**
* @param $iActionCode
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \SecurityException
*/
protected function CheckIfProfileIsAllowed($iActionCode)
{
// When initializing or admin, we need to let everything pass trough
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
// Only administrators can manage administrators
$iOrigUserId = $this->GetOriginal('userid');
if (!empty($iOrigUserId))
{
$oUser = MetaModel::GetObject('User', $iOrigUserId, true, true);
if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator())
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted'));
}
}
$oUser = MetaModel::GetObject('User', $this->Get('userid'), true, true);
if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator())
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted'));
}
if (!UserRights::IsActionAllowed(get_class($this), $iActionCode, DBObjectSet::FromObject($this)))
{
throw new SecurityException(Dict::Format('UI:Error:ObjectCannotBeUpdated'));
}
if (UserRights::IsLoggedIn() && !UserRights::IsAdministrator() && ($this->Get('profile') === ADMIN_PROFILE_NAME))
if (!UserRights::IsAdministrator() && ($this->Get('profile') === ADMIN_PROFILE_NAME))
{
throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin'));
}
@@ -349,6 +380,42 @@ class URP_UserOrg extends UserRightsBaseClassGUI
{
return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name'));
}
protected function OnInsert()
{
$this->CheckIfOrgIsAllowed();
}
protected function OnUpdate()
{
$this->CheckIfOrgIsAllowed();
}
protected function OnDelete()
{
$this->CheckIfOrgIsAllowed();
}
/**
* @throws \CoreException
*/
protected function CheckIfOrgIsAllowed()
{
if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; }
$oUser = UserRights::GetUserObject();
$oAddon = UserRights::GetModuleInstance();
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
if (count($aOrgs) > 0)
{
$iOrigOrgId = $this->GetOriginal('allowed_org_id');
if ((!empty($iOrigOrgId) && !in_array($iOrigOrgId, $aOrgs)) || !in_array($this->Get('allowed_org_id'), $aOrgs))
{
throw new SecurityException(Dict::Format('Class:User/Error:OrganizationNotAllowed'));
}
}
}
}

View File

@@ -97,8 +97,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array ('name', 'description'));
}
protected $m_bCheckReservedNames = true;

View File

@@ -78,8 +78,8 @@ class URP_Profiles extends UserRightsBaseClass
MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array ('name', 'description'));
}
function GetGrantAsHtml($oUserRights, $sClass, $sAction)

View File

@@ -244,9 +244,18 @@ EOF
//echo $this->s_deferred_content;
if (count($this->a_scripts) > 0)
{
echo "<script type=\"text/javascript\">\n";
echo implode("\n", $this->a_scripts);
echo "\n</script>\n";
echo "<script type=\"text/javascript\">\n";
echo implode("\n", $this->a_scripts);
echo "\n</script>\n";
}
if (count($this->a_linked_scripts) > 0)
{
echo "<script type=\"text/javascript\">\n";
foreach($this->a_linked_scripts as $sScriptUrl)
{
echo '$.getScript('.json_encode($sScriptUrl).");\n";
}
echo "\n</script>\n";
}
if (!empty($this->s_deferred_content))
{

View File

@@ -118,7 +118,7 @@ class ApplicationContext
$oSearchFilter = new DBObjectSearch('Organization');
$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
$oSet = new CMDBObjectSet($oSearchFilter);
$iCount = $oSet->Count();
$iCount = $oSet->CountWithLimit(2);
if ($iCount == 1)
{
// Only one possible value for org_id, set it in the context

View File

@@ -53,8 +53,8 @@ class AuditCategory extends cmdbAbstractObject
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'rules_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'definition_set')); // Criteria of the advanced search form
MetaModel::Init_SetZListItems('standard_search', array('description', 'definition_set')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
}
}
?>

View File

@@ -1757,6 +1757,27 @@ EOF
$sConfigJS = json_encode($aConfig);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
$oPage->add_ready_script(
<<<EOF
$('#$iId').bind('update', function(evt){
BlockField('cke_$iId', $('#$iId').attr('disabled'));
//Delayed execution - ckeditor must be properly initialized before setting readonly
var retryCount = 0;
var oMe = $('#$iId');
var delayedSetReadOnly = function () {
if (oMe.data('ckeditorInstance').editable() == undefined && retryCount++ < 10) {
setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration
}
else
{
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
}
};
setTimeout(delayedSetReadOnly, 50);
});
EOF
);
break;
case 'HTML':
@@ -1768,11 +1789,11 @@ EOF
case 'LinkedSet':
if ($oAttDef->IsIndirect())
{
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed(), $aArgs);
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed());
}
else
{
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix, $aArgs);
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix);
}
$aEventsList[] ='validate';
$aEventsList[] ='change';
@@ -2045,7 +2066,7 @@ EOF
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
if ($LockEnabled)
{
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data');
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data');
if ($sOwnershipToken !== null)
{
// We're probably inside something like "apply_modify" where the validation failed and we must prompt the user again to edit the object
@@ -2357,8 +2378,9 @@ EOF
{
if ($oAttDef->IsExternalKey())
{
/** @var DBObjectSet $oAllowedValues */
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs);
if ($oAllowedValues->Count() == 1)
if ($oAllowedValues->CountWithLimit(2) == 1)
{
$oRemoteObj = $oAllowedValues->Fetch();
$oObj->Set($sAttCode, $oRemoteObj->GetKey());
@@ -2367,7 +2389,7 @@ EOF
else
{
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (count($aAllowedValues) == 1)
if (is_array($aAllowedValues) && (count($aAllowedValues) == 1))
{
$aValues = array_keys($aAllowedValues);
$oObj->Set($sAttCode, $aValues[0]);
@@ -2379,7 +2401,7 @@ EOF
return $oObj->DisplayModifyForm( $oPage, $aExtraParams);
}
public function DisplayStimulusForm(WebPage $oPage, $sStimulus)
public function DisplayStimulusForm(WebPage $oPage, $sStimulus, $aPrefillFormParam = null)
{
$sClass = get_class($this);
$iKey = $this->GetKey();
@@ -2395,7 +2417,7 @@ EOF
$sOwnershipToken = null;
if ($LockEnabled)
{
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, false, 'raw_data');
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data');
$aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey);
if ($aLockInfo['success'])
{
@@ -2421,6 +2443,12 @@ EOF
$oPage->add("<h1>$sActionDetails</h1>\n");
$sTargetState = $aTransitions[$sStimulus]['target_state'];
$aExpectedAttributes = $this->GetTransitionAttributes($sStimulus /*, current state*/);
if ($aPrefillFormParam != null)
{
$aPrefillFormParam['expected_attributes'] = $aExpectedAttributes;
$this->PrefillForm('state_change', $aPrefillFormParam);
$aExpectedAttributes = $aPrefillFormParam['expected_attributes'];
}
$sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position');
if ($sButtonsPosition == 'bottom')
{
@@ -2430,7 +2458,7 @@ EOF
$oPage->add('</div>');
}
$oPage->add("<div class=\"wizContainer\">\n");
$oPage->add("<form id=\"apply_stimulus\" method=\"post\" onSubmit=\"return OnSubmit('apply_stimulus');\">\n");
$oPage->add("<form id=\"apply_stimulus\" method=\"post\" enctype=\"multipart/form-data\" onSubmit=\"return OnSubmit('apply_stimulus');\">\n");
$aDetails = array();
$iFieldIndex = 0;
$aFieldsMap = array();
@@ -2476,8 +2504,9 @@ EOF
{
if ($oAttDef->IsExternalKey())
{
/** @var DBObjectSet $oAllowedValues */
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', $this->Get($sAttCode));
if ($oAllowedValues->Count() == 1)
if ($oAllowedValues->CountWithLimit(2) == 1)
{
$oRemoteObj = $oAllowedValues->Fetch();
$this->Set($sAttCode, $oRemoteObj->GetKey());
@@ -2486,7 +2515,7 @@ EOF
else
{
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (count($aAllowedValues) == 1)
if (is_array($aAllowedValues) && count($aAllowedValues) == 1)
{
$aValues = array_keys($aAllowedValues);
$this->Set($sAttCode, $aValues[0]);
@@ -3461,7 +3490,7 @@ EOF
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
if (count($aNewIssues) > 0)
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
}
@@ -3503,7 +3532,7 @@ EOF
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
if (count($aNewIssues) > 0)
if (is_array($aNewIssues) && count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
}
@@ -3681,7 +3710,7 @@ EOF
$currValue = $oObj->Get($sAttCode);
if ($oAttDef instanceof AttributeCaseLog)
{
$currValue = ' '; // Don't put an empty string, in case the field would be considered as mandatory...
$currValue = ''; // Put a single scalar value to force caselog to mock a new entry. For more info see N°1059.
}
if (is_object($currValue)) continue; // Skip non scalar values...
if(!array_key_exists($currValue, $aValues[$sAttCode]))
@@ -3883,7 +3912,7 @@ EOF
$bResult = (count($aErrors) == 0);
if ($bResult)
{
list($bResult, $aErrors) = $oObj->CheckToWrite(true /* Enforce Read-only fields */);
list($bResult, $aErrors) = $oObj->CheckToWrite();
}
if ($bPreview)
{

View File

@@ -148,14 +148,12 @@ abstract class Dashboard
protected function InitDashletFromDOMNode($oDomNode)
{
$sId = $oDomNode->getAttribute('id');
$sClass = $oDomNode->getAttribute('xsi:type');
$sDashletType = $oDomNode->getAttribute('xsi:type');
// Test if dashlet can be instanciated, otherwise (uninstalled, broken, ...) we display a placeholder
if(!class_exists($sClass))
{
$sClass = 'DashletUnknown';
}
$sClass = static::GetDashletClassFromType($sDashletType);
$oNewDashlet = new $sClass($this->oMetaModel, $sId);
$oNewDashlet->SetDashletType($sDashletType);
$oNewDashlet->FromDOMNode($oDomNode);
return $oNewDashlet;
@@ -234,7 +232,7 @@ abstract class Dashboard
$oNode = $oDoc->createElement('dashlet');
$oDashletsNode->appendChild($oNode);
$oNode->setAttribute('id', $oDashlet->GetID());
$oNode->setAttribute('xsi:type', get_class($oDashlet));
$oNode->setAttribute('xsi:type', $oDashlet->GetDashletType());
$oDashletRank = $oDoc->createElement('rank', $iDashletRank);
$oNode->appendChild($oDashletRank);
$iDashletRank++;
@@ -259,7 +257,10 @@ abstract class Dashboard
$sDashletClass = $aDashletParams['dashlet_class'];
$sId = $aDashletParams['dashlet_id'];
$oNewDashlet = new $sDashletClass($this->oMetaModel, $sId);
if (isset($aDashletParams['dashlet_type']))
{
$oNewDashlet->SetDashletType($aDashletParams['dashlet_type']);
}
$oForm = $oNewDashlet->GetForm();
$oForm->SetParamsContainer($sId);
$oForm->SetPrefix('');
@@ -513,6 +514,15 @@ EOF
}
abstract protected function SetFormParams($oForm);
public static function GetDashletClassFromType($sType, $oFactory = null)
{
if (is_subclass_of($sType, 'Dashlet'))
{
return $sType;
}
return 'DashletUnknown';
}
}
class RuntimeDashboard extends Dashboard

View File

@@ -32,6 +32,7 @@ abstract class Dashlet
protected $bFormRedrawNeeded;
protected $aProperties; // array of {property => value}
protected $aCSSClasses;
protected $sDashletType;
public function __construct(ModelReflection $oModelReflection, $sId)
{
@@ -41,6 +42,7 @@ abstract class Dashlet
$this->bFormRedrawNeeded = false; // By default: no need to redraw the form (independent fields)
$this->aProperties = array(); // By default: there is no property
$this->aCSSClasses = array('dashlet');
$this->sDashletType = get_class($this);
}
// Assuming that a property has the type of its default value, set in the constructor
@@ -132,7 +134,14 @@ abstract class Dashlet
public function FromXml($sXml)
{
$oDomDoc = new DOMDocument('1.0', 'UTF-8');
libxml_clear_errors();
$oDomDoc->loadXml($sXml);
$aErrors = libxml_get_errors();
if (count($aErrors) > 0)
{
throw new DOMException("Malformed XML");
}
$this->FromDOMNode($oDomDoc->firstChild);
}
@@ -214,9 +223,10 @@ abstract class Dashlet
if ($bEditMode)
{
$sClass = get_class($this);
$sType = $this->sDashletType;
$oPage->add_ready_script(
<<<EOF
$('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass'});
$('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass', 'dashlet_type': '$sType'});
EOF
);
}
@@ -280,7 +290,7 @@ EOF
);
}
public function GetForm()
public function GetForm($aInfo = array())
{
$oForm = new DesignerForm();
$sPrefix = "dashlet_".$this->GetID();
@@ -293,6 +303,9 @@ EOF
$oDashletClassField = new DesignerHiddenField('dashlet_class', '', get_class($this));
$oForm->AddField($oDashletClassField);
$oDashletTypeField = new DesignerHiddenField('dashlet_type', '', $this->sDashletType);
$oForm->AddField($oDashletTypeField);
$oDashletIdField = new DesignerHiddenField('dashlet_id', '', $this->GetID());
$oForm->AddField($oDashletIdField);
@@ -313,6 +326,84 @@ EOF
{
// Default: do nothing since it's not supported
}
protected function GetGroupByOptions($sOql)
{
$aGroupBy = array();
try
{
$oQuery = $this->oModelReflection->GetQuery($sOql);
$sClass = $oQuery->GetClass();
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
{
if ($sAttType == 'AttributeLinkedSet')
{
continue;
}
if (is_subclass_of($sAttType, 'AttributeLinkedSet'))
{
continue;
}
if ($sAttType == 'AttributeFriendlyName')
{
continue;
}
if (is_subclass_of($sAttType, 'AttributeFriendlyName'))
{
continue;
}
if ($sAttType == 'AttributeExternalField')
{
continue;
}
if (is_subclass_of($sAttType, 'AttributeExternalField'))
{
continue;
}
if ($sAttType == 'AttributeOneWayPassword')
{
continue;
}
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aGroupBy[$sAttCode] = $sLabel;
if (is_subclass_of($sAttType, 'AttributeDateTime') || $sAttType == 'AttributeDateTime')
{
$aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
$aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month',
$sLabel);
$aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek',
$sLabel);
$aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth',
$sLabel);
}
}
asort($aGroupBy);
}
catch(Exception $e)
{
// Bad OQL is ignored
}
return $aGroupBy;
}
/**
* @return string
*/
public function GetDashletType()
{
return $this->sDashletType;
}
/**
* @param string $sDashletType
*/
public function SetDashletType($sDashletType)
{
$this->sDashletType = $sDashletType;
}
}
/**
@@ -326,39 +417,20 @@ class DashletUnknown extends Dashlet
{
static protected $aClassList = null;
protected $sOriginalDashletClass;
protected $sOriginalDashletXML;
public function __construct($oModelReflection, $sId)
{
parent::__construct($oModelReflection, $sId);
$this->sOriginalDashletClass = 'Unknown';
$this->sOriginalDashletXML = '';
$this->aCSSClasses[] = 'dashlet-unknown';
}
public function GetOriginalDashletClass()
{
return $this->sOriginalDashletClass;
}
public function SetOriginalDashletClass($sOriginalDashletClass)
{
$this->sOriginalDashletClass = $sOriginalDashletClass;
}
public function FromDOMNode($oDOMNode)
{
// Parent won't do anything as there is no property declared
parent::FromDOMNode($oDOMNode);
// Original dashlet
// - Class
if($oDOMNode->hasAttribute('xsi:type'))
{
$this->sOriginalDashletClass = $oDOMNode->getAttribute('xsi:type');
}
// Build properties from XML
$this->sOriginalDashletXML = "";
foreach($oDOMNode->childNodes as $oDOMChildNode)
@@ -383,13 +455,35 @@ class DashletUnknown extends Dashlet
$this->OnUpdate();
}
/**
* @param $oDOMNode
*
* @throws \DOMFormatException
*/
public function ToDOMNode($oDOMNode)
{
$oDoc = new DOMDocument();
libxml_clear_errors();
$oDoc->loadXML('<root>'.$this->sOriginalDashletXML.'</root>');
$aErrors = libxml_get_errors();
if (count($aErrors) > 0)
{
throw new DOMFormatException('Dashlet definition not correctly formatted!');
}
foreach($oDoc->documentElement->childNodes as $oDOMChildNode)
{
$oPropNode = $oDOMNode->ownerDocument->importNode($oDOMChildNode, true);
$oDOMNode->appendChild($oPropNode);
}
}
public function FromParams($aParams)
{
// For unknown dashlet, parameters are not parsed but passed as a raw xml
if(array_key_exists('xml', $aParams))
{
// A namspace must be present for the "xsi:type" attribute, otherwise a warning will be thrown.
$sXML = '<dashlet id="'.$aParams['dashlet_id'].'" xsi:type="'.$aParams['dashlet_class'].'" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$aParams['xml'].'</dashlet>';
// A namespace must be present for the "xsi:type" attribute, otherwise a warning will be thrown.
$sXML = '<dashlet id="'.$aParams['dashlet_id'].'" xsi:type="'.$aParams['dashlet_type'].'" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$aParams['xml'].'</dashlet>';
$this->FromXml($sXML);
}
$this->OnUpdate();
@@ -400,7 +494,7 @@ class DashletUnknown extends Dashlet
$aInfos = static::GetInfo();
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
$sExplainText = ($bEditMode) ? Dict::Format('UI:DashletUnknown:RenderText:Edit', $this->sOriginalDashletClass) : Dict::S('UI:DashletUnknown:RenderText:View');
$sExplainText = ($bEditMode) ? Dict::Format('UI:DashletUnknown:RenderText:Edit', $this->GetDashletType()) : Dict::S('UI:DashletUnknown:RenderText:View');
$oPage->add('<div class="dashlet-content">');
@@ -415,7 +509,7 @@ class DashletUnknown extends Dashlet
$aInfos = static::GetInfo();
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
$sExplainText = Dict::Format('UI:DashletUnknown:RenderNoDataText:Edit', $this->sOriginalDashletClass);
$sExplainText = Dict::Format('UI:DashletUnknown:RenderNoDataText:Edit', $this->GetDashletType());
$oPage->add('<div class="dashlet-content">');
@@ -425,6 +519,15 @@ class DashletUnknown extends Dashlet
$oPage->add('</div>');
}
public function GetForm($aInfo = array())
{
if (isset($aInfo['configuration']) && empty($this->sOriginalDashletXML))
{
$this->sOriginalDashletXML = $aInfo['configuration'];
}
return parent::GetForm($aInfo);
}
public function GetPropertiesFields(DesignerForm $oForm)
{
$oField = new DesignerLongTextField('xml', Dict::S('UI:DashletUnknown:Prop-XMLConfiguration'), $this->sOriginalDashletXML);
@@ -446,7 +549,7 @@ class DashletUnknown extends Dashlet
if($bHasSubProperties)
{
$sTmp = $oDOMNode->ownerDocument->saveXML($oDOMNode, LIBXML_NOENT);
$sTmp = trim(preg_replace("/(<".$oDOMNode->tagName.".*>|<\/".$oDOMNode->tagName.">)/", "", $sTmp));
$sTmp = trim(preg_replace("/(<".$oDOMNode->tagName."[^>]*>|<\/".$oDOMNode->tagName.">)/", "", $sTmp));
return $sTmp;
}
else
@@ -490,9 +593,6 @@ class DashletUnknown extends Dashlet
class DashletProxy extends DashletUnknown
{
protected $sOriginalDashletClass;
protected $sOriginalDashletXML;
public function __construct($oModelReflection, $sId)
{
parent::__construct($oModelReflection, $sId);
@@ -503,21 +603,30 @@ class DashletProxy extends DashletUnknown
unset($this->aCSSClasses[$key]);
}
$this->sOriginalDashletClass = 'Proxy';
$this->sOriginalDashletXML = '';
$this->aCSSClasses[] = 'dashlet-proxy';
}
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
// This should never be called.
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div>This dashlet is not supposed to be rendered as it is just a proxy for third-party widgets.</div>');
$oPage->add('</div>');
}
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
// TODO
$oPage->add('<div>RENDER NO DATA TO DO! (PREVIEW OR SO)</div>');
$aInfos = static::GetInfo();
$sIconUrl = utils::GetAbsoluteUrlAppRoot().$aInfos['icon'];
$sExplainText = Dict::Format('UI:DashletProxy:RenderNoDataText:Edit', $this->GetDashletType());
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="dashlet-pxy-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-pxy-text">'.$sExplainText.'</div>');
$oPage->add('</div>');
}
static public function GetInfo()
@@ -702,19 +811,21 @@ abstract class DashletGroupBy extends Dashlet
$this->aProperties['query'] = 'SELECT Contact';
$this->aProperties['group_by'] = 'status';
$this->aProperties['style'] = 'table';
$this->aProperties['agregation_function'] = 'count';
$this->aProperties['agregation_attribute'] = '';
$this->aProperties['aggregation_function'] = 'count';
$this->aProperties['aggregation_attribute'] = '';
$this->aProperties['limit'] = '';
$this->aProperties['order_direction'] = 'desc';
$this->aProperties['order_by'] = '';
$this->aProperties['order_direction'] = '';
}
protected $sGroupByLabel = null;
protected $sGroupByExpr = null;
protected $sGroupByAttCode = null;
protected $sFunction = null;
protected $sAgregationFunction = null;
protected $sAgregationAttribute = null;
protected $sAggregationFunction = null;
protected $sAggregationAttribute = null;
protected $sLimit = null;
protected $sOrderBy = null;
protected $sOrderDirection = null;
protected $sClass = null;
@@ -732,10 +843,22 @@ abstract class DashletGroupBy extends Dashlet
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$this->sAgregationFunction = $this->aProperties['agregation_function'];
$this->sAgregationAttribute = $this->aProperties['agregation_attribute'];
$this->sAggregationFunction = $this->aProperties['aggregation_function'];
$this->sAggregationAttribute = $this->aProperties['aggregation_attribute'];
$this->sLimit = $this->aProperties['limit'];
$this->sOrderDirection = $this->aProperties['order_direction'];
$this->sOrderBy = $this->aProperties['order_by'];
if (empty($this->sOrderBy))
{
if ($this->aProperties['style'] == 'pie')
{
$this->sOrderBy = 'function';
}
else
{
$this->sOrderBy = 'attribute';
}
}
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
try
@@ -750,6 +873,7 @@ abstract class DashletGroupBy extends Dashlet
$this->sClass = null;
$sClassAlias = '';
}
// Check groupby... it can be wrong at this stage
if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
{
@@ -761,6 +885,28 @@ abstract class DashletGroupBy extends Dashlet
$this->sGroupByAttCode = $sGroupBy;
$this->sFunction = null;
}
if (empty($this->aProperties['order_direction']))
{
$aAttributeTypes = $this->oModelReflection->ListAttributes($this->sClass);
if (isset($aAttributeTypes[$this->sGroupByAttCode]))
{
$sAttributeType = $aAttributeTypes[$this->sGroupByAttCode];
if (is_subclass_of($sAttributeType, 'AttributeDateTime') || $sAttributeType == 'AttributeDateTime')
{
$this->sOrderDirection = 'asc';
}
else
{
$this->sOrderDirection = 'desc';
}
}
}
else
{
$this->sOrderDirection = $this->aProperties['order_direction'];
}
if ((!is_null($this->sClass)) && $this->oModelReflection->IsValidAttCode($this->sClass, $this->sGroupByAttCode))
{
$sAttLabel = $this->oModelReflection->GetLabel($this->sClass, $this->sGroupByAttCode);
@@ -832,10 +978,11 @@ abstract class DashletGroupBy extends Dashlet
'chart_title' => $sTitle,
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'agregation_function' => $this->sAgregationFunction,
'agregation_attribute' => $this->sAgregationAttribute,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
$sHtmlTitle = ''; // done in the itop block
break;
@@ -847,10 +994,11 @@ abstract class DashletGroupBy extends Dashlet
'chart_title' => $sTitle,
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'agregation_function' => $this->sAgregationFunction,
'agregation_attribute' => $this->sAgregationAttribute,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
$sHtmlTitle = ''; // done in the itop block
break;
@@ -862,10 +1010,11 @@ abstract class DashletGroupBy extends Dashlet
$aExtraParams = array(
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'agregation_function' => $this->sAgregationFunction,
'agregation_attribute' => $this->sAgregationAttribute,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
break;
}
@@ -885,7 +1034,6 @@ abstract class DashletGroupBy extends Dashlet
protected function MakeSimulatedData()
{
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
@@ -962,36 +1110,6 @@ abstract class DashletGroupBy extends Dashlet
$oPage->add('</div>');
}
protected function GetGroupByOptions($sOql)
{
$oQuery = $this->oModelReflection->GetQuery($sOql);
$sClass = $oQuery->GetClass();
$aGroupBy = array();
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
{
if ($sAttType == 'AttributeLinkedSet') continue;
if (is_subclass_of($sAttType, 'AttributeLinkedSet')) continue;
if ($sAttType == 'AttributeFriendlyName') continue;
if (is_subclass_of($sAttType, 'AttributeFriendlyName')) continue;
if ($sAttType == 'AttributeExternalField') continue;
if (is_subclass_of($sAttType, 'AttributeExternalField')) continue;
if ($sAttType == 'AttributeOneWayPassword') continue;
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aGroupBy[$sAttCode] = $sLabel;
if (is_subclass_of($sAttType, 'AttributeDateTime') || $sAttType == 'AttributeDateTime')
{
$aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
$aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel);
$aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel);
$aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth', $sLabel);
}
}
asort($aGroupBy);
return $aGroupBy;
}
/**
* @param DesignerForm $oForm
* @throws DictExceptionMissingString
@@ -1018,6 +1136,7 @@ abstract class DashletGroupBy extends Dashlet
{
$oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']);
$oField->SetReadOnly();
$aGroupBy = array();
}
$oForm->AddField($oField);
@@ -1034,7 +1153,7 @@ abstract class DashletGroupBy extends Dashlet
$aFunctionAttributes = $this->GetNumericAttributes($this->aProperties['query']);
$aFunctions = $this->GetAllowedFunctions($aFunctionAttributes);
$oSelectorField = new DesignerFormSelectorField('agregation_function', Dict::S('UI:DashletGroupBy:Prop-Function'), $this->aProperties['agregation_function']);
$oSelectorField = new DesignerFormSelectorField('aggregation_function', Dict::S('UI:DashletGroupBy:Prop-Function'), $this->aProperties['aggregation_function']);
$oForm->AddField($oSelectorField);
$oSelectorField->SetMandatory();
// Count sub-menu
@@ -1043,25 +1162,52 @@ abstract class DashletGroupBy extends Dashlet
foreach($aFunctions as $sFct => $sLabel)
{
$oSubForm = new DesignerForm();
$oField = new DesignerComboField('agregation_attribute', Dict::S('UI:DashletGroupBy:Prop-FunctionAttribute'), $this->aProperties['agregation_attribute']);
$oField = new DesignerComboField('aggregation_attribute', Dict::S('UI:DashletGroupBy:Prop-FunctionAttribute'), $this->aProperties['aggregation_attribute']);
$oField->SetMandatory();
$oField->SetAllowedValues($aFunctionAttributes);
$oSubForm->AddField($oField);
$oSelectorField->AddSubForm($oSubForm, $sLabel, $sFct);
}
$aOrderField = array();
if (isset($this->aProperties['group_by']) && isset($aGroupBy[$this->aProperties['group_by']]))
{
$aOrderField['attribute'] = $aGroupBy[$this->aProperties['group_by']];
}
if ($this->aProperties['aggregation_function'] == 'count')
{
$aOrderField['function'] = Dict::S('UI:GroupBy:count');
}
else
{
$aOrderField['function'] = $aFunctions[$this->aProperties['aggregation_function']];
}
$oSelectorField = new DesignerFormSelectorField('order_by', Dict::S('UI:DashletGroupBy:Prop-OrderField'), $this->aProperties['order_by']);
$oForm->AddField($oSelectorField);
$oSelectorField->SetMandatory();
foreach($aOrderField as $sField => $sLabel)
{
$oSubForm = new DesignerForm();
if ($sField == 'function')
{
$oField = new DesignerIntegerField('limit', Dict::S('UI:DashletGroupBy:Prop-Limit'), $this->aProperties['limit']);
$oSubForm->AddField($oField);
}
$oSelectorField->AddSubForm($oSubForm, $sLabel, $sField);
}
$aOrderDirections = array(
'asc' => Dict::S('UI:DashletGroupBy:Order:asc'),
'desc' => Dict::S('UI:DashletGroupBy:Order:desc'),
);
$oField = new DesignerComboField('order_direction', Dict::S('UI:DashletGroupBy:Prop-OrderDirection'), $this->aProperties['order_direction']);
$sOrderDirection = empty($this->aProperties['order_direction']) ? $this->sOrderDirection : $this->aProperties['order_direction'];
$oField = new DesignerComboField('order_direction', Dict::S('UI:DashletGroupBy:Prop-OrderDirection'), $sOrderDirection);
$oField->SetMandatory();
$oField->SetAllowedValues($aOrderDirections);
$oForm->AddField($oField);
$oField = new DesignerIntegerField('limit', Dict::S('UI:DashletGroupBy:Prop-Limit'), $this->aProperties['limit']);
$oForm->AddField($oField);
}
/**
@@ -1076,7 +1222,7 @@ abstract class DashletGroupBy extends Dashlet
}
return array(
$this->aProperties['group_by'] => $this->oModelReflection->GetLabel($this->sClass, $this->aProperties['group_by']),
'_itop_'.$this->aProperties['agregation_function'].'_' => Dict::S('UI:GroupBy:'.$this->aProperties['agregation_function']));
'_itop_'.$this->aProperties['aggregation_function'].'_' => Dict::S('UI:GroupBy:'.$this->aProperties['aggregation_function']));
}
/**
@@ -1104,24 +1250,32 @@ abstract class DashletGroupBy extends Dashlet
protected function GetNumericAttributes($sOql)
{
$aFunctionAttributes = array();
$oQuery = $this->oModelReflection->GetQuery($sOql);
$sClass = $oQuery->GetClass();
if (is_null($sClass))
try
{
return $aFunctionAttributes;
}
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
{
switch ($sAttType)
$oQuery = $this->oModelReflection->GetQuery($sOql);
$sClass = $oQuery->GetClass();
if (is_null($sClass))
{
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeInteger':
case 'AttributePercentage':
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aFunctionAttributes[$sAttCode] = $sLabel;
break;
return $aFunctionAttributes;
}
foreach($this->oModelReflection->ListAttributes($sClass) as $sAttCode => $sAttType)
{
switch ($sAttType)
{
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeInteger':
case 'AttributePercentage':
case 'AttributeSubItem': // TODO: Known limitation: no unit displayed (values in sec)
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aFunctionAttributes[$sAttCode] = $sLabel;
break;
}
}
}
catch (Exception $e)
{
// Ignore bad OQL
}
return $aFunctionAttributes;
@@ -1176,11 +1330,11 @@ abstract class DashletGroupBy extends Dashlet
$oDashlet->bRedrawNeeded = true;
$oDashlet->bFormRedrawNeeded = true;
}
if (in_array('group_by', $aUpdatedFields) || in_array('agregation_attribute', $aUpdatedFields) || in_array('order_direction', $aUpdatedFields) || in_array('limit', $aUpdatedFields))
if (in_array('aggregation_attribute', $aUpdatedFields) || in_array('order_direction', $aUpdatedFields) || in_array('order_by', $aUpdatedFields) || in_array('limit', $aUpdatedFields))
{
$oDashlet->bRedrawNeeded = true;
}
if (in_array('agregation_function', $aUpdatedFields))
if (in_array('group_by', $aUpdatedFields) || in_array('aggregation_function', $aUpdatedFields))
{
$oDashlet->bRedrawNeeded = true;
$oDashlet->bFormRedrawNeeded = true;
@@ -1332,7 +1486,6 @@ class DashletGroupByBars extends DashletGroupBy
$sJSNames = json_encode($aNames);
$sJson = json_encode($aDisplayValues);
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
$oPage->add_ready_script(
<<<EOF
window.setTimeout(function() {
@@ -1404,7 +1557,6 @@ class DashletGroupByTable extends DashletGroupBy
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sTitle = $this->aProperties['title'];
$aDisplayValues = $this->MakeSimulatedData();
$iTotal = 0;
@@ -1614,7 +1766,6 @@ class DashletHeaderDynamic extends Dashlet
$sSubtitle = $this->aProperties['subtitle'];
$sQuery = $this->aProperties['query'];
$sGroupBy = $this->aProperties['group_by'];
$aValues = $this->aProperties['values'];
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
@@ -1631,14 +1782,6 @@ class DashletHeaderDynamic extends Dashlet
$iTotal = 0;
$aValues = $this->GetValues();
if (count($aValues) > 0)
{
// Stats grouped by <group_by>
}
else
{
// Simple stats
}
$oPage->add('<div class="display_block" id="'.$sBlockId.'">');
$oPage->add('<div class="summary-details">');
@@ -1663,7 +1806,6 @@ class DashletHeaderDynamic extends Dashlet
$sTitle = $this->oModelReflection->DictString($sTitle);
$sSubtitle = $this->oModelReflection->DictFormat($sSubtitle, $iTotal);
// $sSubtitle = "original: $sSubtitle, S:".$this->oModelReflection->DictString($sSubtitle).", Format: '".$this->oModelReflection->DictFormat($sSubtitle, $iTotal)."'";
$oPage->add('<h1>'.$sTitle.'</h1>');
$oPage->add('<a class="summary">'.$sSubtitle.'</a>');
@@ -1693,16 +1835,7 @@ class DashletHeaderDynamic extends Dashlet
// Group by field: build the list of possible values (attribute codes + ...)
$oQuery = $this->oModelReflection->GetQuery($this->aProperties['query']);
$sClass = $oQuery->GetClass();
$aGroupBy = array();
foreach($this->oModelReflection->ListAttributes($sClass, 'AttributeEnum,AttributeFinalClass') as $sAttCode => $sAttType)
{
if (is_subclass_of($sAttType, 'AttributeFinalClass') || ($sAttType == 'AttributeFinalClass'))
{
if (!$this->oModelReflection->HasChildrenClasses($sClass)) continue;
}
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aGroupBy[$sAttCode] = $sLabel;
}
$aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletHeaderDynamic:Prop-GroupBy'), $this->aProperties['group_by']);
$oField->SetMandatory();
$oField->SetAllowedValues($aGroupBy);
@@ -1821,7 +1954,7 @@ class DashletBadge extends Dashlet
$aExtraParams = array(
'context_filter' => 1,
);
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
$oPage->add('</div>');
@@ -1870,7 +2003,6 @@ class DashletBadge extends Dashlet
foreach($aClasses as $sClass => $sLabel)
{
$sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false);
$sIconFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), APPROOT, $sIconUrl);
if ($sIconUrl == '')
{
// The icon does not exist, let's use a transparent one of the same size.

View File

@@ -108,11 +108,15 @@ class DisplayBlock
$oBlock = new DisplayBlock($oDummyFilter, $sStyle, false, $aParams); // DisplayBlocks built this way are synchronous
return $oBlock;
}
/**
* Constructs a DisplayBlock object from an XML template
*
* @param $sTemplate string The XML template
*
* @return DisplayBlock The DisplayBlock object, or null if the template is invalid
* @throws \ApplicationException
* @throws \OQLException
*/
public static function FromTemplate($sTemplate)
{
@@ -122,7 +126,6 @@ class DisplayBlock
$aParams = array();
if (($iStartPos === false) || ($iEndPos === false)) return null; // invalid template
$sITopBlock = substr($sTemplate,$iStartPos, $iEndPos-$iStartPos+strlen('</'.self::TAG_BLOCK.'>'));
$sITopData = substr($sTemplate, 1+$iEndTag, $iEndPos - $iEndTag - 1);
$sITopTag = substr($sTemplate, $iStartPos + strlen('<'.self::TAG_BLOCK), $iEndTag - $iStartPos - strlen('<'.self::TAG_BLOCK));
@@ -143,10 +146,6 @@ class DisplayBlock
{
$sBlockClass = $aMatches[1];
}
if (preg_match('/ objectclass="(.*)"/U',$sITopTag, $aMatches))
{
$sObjectClass = $aMatches[1];
}
if (preg_match('/ encoding="(.*)"/U',$sITopTag, $aMatches))
{
$sEncoding = strtolower($aMatches[1]);
@@ -195,6 +194,7 @@ class DisplayBlock
}
}
$oFilter = null;
switch($sEncoding)
{
case 'text/serialize':
@@ -326,7 +326,8 @@ class DisplayBlock
{
$aQueryParams = $aExtraParams['query_params'];
}
if ($this->m_sStyle != 'links')
// In case of search, the context filtering is done by the search itself
if (($this->m_sStyle != 'links') && ($this->m_sStyle != 'search'))
{
$oAppContext = new ApplicationContext();
$sClass = $this->m_oFilter->GetClass();
@@ -428,71 +429,7 @@ class DisplayBlock
case 'count':
if (isset($aExtraParams['group_by']))
{
$sAlias = $this->m_oFilter->GetClassAlias();
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
else
{
// Backward compatibility: group_by is simply a field id
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
// Security filtering
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ($oAttDef instanceof AttributeOneWayPassword)
{
throw new Exception('Grouping on password fields is not supported.');
}
}
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$aQueryParams = array();
if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params'];
}
$aFunctions = array();
$aOrderBy = array();
$sAgregationFunction = 'count';
$sFctVar = '_itop_count_';
$sAgregationAttr = '';
if (isset($aExtraParams['agregation_function']) && !empty($aExtraParams['agregation_attribute']))
{
$sAgregationFunction = $aExtraParams['agregation_function'];
$sAgregationAttr = $aExtraParams['agregation_attribute'];
$oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAgregationAttr.'`');
$oFctExpr = new FunctionExpression(strtoupper($sAgregationFunction), array($oAttrExpr));
$sFctVar = '_itop_'.$sAgregationFunction.'_';
$aFunctions = array($sFctVar => $oFctExpr);
}
if (!empty($sAgregationAttr))
{
$sClass = $this->m_oFilter->GetClass();
$sAgregationAttr = MetaModel::GetLabel($sClass, $sAgregationAttr);
}
$iLimit = 0;
if (isset($aExtraParams['limit']))
{
$iLimit = intval($aExtraParams['limit']);
}
if (isset($aExtraParams['order_direction']))
{
$aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc'));
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true, $aFunctions, $aOrderBy, $iLimit);
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
$aRes = CMDBSource::QueryToArray($sSql);
@@ -527,7 +464,7 @@ class DisplayBlock
}
$aAttribs =array(
'group' => array('label' => $sGroupByLabel, 'description' => ''),
'value' => array('label'=> Dict::S('UI:GroupBy:'.$sAgregationFunction), 'description' => Dict::Format('UI:GroupBy:'.$sAgregationFunction.'+', $sAgregationAttr))
'value' => array('label'=> Dict::S('UI:GroupBy:'.$sAggregationFunction), 'description' => Dict::Format('UI:GroupBy:'.$sAggregationFunction.'+', $sAggregationAttr))
);
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
$sHtml .= $oPage->GetP(Dict::Format($sFormat, $iTotalCount));
@@ -658,7 +595,7 @@ class DisplayBlock
}
if (count($aAuthorizedClasses) > 0)
{
if($this->m_oSet->Count() > 0)
if($this->m_oSet->CountWithLimit(1) > 0)
{
$sHtml .= cmdbAbstractObject::GetDisplayExtendedSet($oPage, $this->m_oSet, $aExtraParams);
}
@@ -677,7 +614,7 @@ class DisplayBlock
else
{
// The list is made of only 1 class of objects, actions on the list are possible
if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
if ( ($this->m_oSet->CountWithLimit(1)> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
{
$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams);
}
@@ -715,16 +652,21 @@ class DisplayBlock
if (isset($aExtraParams['update_history']) && true == $aExtraParams['update_history'])
{
$sSearchFilter = $this->m_oSet->GetFilter()->serialize();
// Limit the size of the URL (N°1585 - request uri too long)
if (strlen($sSearchFilter) < SERVER_MAX_URL_LENGTH)
{
$seventAttachedData = json_encode(array(
'filter' => $sSearchFilter,
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
'breadcrumb_instance_id' => MetaModel::GetConfig()->GetItopInstanceid(),
'breadcrumb_icon' => utils::GetAbsoluteUrlAppRoot().'images/breadcrumb-search.png'
));
$seventAttachedData = json_encode(array(
'filter' => $this->m_oSet->GetFilter()->serialize(),
'breadcrumb_id' => "ui-search-".$this->m_oSet->GetClass(),
'breadcrumb_label' => MetaModel::GetName($this->m_oSet->GetClass()),
'breadcrumb_max_count' => utils::GetConfig()->Get('breadcrumb.max_count'),
'breadcrumb_instance_id'=> MetaModel::GetConfig()->GetItopInstanceid(),
'breadcrumb_icon' => utils::GetAbsoluteUrlAppRoot().'images/breadcrumb-search.png'
));
$oPage->add_ready_script("$('body').trigger('update_history.itop', [$seventAttachedData])");
$oPage->add_ready_script("$('body').trigger('update_history.itop', [$seventAttachedData])");
}
}
}
break;
@@ -732,7 +674,7 @@ class DisplayBlock
case 'links':
//$bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false;
//$bSelectMode = isset($aExtraParams['select']) ? ($aExtraParams['select'] == 'true') : false;
if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
if ( ($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
{
//$sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : '';
$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $aExtraParams);
@@ -748,8 +690,6 @@ class DisplayBlock
{
if ((UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
{
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
$sDefaults = '';
if (isset($this->m_aParams['default']))
{
@@ -777,7 +717,6 @@ class DisplayBlock
$bContextFilter = isset($aExtraParams['context_filter']) ? isset($aExtraParams['context_filter']) != 0 : false;
if ($bContextFilter)
{
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass()));
foreach($oAppContext->GetNames() as $sFilterCode)
{
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
@@ -821,7 +760,6 @@ class DisplayBlock
$bContextFilter = isset($aExtraParams['context_filter']) ? isset($aExtraParams['context_filter']) != 0 : false;
if ($bContextFilter)
{
$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass()));
foreach($oAppContext->GetNames() as $sFilterCode)
{
$sContextParamValue = $oAppContext->GetCurrentValue($sFilterCode, null);
@@ -847,9 +785,15 @@ class DisplayBlock
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
// Generate one count + group by query [#1330]
$oGroupByExpr = Expression::FromOQL($sClass.'.'.$sStateAttrCode);
$sClassAlias = $this->m_oFilter->GetClassAlias();
$oGroupByExpr = Expression::FromOQL($sClassAlias.'.'.$sStateAttrCode);
$aGroupBy = array('group1' => $oGroupByExpr);
$sCountGroupByQuery = $this->m_oFilter->MakeGroupByQuery(array(), $aGroupBy, false);
$oGroupBySearch = $this->m_oFilter->DeepClone();
if (isset($this->m_bShowObsoleteData))
{
$oGroupBySearch->SetShowObsoleteData($this->m_bShowObsoleteData);
}
$sCountGroupByQuery = $oGroupBySearch->MakeGroupByQuery(array(), $aGroupBy, false);
$aCountGroupByResults = CMDBSource::QueryToArray($sCountGroupByQuery);
$aCountsQueryResults = array();
foreach ($aCountGroupByResults as $aCountGroupBySingleResult)
@@ -873,6 +817,10 @@ class DisplayBlock
{
$oSingleGroupByValueFilter = $this->m_oFilter->DeepClone();
$oSingleGroupByValueFilter->AddCondition($sStateAttrCode, $sStateValue, '=');
if (isset($this->m_bShowObsoleteData))
{
$oSingleGroupByValueFilter->SetShowObsoleteData($this->m_bShowObsoleteData);
}
$sHyperlink = utils::GetAbsoluteUrlAppRoot()
.'pages/UI.php?operation=search&'.$oAppContext->GetForLink()
.'&filter='.urlencode($oSingleGroupByValueFilter->serialize());
@@ -916,36 +864,6 @@ class DisplayBlock
}
$sAjaxLink = utils::GetAbsoluteUrlAppRoot().'webservices/export.php';
/*
$sCSVData = cmdbAbstractObject::GetSetAsCSV($this->m_oSet, array('fields_advanced' => $bAdvancedMode));
$sCharset = MetaModel::GetConfig()->Get('csv_file_default_charset');
if ($sCharset == 'UTF-8')
{
$bLostChars = false;
}
else
{
$sConverted = @iconv('UTF-8', $sCharset, $sCSVData);
$sRestored = @iconv($sCharset, 'UTF-8', $sConverted);
$bLostChars = ($sRestored != $sCSVData);
}
if ($bLostChars)
{
$sCharsetNotice = "&nbsp;&nbsp;<span id=\"csv_charset_issue\">";
$sCharsetNotice .= '<img src="../images/error.png" style="vertical-align:middle"/>';
$sCharsetNotice .= "</span>";
$sTip = "<p>".htmlentities(Dict::S('UI:CSVExport:LostChars'), ENT_QUOTES, 'UTF-8')."</p>";
$sTip .= "<p>".htmlentities(Dict::Format('UI:CSVExport:LostChars+', $sCharset), ENT_QUOTES, 'UTF-8')."</p>";
$oPage->add_ready_script("$('#csv_charset_issue').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
else
{
$sCharsetNotice = '';
}
*/
$sCharsetNotice = false;
$sHtml .= "<div>";
$sHtml .= '<table style="width:100%" class="transparent">';
@@ -1001,19 +919,19 @@ class DisplayBlock
$sFilter = $this->m_oFilter->serialize();
$oContext = new ApplicationContext();
$sContextParam = $oContext->GetForLink();
$sAgregationFunction = isset($aExtraParams['agregation_function']) ? $aExtraParams['agregation_function'] : '';
$sAgregationAttr = isset($aExtraParams['agregation_attribute']) ? $aExtraParams['agregation_attribute'] : '';
$sAggregationFunction = isset($aExtraParams['aggregation_function']) ? $aExtraParams['aggregation_function'] : '';
$sAggregationAttr = isset($aExtraParams['aggregation_attribute']) ? $aExtraParams['aggregation_attribute'] : '';
$sLimit = isset($aExtraParams['limit']) ? $aExtraParams['limit'] : '';
$sOrderBy = isset($aExtraParams['order_by']) ? $aExtraParams['order_by'] : '';
$sOrderDirection = isset($aExtraParams['order_direction']) ? $aExtraParams['order_direction'] : '';
if (isset($aExtraParams['group_by_label']))
{
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[group_by_label]={$aExtraParams['group_by_label']}&params[chart_type]=$sChartType&params[currentId]=$sId{$iChartCounter}&params[order_direction]=$sOrderDirection&params[order_by]=$sOrderBy&params[limit]=$sLimit&params[agregation_function]=$sAgregationFunction&params[agregation_attribute]=$sAgregationAttr&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[group_by_label]={$aExtraParams['group_by_label']}&params[chart_type]=$sChartType&params[currentId]=$sId{$iChartCounter}&params[order_direction]=$sOrderDirection&params[order_by]=$sOrderBy&params[limit]=$sLimit&params[aggregation_function]=$sAggregationFunction&params[aggregation_attribute]=$sAggregationAttr&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
}
else
{
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[currentId]=$sId{$iChartCounter}&params[order_direction]=$sOrderDirection&params[order_by]=$sOrderBy&params[limit]=$sLimit&params[agregation_function]=$sAgregationFunction&params[agregation_attribute]=$sAgregationAttr&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
$sUrl = json_encode(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=chart&params[group_by]=$sGroupBy{$sGroupByExpr}&params[chart_type]=$sChartType&params[currentId]=$sId{$iChartCounter}&params[order_direction]=$sOrderDirection&params[order_by]=$sOrderBy&params[limit]=$sLimit&params[aggregation_function]=$sAggregationFunction&params[aggregation_attribute]=$sAggregationAttr&id=$sId{$iChartCounter}&filter=".urlencode($sFilter).'&'.$sContextParam);
}
$oPage->add_ready_script(
@@ -1032,47 +950,7 @@ EOF
if (isset($aExtraParams['group_by']))
{
$sAlias = $this->m_oFilter->GetClassAlias();
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
}
else
{
// Backward compatibility: group_by is simply a field id
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$aQueryParams = array();
if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params'];
}
$aFunctions = array();
$aOrderBy = array();
$sFctVar = '_itop_count_';
if (isset($aExtraParams['agregation_function']) && !empty($aExtraParams['agregation_attribute']))
{
$sAgregationFunction = $aExtraParams['agregation_function'];
$sAgregationAttr = $aExtraParams['agregation_attribute'];
$oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAgregationAttr.'`');
$oFctExpr = new FunctionExpression(strtoupper($sAgregationFunction), array($oAttrExpr));
$sFctVar = '_itop_'.$sAgregationFunction.'_';
$aFunctions = array($sFctVar => $oFctExpr);
}
$iLimit = 0;
if (isset($aExtraParams['limit']))
{
$iLimit = intval($aExtraParams['limit']);
}
if (isset($aExtraParams['order_direction']))
{
$aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc'));
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true, $aFunctions, $aOrderBy, $iLimit);
$this->MakeGroupByQuery($aExtraParams, $oGroupByExp, $sGroupByLabel, $aGroupBy, $sAggregationFunction, $sFctVar, $sAggregationAttr, $sSql);
$aRes = CMDBSource::QueryToArray($sSql);
$oContext = new ApplicationContext();
$sContextParam = $oContext->GetForLink();
@@ -1109,7 +987,6 @@ EOF
$sJSNames = json_encode($aNames);
$sJson = json_encode($aValues);
$sJSCount = json_encode(Dict::S('UI:GroupBy:Count'));
$oPage->add_ready_script(
<<<EOF
var chart = c3.generate({
@@ -1185,6 +1062,7 @@ var chart = c3.generate({
var aURLs = $sJSURLs;
window.location.href= aURLs[d.index];
},
order: null,
},
legend: {
show: true,
@@ -1327,7 +1205,7 @@ EOF
// In all other cases, just add the condition directly
if (!$bConditionAdded)
{
$this->m_oFilter->AddCondition($sFilterCode, $condition, null, $bParseSearchString); // Use the default 'loose' operator
$this->m_oFilter->AddCondition($sFilterCode, $condition, null); // Use the default 'loose' operator
}
}
@@ -1348,6 +1226,97 @@ EOF
{
return $this->m_oSet->Count();
}
/**
* @param $aExtraParams
* @param $oGroupByExp
* @param $sGroupByLabel
* @param $aGroupBy
* @param $sAggregationFunction
* @param $sFctVar
* @param $sAggregationAttr
* @param $sSql
*
* @throws \Exception
*/
protected function MakeGroupByQuery(&$aExtraParams, &$oGroupByExp, &$sGroupByLabel, &$aGroupBy, &$sAggregationFunction, &$sFctVar, &$sAggregationAttr, &$sSql)
{
$sAlias = $this->m_oFilter->GetClassAlias();
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
else
{
// Backward compatibility: group_by is simply a field id
$oGroupByExp = new FieldExpression($aExtraParams['group_by'], $sAlias);
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
// Security filtering
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
$aMatches = array();
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ($oAttDef instanceof AttributeOneWayPassword)
{
throw new Exception('Grouping on password fields is not supported.');
}
}
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$aQueryParams = array();
if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params'];
}
$aFunctions = array();
$sAggregationFunction = 'count';
$sFctVar = '_itop_count_';
$sAggregationAttr = '';
if (isset($aExtraParams['aggregation_function']) && !empty($aExtraParams['aggregation_attribute']))
{
$sAggregationFunction = $aExtraParams['aggregation_function'];
$sAggregationAttr = $aExtraParams['aggregation_attribute'];
$oAttrExpr = Expression::FromOQL('`'.$sAlias.'`.`'.$sAggregationAttr.'`');
$oFctExpr = new FunctionExpression(strtoupper($sAggregationFunction), array($oAttrExpr));
$sFctVar = '_itop_'.$sAggregationFunction.'_';
$aFunctions = array($sFctVar => $oFctExpr);
}
if (!empty($sAggregationAttr))
{
$sClass = $this->m_oFilter->GetClass();
$sAggregationAttr = MetaModel::GetLabel($sClass, $sAggregationAttr);
}
$iLimit = 0;
if (isset($aExtraParams['limit']))
{
$iLimit = intval($aExtraParams['limit']);
}
$aOrderBy = array();
if (isset($aExtraParams['order_direction']) && isset($aExtraParams['order_by']))
{
switch ($aExtraParams['order_by'])
{
case 'attribute':
$aOrderBy = array('grouped_by_1' => ($aExtraParams['order_direction'] === 'asc'));
break;
case 'function':
$aOrderBy = array($sFctVar => ($aExtraParams['order_direction'] === 'asc'));
break;
}
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy, true, $aFunctions, $aOrderBy, $iLimit);
}
}
/**
@@ -1536,7 +1505,6 @@ class MenuBlock extends DisplayBlock
$oReflectionClass = new ReflectionClass($sClass);
$oSet = new CMDBObjectSet($this->m_oFilter);
$sFilter = $this->m_oFilter->serialize();
$sFilterDesc = $this->m_oFilter->ToOql(true);
$aActions = array();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
@@ -1655,6 +1623,7 @@ class MenuBlock extends DisplayBlock
if ($bLocked && $bRawModifiedAllowed)
{
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
/** @var array $aAllowedProfiles */
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
$bCanKill = false;
@@ -1723,7 +1692,6 @@ class MenuBlock extends DisplayBlock
$sTargetAttr = $aExtraParams['target_attr'];
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
$sTargetClass = $oAttDef->GetTargetClass();
$bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
if ($bIsModifyAllowed) { $aActions['UI:Menu:Add'] = array ('label' => Dict::S('UI:Menu:Add'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}") + $aActionParams; }
if ($bIsBulkModifyAllowed) { $aActions['UI:Menu:Manage'] = array ('label' => Dict::S('UI:Menu:Manage'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}") + $aActionParams; }
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#") + $aActionParams; }
@@ -1740,7 +1708,7 @@ class MenuBlock extends DisplayBlock
// Do not perform time consuming computations if there are too may objects in the list
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->Count() < $iLimit)))
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit)))
{
// Life cycle actions may be available... if all objects are in the same state
//
@@ -1814,7 +1782,8 @@ class MenuBlock extends DisplayBlock
}
}
}
$param = null;
$iMenuId = null;
// New extensions based on iPopupMenuItem interface
switch($this->m_sStyle)
{
@@ -1847,11 +1816,7 @@ class MenuBlock extends DisplayBlock
}
}
}
else
{
$aShortcutActions = array();
}
if (!$oPage->IsPrintableVersion())
{
if (count($aFavoriteActions) > 0)
@@ -1867,7 +1832,7 @@ class MenuBlock extends DisplayBlock
if ($this->m_sStyle == 'details')
{
$sSearchAction = "window.location=\"{$sRootUrl}pages/UI.php?operation=search_form&class=$sClass{$sContext}\"";
$sSearchAction = "window.location=\"{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}\"";
$sHtml .= "<div class=\"actions_button icon_actions_button\" title=\"".htmlentities(Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass)), ENT_QUOTES, 'UTF-8')."\"><span class=\"search-button fa fa-search\" onclick='$sSearchAction'></span></div>";
}
@@ -1897,7 +1862,7 @@ class MenuBlock extends DisplayBlock
/**
* Appends a menu separator to the current list of actions
* @param Hash $aActions The current actions list
* @param array $aActions The current actions list
* @return void
*/
protected function AddMenuSeparator(&$aActions)

View File

@@ -942,10 +942,7 @@ EOF
// Render the text of the global search form
$sText = htmlentities(utils::ReadParam('text', '', false, 'raw_data'), ENT_QUOTES, 'UTF-8');
$sOnClick = " onclick=\"if ($('#global-search-input').val() != '') { $('#global-search form').submit(); } \"";
$sDefaultPlaceHolder = '';
if (empty($sText)) {
$sDefaultPlaceHolder = Dict::S("UI:YourSearch");
}
$sDefaultPlaceHolder = Dict::S("UI:YourSearch");
if ($this->IsPrintableVersion()) {
$sHtml .= ' <!-- Beginning of page content -->';

View File

@@ -195,7 +195,7 @@ class LoginWebPage extends NiceWebPage
*/
public function ForgotPwdLink()
{
$sUrl = '?loginop=forgot_pwd';
$sUrl = utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?loginop=forgot_pwd';
$sHtml = "<a href=\"$sUrl\" target=\"_blank\">".Dict::S('UI:Login:ForgotPwd')."</a>";
return $sHtml;
}
@@ -230,7 +230,8 @@ class LoginWebPage extends NiceWebPage
try
{
UserRights::Login($sAuthUser); // Set the user's language (if possible!)
$oUser = UserRights::GetUserObject();
/** @var UserInternal $oUser */
$oUser = UserRights::GetUserObject();
if ($oUser == null)
{
throw new Exception(Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser));
@@ -254,15 +255,12 @@ class LoginWebPage extends NiceWebPage
$sToken = substr(md5(APPROOT.uniqid()), 0, 16);
$oUser->Set('reset_pwd_token', $sToken);
CMDBObject::SetTrackInfo('Reset password');
$oUser->AllowWrite(true);
$oUser->DBUpdate();
$oEmail = new Email();
$oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
if ($sFrom == '')
{
$sFrom = $sTo;
}
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject'));
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
@@ -704,26 +702,36 @@ EOF
}
}
}
/**
* Check if the user is already authentified, if yes, then performs some additional validations:
* - if $bMustBeAdmin is true, then the user must be an administrator, otherwise an error is displayed
* - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected to the portal
* - if $bIsAllowedToPortalUsers is false and the user has only access to the portal, then the user is redirected
* to the portal
*
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
* @param bool $bIsAllowedToPortalUsers Whether or not the current page is considered as part of the portal
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
*
* @return int|mixed|string
* @throws \Exception
*/
static function DoLogin($bMustBeAdmin = false, $bIsAllowedToPortalUsers = false, $iOnExit = self::EXIT_PROMPT)
{
$sRequestedPortalId = $bIsAllowedToPortalUsers ? 'legacy_portal' : 'backoffice';
return self::DoLoginEx($sRequestedPortalId, $bMustBeAdmin, $iOnExit);
}
/**
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards the desired "portal"
* Check if the user is already authentified, if yes, then performs some additional validations to redirect towards
* the desired "portal"
*
* @param string|null $sRequestedPortalId The requested "portal" interface, null for any
* @param bool $bMustBeAdmin Whether or not the user must be an admin to access the current page
* @param int iOnExit What action to take if the user is not logged on (one of the class constants EXIT_...)
*
* @return int|mixed|string
* @throws \Exception
*/
static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
{

View File

@@ -636,7 +636,10 @@ abstract class MenuNode
}
if ($this->m_aEnableActions[$index] != null)
{
// Menus access rights ignore the archive mode
utils::PushArchiveMode(false);
$iResult = UserRights::IsActionAllowed($sClass, $this->m_aEnableActions[$index]);
utils::PopArchiveMode();
if (!($iResult & $this->m_aEnableActionResults[$index]))
{
return false;

View File

@@ -54,7 +54,8 @@ abstract class Query extends cmdbAbstractObject
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'fields')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
@@ -83,7 +84,6 @@ class QueryOQL extends Query
MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'fields', 'oql')); // 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())

View File

@@ -1,49 +1,62 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* File to include to initialize the datamodel in memory
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/contexttag.class.inc.php');
session_name('itop-'.md5(APPROOT));
session_start();
$sSwitchEnv = utils::ReadParam('switch_env', null);
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
{
$_SESSION['itop_env'] = $sSwitchEnv;
$sEnv = $sSwitchEnv;
// 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, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* File to include to initialize the datamodel in memory
*
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/contexttag.class.inc.php');
session_name('itop-'.md5(APPROOT));
session_start();
$sSwitchEnv = utils::ReadParam('switch_env', null);
$bAllowCache = true;
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)) && isset($_SESSION['itop_env']) && ($_SESSION['itop_env'] !== $sSwitchEnv))
{
$_SESSION['itop_env'] = $sSwitchEnv;
$sEnv = $sSwitchEnv;
$bAllowCache = false;
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
if (function_exists('opcache_reset'))
{
// Zend opcode cache
opcache_reset();
}
if (function_exists('apc_clear_cache'))
{
// APC(u) cache
apc_clear_cache();
}
// 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, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);

View File

@@ -101,7 +101,7 @@ class UIExtKeyWidget
/**
* Get the HTML fragment corresponding to the ext key editing widget
* @param WebPage $oP The web page used for all the output
* @param Hash $aArgs Extra context arguments
* @param array $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select', $bSearchMultiple = true)
@@ -145,7 +145,12 @@ class UIExtKeyWidget
throw new Exception('Implementation: null value for allowed values definition');
}
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
if ($oAllowedValues->Count() < $iMaxComboLength)
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
if (!$oAllowedValues->CountExceeds($iMaxComboLength))
{
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
switch($sDisplayStyle)
@@ -170,7 +175,6 @@ class UIExtKeyWidget
case 'select':
case 'list':
default:
$sSelectMode = 'true';
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
@@ -203,13 +207,13 @@ class UIExtKeyWidget
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
{
// When there is only once choice, select it by default
$sSelected = ' selected';
$sSelected = 'selected';
}
else
{
$sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? ' selected' : '';
$sSelected = (is_array($value) && in_array($key, $value)) || ($value == $key) ? 'selected' : '';
}
$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
$sHTMLValue .= "<option value=\"$key\" $sSelected>$display_value</option>\n";
}
$sHTMLValue .= "</select>\n";
$sHTMLValue .= "</div>\n";
@@ -229,7 +233,7 @@ class UIExtKeyWidget
}
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
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') } );
@@ -241,8 +245,6 @@ EOF
else
{
// Too many choices, use an autocomplete
$sSelectMode = 'false';
// Check that the given value is allowed
$oSearch = $oAllowedValues->GetFilter();
$oSearch->AddCondition('id', $value);
@@ -261,10 +263,9 @@ EOF
$sDisplayValue = $this->GetObjectName($value);
}
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
$iFieldSize = isset($aArgs['iFieldSize']) ? $aArgs['iFieldSize'] : 20; //@@@ $this->oAttDef->GetMaxSize();
// the input for the auto-complete
$sHTMLValue .= "<input class=\"field_autocomplete\" count=\"".$oAllowedValues->Count()."\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<span class=\"field_input_btn\"><img id=\"mini_search_{$this->iId}\" style=\"border:0;vertical-align:middle;cursor:pointer;\" src=\"../images/mini_search.gif?t=".utils::GetCacheBusterTimestamp()."\" onClick=\"oACWidget_{$this->iId}.Search();\"/></span>";
// another hidden input to store & pass the object's Id
@@ -274,7 +275,7 @@ EOF
// 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, '{$this->sAttCode}', $sJSSearchMode);
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
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',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 !
@@ -376,9 +377,13 @@ EOF
/**
* Search for objects to be selected
*
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param $sFilter
* @param string $sRemoteClass Name of the "remote" class to perform the search on, must be a derived class of m_sRemoteClass
* @param Array $aAlreadyLinkedIds List of IDs of objects of "remote" class already linked, to be filtered out of the search
* @param null $oObj
*
* @throws \OQLException
*/
public function SearchObjectsToSelect(WebPage $oP, $sFilter, $sRemoteClass = '', $oObj = null)
{
@@ -401,14 +406,20 @@ EOF
$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
}
/**
* Search for objects to be selected
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sFilter The OQL expression used to define/limit limit the scope of possible values
* @param DBObject $oObj The current object for the OQL context
* @param string $sContains The text of the autocomplete to filter the results
*/
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV)
/**
* Search for objects to be selected
*
* @param WebPage $oP The page used for the output (usually an AjaxWebPage)
* @param string $sFilter The OQL expression used to define/limit limit the scope of possible values
* @param DBObject $oObj The current object for the OQL context
* @param string $sContains The text of the autocomplete to filter the results
* @param string $sOutputFormat
* @param null $sOperation for the values @see ValueSetObjects->LoadValues()
*
* @throws CoreException
* @throws OQLException
*/
public function AutoComplete(WebPage $oP, $sFilter, $oObj = null, $sContains, $sOutputFormat = self::ENUM_OUTPUT_FORMAT_CSV, $sOperation = null)
{
if (is_null($sFilter))
{
@@ -419,15 +430,60 @@ EOF
$iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
$iMax = 150;
$oValuesSet->SetLimit($iMax);
$oValuesSet->SetSort(false);
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains);
if (empty($sOperation) || 'equals_start_with' == $sOperation)
{
$aValues = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
asort($aValues);
$iMax -= count($aValues);
if ($iMax > 0 ) {
$oValuesSet->SetLimit($iMax);
$aValuesStartWith = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
asort($aValuesStartWith);
foreach ($aValuesStartWith as $sKey => $sFriendlyName) {
if (!isset($aValues[$sKey])) {
$aValues[$sKey] = $sFriendlyName;
}
}
}
}
else
{
$aValues = array();
}
$iMax -= count($aValues);
if ($iMax > 0 && (empty($sOperation) || 'contains' == $sOperation))
{
$oValuesSet->SetLimit($iMax);
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
asort($aValuesContains);
foreach($aValuesContains as $sKey => $sFriendlyName)
{
if (!isset($aValues[$sKey]))
{
$aValues[$sKey] = $sFriendlyName;
}
}
}
switch($sOutputFormat)
{
case static::ENUM_OUTPUT_FORMAT_JSON:
// Array flip to preserve values order on the label, otherwise the JS will re-order regarding the keys.
$oP->SetContentType('application/json');
$oP->add(json_encode(array_flip($aValues)));
$aJsonMap = array();
foreach ($aValues as $sKey => $sLabel)
{
$aJsonMap[] = array('value' => $sKey, 'label' => $sLabel);
}
$oP->SetContentType('application/json');
$oP->add(json_encode($aJsonMap));
break;
case static::ENUM_OUTPUT_FORMAT_CSV:
@@ -461,12 +517,15 @@ EOF
}
}
/**
/**
* Get the form to select a leaf class from the $this->sTargetClass (that should be abstract)
* Note: Inspired from UILinksWidgetDirect::GetObjectCreationDialog()
*
* @param WebPage $oPage
*/
* @param WebPage $oPage
*
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
public function GetClassSelectionForm(WebPage $oPage)
{
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
@@ -568,21 +627,11 @@ EOF
{
throw new Exception('Implementation: null value for allowed values definition');
}
try
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
}
catch(MissingQueryArgument $e)
{
// When used in a search form the $this parameter may be missing, in this case return all possible values...
// 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);
}
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj, 'current_extkey_id' => $currValue));
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
$sHKAttCode = MetaModel::IsHierarchicalClass($this->sTargetClass);

View File

@@ -275,7 +275,10 @@ class UILinksWidgetDirect
$sJSONLabels = json_encode($aLabels);
$sJSONButtons = json_encode($aButtons);
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper });");
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->sLinkedClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});");
}
/**
@@ -337,6 +340,7 @@ class UILinksWidgetDirect
'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}",
'table_id' => "add_{$this->sInputid}",
'table_inner_id' => "ResultsToAdd_{$this->sInputid}",
'selection_mode' => true,
'cssCount' => "#count_{$this->sInputid}",
'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sHiddenCriteria,

View File

@@ -365,9 +365,12 @@ EOF
}
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
$sDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->m_sRemoteClass);
$sJSDoSearch = $bDoSearch ? '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, $sWizHelper, '{$this->m_sExtKeyToRemote}');
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, '{$this->m_sExtKeyToRemote}', $sJSDoSearch);
oWidget{$this->m_iInputId}.Init();
EOF
);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -86,12 +86,20 @@ class WizardHelper
}
elseif($sLinkedAttDef instanceof AttributeDateTime)
{
$sDateClass = get_class($sLinkedAttDef);
$sDate = $aLinkedObject[$sLinkedAttCode];
if($sDate !== null && $sDate !== '')
{
$oDateTimeFormat = AttributeDateTime::GetFormat();
$oDateTimeFormat = $sDateClass::GetFormat();
$oDate = $oDateTimeFormat->Parse($sDate);
$sDate = $oDate->format('Y-m-d H:i:s');
if ($sDateClass == "AttributeDate")
{
$sDate = $oDate->format('Y-m-d');
}
else
{
$sDate = $oDate->format('Y-m-d H:i:s');
}
}
$oLinkedObj->Set($sLinkedAttCode, $sDate);

18
composer.json Normal file
View File

@@ -0,0 +1,18 @@
{
"require": {
"php": ">=5.6.0",
"ext-mysql": "*",
"ext-ldap": "*",
"ext-mcrypt": "*",
"ext-cli": "*",
"ext-soap": "*",
"ext-json": "*",
"ext-zip": "*",
"ext-mysqli": "*"
},
"config": {
"platform": {
"php": "5.6.0"
}
}
}

View File

@@ -60,7 +60,7 @@ abstract class Action extends cmdbAbstractObject
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
@@ -262,22 +262,34 @@ class ActionEmail extends ActionNotification
{
$sPrefix = '';
}
if ($oLog)
{
$oLog->Set('message', $sPrefix . $sRes);
}
$oLog->DBUpdate();
}
}
catch (Exception $e)
{
if ($oLog)
{
$oLog->Set('message', 'Error: '.$e->getMessage());
try
{
$oLog->DBUpdate();
}
catch (Exception $eSecondTryUpdate)
{
IssueLog::Error("Failed to process email ".$oLog->GetKey()." - reason: ".$e->getMessage()."\nTrace:\n".$e->getTraceAsString());
$oLog->Set('message', 'Error: more details in the log for email "'.$oLog->GetKey().'"');
$oLog->DBUpdate();
}
}
}
if ($oLog)
{
$oLog->DBUpdate();
}
}
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
@@ -348,7 +360,7 @@ class ActionEmail extends ActionNotification
$sTestBody .= "</div>\n";
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
$oEmail->SetRecipientTO($this->Get('test_recipient'));
$oEmail->SetRecipientFrom($this->Get('test_recipient'));
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
}

View File

@@ -238,7 +238,20 @@ abstract class AsyncTask extends DBObject
{
$iRetryDelay = $this->GetRetryDelay($iErrorCode);
IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage.' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task. Remaining retries: '.$iRemaining.'. Next retry in '.$iRetryDelay.'s'");
try
{
$oEventLog->DBUpdate();
}
catch (Exception $e)
{
$oEventLog->Set('message', "Failed to process async task. Remaining retries: '.$iRemaining.'. Next retry in '.$iRetryDelay.'s', more details in the log");
$oEventLog->DBUpdate();
}
}
$this->Set('remaining_retries', $iRemaining - 1);
$this->Set('status', 'planned');
$this->Set('started', null);
@@ -247,7 +260,20 @@ abstract class AsyncTask extends DBObject
else
{
IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$sErrorMessage);
if ($this->Get('event_id') != 0)
{
$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
$oEventLog->Set('message', "$sErrorMessage\nFailed to process async task.");
try
{
$oEventLog->DBUpdate();
}
catch (Exception $e)
{
$oEventLog->Set('message', 'Failed to process async task, more details in the log');
$oEventLog->DBUpdate();
}
}
$this->Set('status', 'error');
$this->Set('started', null);
$this->Set('planned', null);

View File

@@ -740,10 +740,14 @@ abstract class AttributeDefinition
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param $value mixed The current value of the field
* @param $sVerb string The verb specifying the representation of the value
* @param $oHostObject DBObject The object
* @param $bLocalize bool Whether or not to localize the value
*
* @return mixed|null|string
* @throws \Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
@@ -852,7 +856,7 @@ abstract class AttributeDefinition
* does nothing special, and just calls the default (loose) operator
* @param string $sSearchText The search string to analyze for smart patterns
* @param FieldExpression The FieldExpression representing the atttribute code in this OQL query
* @param Hash $aParams Values of the query parameters
* @param array $aParams Values of the query parameters
* @return Expression The search condition to be added (AND) to the current search
*/
public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams)
@@ -888,7 +892,7 @@ abstract class AttributeDefinition
/**
* The part of the current attribute in the object's signature, for the supplied value
* @param unknown $value The value of this attribute for the object
* @param mixed $value The value of this attribute for the object
* @return string The "signature" for this field/attribute
*/
public function Fingerprint($value)
@@ -1008,7 +1012,6 @@ class AttributeLinkedSet extends AttributeDefinition
$sObjClass = get_class($oObj);
$sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n";
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
{
if ($sAttCode == 'finalclass')
@@ -1105,10 +1108,14 @@ class AttributeLinkedSet extends AttributeDefinition
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param $value mixed The current value of the field
* @param $sVerb string The verb specifying the representation of the value
* @param $oHostObject DBObject The object
* @param $bLocalize bool Whether or not to localize the value
*
* @return string
* @throws \Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
@@ -1405,6 +1412,30 @@ class AttributeLinkedSet extends AttributeDefinition
return $oSet;
}
/**
* @param $proposedValue
* @param $oHostObj
*
* @return mixed
* @throws \Exception
*/
public function MakeRealValue($proposedValue, $oHostObj){
if($proposedValue === null)
{
$sLinkedClass = $this->GetLinkedClass();
$aLinkedObjectsArray = array();
$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
return new ormLinkSet(
get_class($oHostObj),
$this->GetCode(),
$oSet
);
}
return $proposedValue;
}
/**
* @param ormLinkSet $val1
* @param ormLinkSet $val2
@@ -1425,7 +1456,9 @@ class AttributeLinkedSet extends AttributeDefinition
/**
* Find the corresponding "link" attribute on the target class, if any
*
* @return null | AttributeDefinition
* @throws \Exception
*/
public function GetMirrorLinkAttribute()
{
@@ -1509,6 +1542,7 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
/**
* Find the corresponding "link" attribute on the target class, if any
* @return null | AttributeDefinition
* @throws \CoreException
*/
public function GetMirrorLinkAttribute()
{
@@ -1544,7 +1578,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
protected function GetSQLCol($bFullSpec = false)
{
return 'VARCHAR(255)'
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
protected function GetSQLColSpec()
@@ -2171,7 +2205,7 @@ class AttributeString extends AttributeDBField
protected function GetSQLCol($bFullSpec = false)
{
return 'VARCHAR(255)'
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
@@ -2403,6 +2437,7 @@ class AttributeApplicationLanguage extends AttributeString
class AttributeFinalClass extends AttributeString
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public $m_sValue;
public function __construct($sCode, $aParams)
{
@@ -2571,7 +2606,7 @@ class AttributePassword extends AttributeString
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(64)"
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
@@ -2704,7 +2739,7 @@ class AttributeText extends AttributeString
protected function GetSQLCol($bFullSpec = false)
{
return "TEXT".CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
return "TEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetSQLColumns($bFullSpec = false)
@@ -2714,7 +2749,7 @@ class AttributeText extends AttributeString
if ($this->GetOptional('format', null) != null )
{
// Add the extra column only if the property 'format' is specified for the attribute
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
$aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::GetSqlStringColumnDefinition();
if ($bFullSpec)
{
$aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records
@@ -2754,7 +2789,6 @@ class AttributeText extends AttributeString
$sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i';
if (preg_match_all($sPattern, $sText, $aAllMatches, PREG_SET_ORDER /* important !*/ |PREG_OFFSET_CAPTURE /* important ! */))
{
$aUrls = array();
$i = count($aAllMatches);
// Replace the URLs by an actual hyperlink <a href="...">...</a>
// Let's do it backwards so that the initial positions are not modified by the replacement
@@ -3038,7 +3072,7 @@ class AttributeLongText extends AttributeText
{
protected function GetSQLCol($bFullSpec = false)
{
return "LONGTEXT".CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetMaxSize()
@@ -3220,7 +3254,7 @@ class AttributeCaseLog extends AttributeLongText
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'LONGTEXT' // 2^32 (4 Gb)
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_index'] = 'BLOB';
return $aColumns;
}
@@ -3291,10 +3325,14 @@ class AttributeCaseLog extends AttributeLongText
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param $value mixed The current value of the field
* @param $sVerb string The verb specifying the representation of the value
* @param $oHostObject DBObject The object
* @param $bLocalize bool Whether or not to localize the value
*
* @return mixed
* @throws \Exception
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
@@ -3604,13 +3642,13 @@ class AttributeEnum extends AttributeString
// make sure that this string will match the field type returned by the DB
// (used to perform a comparison between the current DB format and the data model)
return "ENUM(".implode(",", $aValues).")"
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
else
{
return "VARCHAR(255)"
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax!
}
}
@@ -3812,7 +3850,7 @@ class AttributeEnum extends AttributeString
$aLocalizedValues = array();
foreach ($aRawValues as $sKey => $sValue)
{
$aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey));
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
}
return $aLocalizedValues;
}
@@ -4158,7 +4196,6 @@ class AttributeDateTime extends AttributeDBField
return parent::GetForTemplate($value, $sVerb, $oHostObject, $bLocalize);
break;
}
return null;
}
static public function ListExpectedParams()
@@ -4279,7 +4316,7 @@ class AttributeDateTime extends AttributeDBField
try
{
$oFormat = new DateTimeFormat($this->GetInternalFormat());
$oTrash = $oFormat->Parse($proposedValue);
$oFormat->Parse($proposedValue);
}
catch (Exception $e)
{
@@ -4294,17 +4331,10 @@ class AttributeDateTime extends AttributeDBField
public function ScalarToSQL($value)
{
if (is_null($value))
if (empty($value))
{
return null;
}
elseif (empty($value))
{
// Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null.
// todo: this is NOT valid in strict mode (default setting for MySQL 5.7)
// todo: if to be kept, this should be overloaded for AttributeDate (0000-00-00)
return '0000-00-00 00:00:00';
}
return $value;
}
@@ -4345,7 +4375,7 @@ class AttributeDateTime extends AttributeDBField
* does nothing special, and just calls the default (loose) operator
* @param string $sSearchText The search string to analyze for smart patterns
* @param FieldExpression The FieldExpression representing the atttribute code in this OQL query
* @param Hash $aParams Values of the query parameters
* @param array $aParams Values of the query parameters
* @return Expression The search condition to be added (AND) to the current search
*/
public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false)
@@ -4388,7 +4418,6 @@ class AttributeDateTime extends AttributeDBField
$sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2';
$oRightExpr = new VariableExpression($sParamName2);
$sOperator = $this->GetBasicFilterLooseOperator();
if ($bParseSearchString)
{
$aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]);
@@ -4422,7 +4451,7 @@ class AttributeDateTime extends AttributeDBField
break;
default:
$oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams, $bParseSearchString);
$oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams);
}
@@ -4478,8 +4507,7 @@ class AttributeDuration extends AttributeInteger
public static function FormatDuration($duration)
{
$aDuration = self::SplitDuration($duration);
$sResult = '';
if ($duration < 60)
{
// Less than 1 min
@@ -4641,7 +4669,7 @@ class AttributeDeadline extends AttributeDateTime
{
$sDifference = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference));
}
$sFormat = MetaModel::GetConfig()->Get('deadline_format', '$difference$');
$sFormat = MetaModel::GetConfig()->Get('deadline_format');
$sResult = str_replace(array('$date$', '$difference$'), array($sDate, $sDifference), $sFormat);
}
@@ -4653,8 +4681,7 @@ class AttributeDeadline extends AttributeDateTime
$days = floor($duration / 86400);
$hours = floor(($duration - (86400*$days)) / 3600);
$minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60);
$sResult = '';
if ($duration < 60)
{
// Less than 1 min
@@ -4841,6 +4868,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
/**
* Find the corresponding "link" attribute on the target class, if any
* @return null | AttributeDefinition
* @throws \CoreException
*/
public function GetMirrorLinkAttribute()
{
@@ -5210,6 +5238,7 @@ class AttributeExternalField extends AttributeDefinition
/**
* @return bool
* @throws \CoreException
*/
public function IsFriendlyName()
{
@@ -5434,7 +5463,7 @@ class AttributeURL extends AttributeString
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(2048)"
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
@@ -5611,20 +5640,14 @@ class AttributeBlob extends AttributeDefinition
{
$aColumns = array();
$aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb)
$aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
$aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
$aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}
public function GetFilterDefinitions()
{
return array();
// still not working... see later...
return array(
$this->GetCode().'->filename' => new FilterFromAttribute($this, '_filename'),
$this->GetCode().'_mimetype' => new FilterFromAttribute($this, '_mimetype'),
$this->GetCode().'_mimetype' => new FilterFromAttribute($this, '_mimetype')
);
}
public function GetBasicFilterOperators()
@@ -5647,6 +5670,7 @@ class AttributeBlob extends AttributeDefinition
{
return $value->GetAsHTML();
}
return '';
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
@@ -5811,6 +5835,32 @@ class AttributeImage extends AttributeBlob
{
return '\\Combodo\\iTop\\Form\\Field\\ImageField';
}
public function MakeFormField(DBObject $oObject, $oFormField = null)
{
if ($oFormField === null)
{
$sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode());
}
parent::MakeFormField($oObject, $oFormField);
// Generating urls
$value = $oObject->Get($this->GetCode());
if (is_object($value) && !$value->IsEmpty())
{
$oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
$oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode()));
}
else
{
$oFormField->SetDownloadUrl($this->Get('default_image'));
$oFormField->SetDisplayUrl($this->Get('default_image'));
}
return $oFormField;
}
}
/**
* A stop watch is an ormStopWatch object, it is stored as several columns in the database
@@ -5943,7 +5993,6 @@ class AttributeStopWatch extends AttributeDefinition
self::DateToSeconds($aCols[$sPrefix.'_stopped'])
);
$aThresholds = array();
foreach ($this->ListThresholds() as $iThreshold => $aDefinition)
{
$sThPrefix = '_'.$iThreshold;
@@ -6047,6 +6096,7 @@ class AttributeStopWatch extends AttributeDefinition
{
return $value->GetAsHTML($this, $oHostObject);
}
return '';
}
public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
@@ -6755,7 +6805,7 @@ class AttributeOneWayPassword extends AttributeDefinition
public function GetImportColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
$aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}
@@ -6799,6 +6849,7 @@ class AttributeOneWayPassword extends AttributeDefinition
{
return $value->GetAsHTML();
}
return '';
}
public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
@@ -6828,7 +6879,7 @@ class AttributeTable extends AttributeDBField
protected function GetSQLCol($bFullSpec = false)
{
return "LONGTEXT".CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION;
return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition();
}
public function GetMaxSize()
@@ -7054,6 +7105,7 @@ class AttributePropertySet extends AttributeTable
class AttributeFriendlyName extends AttributeDefinition
{
const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
public $m_sValue;
public function __construct($sCode)
{
@@ -7235,7 +7287,7 @@ class AttributeRedundancySettings extends AttributeDBField
protected function GetSQLCol($bFullSpec = false)
{
return "VARCHAR(20)"
.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION
.CMDBSource::GetSqlStringColumnDefinition()
.($bFullSpec ? $this->GetSQLColSpec() : '');
}
@@ -7309,12 +7361,16 @@ class AttributeRedundancySettings extends AttributeDBField
}
}
}
return array();
}
/**
* Find the user option label
* @param user option : disabled|cout|percent
*/
*
* @param user option : disabled|cout|percent
*
* @return string
*/
public function GetUserOptionFormat($sUserOption, $sDefault = null)
{
$sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, null, true /*user lang*/);
@@ -7564,7 +7620,7 @@ class AttributeRedundancySettings extends AttributeDBField
$sOptionName = $sHtmlNamesPrefix.'_user_option';
$sOptionId = $sOptionName.'_'.$sUserOption;
$sChecked = $bSelected ? 'checked' : '';
$sRet = '<input type="radio" name="'.$sOptionName.'" id="'.$sOptionId.'" value="'.$sUserOption.'"'.$sChecked.'> <label for="'.$sOptionId.'">'.$sLabel.'</label>';
$sRet = '<input type="radio" name="'.$sOptionName.'" id="'.$sOptionId.'" value="'.$sUserOption.'" '.$sChecked.'> <label for="'.$sOptionId.'">'.$sLabel.'</label>';
}
else
{
@@ -7716,7 +7772,9 @@ class AttributeCustomFields extends AttributeDefinition
/**
* @param DBObject $oHostObject
* @param null $sFormPrefix
* @return Combodo\iTop\Form\Form
* @throws \Exception
*/
public function GetForm(DBObject $oHostObject, $sFormPrefix = null)
{
@@ -7785,7 +7843,7 @@ class AttributeCustomFields extends AttributeDefinition
/**
* The part of the current attribute in the object's signature, for the supplied value
* @param $value The value of this attribute for the object
* @param ormCustomFieldsValue $value The value of this attribute for the object
* @return string The "signature" for this field/attribute
*/
public function Fingerprint($value)
@@ -7832,6 +7890,8 @@ class AttributeCustomFields extends AttributeDefinition
/**
* Cleanup data upon object deletion (object id still available here)
* @param DBObject $oHostObject
* @return
* @throws \CoreException
*/
public function DeleteValue(DBObject $oHostObject)
{
@@ -7893,10 +7953,13 @@ class AttributeCustomFields extends AttributeDefinition
/**
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
*
* @param $value mixed The current value of the field
* @param $sVerb string The verb specifying the representation of the value
* @param $oHostObject DBObject The object
* @param $bLocalize bool Whether or not to localize the value
*
* @return string
*/
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
{
@@ -8024,11 +8087,6 @@ class AttributeObsolescenceFlag extends AttributeBoolean
public function GetSQLExpressions($sPrefix = '')
{
return array();
if ($sPrefix == '')
{
$sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property
}
return array('' => $sPrefix);
}
public function GetSQLColumns($bFullSpec = false) {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation)
public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update)

View File

@@ -73,6 +73,8 @@ class BulkExportResult extends DBObject
MetaModel::Init_AddAttribute(new AttributeString("temp_file_path", array("allowed_values"=>null, "sql"=>"temp_file_path", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("search", array("allowed_values"=>null, "sql"=>"search", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("status_info", array("allowed_values"=>null, "sql"=>"status_info", "default_value"=>'', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBoolean("localize_output", array("allowed_values"=>null, "sql"=>"localize_output", "default_value"=>true, "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
@@ -151,13 +153,16 @@ abstract class BulkExport
$this->sTmpFile = '';
$this->bLocalizeOutput = false;
}
/**
* Find the first class capable of exporting the data in the given format
* @param string $sFormatCode The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...)
* @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats
* @return BulkExport|NULL
*/
/**
* Find the first class capable of exporting the data in the given format
*
* @param string $sFormatCode The lowercase format (e.g. html, csv, spreadsheet, xlsx, xml, json, pdf...)
* @param DBSearch $oSearch The search/filter defining the set of objects to export or null when listing the supported formats
*
* @return BulkExport|null
* @throws ReflectionException
*/
static public function FindExporter($sFormatCode, $oSearch = null)
{
foreach(get_declared_classes() as $sPHPClass)
@@ -178,12 +183,17 @@ abstract class BulkExport
}
return null;
}
/**
* Find the exporter corresponding to the given persistent token
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
* @return iBulkExport|NULL
*/
/**
* Find the exporter corresponding to the given persistent token
*
* @param int $iPersistentToken The identifier of the BulkExportResult object storing the information
*
* @return iBulkExport|null
* @throws ArchivedObjectException
* @throws CoreException
* @throws ReflectionException
*/
static public function FindExporterFromToken($iPersistentToken = null)
{
$oBulkExporter = null;
@@ -192,7 +202,7 @@ abstract class BulkExport
{
$sFormatCode = $oInfo->Get('format');
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
if ($oBulkExporter)
{
@@ -200,6 +210,10 @@ abstract class BulkExport
$oBulkExporter->SetObjectList($oSearch);
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
$oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
$oBulkExporter->sTmpFile = $oInfo->Get('temp_file_path');
$oBulkExporter->oBulkExportResult = $oInfo;
}
@@ -258,6 +272,14 @@ abstract class BulkExport
{
$this->iChunkSize = $iChunkSize;
}
/**
* @param $bLocalizeOutput
*/
public function SetLocalizeOutput($bLocalizeOutput)
{
$this->bLocalizeOutput = $bLocalizeOutput;
}
/**
* (non-PHPdoc)
@@ -320,9 +342,10 @@ abstract class BulkExport
$this->oBulkExportResult = new BulkExportResult();
$this->oBulkExportResult->Set('format', $this->sFormatCode);
$this->oBulkExportResult->Set('search', $this->oSearch->serialize());
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
}
$this->oBulkExportResult->Set('chunk_size', $this->iChunkSize);
$this->oBulkExportResult->Set('temp_file_path', $this->sTmpFile);
$this->oBulkExportResult->Set('localize_output', $this->bLocalizeOutput);
}
$this->oBulkExportResult->Set('status_info', json_encode($this->GetStatusInfo()));
utils::PushArchiveMode(false);
$ret = $this->oBulkExportResult->DBWrite();

View File

@@ -108,16 +108,6 @@ class MySQLHasGoneAwayException extends MySQLException
*/
class CMDBSource
{
/**
* SQL charset & collation declaration for text columns
*
* Using an attribute instead of a constant to avoid crash in the setup for older PHP versions
*
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-column.html
* @since 2.5 #1001 switch to utf8mb4
*/
public static $SQL_STRING_COLUMNS_CHARSET_DEFINITION = ' CHARACTER SET '.DEFAULT_CHARACTER_SET.' COLLATE '.DEFAULT_COLLATION;
protected static $m_sDBHost;
protected static $m_sDBUser;
protected static $m_sDBPwd;
@@ -136,6 +126,20 @@ class CMDBSource
/** @var mysqli $m_oMysqli */
protected static $m_oMysqli;
/**
* SQL charset & collation declaration for text columns
*
* Using a function instead of a constant or attribute to avoid crash in the setup for older PHP versions (cannot
* use expression as value)
*
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-column.html
* @since 2.5 #1001 switch to utf8mb4
*/
public static function GetSqlStringColumnDefinition()
{
return ' CHARACTER SET '.DEFAULT_CHARACTER_SET.' COLLATE '.DEFAULT_COLLATION;
}
/**
* @param Config $oConfig
*
@@ -492,15 +496,19 @@ class CMDBSource
public static function DBPwd() {return self::$m_sDBPwd;}
public static function DBName() {return self::$m_sDBName;}
/**
* Quote variable and protect against SQL injection attacks
* Code found in the PHP documentation: quote_smart($value)
*
* @param mixed $value
* @param bool $bAlways should be set to true when the purpose is to create a IN clause,
* otherwise and if there is a mix of strings and numbers, the clause would always be false
* @param string $cQuoteStyle
*
* @return array|string
*/
public static function Quote($value, $bAlways = false, $cQuoteStyle = "'")
{
// Quote variable and protect against SQL injection attacks
// Code found in the PHP documentation: quote_smart($value)
// bAlways should be set to true when the purpose is to create a IN clause,
// otherwise and if there is a mix of strings and numbers, the clause
// would always be false
if (is_null($value))
{
return 'NULL';
@@ -1055,7 +1063,7 @@ class CMDBSource
}
return 'ALTER TABLE `'.$sTableName.'` '.self::$SQL_STRING_COLUMNS_CHARSET_DEFINITION.';';
return 'ALTER TABLE `'.$sTableName.'` '.self::GetSqlStringColumnDefinition().';';
}
@@ -1201,6 +1209,6 @@ class CMDBSource
return null;
}
return 'ALTER DATABASE'.CMDBSource::$SQL_STRING_COLUMNS_CHARSET_DEFINITION.';';
return 'ALTER DATABASE'.CMDBSource::GetSqlStringColumnDefinition().';';
}
}

View File

@@ -265,8 +265,8 @@ class Config
'min_autocomplete_chars' => array(
'type' => 'integer',
'description' => 'The minimum number of characters to type in order to trigger the "autocomplete" behavior',
'default' => 3,
'value' => 3,
'default' => 2,
'value' => 2,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
@@ -495,6 +495,22 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_default_sender_address' => array(
'type' => 'string',
'description' => 'Default address provided in the email from header field.',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'email_default_sender_label' => array(
'type' => 'string',
'description' => 'Default label provided in the email from header field.',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'apc_cache.enabled' => array(
'type' => 'bool',
'description' => 'If set, the APC cache is allowed (the PHP extension must also be active)',
@@ -1037,9 +1053,9 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'inline_image_garbage_collector_interval' => array(
'draft_attachments_lifetime' => array(
'type' => 'integer',
'description' => 'Frequency (in seconds) at which the inline image garbage collector will run.',
'description' => 'Lifetime (in seconds) of drafts\' attachments and inline images: after this duration, the garbage collector will delete them.',
'default' => 3600,
'value' => '',
'source_of_value' => '',
@@ -1095,12 +1111,20 @@ class Config
),
'search_manual_submit' => array(
'type' => 'array',
'description' => 'Force manual submit of search requests (class => true)',
'description' => 'Force manual submit of search all requests',
'default' => false,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'high_cardinality_classes' => array(
'type' => 'array',
'description' => 'List of classes with high cardinality (Force manual submit of search)',
'default' => array(),
'value' => array(),
'source_of_value' => '',
'show_in_conf_sample' => true,
),
);
public function IsProperty($sPropCode)
@@ -1160,10 +1184,10 @@ class Config
}
public function Get($sPropCode)
{
return $this->m_aSettings[$sPropCode]['value'];
}
public function Get($sPropCode)
{
return $this->m_aSettings[$sPropCode]['value'];
}
/**
* Event log options (see LOG_... definition)
@@ -1336,6 +1360,12 @@ class Config
throw new ConfigException('Error in configuration file',
array('file' => $sConfigFile, 'error' => $e->getMessage()));
}
catch(Error $e)
{
// PHP 7
throw new ConfigException('Error in configuration file',
array('file' => $sConfigFile, 'error' => $e->getMessage().' at line '.$e->getLine()));
}
if (strlen($sNoise) > 0)
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)

View File

@@ -127,5 +127,3 @@ class SecurityException extends CoreException
class ArchivedObjectException extends CoreException
{
}
?>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<user_rights>
<profiles>
<profile id="1024" _delta="define">
@@ -9,4 +9,250 @@
</profile>
</profiles>
</user_rights>
<meta>
<classes>
<class id="User" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>core,grant_by_profile</category>
</properties>
<fields>
<field id="contactid" xsi:type="AttributeExternalKey">
<target_class>Person</target_class>
</field>
<field id="last_name" xsi:type="AttributeExternalField"/>
<field id="first_name" xsi:type="AttributeExternalField"/>
<field id="email" xsi:type="AttributeExternalField"/>
<field id="org_id" xsi:type="AttributeExternalField"/>
<field id="login" xsi:type="AttributeString"/>
<field id="language" xsi:type="AttributeApplicationLanguage"/>
<field id="status" xsi:type="AttributeEnum"/>
<field id="profile_list" xsi:type="AttributeLinkedSetIndirect"/>
<field id="allowed_org_list" xsi:type="AttributeLinkedSetIndirect"/>
<field id="finalclass" xsi:type="AttributeFinalClass"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="contactid_friendlyname" xsi:type="AttributeExternalField"/>
<field id="contactid_obsolescence_flag" xsi:type="AttributeExternalField"/>
<field id="org_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="org_id_obsolescence_flag" xsi:type="AttributeExternalField"/>
</fields>
</class>
<class id="URP_Profiles" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>addon/userrights,grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="user_list" xsi:type="AttributeLinkedSetIndirect"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="URP_UserProfile" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>addon/userrights,grant_by_profile</category>
</properties>
<fields>
<field id="userid" xsi:type="AttributeExternalKey">
<target_class>User</target_class>
</field>
<field id="userlogin" xsi:type="AttributeExternalField"/>
<field id="profileid" xsi:type="AttributeExternalKey">
<target_class>URP_Profiles</target_class>
</field>
<field id="profile" xsi:type="AttributeExternalField"/>
<field id="reason" xsi:type="AttributeString"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="userid_friendlyname" xsi:type="AttributeExternalField"/>
<field id="userid_finalclass_recall" xsi:type="AttributeExternalField"/>
<field id="profileid_friendlyname" xsi:type="AttributeExternalField"/>
</fields>
</class>
<class id="URP_UserOrg" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>addon/userrights,grant_by_profile</category>
</properties>
<fields>
<field id="userid" xsi:type="AttributeExternalKey">
<target_class>User</target_class>
</field>
<field id="userlogin" xsi:type="AttributeExternalField"/>
<field id="allowed_org_id" xsi:type="AttributeExternalKey">
<target_class>Organization</target_class>
</field>
<field id="allowed_org_name" xsi:type="AttributeExternalField"/>
<field id="reason" xsi:type="AttributeString"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="userid_friendlyname" xsi:type="AttributeExternalField"/>
<field id="userid_finalclass_recall" xsi:type="AttributeExternalField"/>
<field id="allowed_org_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="allowed_org_id_obsolescence_flag" xsi:type="AttributeExternalField"/>
</fields>
</class>
<class id="Action" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>grant_by_profile,core/cmdb</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="status" xsi:type="AttributeEnum"/>
<field id="trigger_list" xsi:type="AttributeLinkedSetIndirect"/>
<field id="finalclass" xsi:type="AttributeFinalClass"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="Trigger" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>grant_by_profile,core/cmdb</category>
</properties>
<fields>
<field id="description" xsi:type="AttributeString"/>
<field id="action_list" xsi:type="AttributeLinkedSetIndirect"/>
<field id="finalclass" xsi:type="AttributeFinalClass"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="SynchroDataSource" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>core/cmdb,view_in_gui,grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeText"/>
<field id="status" xsi:type="AttributeEnum"/>
<field id="user_id" xsi:type="AttributeExternalKey">
<target_class>User</target_class>
</field>
<field id="notify_contact_id" xsi:type="AttributeExternalKey">
<target_class>Contact</target_class>
</field>
<field id="scope_class" xsi:type="AttributeClass"/>
<field id="database_table_name" xsi:type="AttributeString"/>
<field id="scope_restriction" xsi:type="AttributeString"/>
<field id="full_load_periodicity" xsi:type="AttributeDuration"/>
<field id="reconciliation_policy" xsi:type="AttributeEnum"/>
<field id="action_on_zero" xsi:type="AttributeEnum"/>
<field id="action_on_one" xsi:type="AttributeEnum"/>
<field id="action_on_multiple" xsi:type="AttributeEnum"/>
<field id="delete_policy" xsi:type="AttributeEnum"/>
<field id="delete_policy_update" xsi:type="AttributeString"/>
<field id="delete_policy_retention" xsi:type="AttributeDuration"/>
<field id="attribute_list" xsi:type="AttributeLinkedSet"/>
<field id="user_delete_policy" xsi:type="AttributeEnum"/>
<field id="url_icon" xsi:type="AttributeURL"/>
<field id="url_application" xsi:type="AttributeString"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="user_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="user_id_finalclass_recall" xsi:type="AttributeExternalField"/>
<field id="notify_contact_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="notify_contact_id_finalclass_recall" xsi:type="AttributeExternalField"/>
<field id="notify_contact_id_obsolescence_flag" xsi:type="AttributeExternalField"/>
</fields>
</class>
<class id="SynchroAttribute" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>core/cmdb,view_in_gui,grant_by_profile</category>
</properties>
<fields>
<field id="sync_source_id" xsi:type="AttributeExternalKey">
<target_class>SynchroDataSource</target_class>
</field>
<field id="sync_source_name" xsi:type="AttributeExternalField"/>
<field id="attcode" xsi:type="AttributeString"/>
<field id="update" xsi:type="AttributeBoolean"/>
<field id="reconcile" xsi:type="AttributeBoolean"/>
<field id="update_policy" xsi:type="AttributeEnum"/>
<field id="finalclass" xsi:type="AttributeFinalClass"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="sync_source_id_friendlyname" xsi:type="AttributeExternalField"/>
</fields>
</class>
<class id="AuditRule" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>application, grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="query" xsi:type="AttributeOQL"/>
<field id="valid_flag" xsi:type="AttributeEnum"/>
<field id="category_id" xsi:type="AttributeExternalKey">
<target_class>AuditCategory</target_class>
</field>
<field id="category_name" xsi:type="AttributeExternalField"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="category_id_friendlyname" xsi:type="AttributeExternalField"/>
</fields>
</class>
<class id="AuditCategory" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>application, grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="definition_set" xsi:type="AttributeOQL"/>
<field id="rules_list" xsi:type="AttributeLinkedSet"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="Query" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>core/cmdb,view_in_gui,application,grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeText"/>
<field id="fields" xsi:type="AttributeText"/>
<field id="finalclass" xsi:type="AttributeFinalClass"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="lnkTriggerAction" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>
<properties>
<category>grant_by_profile,core/cmdb,application</category>
</properties>
<fields>
<field id="action_id" xsi:type="AttributeExternalKey">
<target_class>Action</target_class>
</field>
<field id="action_name" xsi:type="AttributeExternalField"/>
<field id="trigger_id" xsi:type="AttributeExternalKey">
<target_class>Trigger</target_class>
</field>
<field id="trigger_name" xsi:type="AttributeExternalField"/>
<field id="order" xsi:type="AttributeInteger"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
<field id="action_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="action_id_finalclass_recall" xsi:type="AttributeExternalField"/>
<field id="trigger_id_friendlyname" xsi:type="AttributeExternalField"/>
<field id="trigger_id_finalclass_recall" xsi:type="AttributeExternalField"/>
</fields>
</class>
</classes>
</meta>
</itop_design>

View File

@@ -568,7 +568,8 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
}
return $this->m_aOrigValues[$sAttCode];
$aOrigValues = $this->m_aOrigValues;
return isset($aOrigValues[$sAttCode]) ? $aOrigValues[$sAttCode] : null;
}
/**
@@ -1234,7 +1235,7 @@ abstract class DBObject implements iDisplay
elseif ($oAtt->IsScalar())
{
$aValues = $oAtt->GetAllowedValues($this->ToArgsForQuery());
if (count($aValues) > 0)
if (is_array($aValues) && (count($aValues) > 0))
{
if (!array_key_exists($toCheck, $aValues))
{
@@ -2939,7 +2940,7 @@ abstract class DBObject implements iDisplay
$oSearch->AllowAllData();
}
$oSet = new CMDBObjectSet($oSearch);
if ($oSet->Count() > 0)
if ($oSet->CountExceeds(0))
{
$aDependentObjects[$sRemoteClass][$sExtKeyAttCode] = array(
'attribute' => $oExtKeyAttDef,

View File

@@ -100,8 +100,13 @@ class DBObjectSearch extends DBSearch
/**
* Change the class (only subclasses are supported as of now, because the conditions must fit the new class)
* Defaults to the first selected class (most of the time it is also the first joined class
*/
* Defaults to the first selected class (most of the time it is also the first joined class
*
* @param $sNewClass
* @param null $sAlias
*
* @throws \CoreException
*/
public function ChangeClass($sNewClass, $sAlias = null)
{
if (is_null($sAlias))
@@ -186,7 +191,9 @@ class DBObjectSearch extends DBSearch
*
* @param $sOldName
* @param $sNewName
*
* @return bool True if the alias has been found and changed
* @throws \Exception
*/
public function RenameAlias($sOldName, $sNewName)
{
@@ -381,7 +388,7 @@ class DBObjectSearch extends DBSearch
else
{
$oAttDef = MetaModel::GetAttributeDef($this->GetClass(), $sFilterCode);
$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams, $bParseSearchString);
$oNewCondition = $oAttDef->GetSmartConditionExpression($value, $oField, $this->m_aParams);
$this->AddConditionExpression($oNewCondition);
return;
}
@@ -490,6 +497,8 @@ class DBObjectSearch extends DBSearch
* @param bool $bPositiveMatch if true will add a IN filter, else a NOT IN
*
* @throws \CoreException
*
* @since 2.5 N°1418
*/
public function AddConditionForInOperatorUsingParam($sFilterCode, $aValues, $bPositiveMatch = true)
{
@@ -499,9 +508,11 @@ class DBObjectSearch extends DBSearch
$sInParamName = $this->GenerateUniqueParamName();
$oParamExpression = new VariableExpression($sInParamName);
$this->SetInternalParams(array($sInParamName => $aValues));
$this->GetInternalParamsByRef()[$sInParamName] = $aValues;
$oInCondition = new BinaryExpression($oFieldExpression, $sOperator, $oParamExpression);
$oListExpression = new ListExpression(array($oParamExpression));
$oInCondition = new BinaryExpression($oFieldExpression, $sOperator, $oListExpression);
$this->AddConditionExpression($oInCondition);
}
@@ -522,10 +533,12 @@ class DBObjectSearch extends DBSearch
/**
* Specify a condition on external keys or link sets
* @param sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
* Example: infra_list->ci_id->location_id->country
* @param value The value to match (can be an array => IN(val1, val2...)
* @param string $sAttSpec Can be either an attribute code or extkey->[sAttSpec] or linkset->[sAttSpec] and so on, recursively
* Example: infra_list->ci_id->location_id->country
* @param $value
* @return void
* @throws \CoreException
* @throws \CoreWarning
*/
public function AddConditionAdvanced($sAttSpec, $value)
{
@@ -722,9 +735,9 @@ class DBObjectSearch extends DBSearch
* - convert a translation table (format optimized for the translation in an expression tree) into simple hash
* - compile over an eventually existing map
*
* @param $aRealiasingMap Map to update
* @param $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
* @return array of <old-alias> => <new-alias>
* @param array $aRealiasingMap Map to update
* @param array $aAliasTranslation Translation table resulting from calls to MergeWith_InNamespace
* @return void of <old-alias> => <new-alias>
*/
protected function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
{
@@ -754,7 +767,7 @@ class DBObjectSearch extends DBSearch
* This a workaround to handle some cases in which the list of classes is not correctly updated.
* This code should disappear as soon as DBObjectSearch get split between a container search class and a Node class
*
* @param $aClasses List to be completed
* @param array $aClasses List to be completed
*/
protected function RecomputeClassList(&$aClasses)
{
@@ -874,6 +887,8 @@ class DBObjectSearch extends DBSearch
* @param $sForeignExtKeyAttCode
* @param int $iOperatorCode
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
* @return void
* @throws \CoreException
*/
public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, $iOperatorCode = TREE_OPERATOR_EQUALS, &$aRealiasingMap = null)
{
@@ -896,7 +911,7 @@ class DBObjectSearch extends DBSearch
// NO: $oFilter = $oFilter->DeepClone();
// See also: Trac #639, and self::AddCondition_PointingTo()
$aAliasTranslation = array();
$res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
$this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation, $iOperatorCode);
$this->TransferConditionExpression($oFilter, $aAliasTranslation);
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
@@ -920,7 +935,6 @@ class DBObjectSearch extends DBSearch
}
}
$this->RecomputeClassList($this->m_aClasses);
return $res;
}
protected function AddCondition_ReferencedBy_InNameSpace(DBSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation, $iOperatorCode)
@@ -979,7 +993,10 @@ class DBObjectSearch extends DBSearch
$oRightFilter = $oRightFilter->DeepClone();
$bAllowAllData = ($oLeftFilter->IsAllDataAllowed() && $oRightFilter->IsAllDataAllowed());
$oLeftFilter->AllowAllData($bAllowAllData);
if ($bAllowAllData)
{
$oLeftFilter->AllowAllData();
}
if ($oLeftFilter->GetClass() != $oRightFilter->GetClass())
{
@@ -1071,11 +1088,45 @@ class DBObjectSearch extends DBSearch
return $this->m_aParams = $aParams;
}
/**
* @return array <strong>warning</strong> : array returned by value
* @see self::GetInternalParamsByRef to get the attribute by reference
*/
public function GetInternalParams()
{
return $this->m_aParams;
}
/**
* @return array
* @see http://php.net/manual/en/language.references.return.php
* @since 2.5.1 N°1582
*/
public function &GetInternalParamsByRef()
{
return $this->m_aParams;
}
/**
* @param string $sKey
* @param mixed $value
* @param bool $bDoNotOverride
*
* @throws \CoreUnexpectedValue if $bDoNotOverride and $sKey already exists
*/
public function AddInternalParam($sKey, $value, $bDoNotOverride = false)
{
if ($bDoNotOverride)
{
if (array_key_exists($sKey, $this->m_aParams))
{
throw new CoreUnexpectedValue("The key $sKey already exists with value : ".$this->m_aParams[$sKey]);
}
}
$this->m_aParams[$sKey] = $value;
}
public function GetQueryParams($bExcludeMagicParams = true)
{
$aParams = array();
@@ -1124,117 +1175,17 @@ class DBObjectSearch extends DBSearch
{
return $this->m_oSearchCondition->ListConstantFields();
}
/**
* Turn the parameters (:xxx) into scalar values in order to easily
* serialize a search
*/
* @param $aArgs
*/
public function ApplyParameters($aArgs)
{
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
$this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
}
/**
*
* @todo: check if the clone is mandatory or optional. (the performance would be better without cloning)
*
* @param bool $bClone
*
* @return DBObjectSearch|DBSearch
*/
public function ShorthandExpansion($bClone = false)
{
if ($bClone)
{
$oDbObject = $this->DeepClone();
}
else
{
$oDbObject = $this;
}
/**
* This callback add joins based on the classes found inside ExternalFieldExpression::$aFields
* to do so, it is applied on each Expression of $this->m_oSearchCondition.
*
* @param $oExpression
*/
$callback = function ($oExpression) use ($oDbObject)
{
if (!$oExpression instanceof ExternalFieldExpression) {
return;
}
/** @var FieldExpression[] $aFieldExpressionsPointingTo */
$aFields = $oExpression->GetFields();
$aRealiasingMap = array();
$aExpressionNewConditions = array();
foreach ($aFields as $aFieldExpressionPointingTo)
{
$oFilter = new DBObjectSearch($aFieldExpressionPointingTo['sClass']);
$aExpressionNewConditions[] = array(
'oFilter' => $oFilter,
'sExtKeyAttCode' => $aFieldExpressionPointingTo['sAttCode'],
);
$aRealiasingMap[$aFieldExpressionPointingTo['sClass']] = $aFieldExpressionPointingTo['sAlias'];
}
/**
* the iteration below is weird because wee need to
* - iterate in the reverse order
* - the iteration access the "index+1" so wee start at "length-1" (which is "count()-2")
* - whe stop at the index 1, because the index 0 is merged into the $oDbObject
*/
for ($i = count($aExpressionNewConditions) - 2; $i > 0; $i--)
{
$aExpressionNewConditions[$i]['oFilter']->AddCondition_PointingTo(
$aExpressionNewConditions[$i+1]['oFilter'],
$aExpressionNewConditions[$i]['sExtKeyAttCode'],
TREE_OPERATOR_EQUALS,
$aRealiasingMap
);
}
$oDbObject->AddCondition_PointingTo(
$aExpressionNewConditions[1]['oFilter'],
$aExpressionNewConditions[0]['sExtKeyAttCode'],
TREE_OPERATOR_EQUALS,
$aRealiasingMap
);
foreach ($aRealiasingMap as $sOldAlias => $sNewAlias)
{
if ($sOldAlias == $sNewAlias)
{
continue;
}
if ($sOldAlias != $aFields['sAlias'])
{
continue;
}
$aFields['sAlias'] = $sNewAlias;
}
$oExpression->SetFields($aFields);
}; //end of the callback definition
//Add the joins
$this->m_oSearchCondition->Browse($callback);
//replace the ExternalFieldExpression by a FieldExpression (based on last ExternalFieldExpression::$aFields)
$this->m_oSearchCondition->Translate(array(), false, false);//TODO: check if this call is correct
return $oDbObject;
}
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
{
// Currently unused, but could be useful later
@@ -1363,23 +1314,6 @@ class DBObjectSearch extends DBSearch
$oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases);
return new BinaryExpression($oLeft, $sOperator, $oRight);
}
elseif ($oExpression instanceof ExternalFieldOqlExpression)
{
//TODO : convert FieldOqlExpression[] to FieldExpression[]
$aOqlFieldExpression = $oExpression->GetExpressions();
$aFields = array();
foreach ($aOqlFieldExpression as $oOqlFieldExpression)
{
$aFields[] = array(
'sClass' => $oOqlFieldExpression->GetParent(),
'sAlias' => $oOqlFieldExpression->GetParent(),
'sAttCode' => $oOqlFieldExpression->GetName(),
);
}
return new ExternalFieldExpression($oExpression->GetName(), $aFields);
}
elseif ($oExpression instanceof FieldOqlExpression)
{
$sClassAlias = $oExpression->GetParent();
@@ -1618,11 +1552,6 @@ class DBObjectSearch extends DBSearch
$oSearch = $this->Intersect($oVisibleObjects);
$oSearch->SetDataFiltered();
}
else
{
// should be true at this point, meaning that no additional filtering
// is required
}
}
// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
@@ -1648,7 +1577,7 @@ class DBObjectSearch extends DBSearch
$aContextData['sRequestUri'] = '';
}
// Need to identify the querySELECT `Contact` FROM Contact AS `Contact` JOIN Organization AS `Organization` ON `Contact`.org_id = `Organization`.id JOIN DeliveryModel AS `DeliveryModel` ON `Organization`.deliverymodel_id = `DeliveryModel`.id WHERE ((((Contact.org_id->deliverymodel_id->name = 'Standard support') AND 1) AND 1) AND 1)
// Need to identify the query
$sOqlQuery = $oSearch->ToOql(false, null, true);
if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false))
{
@@ -1754,8 +1683,7 @@ class DBObjectSearch extends DBSearch
if (!isset($oSQLQuery))
{
$oSearch = $oSearch->ShorthandExpansion(true);//TODO : check if the clone is really needed (1st param of ShorthandExpansion)
$oKPI = new ExecutionKPI();
$oKPI = new ExecutionKPI();
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
@@ -1782,7 +1710,9 @@ class DBObjectSearch extends DBSearch
* @param array $aGroupByExpr
* @param array $aSelectedClasses
* @param array $aSelectExpr
*
* @return null|SQLObjectQuery
* @throws \CoreException
*/
protected function BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr = null, $aSelectedClasses = null, $aSelectExpr = null)
{
@@ -1790,7 +1720,7 @@ class DBObjectSearch extends DBSearch
$oSQLQuery = $this->MakeSQLObjectQuery($oBuild, $aAttToLoad, array());
$oSQLQuery->SetCondition($oBuild->m_oQBExpressions->GetCondition());
if ($aGroupByExpr)
if (is_array($aGroupByExpr))
{
$aCols = $oBuild->m_oQBExpressions->GetGroupBy();
$oSQLQuery->SetGroupBy($aCols);
@@ -1844,6 +1774,7 @@ class DBObjectSearch extends DBSearch
* @param null $aAttToLoad
* @param array $aValues
* @return null|SQLObjectQuery
* @throws \CoreException
*/
protected function MakeSQLObjectQuery(&$oBuild, $aAttToLoad = null, $aValues = array())
{
@@ -2324,7 +2255,7 @@ class DBObjectSearch extends DBSearch
self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField");
if ($oKeyAttDef->IsNullAllowed())
{
$oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField, $sExternalKeyTable);
$oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField);
}
else
{
@@ -2376,9 +2307,13 @@ class DBObjectSearch extends DBSearch
}
/**
* Get the expression for the class and its subclasses (if finalclass = 'subclass' ...)
* Simplifies the final expression by grouping classes having the same expression
*/
* Get the expression for the class and its subclasses (if finalclass = 'subclass' ...)
* Simplifies the final expression by grouping classes having the same expression
* @param $sClass
* @param $sAttCode
* @return \FunctionExpression|mixed|null
* @throws \CoreException
*/
static public function GetPolymorphicExpression($sClass, $sAttCode)
{
$oExpression = ExpressionCache::GetCachedExpression($sClass, $sAttCode);

View File

@@ -77,18 +77,20 @@ class DBObjectSet implements iDBObjectSetIterator
* @var mysqli_result
*/
protected $m_oSQLResult;
protected $m_bSort;
/**
* Create a new set based on a Search definition.
*
* @param DBSearch $oFilter The search filter defining the objects which are part of the set (multiple columns/objects per row are supported)
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param hash $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
* @param hash $aExtendedDataSpec
* @param array $aOrderBy Array of '[<classalias>.]attcode' => bAscending
* @param array $aArgs Values to substitute for the search/query parameters (if any). Format: param_name => value
* @param array $aExtendedDataSpec
* @param int $iLimitCount Maximum number of rows to load (i.e. equivalent to MySQL's LIMIT start, count)
* @param int $iLimitStart Index of the first row to load (i.e. equivalent to MySQL's LIMIT start, count)
* @param bool $bSort if false no order by is done
*/
public function __construct(DBSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0)
public function __construct(DBSearch $oFilter, $aOrderBy = array(), $aArgs = array(), $aExtendedDataSpec = null, $iLimitCount = 0, $iLimitStart = 0, $bSort = true)
{
$this->m_oFilter = $oFilter->DeepClone();
$this->m_aAddedIds = array();
@@ -98,6 +100,7 @@ class DBObjectSet implements iDBObjectSetIterator
$this->m_aExtendedDataSpec = $aExtendedDataSpec;
$this->m_iLimitCount = $iLimitCount;
$this->m_iLimitStart = $iLimitStart;
$this->m_bSort = $bSort;
$this->m_iNumTotalDBRows = null;
$this->m_iNumLoadedDBRows = 0;
@@ -567,7 +570,7 @@ class DBObjectSet implements iDBObjectSetIterator
$aAttributes = array();
foreach ($aAliases as $sAlias => $bClassDirection)
{
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClass($sAlias)) as $sAttCode => $bAttributeDirection)
foreach (MetaModel::GetOrderByDefault($this->m_oFilter->GetClassName($sAlias)) as $sAttCode => $bAttributeDirection)
{
$bDirection = $bClassDirection ? $bAttributeDirection : !$bAttributeDirection;
$aAttributes[$sAlias.'.'.$sAttCode] = $bDirection;
@@ -601,10 +604,15 @@ class DBObjectSet implements iDBObjectSetIterator
*
* Limitation: the sort order has no effect on objects added in-memory
*
* @return hash Format: field_code => boolean (true = ascending, false = descending)
* @return array Format: field_code => boolean (true = ascending, false = descending)
*/
public function GetRealSortOrder()
{
if (!$this->m_bSort)
{
// No order by
return array();
}
// Get the class default sort order if not specified with the API
//
if (empty($this->m_aOrderBy))
@@ -719,7 +727,75 @@ class DBObjectSet implements iDBObjectSetIterator
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
}
/** Check if the count exceeds a given limit
* @param $iLimit
*
* @return bool
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function CountExceeds($iLimit)
{
if (is_null($this->m_iNumTotalDBRows))
{
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
CMDBSource::FreeResult($resQuery);
$iCount = intval($aRow['COUNT']);
}
else
{
$iCount = 0;
}
}
else
{
$iCount = $this->m_iNumTotalDBRows;
}
return ($iCount > $iLimit);
}
/** Count only up to the given limit
* @param $iLimit
*
* @return int
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function CountWithLimit($iLimit)
{
if (is_null($this->m_iNumTotalDBRows))
{
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL);
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
CMDBSource::FreeResult($resQuery);
$iCount = intval($aRow['COUNT']);
}
else
{
$iCount = 0;
}
}
else
{
$iCount = $this->m_iNumTotalDBRows;
}
return $iCount;
}
/**
* Number of rows available in memory (loaded from DB + added in memory)
*

View File

@@ -1,20 +1,23 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Copyright (c) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Design document and associated nodes
@@ -57,6 +60,9 @@ class DesignDocument extends DOMDocument
/**
* Overload of the standard API
*
* @param $filename
* @param int $options
*/
public function load($filename, $options = 0)
{
@@ -65,6 +71,11 @@ class DesignDocument extends DOMDocument
/**
* Overload of the standard API
*
* @param $filename
* @param int $options
*
* @return int
*/
public function save($filename, $options = 0)
{
@@ -84,18 +95,18 @@ class DesignDocument extends DOMDocument
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
return '';
}
/**
* Quote and escape strings for use within an XPath expression
* Usage: DesignDocument::GetNodes('class[@id='.DesignDocument::XPathQuote($sId).']');
* @param $sValue The value to be quoted
* @param string $sValue The value to be quoted
* @return string to be used within an XPath
*/
public static function XPathQuote($sValue)
@@ -115,7 +126,7 @@ class DesignDocument extends DOMDocument
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @param DesignNode|null $oContextNode The node to start the search from
* @param DesignElement $oContextNode The node to start the search from
* @return \DOMNodeList
*/
public function GetNodes($sXPath, $oContextNode = null)
@@ -134,7 +145,7 @@ class DesignDocument extends DOMDocument
/**
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
* @param $oNode The node to describe
* @param DesignElement $oNode The node to describe
* @return string
*/
public static function GetItopNodePath($oNode)
@@ -166,8 +177,11 @@ class DesignElement extends \DOMElement
/**
* Create an HTML representation of the DOM, for debugging purposes
*
* @param bool|false $bReturnRes Echoes or returns the HTML representation
*
* @return mixed void or the HTML representation of the DOM
* @throws \Exception
*/
public function Dump($bReturnRes = false)
{
@@ -180,19 +194,16 @@ class DesignElement extends \DOMElement
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
return '';
}
/**
* Returns the node directly under the given node
* @param $sTagName
* @param bool|true $bMustExist
* @return MFElement
* @return \MFElement
* @throws DOMFormatException
*/
public function GetUniqueElement($sTagName, $bMustExist = true)
@@ -216,7 +227,7 @@ class DesignElement extends \DOMElement
/**
* Returns the node directly under the current node, or null if missing
* @param $sTagName
* @return MFElement
* @return \MFElement
* @throws DOMFormatException
*/
public function GetOptionalElement($sTagName)
@@ -252,9 +263,12 @@ class DesignElement extends \DOMElement
/**
* Get the TEXT value from a child node
*
* @param string $sTagName
* @param string|null $sDefault
*
* @return string
* @throws \DOMFormatException
*/
public function GetChildText($sTagName, $sDefault = null)
{

View File

@@ -1438,22 +1438,22 @@ class DisplayableGraph extends SimpleGraph
}
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
}
$sSftShort = Dict::S('UI:ElementsDisplayed');
$sSearchToggle = Dict::S('UI:Search:Toggle');
$oP->add("<div class=\"not-printable\">\n");
$oP->add("<div id=\"ds_flash\" class=\"search_box\" style=\"display:none;\">\n");
if (!$oP->IsPrintableVersion())
{
$oP->add_ready_script(
$oP->add(
<<<EOF
$( "#tabbedContent_0" ).tabs({ heightStyle: "fill" });
<div id="ds_flash" class="search_box">
<form id="dh_flash" class="search_form_handler closed">
<h2 class="sf_title"><span class="sft_long">$sSftShort</span><span class="sft_short">$sSftShort</span><span class="sft_toggler fa fa-caret-down pull-right" title="$sSearchToggle"></span></h2>
<div id="dh_flash_criterion_outer" class="sf_criterion_area"><div class="sf_criterion_row">
EOF
);
}
);
$oP->add_ready_script(
<<<EOF
$("#dh_flash").click( function() {
$("#ds_flash").slideToggle('normal', function() { $("#ds_flash").parent().resize(); $("#dh_flash").trigger('toggle_complete'); } );
$("#dh_flash").toggleClass('open');
$("#dh_flash > .sf_title").click( function() {
$("#dh_flash").toggleClass('closed');
});
$('#ReloadMovieBtn').button().button('disable');
EOF
@@ -1476,9 +1476,8 @@ EOF
$idx++;
}
$oP->add("<p style=\"text-align:right\"><button type=\"button\" id=\"ReloadMovieBtn\" onClick=\"DoReload()\">".Dict::S('UI:Button:Refresh')."</button></p>");
$oP->add("</div></div></form>");
$oP->add("</div>\n");
$oP->add("<div class=\"HRDrawer\"></div>\n");
$oP->add("<div id=\"dh_flash\" class=\"DrawerHandle\">".Dict::S('UI:ElementsDisplayed')."</div>\n");
$oP->add("</div>\n"); // class="not-printable"
$aAdditionalContexts = array();

View File

@@ -57,6 +57,7 @@ class EMail
{
$this->m_aData = array();
$this->m_oMessage = Swift_Message::newInstance();
$this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label'));
}
/**
@@ -260,6 +261,11 @@ class EMail
public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null)
{
//select a default sender if none is provided.
if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){
$this->SetRecipientFrom($this->m_aData['to']);
}
if ($bForceSynchronous)
{
return $this->SendSynchronous($aIssues, $oLog);

View File

@@ -184,9 +184,9 @@ class HTMLDOMSanitizer extends HTMLSanitizer
'table' => array('style', 'width', 'summary', 'align', 'border', 'cellpadding', 'cellspacing'),
'thead' => array('style'),
'tbody' => array('style'),
'tr' => array('style'),
'td' => array('style', 'colspan'),
'th' => array('style'),
'tr' => array('style', 'colspan', 'rowspan'),
'td' => array('style', 'colspan', 'rowspan'),
'th' => array('style', 'colspan', 'rowspan'),
'fieldset' => array('style'),
'legend' => array('style'),
'font' => array('face', 'color', 'style', 'size'),

View File

@@ -497,10 +497,10 @@ EOF
*/
class InlineImageGC implements iBackgroundProcess
{
public function GetPeriodicity()
{
return MetaModel::GetConfig()->Get('inline_image_garbage_collector_interval'); // run every definied time
}
public function GetPeriodicity()
{
return 3600; // Runs every hour
}
public function Process($iTimeLimit)
{

View File

@@ -638,6 +638,20 @@ abstract class MetaModel
return reset($aAttributes);
}
/**
* Returns the list of attributes composing the friendlyname
*
* @param $sClass
*
* @return array
*/
final static public function GetFriendlyNameAttributeCodeList($sClass)
{
$aNameSpec = self::GetNameSpec($sClass);
$aAttributes = $aNameSpec[1];
return $aAttributes;
}
/**
* @param string $sClass
*
@@ -5034,6 +5048,7 @@ abstract class MetaModel
// Check that any defined field exists
//
$aTableInfo['Fields'][$sKeyField]['used'] = true;
$aFriendlynameAttcodes = self::GetFriendlyNameAttributeCodeList($sClass);
foreach(self::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if (!$oAttDef->CopyOnAllTables())
@@ -5050,6 +5065,14 @@ abstract class MetaModel
$aTableInfo['Fields'][$sField]['used'] = true;
$bIndexNeeded = $oAttDef->RequiresIndex();
if (!$bIndexNeeded)
{
// Add an index on the columns of the friendlyname
if (in_array($sField, $aFriendlynameAttcodes))
{
$bIndexNeeded = true;
}
}
$sFieldDefinition = "`$sField` $sDBFieldSpec";
if (!CMDBSource::IsField($sTable, $sField))
@@ -5155,6 +5178,11 @@ abstract class MetaModel
{
$sIndexId = implode('_', $aColumns);
if (isset($aTableInfo['Indexes'][$sIndexId]['used']) && $aTableInfo['Indexes'][$sIndexId]['used'])
{
continue;
}
$aLength = self::DBGetIndexesLength($sClass, $aColumns, $aTableInfo);
$aTableInfo['Indexes'][$sIndexId]['used'] = true;
@@ -6185,7 +6213,7 @@ abstract class MetaModel
{
$sQuerySign .= '_all_';
}
if (count($aModifierProperties))
if (is_array($aModifierProperties) && (count($aModifierProperties) > 0))
{
array_multisort($aModifierProperties);
$sModifierProperties = json_encode($aModifierProperties);

File diff suppressed because it is too large Load Diff

View File

@@ -115,8 +115,6 @@ class OQLLexerRaw
'/\GWHERE/ ',
'/\GJOIN/ ',
'/\GON/ ',
'/\G->/ ',
'/\G:/ ',
'/\G\// ',
'/\G\\*/ ',
'/\G\\+/ ',
@@ -318,339 +316,329 @@ class OQLLexerRaw
function yy_r1_8($yy_subpatterns)
{
$this->token = OQLParser::ARROW;
$this->token = OQLParser::MATH_DIV;
}
function yy_r1_9($yy_subpatterns)
{
$this->token = OQLParser::COLON;
$this->token = OQLParser::MATH_MULT;
}
function yy_r1_10($yy_subpatterns)
{
$this->token = OQLParser::MATH_DIV;
$this->token = OQLParser::MATH_PLUS;
}
function yy_r1_11($yy_subpatterns)
{
$this->token = OQLParser::MATH_MULT;
$this->token = OQLParser::MATH_MINUS;
}
function yy_r1_12($yy_subpatterns)
{
$this->token = OQLParser::MATH_PLUS;
$this->token = OQLParser::LOG_AND;
}
function yy_r1_13($yy_subpatterns)
{
$this->token = OQLParser::MATH_MINUS;
$this->token = OQLParser::LOG_OR;
}
function yy_r1_14($yy_subpatterns)
{
$this->token = OQLParser::LOG_AND;
$this->token = OQLParser::BITWISE_OR;
}
function yy_r1_15($yy_subpatterns)
{
$this->token = OQLParser::LOG_OR;
$this->token = OQLParser::BITWISE_AND;
}
function yy_r1_16($yy_subpatterns)
{
$this->token = OQLParser::BITWISE_OR;
$this->token = OQLParser::BITWISE_XOR;
}
function yy_r1_17($yy_subpatterns)
{
$this->token = OQLParser::BITWISE_AND;
$this->token = OQLParser::BITWISE_LEFT_SHIFT;
}
function yy_r1_18($yy_subpatterns)
{
$this->token = OQLParser::BITWISE_XOR;
$this->token = OQLParser::BITWISE_RIGHT_SHIFT;
}
function yy_r1_19($yy_subpatterns)
{
$this->token = OQLParser::BITWISE_LEFT_SHIFT;
$this->token = OQLParser::COMA;
}
function yy_r1_20($yy_subpatterns)
{
$this->token = OQLParser::BITWISE_RIGHT_SHIFT;
$this->token = OQLParser::PAR_OPEN;
}
function yy_r1_21($yy_subpatterns)
{
$this->token = OQLParser::COMA;
$this->token = OQLParser::PAR_CLOSE;
}
function yy_r1_22($yy_subpatterns)
{
$this->token = OQLParser::PAR_OPEN;
$this->token = OQLParser::REGEXP;
}
function yy_r1_23($yy_subpatterns)
{
$this->token = OQLParser::PAR_CLOSE;
$this->token = OQLParser::EQ;
}
function yy_r1_24($yy_subpatterns)
{
$this->token = OQLParser::REGEXP;
$this->token = OQLParser::NOT_EQ;
}
function yy_r1_25($yy_subpatterns)
{
$this->token = OQLParser::EQ;
$this->token = OQLParser::GT;
}
function yy_r1_26($yy_subpatterns)
{
$this->token = OQLParser::NOT_EQ;
$this->token = OQLParser::LT;
}
function yy_r1_27($yy_subpatterns)
{
$this->token = OQLParser::GT;
$this->token = OQLParser::GE;
}
function yy_r1_28($yy_subpatterns)
{
$this->token = OQLParser::LT;
$this->token = OQLParser::LE;
}
function yy_r1_29($yy_subpatterns)
{
$this->token = OQLParser::GE;
$this->token = OQLParser::LIKE;
}
function yy_r1_30($yy_subpatterns)
{
$this->token = OQLParser::LE;
$this->token = OQLParser::NOT_LIKE;
}
function yy_r1_31($yy_subpatterns)
{
$this->token = OQLParser::LIKE;
$this->token = OQLParser::IN;
}
function yy_r1_32($yy_subpatterns)
{
$this->token = OQLParser::NOT_LIKE;
$this->token = OQLParser::NOT_IN;
}
function yy_r1_33($yy_subpatterns)
{
$this->token = OQLParser::IN;
$this->token = OQLParser::INTERVAL;
}
function yy_r1_34($yy_subpatterns)
{
$this->token = OQLParser::NOT_IN;
$this->token = OQLParser::F_IF;
}
function yy_r1_35($yy_subpatterns)
{
$this->token = OQLParser::INTERVAL;
$this->token = OQLParser::F_ELT;
}
function yy_r1_36($yy_subpatterns)
{
$this->token = OQLParser::F_IF;
$this->token = OQLParser::F_COALESCE;
}
function yy_r1_37($yy_subpatterns)
{
$this->token = OQLParser::F_ELT;
$this->token = OQLParser::F_ISNULL;
}
function yy_r1_38($yy_subpatterns)
{
$this->token = OQLParser::F_COALESCE;
$this->token = OQLParser::F_CONCAT;
}
function yy_r1_39($yy_subpatterns)
{
$this->token = OQLParser::F_ISNULL;
$this->token = OQLParser::F_SUBSTR;
}
function yy_r1_40($yy_subpatterns)
{
$this->token = OQLParser::F_CONCAT;
$this->token = OQLParser::F_TRIM;
}
function yy_r1_41($yy_subpatterns)
{
$this->token = OQLParser::F_SUBSTR;
$this->token = OQLParser::F_DATE;
}
function yy_r1_42($yy_subpatterns)
{
$this->token = OQLParser::F_TRIM;
$this->token = OQLParser::F_DATE_FORMAT;
}
function yy_r1_43($yy_subpatterns)
{
$this->token = OQLParser::F_DATE;
$this->token = OQLParser::F_CURRENT_DATE;
}
function yy_r1_44($yy_subpatterns)
{
$this->token = OQLParser::F_DATE_FORMAT;
$this->token = OQLParser::F_NOW;
}
function yy_r1_45($yy_subpatterns)
{
$this->token = OQLParser::F_CURRENT_DATE;
$this->token = OQLParser::F_TIME;
}
function yy_r1_46($yy_subpatterns)
{
$this->token = OQLParser::F_NOW;
$this->token = OQLParser::F_TO_DAYS;
}
function yy_r1_47($yy_subpatterns)
{
$this->token = OQLParser::F_TIME;
$this->token = OQLParser::F_FROM_DAYS;
}
function yy_r1_48($yy_subpatterns)
{
$this->token = OQLParser::F_TO_DAYS;
$this->token = OQLParser::F_YEAR;
}
function yy_r1_49($yy_subpatterns)
{
$this->token = OQLParser::F_FROM_DAYS;
$this->token = OQLParser::F_MONTH;
}
function yy_r1_50($yy_subpatterns)
{
$this->token = OQLParser::F_YEAR;
$this->token = OQLParser::F_DAY;
}
function yy_r1_51($yy_subpatterns)
{
$this->token = OQLParser::F_MONTH;
$this->token = OQLParser::F_HOUR;
}
function yy_r1_52($yy_subpatterns)
{
$this->token = OQLParser::F_DAY;
$this->token = OQLParser::F_MINUTE;
}
function yy_r1_53($yy_subpatterns)
{
$this->token = OQLParser::F_HOUR;
$this->token = OQLParser::F_SECOND;
}
function yy_r1_54($yy_subpatterns)
{
$this->token = OQLParser::F_MINUTE;
$this->token = OQLParser::F_DATE_ADD;
}
function yy_r1_55($yy_subpatterns)
{
$this->token = OQLParser::F_SECOND;
$this->token = OQLParser::F_DATE_SUB;
}
function yy_r1_56($yy_subpatterns)
{
$this->token = OQLParser::F_DATE_ADD;
$this->token = OQLParser::F_ROUND;
}
function yy_r1_57($yy_subpatterns)
{
$this->token = OQLParser::F_DATE_SUB;
$this->token = OQLParser::F_FLOOR;
}
function yy_r1_58($yy_subpatterns)
{
$this->token = OQLParser::F_ROUND;
$this->token = OQLParser::F_INET_ATON;
}
function yy_r1_59($yy_subpatterns)
{
$this->token = OQLParser::F_FLOOR;
$this->token = OQLParser::F_INET_NTOA;
}
function yy_r1_60($yy_subpatterns)
{
$this->token = OQLParser::F_INET_ATON;
$this->token = OQLParser::BELOW;
}
function yy_r1_61($yy_subpatterns)
{
$this->token = OQLParser::F_INET_NTOA;
$this->token = OQLParser::BELOW_STRICT;
}
function yy_r1_62($yy_subpatterns)
{
$this->token = OQLParser::BELOW;
$this->token = OQLParser::NOT_BELOW;
}
function yy_r1_63($yy_subpatterns)
{
$this->token = OQLParser::BELOW_STRICT;
$this->token = OQLParser::NOT_BELOW_STRICT;
}
function yy_r1_64($yy_subpatterns)
{
$this->token = OQLParser::NOT_BELOW;
$this->token = OQLParser::ABOVE;
}
function yy_r1_65($yy_subpatterns)
{
$this->token = OQLParser::NOT_BELOW_STRICT;
$this->token = OQLParser::ABOVE_STRICT;
}
function yy_r1_66($yy_subpatterns)
{
$this->token = OQLParser::ABOVE;
$this->token = OQLParser::NOT_ABOVE;
}
function yy_r1_67($yy_subpatterns)
{
$this->token = OQLParser::ABOVE_STRICT;
$this->token = OQLParser::NOT_ABOVE_STRICT;
}
function yy_r1_68($yy_subpatterns)
{
$this->token = OQLParser::NOT_ABOVE;
$this->token = OQLParser::HEXVAL;
}
function yy_r1_69($yy_subpatterns)
{
$this->token = OQLParser::NOT_ABOVE_STRICT;
$this->token = OQLParser::NUMVAL;
}
function yy_r1_70($yy_subpatterns)
{
$this->token = OQLParser::HEXVAL;
$this->token = OQLParser::STRVAL;
}
function yy_r1_71($yy_subpatterns)
{
$this->token = OQLParser::NUMVAL;
$this->token = OQLParser::NAME;
}
function yy_r1_72($yy_subpatterns)
{
$this->token = OQLParser::STRVAL;
}
function yy_r1_73($yy_subpatterns)
{
$this->token = OQLParser::NAME;
}
function yy_r1_74($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
}
function yy_r1_75($yy_subpatterns)
function yy_r1_73($yy_subpatterns)
{
$this->token = OQLParser::DOT;

View File

@@ -88,8 +88,6 @@ where = "WHERE"
join = "JOIN"
on = "ON"
coma = ","
arrow = "->"
colon = ":"
par_open = "("
par_close = ")"
math_div = "/"
@@ -172,7 +170,7 @@ numval = /([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]*)/
dot = "."
dot = "."
*/
/*!lex2php
@@ -200,12 +198,6 @@ join {
on {
$this->token = OQLParser::ON;
}
arrow {
$this->token = OQLParser::ARROW;
}
colon {
$this->token = OQLParser::COLON;
}
math_div {
$this->token = OQLParser::MATH_DIV;
}
@@ -404,7 +396,6 @@ varname {
dot {
$this->token = OQLParser::DOT;
}
*/
}

File diff suppressed because it is too large Load Diff

View File

@@ -154,16 +154,8 @@ scalar(A) ::= str_scalar(X). { A = X; }
num_scalar(A) ::= num_value(X). { A = new ScalarOqlExpression(X); }
str_scalar(A) ::= str_value(X). { A = new ScalarOqlExpression(X); }
basic_field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
basic_field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }
field_id(A) ::= basic_field_id(X). { A = X; }
field_id(A) ::= field_id(X) ARROW name(Y).
{
$expr = new FieldOqlExpression(Y);
A = new ExternalFieldOqlExpression(X, $expr);
}
field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }
class_name(A) ::= name(X). { A=X; }

View File

@@ -168,108 +168,9 @@ class ScalarOqlExpression extends ScalarExpression implements CheckableExpressio
}
}
class ExternalFieldOqlExpression extends ExternalFieldExpression implements CheckableExpression
{
protected $m_aExpression = array();
protected $m_sName = null;
function __construct($oExpr1, $oExpr2)
{
$this->m_sName = '';
if ($oExpr1 instanceof ExternalFieldOqlExpression)
{
$this->m_aExpression = $oExpr1->GetExpressions();
}
else
{
$this->m_aExpression[] = $oExpr1;
}
$this->m_aExpression[] = $oExpr2;
$this->m_sName = $oExpr1->GetValue().'->'.$oExpr2->GetValue();
parent::__construct($this->m_sName, $this->m_aExpression);
}
public function GetExpressions()
{
return $this->m_aExpression;
}
public function GetName()
{
return $this->m_sName;
}
/**
* Check the validity of the expression with regard to the data model
* and the query in which it is used
*
* @param ModelReflection $oModelReflection MetaModel to consider
* @param array $aAliases Aliases to class names (for the current query)
* @param string $sSourceQuery For the reporting
*
* @throws OqlNormalizeException
*/
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
$sParentAlias = null;
foreach($this->m_aExpression as $i => $oFieldOqlExpression)
{
if (is_null($sParentAlias))
{
$oFieldOqlExpression->RefreshAlias($oModelReflection, $aAliases, $sSourceQuery, FieldOqlExpression::ALLOW_EXTERNAL_FIELDS);
}
else
{
$oFieldOqlExpression->SetParent($sParentAlias);
}
$oFieldOqlExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
$sClass = $aAliases[$oFieldOqlExpression->GetParent()];
$bLastIteration = ($i == (count($this->m_aExpression) - 1));
if (!$bLastIteration)
{
if ($oFieldOqlExpression->GetName() == 'id')
{
$sTargetClass = null;
}
else
{
$sTargetClass = $oModelReflection->GetAttributeProperty($sClass, $oFieldOqlExpression->GetName(), 'targetclass');
}
if (is_null($sTargetClass))
{
throw new OqlNormalizeException('Forbidden operation for attribute', $sSourceQuery, $oFieldOqlExpression->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
}
$aAliases[$sTargetClass] = $sTargetClass;
$sParentAlias = $sTargetClass;
}
else
{
if ($oFieldOqlExpression->GetName() != 'id') //on lastIteration, the id field can be used, but since it is not supporter by IsValidAttCode we must avoid it
{
if (!$oModelReflection->IsValidAttCode($sClass, $oFieldOqlExpression->GetName()))
{
throw new OqlNormalizeException('Invalid attribute', $sSourceQuery, $oFieldOqlExpression->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
}
}
}
}
}
}
class FieldOqlExpression extends FieldExpression implements CheckableExpression
{
const ALLOW_EXTERNAL_FIELDS = true;
const DISALLOW_EXTERNAL_FIELDS = false;
protected $m_oParent;
protected $m_oParent;
protected $m_oName;
public function __construct($oName, $oParent = null)
@@ -302,7 +203,23 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
{
// Try to find an alias
// Build an array of field => array of aliases
$this->RefreshAlias($oModelReflection, $aAliases, $sSourceQuery);
$aFieldClasses = array();
foreach($aAliases as $sAlias => $sReal)
{
foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
{
$aFieldClasses[$sAnFltCode][] = $sAlias;
}
}
if (!array_key_exists($sFltCode, $aFieldClasses))
{
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
}
if (count($aFieldClasses[$sFltCode]) > 1)
{
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
}
$sClassAlias = $aFieldClasses[$sFltCode][0];
}
else
{
@@ -317,62 +234,6 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
}
}
}
/**
* Used by self::Check and ExternalFieldOqlExpression::Check => throws exception if error
*
* this method has a side effect : if not previously set $this->m_sParent if computed then set.
*
* @param \ModelReflection $oModelReflection
* @param $aAliases
* @param $sSourceQuery
* @param bool $bAllowExternalFields should external fields be authorised? used only by @see ExternalFieldOqlExpression
*
* @throws OqlNormalizeException
*/
public function RefreshAlias(ModelReflection $oModelReflection, $aAliases, $sSourceQuery, $bAllowExternalFields = self::DISALLOW_EXTERNAL_FIELDS)
{
$sClassAlias = $this->GetParent();
$sFltCode = $this->GetName();
if (empty($sClassAlias))
{
// Try to find an alias
// Build an array of field => array of aliases
$aFieldClasses = array();
foreach($aAliases as $sAlias => $sReal)
{
foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
{
$aFieldClasses[$sAnFltCode][] = $sAlias;
}
}
if (!array_key_exists($sFltCode, $aFieldClasses))
{
if (count($aAliases) > 1)
{
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
}
$sClassAlias = reset($aAliases);
if (self::DISALLOW_EXTERNAL_FIELDS == $bAllowExternalFields || false == $oModelReflection->IsValidAttCode($sClassAlias, $sFltCode))
{
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
}
$this->SetParent($sClassAlias);
}
elseif (count($aFieldClasses[$sFltCode]) > 1)
{
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
}
else
{
$sClassAlias = $aFieldClasses[$sFltCode][0];
$this->SetParent($sClassAlias);
}
}
}
}
class VariableOqlExpression extends VariableExpression implements CheckableExpression

View File

@@ -1 +1 @@
2018-04-26
2015-08-31

View File

@@ -74,6 +74,14 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
*/
protected $iCursor = 0;
/**
* __toString magical function overload.
*/
public function __toString()
{
return '';
}
/**
* ormLinkSet constructor.
* @param $sHostClass
@@ -531,6 +539,27 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
}
}
/**
* Get the list of all modified (added, modified and removed) links
*
* @return array of link objects
* @throws \Exception
*/
public function ListModifiedLinks()
{
$aAdded = $this->aAdded;
$aModified = $this->aModified;
$aRemoved = array();
if (count($this->aRemoved) > 0)
{
$oSearch = new DBObjectSearch($this->sClass);
$oSearch->AddCondition('id', $this->aRemoved, 'IN');
$oSet = new DBObjectSet($oSearch);
$aRemoved = $oSet->ToArray();
}
return array_merge($aAdded, $aModified, $aRemoved);
}
/**
* @param DBObject $oHostObject
*/

View File

@@ -433,15 +433,19 @@ class RelationGraph extends SimpleGraph
}
elseif ($oObjectNode->GetProperty('developped', false))
{
// No need to execute the queries again... just dig into the nodes down/up to iMaxDepth
//
$aRelatedEdges = $bDown ? $oObjectNode->GetOutgoingEdges() : $oObjectNode->GetIncomingEdges();
foreach ($aRelatedEdges as $oRelatedEdge)
{
$oRelatedNode = $bDown ? $oRelatedEdge->GetSinkNode() : $oRelatedEdge->GetSourceNode();
// Recurse (decrement the depth)
$this->AddRelatedObjects($sRelCode, $bDown, $oRelatedNode, $iMaxDepth - 1, $bEnableRedundancy);
}
// No need to explore the underlying graph at all. We can stop here since the node has already been developped.
// Otherwise in case of "loops" in the graph we would recurse up to the max depth limit
// without producing any difference in the resulting graph... but potentially taking a LOOOONG time.
return;
// Former code was
//$aRelatedEdges = $bDown ? $oObjectNode->GetOutgoingEdges() : $oObjectNode->GetIncomingEdges();
//foreach ($aRelatedEdges as $oRelatedEdge)
//{
// $oRelatedNode = $bDown ? $oRelatedEdge->GetSinkNode() : $oRelatedEdge->GetSourceNode();
// // Recurse (decrement the depth)
// $this->AddRelatedObjects($sRelCode, $bDown, $oRelatedNode, $iMaxDepth - 1, $bEnableRedundancy);
//}
}
else
{

View File

@@ -234,7 +234,6 @@ EOF
}
}
$sData = '';
$sData .= '<style>table br {mso-data-placement:same-cell;}</style>'; // Trick for Excel: keep line breaks inside the same cell !
$sData .= "<table border=\"1\">\n";
$sData .= "<tr>\n";
foreach($aData as $sLabel)

View File

@@ -339,6 +339,14 @@ class SQLObjectQuery extends SQLQuery
$this->PrepareRendering();
$sFrom = self::ClauseFrom($this->__aFrom, $sIndent);
$sWhere = self::ClauseWhere($this->m_oConditionExpr, $aArgs);
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
if ($bGetCount)
{
if (count($this->__aSelectedIdFields) > 0)
@@ -349,11 +357,13 @@ class SQLObjectQuery extends SQLQuery
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
}
$sCountFields = implode(', ', $aCountFields);
$sSQL = "SELECT$sLineSep COUNT(DISTINCT $sCountFields) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere";
// Count can be limited for performance reason, in this case the total amount is not important,
// we only need to know if the number of entries is greater than a certain amount.
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _tatooine_";
}
else
{
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep WHERE $sWhere";
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep 1 $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _tatooine_";
}
}
else
@@ -364,14 +374,7 @@ class SQLObjectQuery extends SQLQuery
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
}
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
$sSQL = "SELECT$sLineSep DISTINCT $sSelect$sLineSep FROM $sFrom$sLineSep WHERE $sWhere$sLineSep $sOrderBy $sLimit";
}
return $sSQL;

View File

@@ -103,29 +103,33 @@ class SQLUnionQuery extends SQLQuery
// Render SELECTS without orderby/limit/count
$aSelects[] = $oSQLQuery->RenderSelect(array(), $aArgs, 0, 0, false, $bBeautifulQuery);
}
$sSelects = '('.implode(")$sLineSep UNION$sLineSep(", $aSelects).')';
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
}
else
{
$sLimit = '';
}
if ($bGetCount)
{
$sSelects = '('.implode(" $sLimit)$sLineSep UNION$sLineSep(", $aSelects)." $sLimit)";
$sFrom = "($sLineSep$sSelects$sLineSep) as __selects__";
$sSQL = "SELECT$sLineSep COUNT(*) AS COUNT$sLineSep FROM $sFrom$sLineSep";
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep 1 $sLineSep FROM $sFrom$sLineSep) AS _union_tatooine_";
}
else
{
$sOrderBy = $this->aQueries[0]->RenderOrderByClause($aOrderBy);
if (!empty($sOrderBy))
{
$sOrderBy = "ORDER BY $sOrderBy$sLineSep";
}
if ($iLimitCount > 0)
{
$sLimit = 'LIMIT '.$iLimitStart.', '.$iLimitCount;
$sOrderBy = "ORDER BY $sOrderBy$sLineSep $sLimit";
$sSQL = '('.implode(")$sLineSep UNION$sLineSep (", $aSelects).')'.$sLineSep.$sOrderBy;
}
else
{
$sLimit = '';
$sSQL = '('.implode(" $sLimit)$sLineSep UNION$sLineSep (", $aSelects)." $sLimit)";
}
$sSQL = $sSelects.$sLineSep.$sOrderBy.' '.$sLimit;
}
return $sSQL;
}

View File

@@ -283,18 +283,21 @@ abstract class User extends cmdbAbstractObject
}
}
}
// Check that this user has at least one profile assigned
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0)
// Check that this user has at least one profile assigned when profiles have changed
if (array_key_exists('profile_list', $aChanges))
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneProfileIsNeeded');
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneProfileIsNeeded');
}
}
// Only administrators can manage administrators
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator())
{
$this->m_aCheckIssues[] = Dict::Format('UI:Login:Error:AccessRestricted');
}
// Check users with restricted organizations
if (!UserRights::IsAdministrator())
{
$oUser = UserRights::GetUserObject();
@@ -304,19 +307,28 @@ abstract class User extends cmdbAbstractObject
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
if (count($aOrgs) > 0)
{
/** @var ORMLinkset $oSet */
$oSet = $this->Get('allowed_org_list');
if ($oSet->Count() == 0)
// Check that the modified User belongs to one of our organization
if (!in_array($this->GetOriginal('org_id'), $aOrgs) || !in_array($this->Get('org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneOrganizationIsNeeded');
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:UserOrganizationNotAllowed');
}
else
// Check users with restricted organizations when allowed organizations have changed
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges))
{
while ($oUserOrg = $oSet->Fetch())
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() == 0)
{
if (!in_array($oUserOrg->Get('allowed_org_id'), $aOrgs))
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneOrganizationIsNeeded');
}
else
{
$aModifiedLinks = $oSet->ListModifiedLinks();
foreach($aModifiedLinks as $oLink)
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:OrganizationNotAllowed');
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:OrganizationNotAllowed');
}
}
}
}
@@ -961,10 +973,12 @@ class UserRights
}
/**
* Add additional filter for organization silos to all the requests.
*
* @param $sClass
* @param array $aSettings
*
* @return bool
* @return bool|\Expression
*/
public static function GetSelectFilter($sClass, $aSettings = array())
{
@@ -975,7 +989,7 @@ class UserRights
try
{
// Check only bizmodel to let API work like in 2.4.1
// Check Bug 1436 for details
if (MetaModel::HasCategory($sClass, 'bizmodel'))
{
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);

View File

@@ -52,7 +52,7 @@ abstract class ValueSetDefinition
}
public function GetValues($aArgs, $sContains = '')
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded)
{
@@ -93,12 +93,16 @@ abstract class ValueSetDefinition
class ValueSetObjects extends ValueSetDefinition
{
protected $m_sContains;
protected $m_sOperation;
protected $m_sFilterExpr; // in OQL
protected $m_sValueAttCode;
protected $m_aOrderBy;
protected $m_aExtraConditions;
private $m_bAllowAllData;
private $m_aModifierProperties;
private $m_bSort;
private $m_iLimit;
/**
* @param hash $aOrderBy Array of '[<classalias>.]attcode' => bAscending
@@ -106,12 +110,15 @@ class ValueSetObjects extends ValueSetDefinition
public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
{
$this->m_sContains = '';
$this->m_sOperation = '';
$this->m_sFilterExpr = $sFilterExp;
$this->m_sValueAttCode = $sValueAttCode;
$this->m_aOrderBy = $aOrderBy;
$this->m_bAllowAllData = $bAllowAllData;
$this->m_aModifierProperties = $aModifierProperties;
$this->m_aExtraConditions = array();
$this->m_bSort = true;
$this->m_iLimit = 0;
}
public function SetModifierProperty($sPluginClass, $sProperty, $value)
@@ -163,11 +170,20 @@ class ValueSetObjects extends ValueSetDefinition
return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
}
public function GetValues($aArgs, $sContains = '')
/**
* @param $aArgs
* @param string $sContains
* @param string $sOperation for the values @see self::LoadValues()
*
* @return array
* @throws CoreException
* @throws OQLException
*/
public function GetValues($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains))
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
{
$this->LoadValues($aArgs, $sContains);
$this->LoadValues($aArgs, $sContains, $sOperation);
$this->m_bIsLoaded = true;
}
// The results are already filtered and sorted (on friendly name)
@@ -175,9 +191,19 @@ class ValueSetObjects extends ValueSetDefinition
return $aRet;
}
protected function LoadValues($aArgs, $sContains = '')
/**
* @param $aArgs
* @param string $sContains
* @param string $sOperation 'contains' or 'equals_start_with'
*
* @return bool
* @throws \CoreException
* @throws \OQLException
*/
protected function LoadValues($aArgs, $sContains = '', $sOperation = 'contains')
{
$this->m_sContains = $sContains;
$this->m_sOperation = $sOperation;
$this->m_aValues = array();
@@ -202,12 +228,72 @@ class ValueSetObjects extends ValueSetDefinition
}
}
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
$aFields = $oExpression->ListRequiredFields();
$sClass = $oFilter->GetClass();
foreach($aFields as $sField)
{
$aFieldItems = explode('.', $sField);
if ($aFieldItems[0] != $sClass)
{
$sOperation = 'contains';
break;
}
}
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
switch ($sOperation)
{
case 'equals':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains);
foreach($aAttributes as $sAttribute)
{
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// Unions are much faster than OR conditions
$oFilter = new DBUnionSearch($aFilters);
break;
case 'start_with':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains.'%');
foreach($aAttributes as $sAttribute)
{
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// Unions are much faster than OR conditions
$oFilter = new DBUnionSearch($aFilters);
break;
default:
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
break;
}
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
if (empty($this->m_sValueAttCode))
{
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
}
else
{
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
}
$oObjects->OptimizeColumnLoad($aAttToLoad);
while ($oObject = $oObjects->Fetch())
{
if (empty($this->m_sValueAttCode))
@@ -231,6 +317,22 @@ class ValueSetObjects extends ValueSetDefinition
{
return $this->m_sFilterExpr;
}
/**
* @param $iLimit
*/
public function SetLimit($iLimit)
{
$this->m_iLimit = $iLimit;
}
/**
* @param $bSort
*/
public function SetSort($bSort)
{
$this->m_bSort = $bSort;
}
}

View File

@@ -746,12 +746,11 @@ input.dp-applied {
transition: transform 0.5s linear;
}
.search_form_handler.closed {
margin-top: -0.25em;
/* To remove top padding from the parent .display_block */
margin-bottom: 0.5em;
width: 150px;
overflow: hidden;
border-radius: 0 0 4px 4px;
border-radius: 4px;
background-color: #1c94c4;
}
.search_form_handler.closed .sf_criterion_area {
height: 0;
@@ -2093,6 +2092,7 @@ td.layout_cell {
vertical-align: top;
}
.dashlet-content {
position: relative;
background: #fff;
margin: 0;
overflow: auto;
@@ -2142,12 +2142,12 @@ td.prop_icon {
.dashlet-content .display_block {
text-align: left;
}
.dashlet-unknown .dashlet-content {
.dashlet-unknown .dashlet-content, .dashlet-proxy .dashlet-content {
padding: 8px;
background-color: #f2f2f2;
text-align: center;
}
.dashlet-unknown .dashlet-content .dashlet-ukn-text {
.dashlet-unknown .dashlet-content .dashlet-ukn-text, .dashlet-proxy .dashlet-content .dashlet-ukn-text, .dashlet-unknown .dashlet-content .dashlet-pxy-text, .dashlet-proxy .dashlet-content .dashlet-pxy-text {
margin-top: 10px;
}
.prop_apply .ui-icon-alert {
@@ -2588,7 +2588,6 @@ span.search-button, span.refresh-button {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
filter: gray;
filter: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'><filter id='greyscale'><feColorMatrix type='matrix' values='0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0' /></filter></svg>#greyscale");
opacity: 0.5;
}
#itop-breadcrumb .breadcrumb-item a {
@@ -2838,3 +2837,6 @@ table.listResults .originColor {
#dataModelSplitPane .ui-layout-center {
margin-left: 30px !important;
}
#ds_flash {
margin-bottom: 5px;
}

View File

@@ -839,11 +839,11 @@ input.dp-applied {
}
}
&.closed{
margin-top: -0.25em; /* To remove top padding from the parent .display_block */
margin-bottom: 0.5em;
width: 150px;
overflow: hidden;
border-radius: 0 0 4px 4px;
border-radius: 4px;
background-color: $complement-color;
.sf_criterion_area{
height: 0;
@@ -2414,6 +2414,7 @@ td.layout_cell {
vertical-align: top;
}
.dashlet-content {
position: relative;
background: #fff;
margin:0;
overflow: auto;
@@ -2464,13 +2465,13 @@ td.prop_icon {
.dashlet-content .display_block {
text-align:left;
}
.dashlet-unknown {
.dashlet-unknown, .dashlet-proxy {
.dashlet-content {
padding: 8px;
background-color: #F2F2F2;
text-align: center;
.dashlet-ukn-text {
.dashlet-ukn-text, .dashlet-pxy-text {
margin-top: 10px;
}
}
@@ -2933,7 +2934,6 @@ span.search-button, span.refresh-button {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
filter: gray;
filter: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' height='0'><filter id='greyscale'><feColorMatrix type='matrix' values='0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0' /></filter></svg>#greyscale");
// IE has no filter option: at least, have some effect when hovering...
opacity: 0.5;
@@ -3226,4 +3226,8 @@ table.listResults .originColor{
#dataModelSplitPane .ui-layout-center{
margin-left :30px !important;
}
///////////////////
///////////////////
//Impact analysis filter
#ds_flash{
margin-bottom: 5px;
}

View File

@@ -1,49 +1,45 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
// 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>+
//
// Class: UserExternal
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
'Class:UserExternal' => 'Usuario Externo',
'Class:UserExternal+' => 'Usuario Autenticado fuera de iTop',
));
?>
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
// 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>+
//
// Class: UserExternal
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellaño', array(
'Class:UserExternal' => 'Usuario Externo',
'Class:UserExternal+' => 'Usuario Autenticado fuera de iTop',
));

View File

@@ -1,48 +1,47 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
// 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>+
//
// Class: UserLDAP
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Class:UserLDAP' => 'Usuario LDAP',
'Class:UserLDAP+' => 'Usuario Autenticado vía LDAP',
'Class:UserLDAP/Attribute:password' => 'Contraseña',
'Class:UserLDAP/Attribute:password+' => 'Contraseña',
));
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
// 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>+
//
// Class: UserLDAP
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Class:UserLDAP' => 'Usuario LDAP',
'Class:UserLDAP+' => 'Usuario Autenticado vía LDAP',
'Class:UserLDAP/Attribute:password' => 'Contraseña',
'Class:UserLDAP/Attribute:password+' => 'Contraseña',
));

View File

@@ -1,47 +1,47 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
// 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>+
//
// Class: UserLocal
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Class:UserLocal' => 'Usuario de iTop',
'Class:UserLocal+' => 'Usuario Autenticado vía iTop',
'Class:UserLocal/Attribute:password' => 'Contraseña',
'Class:UserLocal/Attribute:password+' => 'Contraseña',
));
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
// 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>+
//
// Class: UserLocal
//
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Class:UserLocal' => 'Usuario de iTop',
'Class:UserLocal+' => 'Usuario Autenticado vía iTop',
'Class:UserLocal/Attribute:password' => 'Contraseña',
'Class:UserLocal/Attribute:password+' => 'Contraseña',
));

View File

@@ -67,7 +67,7 @@ try
{
$oDoc = utils::ReadPostedDocument('file');
$oAttachment = MetaModel::NewObject('Attachment');
$oAttachment->Set('expire', time() + 3600); // one hour...
$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime'));
$oAttachment->Set('temp_id', $sTempId);
$oAttachment->Set('item_class', $sObjClass);
$oAttachment->SetDefaultOrgId();

View File

@@ -1,44 +1,44 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Attachments:TabTitle_Count' => 'Anexos (%1$d)',
'Attachments:EmptyTabTitle' => 'Anexos',
'Attachments:FieldsetTitle' => 'Anexos',
'Attachments:DeleteBtn' => 'Borrar',
'Attachments:History_File_Added' => 'Anexo %1$s agregado.',
'Attachments:History_File_Removed' => 'Anexo %1$s removido.',
'Attachments:AddAttachment' => 'Agregar Anexo: ',
'Attachments:UploadNotAllowedOnThisSystem' => 'La carga de archivos NO está permitida en este sistema.',
'Attachment:Max_Go' => '(Tamaño Máximo de Archivo: %1$s Gb)',
'Attachment:Max_Mo' => '(Tamaño Máximo de Archivo: %1$s Mb)',
'Attachment:Max_Ko' => '(Tamaño Máximo de Archivo: %1$s Kb)',
'Attachments:NoAttachment' => 'No hay Anexo. ',
'Class:Attachment' => 'Anexo',
'Class:Attachment+' => 'Anexo',
'Attachments:PreviewNotAvailable' => 'Vista preliminar no disponible para este tipo de Anexo.',
));
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Localized data
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
*/
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Attachments:TabTitle_Count' => 'Anexos (%1$d)',
'Attachments:EmptyTabTitle' => 'Anexos',
'Attachments:FieldsetTitle' => 'Anexos',
'Attachments:DeleteBtn' => 'Borrar',
'Attachments:History_File_Added' => 'Anexo %1$s agregado.',
'Attachments:History_File_Removed' => 'Anexo %1$s removido.',
'Attachments:AddAttachment' => 'Agregar Anexo: ',
'Attachments:UploadNotAllowedOnThisSystem' => 'La carga de archivos NO está permitida en este sistema.',
'Attachment:Max_Go' => '(Tamaño Máximo de Archivo: %1$s Gb)',
'Attachment:Max_Mo' => '(Tamaño Máximo de Archivo: %1$s Mb)',
'Attachment:Max_Ko' => '(Tamaño Máximo de Archivo: %1$s Kb)',
'Attachments:NoAttachment' => 'No hay Anexo. ',
'Class:Attachment' => 'Anexo',
'Class:Attachment+' => 'Anexo',
'Attachments:PreviewNotAvailable' => 'Vista preliminar no disponible para este tipo de Anexo.',
));

View File

@@ -79,18 +79,18 @@ class DBRestore extends DBBackup
$aOutput = array();
$iRetCode = 0;
exec($sCommand, $aOutput, $iRetCode);
foreach ($aOutput as $sLine)
foreach($aOutput as $sLine)
{
$this->LogInfo("mysql said: $sLine");
}
if ($iRetCode != 0)
{
$this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode");
foreach ($aOutput as $sLine)
foreach($aOutput as $sLine)
{
$this->LogError("mysql said: $sLine");
}
if (count($aOutput) == 1)
if (count($aOutput) == 1)
{
$sMoreInfo = trim($aOutput[0]);
}
@@ -146,28 +146,22 @@ class DBRestore extends DBBackup
// Load the database
//
$sDataDir = tempnam(SetupUtils::GetTmpDir(), 'itop-');
unlink($sDataDir); // I need a directory, not a file...
$sDataDir = APPROOT.'data/tmp-backup-'.rand(10000, getrandmax());
SetupUtils::builddir($sDataDir); // Here is the directory
$oArchive->extractFileTo($sDataDir, 'itop-dump.sql');
$oArchive->extractTo($sDataDir);
$sDataFile = $sDataDir.'/itop-dump.sql';
$this->LoadDatabase($sDataFile);
try
{
SetupUtils::rrmdir($sDataDir);
}
catch (Exception $e)
{
throw new BackupException("Can't remove data dir", 0, $e);
}
// Update the code
//
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
if ($oArchive->hasFile('delta.xml') !== false)
if (is_file($sDataDir.'/delta.xml'))
{
// Extract and rename delta.xml => <env>.delta.xml;
file_put_contents($sDeltaFile, $oArchive->getFromName('delta.xml'));
rename($sDataDir.'/delta.xml', $sDeltaFile);
}
else
{
@@ -184,16 +178,25 @@ class DBRestore extends DBBackup
throw new BackupException("Can't remove production-modules dir", 0, $e);
}
}
if ($oArchive->hasDir('production-modules/') !== false)
if (is_dir($sDataDir.'/production-modules'))
{
$oArchive->extractDirTo(APPROOT.'data/', 'production-modules/');
rename($sDataDir.'/production-modules', APPROOT.'data/production-modules/');
}
$sConfigFile = APPROOT.'conf/'.$sEnvironment.'/config-itop.php';
@chmod($sConfigFile, 0770); // Allow overwriting the file
$oArchive->extractFileTo(APPROOT.'conf/'.$sEnvironment, 'config-itop.php');
rename($sDataDir.'/config-itop.php', $sConfigFile);
@chmod($sConfigFile, 0444); // Read-only
try
{
SetupUtils::rrmdir($sDataDir);
}
catch (Exception $e)
{
throw new BackupException("Can't remove data dir", 0, $e);
}
$oEnvironment = new RunTimeEnvironment($sEnvironment);
$oEnvironment->CompileFrom($sEnvironment);
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Localized data
*
* @copyright Copyright (C) 2018 Combodo
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'bkp-backup-running' => 'Um backup está sendo executado. Por favor, espere...',
'bkp-restore-running' => 'Uma restauração está sendo executada. Por favor, espere...',
'Menu:BackupStatus' => 'Backups agendados',
'bkp-status-title' => 'Backups agendados',
'bkp-status-checks' => 'Configurações e verificações',
'bkp-mysqldump-ok' => 'mysqldump está presente: %1$s',
'bkp-mysqldump-notfound' => 'mysqldump não pode ser encontrado: %1$s - Por favor, verifique se ele está instalado e no caminho, ou edite o arquivo de configuração para ajustar mysql_bindir.',
'bkp-mysqldump-issue' => 'mysqldump não pode ser executado (retcode=%1$d): Por favor, certifique-se de que está instalado e no caminho, ou edite o arquivo de configuração para ajustar mysql_bindir.',
'bkp-missing-dir' => 'O diretório de destino %1$s não foi encontrado',
'bkp-free-disk-space' => '<b>%1$s livre</b> in %2$s',
'bkp-dir-not-writeable' => '%1$s não é gravável',
'bkp-wrong-format-spec' => 'A especificação atual para formatar os nomes dos arquivos está errada. (%1$s). A especificação padrão foi aplicada: %2$s',
'bkp-name-sample' => 'Os arquivos de backup são nomeados dependendo dos identificadores do banco de dados, data e hora. Exemplo: %1$s',
'bkp-week-days' => 'Backups ocorrerão <b>cada %1$s a %2$s</b>',
'bkp-retention' => 'No máximo <b>%1$d arquivos de backup serão mantidos</b> no diretório destino.',
'bkp-next-to-delete' => 'Será deletado quando ocorrer o próximo backup (veja a configuração de "retention_count")',
'bkp-table-file' => 'Arquivo',
'bkp-table-file+' => 'Apenas arquivos com a extensão .zip são considerados arquivos de backup',
'bkp-table-size' => 'Tamanho',
'bkp-table-size+' => '',
'bkp-table-actions' => 'Ações',
'bkp-table-actions+' => '',
'bkp-status-backups-auto' => 'Backups agendados',
'bkp-status-backups-manual' => 'Backups manuais',
'bkp-status-backups-none' => 'Nenhum backup ainda',
'bkp-next-backup' => 'O próximo backup ocorrerá em <b>%1$s</b> (%2$s) at %3$s',
'bkp-button-backup-now' => 'Backup Agora!',
'bkp-button-restore-now' => 'Restaurar!',
'bkp-confirm-backup' => 'Por favor, confirme que você solicitou que o backup ocorra agora.',
'bkp-confirm-restore' => 'Por favor, confirme que você deseja restaurar o backup %1$s.',
'bkp-wait-backup' => 'Por favor, aguarde o backup concluir...',
'bkp-wait-restore' => 'Por favor, aguarde a restauração concluir...',
'bkp-success-restore' => 'Restauração concluída com sucesso.',
));

View File

@@ -1659,6 +1659,7 @@
$aSearches[$sSubClass] = $oSearch;
$oSet = new DBObjectSet($oSearch);
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
$iTotal += $oSet->Count();
}
}

View File

@@ -61,7 +61,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:Location/Attribute:country+' => '',
'Class:Location/Attribute:physicaldevice_list' => 'Geräte',
'Class:Location/Attribute:physicaldevice_list+' => '',
'Class:Location/Attribute:person_list' => 'kontakte',
'Class:Location/Attribute:person_list' => 'Kontakte',
'Class:Location/Attribute:person_list+' => '',
'Class:Person' => 'Person',
'Class:Person+' => '',
@@ -129,7 +129,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:ApplicationSolution+' => '',
'Class:ApplicationSolution/Attribute:functionalcis_list' => 'CIs',
'Class:ApplicationSolution/Attribute:functionalcis_list+' => '',
'Class:ApplicationSolution/Attribute:businessprocess_list' => 'Geschäftsprozesse',
'Class:ApplicationSolution/Attribute:businessprocess_list' => 'Business-Prozesse',
'Class:ApplicationSolution/Attribute:businessprocess_list+' => '',
'Class:ApplicationSolution/Attribute:status' => 'Status',
'Class:ApplicationSolution/Attribute:status+' => '',
@@ -139,7 +139,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:ApplicationSolution/Attribute:status/Value:inactive+' => '',
'Class:BusinessProcess' => 'Business-Prozess',
'Class:BusinessProcess+' => '',
'Class:BusinessProcess/Attribute:applicationsolutions_list' => 'Applikationslösungen',
'Class:BusinessProcess/Attribute:applicationsolutions_list' => 'Anwendungslösungen',
'Class:BusinessProcess/Attribute:applicationsolutions_list+' => '',
'Class:BusinessProcess/Attribute:status' => 'Status',
'Class:BusinessProcess/Attribute:status+' => '',
@@ -293,7 +293,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:Model/Attribute:type/Value:Phone+' => '',
'Class:Model/Attribute:physicaldevices_list' => 'Phyische Geräte',
'Class:Model/Attribute:physicaldevices_list+' => '',
'Class:NetworkDeviceType' => 'Netzerkgerätetyp',
'Class:NetworkDeviceType' => 'Netzwerkgerätetyp',
'Class:NetworkDeviceType+' => '',
'Class:NetworkDeviceType/Attribute:networkdevicesdevices_list' => 'Netzwerkgeräte',
'Class:NetworkDeviceType/Attribute:networkdevicesdevices_list+' => '',
@@ -373,9 +373,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:lnkApplicationSolutionToFunctionalCI/Attribute:applicationsolution_id+' => '',
'Class:lnkApplicationSolutionToFunctionalCI/Attribute:functionalci_id' => 'FunctionalCI',
'Class:lnkApplicationSolutionToFunctionalCI/Attribute:functionalci_id+' => '',
'Class:lnkApplicationSolutionToBusinessProcess' => 'Verknüpfung Anwendungslösung/Geschäftsprozess',
'Class:lnkApplicationSolutionToBusinessProcess' => 'Verknüpfung Anwendungslösung/Business-Prozess',
'Class:lnkApplicationSolutionToBusinessProcess+' => '',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:businessprocess_id' => 'Geschäftsprozes',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:businessprocess_id' => 'Business-Prozess',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:businessprocess_id+' => '',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:applicationsolution_id' => 'Anwendungslösung',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:applicationsolution_id+' => '',
@@ -999,7 +999,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:lnkApplicationSolutionToFunctionalCI/Attribute:applicationsolution_name+' => '',
'Class:lnkApplicationSolutionToFunctionalCI/Attribute:functionalci_name' => 'FunctionalCI-Name',
'Class:lnkApplicationSolutionToFunctionalCI/Attribute:functionalci_name+' => '',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:businessprocess_name' => 'Geschäftsprozess-Name',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:businessprocess_name' => 'Business-Prozess-Name',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:businessprocess_name+' => '',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:applicationsolution_name' => 'Applikationslösungs-Name',
'Class:lnkApplicationSolutionToBusinessProcess/Attribute:applicationsolution_name+' => '',

View File

@@ -46,7 +46,7 @@ function TestConfig($sContents, $oP)
catch (Error $e)
{
// ParseError only thrown in PHP7
throw new Exception('Error in configuration: '.$e->getMessage());
throw new Exception('Error in configuration: '.$e->getMessage().' at line '.$e->getLine());
}
if (strlen($sNoise) > 0)
{

View File

@@ -31,10 +31,10 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'config-apply' => 'Anwenden',
'config-apply' => 'Anwenden (Ctrl+S)',
'config-cancel' => 'Zurücksetzen',
'config-saved' => 'Successfully recorded.~~',
'config-saved' => 'Erfolgreich gesprichert',
'config-confirm-cancel' => 'Ihre Änderungen werden nicht gespeichert.',
'config-no-change' => 'Keine Änderungen: Die Datei wurde nicht verändert.',
'config-reverted' => 'The configuration has been reverted.~~',
'config-reverted' => 'Die Konfiguration wurde zurückgesetzt',
'config-parse-error' => 'Zeile %2$d: %1$s.<br/>Die Datei wurde nicht aktualisiert.',
'config-current-line' => 'Editiere Zeile: %1$s',
));

View File

@@ -138,7 +138,7 @@ $(function()
if (this.options.redirect_after_completion_url != '')
{
var sUrl = this.options.redirect_after_completion_url;
window.setTimeout(function() { window.location.href = sUrl; }, 500);
window.setTimeout(function() { window.location.href = sUrl; }, 3000);
}
}
else

View File

@@ -158,8 +158,17 @@ function collect_configuration()
}
else
{
// The format of the variable $_SERVER["SERVER_SOFTWARE"] seems to be the following:
// PHP 7 FPM with Apache on Ubuntu: "Apache/2.4.18 (Ubuntu)"
// IIS 7.5 on Windows 7: "Microsoft-IIS/7.5"
// Nginx with PHP FPM on Ubuntu: "nginx/1.10.0"
$aConfiguration['web_server_name'] = substr($_SERVER["SERVER_SOFTWARE"], 0, strpos($_SERVER["SERVER_SOFTWARE"], '/'));
$aConfiguration['web_server_version'] = substr($_SERVER["SERVER_SOFTWARE"], strpos($_SERVER["SERVER_SOFTWARE"], '/'), strpos($_SERVER["SERVER_SOFTWARE"], 'PHP'));
$sWebServerVersion = trim(substr($_SERVER["SERVER_SOFTWARE"], 1+strpos($_SERVER["SERVER_SOFTWARE"], '/')));
if ($sWebServerVersion == '')
{
$sWebServerVersion = 'Unknown';
}
$aConfiguration['web_server_version'] = $sWebServerVersion;
}
// PHP extensions

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -20,7 +20,7 @@
/**
* Localized data
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -132,7 +132,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Class:lnkDocumentToError/Attribute:error_id+' => '',
'Class:lnkDocumentToError/Attribute:error_name' => 'Nome erro',
'Class:lnkDocumentToError/Attribute:error_name+' => '',
'Class:lnkDocumentToError/Attribute:link_type' => 'link_type',
'Class:lnkDocumentToError/Attribute:link_type' => 'Tipo de link',
'Class:lnkDocumentToError/Attribute:link_type+' => '',
));
@@ -180,14 +180,15 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Menu:NewError+' => 'Criar um erro conhecido',
'Menu:SearchError' => 'Pesquisar por um erro conhecido',
'Menu:SearchError+' => 'Pesquisar por erros conhecidos',
'Menu:Problem:KnownErrors' => 'Todos erros conhecidos',
'Menu:Problem:KnownErrors+' => 'Todos erros conhecidos',
'Menu:Problem:KnownErrors' => 'Todos erros conhecidos',
'Menu:Problem:KnownErrors+' => 'Todos erros conhecidos',
'Menu:FAQCategory' => 'Categorias FAQ',
'Menu:FAQCategory+' => 'Todas categorias FAQ',
'Menu:FAQ' => 'FAQs',
'Menu:FAQ+' => 'Todas FAQs',
'Brick:Portal:FAQ:Menu' => 'FAQ',
'Brick:Portal:FAQ:Title' => 'Dúvidas frequentes',
'Brick:Portal:FAQ:Title+' => '<p>Já conferiu nossa lista de perguntas frequentes?</p><p>Talvez encontre a resposta esperada imediatamente.</p>',
));
'Brick:Portal:FAQ:Title' => 'Perguntes frequentes (FAQ)',
'Brick:Portal:FAQ:Title+' => '<p>Com pressa?</p><p>Confira a lista de perguntas mais comuns e (talvez) encontre a resposta esperada imediatamente.</p>',
));

View File

@@ -107,6 +107,17 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'Brick:Portal:Manage:Name' => 'Spravovat položky',
'Brick:Portal:Manage:Table:NoData' => 'Žádná položka',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
'Brick:Portal:Manage:DisplayMode:list' => 'List~~',
'Brick:Portal:Manage:DisplayMode:pie-chart' => 'Pie Chart~~',
'Brick:Portal:Manage:DisplayMode:bar-chart' => 'Bar Chart',
'Brick:Portal:Manage:Others' => 'Others~~',
'Brick:Portal:Manage:All' => 'All~~',
'Brick:Portal:Manage:Group' => 'Group~~',
'Brick:Portal:Manage:fct:count' => 'Total~~',
'Brick:Portal:Manage:fct:sum' => 'Sum~~',
'Brick:Portal:Manage:fct:avg' => 'Average~~',
'Brick:Portal:Manage:fct:min' => 'Min~~',
'Brick:Portal:Manage:fct:max' => 'Max~~',
));
// ObjectBrick brick

View File

@@ -31,9 +31,9 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Page:Home' => 'Start',
'Page:GoPortalHome' => 'Startseite',
'Page:GoPreviousPage' => 'vorherige Seite',
'Page:ReloadPage' => 'Reload page~~',
'Page:ReloadPage' => 'Seite neu laden',
'Portal:Button:Submit' => 'Abschicken',
'Portal:Button:Apply' => 'Update~~',
'Portal:Button:Apply' => 'Anwenden',
'Portal:Button:Cancel' => 'Zurück',
'Portal:Button:Close' => 'Schließen',
'Portal:Button:Add' => 'Hinzu',
@@ -41,12 +41,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Portal:Button:Delete' => 'Löschen',
'Portal:EnvironmentBanner:Title' => 'Sie sind im Moment im <strong>%1$s</strong> Modus',
'Portal:EnvironmentBanner:GoToProduction' => 'Zurück zum PRODUCTION Modus',
'Error:HTTP:401' => 'Authentication~~',
'Error:HTTP:401' => 'Authentifizierung',
'Error:HTTP:404' => 'Seite nicht gefunden.',
'Error:HTTP:500' => 'Oops! Es ist ein Fehler aufgetreten.',
'Error:HTTP:GetHelp' => 'Bitte kontaktieren Sie Ihren %1$s administrator falls das Problem öfter auftaucht.',
'Error:XHR:Fail' => 'Konnte Daten nicht laden, bitte kontaktieren Sie Ihren %1$s administrator',
'Portal:ErrorUserLoggedOut' => 'You are logged out and need to log in again in order to continue.~~',
'Portal:ErrorUserLoggedOut' => 'Sie sind ausgeloggt und müssen sich erneut einloggen, um fortfahren zu können.',
'Portal:Datatables:Language:Processing' => 'Bitte warten...',
'Portal:Datatables:Language:Search' => 'Filter :',
'Portal:Datatables:Language:LengthMenu' => 'Anzahl _MENU_ Einträge pro Seite',
@@ -87,7 +87,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Browse:Name' => 'List durchgehen',
'Brick:Portal:Browse:Mode:List' => 'Liste',
'Brick:Portal:Browse:Mode:Tree' => 'Baum',
'Brick:Portal:Browse:Mode:Mosaic' => 'Mosaic~~',
'Brick:Portal:Browse:Mode:Mosaic' => 'Kachel',
'Brick:Portal:Browse:Action:Drilldown' => 'Drilldown',
'Brick:Portal:Browse:Action:View' => 'Details',
'Brick:Portal:Browse:Action:Edit' => 'Editieren',
@@ -102,7 +102,18 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Manage:Name' => 'Einträge managen',
'Brick:Portal:Manage:Table:NoData' => 'Kein Eintrag.',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
'Brick:Portal:Manage:Table:ItemActions' => 'Aktionen',
'Brick:Portal:Manage:DisplayMode:list' => 'Liste',
'Brick:Portal:Manage:DisplayMode:pie-chart' => 'Kuchendiagramm',
'Brick:Portal:Manage:DisplayMode:bar-chart' => 'Balkendiagramm',
'Brick:Portal:Manage:Others' => 'Andere',
'Brick:Portal:Manage:All' => 'Alle',
'Brick:Portal:Manage:Group' => 'Gruppe',
'Brick:Portal:Manage:fct:count' => 'Anzahl',
'Brick:Portal:Manage:fct:sum' => 'Summe',
'Brick:Portal:Manage:fct:avg' => 'Durchschnitt',
'Brick:Portal:Manage:fct:min' => 'Minimum',
'Brick:Portal:Manage:fct:max' => 'Maximum',
));
// ObjectBrick brick
@@ -120,12 +131,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
// CreateBrick brick
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Create:Name' => 'Schnelles Erstellen',
'Brick:Portal:Create:ChooseType' => 'Please, choose a type~~',
'Brick:Portal:Create:ChooseType' => 'Bitte wählen Sie einen Typ',
));
// Filter brick
Dict::Add('DE DE', 'German', 'Deutsch', array(
'Brick:Portal:Filter:Name' => 'Prefilter a brick~~',
'Brick:Portal:Filter:SearchInput:Placeholder' => 'eg. connect wifi~~',
'Brick:Portal:Filter:SearchInput:Submit' => 'Search~~',
'Brick:Portal:Filter:Name' => 'Brick vorfiltern',
'Brick:Portal:Filter:SearchInput:Placeholder' => 'z.B. connect wifi',
'Brick:Portal:Filter:SearchInput:Submit' => 'Suchen',
));

View File

@@ -108,9 +108,9 @@ Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Manage:Name' => 'Manage items',
'Brick:Portal:Manage:Table:NoData' => 'No item.',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions',
'Brick:Portal:Manage:DisplayType:pie-chart' => 'Pie Chart',
'Brick:Portal:Manage:DisplayType:bar-chart' => 'Bar Chart',
'Brick:Portal:Manage:DisplayType:default' => 'List',
'Brick:Portal:Manage:DisplayMode:list' => 'List',
'Brick:Portal:Manage:DisplayMode:pie-chart' => 'Pie Chart',
'Brick:Portal:Manage:DisplayMode:bar-chart' => 'Bar Chart',
'Brick:Portal:Manage:Others' => 'Others',
'Brick:Portal:Manage:All' => 'All',
'Brick:Portal:Manage:Group' => 'Group',

View File

@@ -103,6 +103,17 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'Brick:Portal:Manage:Name' => 'Administrar elementos',
'Brick:Portal:Manage:Table:NoData' => 'Sin objeto.',
'Brick:Portal:Manage:Table:ItemActions' => 'Acciones',
'Brick:Portal:Manage:DisplayMode:list' => 'List~~',
'Brick:Portal:Manage:DisplayMode:pie-chart' => 'Pie Chart~~',
'Brick:Portal:Manage:DisplayMode:bar-chart' => 'Bar Chart',
'Brick:Portal:Manage:Others' => 'Others~~',
'Brick:Portal:Manage:All' => 'All~~',
'Brick:Portal:Manage:Group' => 'Group~~',
'Brick:Portal:Manage:fct:count' => 'Total~~',
'Brick:Portal:Manage:fct:sum' => 'Sum~~',
'Brick:Portal:Manage:fct:avg' => 'Average~~',
'Brick:Portal:Manage:fct:min' => 'Min~~',
'Brick:Portal:Manage:fct:max' => 'Max~~',
));
// ObjectBrick brick

View File

@@ -108,9 +108,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Manage:Name' => 'Gestion d\'éléments',
'Brick:Portal:Manage:Table:NoData' => 'Aucun élément',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions',
'Brick:Portal:Manage:DisplayType:pie-chart' => 'Secteur',
'Brick:Portal:Manage:DisplayType:bar-chart' => 'Histogramme',
'Brick:Portal:Manage:DisplayType:default' => 'Liste',
'Brick:Portal:Manage:DisplayMode:list' => 'Liste',
'Brick:Portal:Manage:DisplayMode:pie-chart' => 'Secteur',
'Brick:Portal:Manage:DisplayMode:bar-chart' => 'Histogramme',
'Brick:Portal:Manage:Others' => 'Autres',
'Brick:Portal:Manage:All' => 'Total',
'Brick:Portal:Manage:Group' => 'Groupe',

View File

@@ -13,7 +13,6 @@ SetupWebPage::AddModule(
'visible' => true,
// Components
'datamodel' => array(
'portal/src/apis/extensions/d3portaluiextension.class.inc.php',
'portal/src/controllers/abstractcontroller.class.inc.php',
'portal/src/controllers/brickcontroller.class.inc.php',
'portal/src/entities/abstractbrick.class.inc.php',

View File

@@ -97,6 +97,17 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'Brick:Portal:Manage:Name' => 'Beheer items',
'Brick:Portal:Manage:Table:NoData' => 'Geen gegevens',
'Brick:Portal:Manage:Table:ItemActions' => 'Actions~~',
'Brick:Portal:Manage:DisplayMode:list' => 'List~~',
'Brick:Portal:Manage:DisplayMode:pie-chart' => 'Pie Chart~~',
'Brick:Portal:Manage:DisplayMode:bar-chart' => 'Bar Chart',
'Brick:Portal:Manage:Others' => 'Others~~',
'Brick:Portal:Manage:All' => 'All~~',
'Brick:Portal:Manage:Group' => 'Group~~',
'Brick:Portal:Manage:fct:count' => 'Total~~',
'Brick:Portal:Manage:fct:sum' => 'Sum~~',
'Brick:Portal:Manage:fct:avg' => 'Average~~',
'Brick:Portal:Manage:fct:min' => 'Min~~',
'Brick:Portal:Manage:fct:max' => 'Max~~',
));
// ObjectBrick brick

View File

@@ -1,44 +0,0 @@
<?php
// Copyright (c) 2010-2017 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
namespace Combodo\iTop\Portal\API\Extension;
use AbstractPortalUIExtension;
use Silex\Application;
use utils;
class D3PortalUIExtension extends AbstractPortalUIExtension
{
public function GetCSSFiles(Application $oApp)
{
$aCSSFiles = array(
utils::GetAbsoluteUrlAppRoot().'css/c3.min.css?v='.ITOP_VERSION,
);
return $aCSSFiles;
}
public function GetJSFiles(Application $oApp)
{
$aJSFiles = array(
utils::GetAbsoluteUrlAppRoot().'js/d3.min.js?v='.ITOP_VERSION,
utils::GetAbsoluteUrlAppRoot().'js/c3.min.js?v='.ITOP_VERSION,
utils::GetCurrentModuleUrl().'/portal/web/js/export.js?v='.ITOP_VERSION,
);
return $aJSFiles;
}
}

View File

@@ -51,10 +51,10 @@ class BrowseBrickController extends BrickController
// Getting current browse mode (First from router pamater, then default brick value)
$sBrowseMode = (!empty($sBrowseMode)) ? $sBrowseMode : $oBrick->GetDefaultBrowseMode();
// Getting current dataloading mode (First from router parameter, then query parameter, then default brick value)
$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : ( ($oRequest->query->get('sDataLoading') !== null) ? $oRequest->query->get('sDataLoading') : $oBrick->GetDataLoading() );
$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : $oApp['request_manipulator']->ReadParam('sDataLoading', $oBrick->GetDataLoading());
// Getting search value
$sSearchValue = $oRequest->get('sSearchValue', null);
if ($sSearchValue !== null)
$sSearchValue = $oApp['request_manipulator']->ReadParam('sSearchValue', '');
if (!empty($sSearchValue))
{
$sDataLoading = AbstractBrick::ENUM_DATA_LOADING_LAZY;
}
@@ -109,7 +109,7 @@ class BrowseBrickController extends BrickController
// Adding search clause
// Note : For know the search is naive and looks only for the exact match. It doesn't search for words separately
if ($sSearchValue !== null)
if (!empty($sSearchValue))
{
// - Cleaning the search value by exploding and trimming spaces
$aSearchValues = explode(' ', $sSearchValue);
@@ -182,7 +182,7 @@ class BrowseBrickController extends BrickController
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetSelectedClasses($aLevelsClasses);
if ($sSearchValue !== null)
if (!empty($sSearchValue))
{
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
$aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams();
@@ -216,8 +216,8 @@ class BrowseBrickController extends BrickController
{
case BrowseBrick::ENUM_BROWSE_MODE_LIST:
// Retrieving parameters
$iPageNumber = (int) $oRequest->get('iPageNumber', 1);
$iListLength = (int) $oRequest->get('iListLength', BrowseBrick::DEFAULT_LIST_LENGTH);
$iPageNumber = (int) $oApp['request_manipulator']->ReadParam('iPageNumber', 1, FILTER_SANITIZE_NUMBER_INT);
$iListLength = (int) $oApp['request_manipulator']->ReadParam('iListLength', BrowseBrick::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT);
// Getting total records number
$oCountSet = new DBObjectSet($oQuery);
@@ -232,8 +232,8 @@ class BrowseBrickController extends BrickController
case BrowseBrick::ENUM_BROWSE_MODE_TREE:
case BrowseBrick::ENUM_BROWSE_MODE_MOSAIC:
// Retrieving parameters
$sLevelAlias = $oRequest->get('sLevelAlias');
$sNodeId = $oRequest->get('sNodeId');
$sLevelAlias = $oApp['request_manipulator']->ReadParam('sLevelAlias', '');
$sNodeId = $oApp['request_manipulator']->ReadParam('sNodeId', '');
// If no values for those parameters, we might be loading page in lazy mode for the first time, therefore the URL doesn't have those informations.
if (empty($sLevelAlias))
@@ -631,8 +631,9 @@ class BrowseBrickController extends BrickController
if ($aLevelsProperties[$key][$sOptionalAttribute] !== null)
{
$sPropertyName = substr($sOptionalAttribute, 0, -4);
$oAttDef = MetaModel::GetAttributeDef(get_class($value), $aLevelsProperties[$key][$sOptionalAttribute]);
$tmpAttValue = $value->Get($aLevelsProperties[$key][$sOptionalAttribute]);
$tmpAttValue = $value->GetAsHTML($aLevelsProperties[$key][$sOptionalAttribute]);
if($sOptionalAttribute === 'image_att')
{
if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty())
@@ -641,7 +642,7 @@ class BrowseBrickController extends BrickController
}
else
{
$tmpAttValue = MetaModel::GetAttributeDef(get_class($value), $aLevelsProperties[$key][$sOptionalAttribute])->Get('default_image');
$tmpAttValue = $oAttDef->Get('default_image');
}
}
@@ -655,7 +656,7 @@ class BrowseBrickController extends BrickController
foreach ($aLevelsProperties[$key]['fields'] as $aField)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($value), $aField['code']);
$aRow[$key]['fields'][$aField['code']] = $oAttDef->GetValueLabel($value->Get($aField['code']));
$aRow[$key]['fields'][$aField['code']] = $oAttDef->GetAsHTML($value->Get($aField['code']));
}
}
}
@@ -723,8 +724,9 @@ class BrowseBrickController extends BrickController
if ($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute] !== null)
{
$sPropertyName = substr($sOptionalAttribute, 0, -4);
$oAttDef = MetaModel::GetAttributeDef(get_class($aCurrentRowValues[0]), $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]);
$tmpAttValue = $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]);
$tmpAttValue = $aCurrentRowValues[0]->GetAsHTML($aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute]);
if($sOptionalAttribute === 'image_att')
{
if (is_object($tmpAttValue) && !$tmpAttValue->IsEmpty())
@@ -733,7 +735,7 @@ class BrowseBrickController extends BrickController
}
else
{
$tmpAttValue = MetaModel::GetAttributeDef(get_class($aCurrentRowValues[0]), $aLevelsProperties[$aCurrentRowKeys[0]][$sOptionalAttribute])->Get('default_image');
$tmpAttValue = $oAttDef->Get('default_image');
}
}

View File

@@ -19,6 +19,7 @@
namespace Combodo\iTop\Portal\Controller;
use Exception;
use AttributeDate;
use AttributeDateTime;
use AttributeDefinition;
@@ -49,37 +50,36 @@ class ManageBrickController extends BrickController
{
const EXCEL_EXPORT_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/popup-export-excel.html.twig';
/**
* @param \Symfony\Component\HttpFoundation\Request $oRequest
* @param \Silex\Application $oApp
* @param string $sBrickId
* @param string $sDisplayType
* @param string $sGroupingTab
* @param string $sDataLoading
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \OQLException
*/
public function DisplayAction(
Request $oRequest, Application $oApp, $sBrickId, $sGroupingTab, $sDisplayType = null, $sDataLoading = null
) {
/**
* @param Request $oRequest
* @param Application $oApp
* @param string $sBrickId
* @param string $sGroupingTab
* @param string $sDisplayMode
*
* @return Response
*
* @throws \Exception
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \OQLException
*/
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sGroupingTab, $sDisplayMode = null)
{
/** @var ManageBrick $oBrick */
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if (is_null($sDisplayType))
if (is_null($sDisplayMode))
{
$sDisplayType = $oBrick->GetDisplayType();
$sDisplayMode = $oBrick->GetDefaultDisplayMode();
}
$aDisplayParams = $oBrick->GetPresentationDataForDisplayType($sDisplayType);
$aData = $this->GetData($oRequest, $oApp, $sBrickId, $sGroupingTab, $aDisplayParams['need_details']);
$aDisplayParams = $oBrick->GetPresentationDataForTileMode($sDisplayMode);
$aData = $this->GetData($oRequest, $oApp, $sBrickId, $sGroupingTab, $oBrick::AreDetailsNeededForDisplayMode($sDisplayMode));
$aExportFields = $oBrick->GetExportFields();
$aData = $aData + array(
'sDisplayType' => $sDisplayType,
'sDisplayMode' => $sDisplayMode,
'bCanExport' => !empty($aExportFields),
);
// Preparing response
@@ -89,7 +89,7 @@ class ManageBrickController extends BrickController
}
else
{
$sLayoutTemplate = $aDisplayParams['layoutTemplate'];
$sLayoutTemplate = $oBrick::GetPageTemplateFromDisplayMode($sDisplayMode);
$oResponse = $oApp['twig']->render($sLayoutTemplate, $aData);
}
@@ -114,7 +114,7 @@ class ManageBrickController extends BrickController
{
$aData = $this->GetData($oRequest, $oApp, $sBrickId, null);
}
catch (\Exception $e)
catch (Exception $e)
{
// TODO Default values
$aData = array();
@@ -156,9 +156,11 @@ class ManageBrickController extends BrickController
{
$oQuery = DBSearch::FromOQL($oBrick->GetOql());
$sClass = $oQuery->GetClass();
$this->AddScopeToQuery($oQuery, $oApp, $oBrick, $sClass);
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeHelper */
$oScopeHelper = $oApp['scope_validator'];
$oScopeHelper->AddScopeToQuery($oQuery, $sClass);
$aData = array();
$this->ManageSearchValue($oRequest, $aData, $oQuery, $sClass);
$this->ManageSearchValue($oApp, $aData, $oQuery, $sClass);
// Grouping tab
if ($oBrick->HasGroupingTabs())
@@ -254,11 +256,11 @@ class ManageBrickController extends BrickController
$bHasScope = true;
// Getting current dataloading mode (First from router parameter, then query parameter, then default brick value)
$sDataLoading = ($oRequest->get('sDataLoading') !== null) ? $oRequest->get('sDataLoading') : $oBrick->GetDataLoading();
$sDataLoading = $oApp['request_manipulator']->ReadParam('sDataLoading', $oBrick->GetDataLoading());
// - Retrieving the grouping areas to display
$sGroupingArea = $oRequest->get('sGroupingArea');
if (!is_null($sGroupingArea))
$sGroupingArea = $oApp['request_manipulator']->ReadParam('sGroupingArea', '');
if (!empty($sGroupingArea))
{
$bNeedDetails = true;
}
@@ -275,10 +277,7 @@ class ManageBrickController extends BrickController
// Starting to build query
$oQuery = DBSearch::FromOQL($oBrick->GetOql());
$sClass = $oQuery->GetClass();
$sIconURL = \MetaModel::GetClassIcon($sClass, false);
// - Adding search clause if necessary
$this->ManageSearchValue($oRequest, $aData, $oQuery, $sClass);
$sIconURL = MetaModel::GetClassIcon($sClass, false);
// Preparing tabs
// - We need to retrieve distinct values for the grouping attribute
@@ -304,7 +303,11 @@ class ManageBrickController extends BrickController
{
$oConditionQuery = $oQuery->Intersect(DBSearch::FromOQL($aGroup['condition']));
// - Restricting query to scope
if ($this->AddScopeToQuery($oConditionQuery, $oApp, $oBrick, $oConditionQuery->GetClass()))
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeHelper */
$oScopeHelper = $oApp['scope_validator'];
$bHasScope = $oScopeHelper->AddScopeToQuery($oConditionQuery, $oConditionQuery->GetClass());
if ($bHasScope)
{
// - Building ObjectSet
$oConditionSet = new DBObjectSet($oConditionQuery);
@@ -336,8 +339,8 @@ class ManageBrickController extends BrickController
}
}
// - Retrieving the current grouping tab to display and altering the query to do so
if ($sGroupingTab === null)
// - Retrieving the current grouping tab to display if necessary and altering the query to do so
if (empty($sGroupingTab))
{
if ($oBrick->HasGroupingTabs())
{
@@ -345,7 +348,7 @@ class ManageBrickController extends BrickController
$sGroupingTab = key($aGroupingTabsValues);
if ($aGroupingTabsValues[$sGroupingTab]['condition'] !== null)
{
$oQuery = $oQuery->Intersect($aGroupingTabsValues[$sGroupingTab]['condition']);
$oQuery = $aGroupingTabsValues[$sGroupingTab]['condition']->DeepClone();
}
}
}
@@ -353,10 +356,13 @@ class ManageBrickController extends BrickController
{
if ($aGroupingTabsValues[$sGroupingTab]['condition'] !== null)
{
$oQuery = $oQuery->Intersect($aGroupingTabsValues[$sGroupingTab]['condition']);
$oQuery = $aGroupingTabsValues[$sGroupingTab]['condition']->DeepClone();
}
}
// - Adding search clause if necessary
$this->ManageSearchValue($oApp, $aData, $oQuery, $sClass, $aColumnsAttrs);
// Preparing areas
// - We need to retrieve distinct values for the grouping attribute
// Note : Will have to be changed when we consider grouping on something else than the finalclass
@@ -370,7 +376,7 @@ class ManageBrickController extends BrickController
{
$oDistinctQuery = $this->GetScopedQuery($oApp, $oBrick, $sClass);
// Adding grouping conditions
$oFieldExp = new FieldExpression($sGroupingAreaAttCode, $sParentAlias);
$oFieldExp = new FieldExpression($sGroupingAreaAttCode, $oDistinctQuery->GetClassAlias());
$sDistinctSql = $oDistinctQuery->MakeGroupByQuery(array(), array('grouped_by_1' => $oFieldExp), true);
$aDistinctResults = CMDBSource::QueryToArray($sDistinctSql);
@@ -405,7 +411,7 @@ class ManageBrickController extends BrickController
}
// - If specified or lazy loading, we truncate the $aGroupingAreasValues to keep only this one
if ($sGroupingArea !== null)
if (!empty($sGroupingArea))
{
$aGroupingAreasValues = array($sGroupingArea => $aGroupingAreasValues[$sGroupingArea]);
}
@@ -415,14 +421,16 @@ class ManageBrickController extends BrickController
$oAreaQuery = DBSearch::CloneWithAlias($oQuery, $sParentAlias);
if ($aGroupingAreasValue['condition'] !== null)
{
//$oAreaQuery->AddConditionExpression($aGroupingAreasValue['condition']);
$oAreaQuery = $oAreaQuery->Intersect($aGroupingAreasValue['condition']);
$oAreaQuery = $aGroupingAreasValue['condition']->DeepClone();
}
// Restricting query to allowed scope on each classes
// Note: Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass
// Note: We now get view scope instead of edit scope as we allowed users to view/edit objects in the brick regarding their rights
if (!$this->AddScopeToQuery($oAreaQuery, $oApp, $oBrick, $aGroupingAreasValue['value']))
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeHelper */
$oScopeHelper = $oApp['scope_validator'];
$bHasScope = $oScopeHelper->AddScopeToQuery($oAreaQuery, $aGroupingAreasValue['value']);
if (!$bHasScope)
{
// if no scope apply does not allow any data
$oAreaQuery = null;
@@ -459,8 +467,8 @@ class ManageBrickController extends BrickController
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY)
{
// Retrieving parameters
$iPageNumber = (int)$oRequest->get('iPageNumber', 1);
$iListLength = (int)$oRequest->get('iListLength', ManageBrick::DEFAULT_LIST_LENGTH);
$iPageNumber = (int)$oApp['request_manipulator']->ReadParam('iPageNumber', 1, FILTER_SANITIZE_NUMBER_INT);
$iListLength = (int)$oApp['request_manipulator']->ReadParam('iListLength', ManageBrick::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT);
// Getting total records number
$oCountSet = new DBObjectSet($oQuery);
@@ -569,7 +577,7 @@ class ManageBrickController extends BrickController
$oAttDef = MetaModel::GetAttributeDef($sCurrentClass, $sItemAttr);
if ($oAttDef->IsExternalKey())
{
$sValue = $oCurrentRow->Get($sItemAttr.'_friendlyname');
$sValue = $oCurrentRow->GetAsHTML($sItemAttr.'_friendlyname');
// Adding a view action on the external keys
if ($oCurrentRow->Get($sItemAttr) !== $oAttDef->GetNullValue())
@@ -587,13 +595,22 @@ class ManageBrickController extends BrickController
}
}
}
elseif ($oAttDef instanceof AttributeSubItem || $oAttDef instanceof AttributeDuration)
{
$sValue = $oAttDef->GetAsHTML($oCurrentRow->Get($sItemAttr));
}
elseif ($oAttDef instanceof AttributeImage)
{
$oOrmDoc = $oCurrentRow->Get($sItemAttr);
if (is_object($oOrmDoc) && !$oOrmDoc->IsEmpty())
{
$sUrl = $oApp['url_generator']->generate('p_object_document_display', array('sObjectClass' => get_class($oCurrentRow), 'sObjectId' => $oCurrentRow->GetKey(), 'sObjectField' => $sItemAttr, 'cache' => 86400));
}
else
{
$sUrl = $oAttDef->Get('default_image');
}
$sValue = '<img src="' . $sUrl . '" />';
}
else
{
$sValue = $oAttDef->GetValueLabel($oCurrentRow->Get($sItemAttr));
$sValue = $oAttDef->GetAsHTML($oCurrentRow->Get($sItemAttr));
}
unset($oAttDef);
@@ -693,7 +710,7 @@ class ManageBrickController extends BrickController
);
$aUrls[] = $oApp['url_generator']->generate('p_manage_brick', array(
'sBrickId' => $sBrickId,
'sDisplayType' => 'default',
'sDisplayMode' => 'default',
'sGroupingTab' => $aValues['value']
));
}
@@ -728,22 +745,36 @@ class ManageBrickController extends BrickController
return $aData;
}
/**
* @param Request $oRequest
* @param array $aData
* @param DBSearch $oQuery
* @param string $sClass
*/
protected function ManageSearchValue(Request $oRequest, &$aData, DBSearch &$oQuery, $sClass)
/**
* @param Application $oApp
* @param array $aData
* @param DBSearch $oQuery
* @param string $sClass
* @param array $aColumnsAttrs
*
* @throws \Exception
* @throws \CoreException
*/
protected function ManageSearchValue(Application $oApp, &$aData, DBSearch &$oQuery, $sClass, $aColumnsAttrs)
{
// Getting search value
$sSearchValue = $oRequest->get('sSearchValue', null);
$sSearchValue = $oApp['request_manipulator']->ReadParam('sSearchValue', '');
// - Adding search clause if necessary
// Note : This is a very naive search at the moment
if ($sSearchValue !== null)
if (!empty($sSearchValue))
{
$aSearchListItems = MetaModel::GetZListItems($sClass, 'standard_search');
// Putting only valid attributes as one can define attributes of leaf classes in the brick definition (<fields>), but at this stage we are working on the abstract class.
// Note: This won't fix everything as the search will not be looking in all fields.
$aSearchListItems = array();
foreach($aColumnsAttrs as $sColumnAttr)
{
if(MetaModel::IsValidAttCode($sClass, $sColumnAttr))
{
$aSearchListItems[] = $sColumnAttr;
}
}
$oFullBinExpr = null;
foreach ($aSearchListItems as $sSearchItemAttr)
{
@@ -797,7 +828,9 @@ class ManageBrickController extends BrickController
$aGroupingTabsValues = array();
$aDistinctResults = array();
$oDistinctQuery = DBSearch::FromOQL($oBrick->GetOql());
$bHasScope = $this->AddScopeToQuery($oDistinctQuery, $oApp, $oBrick, $oDistinctQuery->GetClass());
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeHelper */
$oScopeHelper = $oApp['scope_validator'];
$bHasScope = $oScopeHelper->AddScopeToQuery($oDistinctQuery, $oDistinctQuery->GetClass());
if ($bHasScope)
{
// - Adding field condition
@@ -831,7 +864,7 @@ class ManageBrickController extends BrickController
{
$oConditionQuery = DBSearch::CloneWithAlias($oQuery, 'GTAB');
$oExpression = new BinaryExpression(new FieldExpression($sGroupingTabAttCode,
$oDistinctQuery->GetClassAlias()), '=', new UnaryExpression($aDistinctResult['grouped_by_1']));
$oConditionQuery->GetClassAlias()), '=', new UnaryExpression($aDistinctResult['grouped_by_1']));
$oConditionQuery->AddConditionExpression($oExpression);
$sHtmlLabel = $oFieldExp->MakeValueLabel($oDistinctQuery, $aDistinctResult['grouped_by_1'], '');
@@ -854,7 +887,7 @@ class ManageBrickController extends BrickController
{
$iOtherCount += $aResult['_itop_count_'];
$oExpr = new BinaryExpression(new FieldExpression($sGroupingTabAttCode,
$oDistinctQuery->GetClassAlias()), '=', new UnaryExpression($aResult['grouped_by_1']));
$oConditionQuery->GetClassAlias()), '=', new UnaryExpression($aResult['grouped_by_1']));
if (is_null($oExpression))
{
$oExpression = $oExpr;
@@ -904,35 +937,10 @@ class ManageBrickController extends BrickController
protected function GetScopedQuery(Application $oApp, ManageBrick $oBrick, $sClass)
{
$oQuery = DBSearch::FromOQL($oBrick->GetOql());
$this->AddScopeToQuery($oQuery, $oApp, $oBrick, $sClass);
/** @var \Combodo\iTop\Portal\Helper\ScopeValidatorHelper $oScopeHelper */
$oScopeHelper = $oApp['scope_validator'];
$oScopeHelper->AddScopeToQuery($oQuery, $sClass);
return $oQuery;
}
/**
* @param DBSearch $oQuery
* @param Application $oApp
* @param ManageBrick $oBrick
* @param string $sClass
*
* @return bool true if scope exists, false if scope is null
*/
protected function AddScopeToQuery(DBSearch &$oQuery, Application $oApp, ManageBrick $oBrick, $sClass)
{
$oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sClass,
UR_ACTION_READ);
if ($oScopeQuery !== null)
{
$oQuery = $oQuery->Intersect($oScopeQuery);
// - Allowing all data if necessary
if ($oScopeQuery->IsAllDataAllowed())
{
$oQuery->AllowAllData();
}
return true;
}
return false;
}
}

View File

@@ -40,6 +40,7 @@ use ListExpression;
use ScalarExpression;
use DBObjectSet;
use AttributeEnum;
use AttributeImage;
use AttributeFinalClass;
use AttributeFriendlyName;
use UserRights;
@@ -61,6 +62,8 @@ class ObjectController extends AbstractController
const ENUM_MODE_VIEW = 'view';
const ENUM_MODE_EDIT = 'edit';
const ENUM_MODE_CREATE = 'create';
const DEFAULT_PAGE_NUMBER = 1;
const DEFAULT_LIST_LENGTH = 10;
/**
@@ -97,6 +100,8 @@ class ObjectController extends AbstractController
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$aData = array('sMode' => 'view');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass), $oObject->GetName());
@@ -117,7 +122,7 @@ class ObjectController extends AbstractController
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if ($oRequest->request->get('operation') === null)
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
@@ -129,8 +134,8 @@ class ObjectController extends AbstractController
else
{
// Adding brick if it was passed
$sBrickId = $oRequest->get('sBrickId');
if ($sBrickId !== null)
$sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
if (!empty($sBrickId))
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if ($oBrick !== null)
@@ -172,6 +177,8 @@ class ObjectController extends AbstractController
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$aData = array('sMode' => 'edit');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Edit:Title', MetaModel::GetName($sObjectClass), $aData['form']['object_name']);
@@ -180,7 +187,7 @@ class ObjectController extends AbstractController
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if ($oRequest->request->get('operation') === null)
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
@@ -192,8 +199,8 @@ class ObjectController extends AbstractController
else
{
// Adding brick if it was passed
$sBrickId = $oRequest->get('sBrickId');
if ($sBrickId !== null)
$sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
if (!empty($sBrickId))
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if ($oBrick !== null)
@@ -225,6 +232,8 @@ class ObjectController extends AbstractController
$oApp->abort(404, Dict::S('UI:ObjectDoesNotExist'));
}
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$aData = array('sMode' => 'create');
$aData['form'] = $this->HandleForm($oRequest, $oApp, $aData['sMode'], $sObjectClass);
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:Create:Title', MetaModel::GetName($sObjectClass));
@@ -233,7 +242,7 @@ class ObjectController extends AbstractController
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if ($oRequest->request->get('operation') === null)
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
@@ -245,8 +254,8 @@ class ObjectController extends AbstractController
else
{
// Adding brick if it was passed
$sBrickId = $oRequest->get('sBrickId');
if ($sBrickId !== null)
$sBrickId = $oApp['request_manipulator']->ReadParam('sBrickId', '');
if (!empty($sBrickId))
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
if ($oBrick !== null)
@@ -347,7 +356,7 @@ class ObjectController extends AbstractController
}
// Retrieving request parameters
$sOperation = $oRequest->request->get('operation');
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
// Retrieving form properties
$aStimuliForms = ApplicationHelper::GetLoadedFormFromClass($oApp, $sObjectClass, 'apply_stimulus');
@@ -382,7 +391,7 @@ class ObjectController extends AbstractController
// TODO : This is a ugly patch to avoid showing a modal with a readonly form to the user as it would prevent user from finishing the transition.
// Instead, we apply the stimulus directly here and then go to the edited object.
if ($sOperation === null)
if (empty($sOperation))
{
if (isset($aData['form']['editable_fields_count']) && $aData['form']['editable_fields_count'] === 0)
{
@@ -390,7 +399,7 @@ class ObjectController extends AbstractController
$oSubRequest = $oRequest;
$oSubRequest->request->set('operation', 'submit');
$oSubRequest->request->set('stimulus_code', null);
$oSubRequest->request->set('stimulus_code', '');
$aData = array('sMode' => 'apply_stimulus');
$aData['form'] = $this->HandleForm($oSubRequest, $oApp, $aData['sMode'], $sObjectClass, $sObjectId, $aFormProperties);
@@ -405,7 +414,7 @@ class ObjectController extends AbstractController
if ($oRequest->isXmlHttpRequest())
{
// We have to check whether the 'operation' parameter is defined or not in order to know if the form is required via ajax (to be displayed as a modal dialog) or if it's a lifecycle call from a existing form.
if ($sOperation === null)
if (empty($sOperation))
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/bricks/object/modal.html.twig', $aData);
}
@@ -429,9 +438,8 @@ class ObjectController extends AbstractController
public static function HandleForm(Request $oRequest, Application $oApp, $sMode, $sObjectClass, $sObjectId = null, $aFormProperties = null)
{
$aFormData = array();
$oRequestParams = $oRequest->request;
$sOperation = $oRequestParams->get('operation');
$bModal = ($oRequest->isXmlHttpRequest() && ($oRequest->request->get('operation') === null) );
$sOperation = $oApp['request_manipulator']->ReadParam('operation', '');
$bModal = ($oRequest->isXmlHttpRequest() && empty($sOperation));
// - Retrieve form properties
if ($aFormProperties === null)
@@ -440,14 +448,14 @@ class ObjectController extends AbstractController
}
// - Create and
if ($sOperation === null)
if (empty($sOperation))
{
// Retrieving action rules
//
// Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
// But it would not be a security issue as it only presets values in the form.
$sActionRulesToken = $oRequest->get('ar_token');
$aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
$sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
$aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
// Preparing object
if ($sObjectId === null)
@@ -520,9 +528,11 @@ class ObjectController extends AbstractController
}
else
{
$aPrefillFormParam = array('user' => $_SESSION["auth_user"],
$aPrefillFormParam = array(
'user' => $_SESSION["auth_user"],
'origin' => 'portal',
'stimulus' => $oRequestParams->get('apply_stimulus')['code']);
'stimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)['code'],
);
$oObject->PrefillForm('state_change', $aPrefillFormParam);
}
@@ -560,9 +570,9 @@ class ObjectController extends AbstractController
else
{
// Update / Submit / Cancel
$sFormManagerClass = $oRequestParams->get('formmanager_class');
$sFormManagerData = $oRequestParams->get('formmanager_data');
if ($sFormManagerClass === null || $sFormManagerData === null)
$sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
$sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
if ( empty($sFormManagerClass) || empty($sFormManagerData) )
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
$oApp->abort(500, 'Parameters formmanager_class and formmanager_data must be defined.');
@@ -584,13 +594,13 @@ class ObjectController extends AbstractController
{
case 'submit':
// Applying modification to object
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oRequestParams->get('current_values'), 'attachmentIds' => $oRequest->get('attachment_ids'), 'formProperties' => $aFormProperties, 'applyStimulus' => $oRequestParams->get('apply_stimulus')));
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'attachmentIds' => $oApp['request_manipulator']->ReadParam('attachment_ids', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties, 'applyStimulus' => $oApp['request_manipulator']->ReadParam('apply_stimulus', null)));
if ($aFormData['validation']['valid'] === true)
{
// Note : We don't use $sObjectId there as it can be null if we are creating a new one. Instead we use the id from the created object once it has been seralized
// Check if stimulus has to be applied
$sStimulusCode = ($oRequestParams->get('stimulus_code') !== null && $oRequestParams->get('stimulus_code') !== '') ? $oRequestParams->get('stimulus_code') : null;
if ($sStimulusCode !== null)
$sStimulusCode = $oApp['request_manipulator']->ReadParam('stimulus_code', '');
if (!empty($sStimulusCode))
{
$aFormData['validation']['redirection'] = array(
'url' => $oApp['url_generator']->generate('p_object_apply_stimulus', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey(), 'sStimulusCode' => $sStimulusCode)),
@@ -598,17 +608,17 @@ class ObjectController extends AbstractController
);
}
// Otherwise, we show the object if there is no default
else
{
// else
// {
// $aFormData['validation']['redirection'] = array(
// 'alternative_url' => $oApp['url_generator']->generate('p_object_edit', array('sObjectClass' => $sObjectClass, 'sObjectId' => $oFormManager->GetObject()->GetKey()))
// );
}
// }
}
break;
case 'update':
$oFormManager->OnUpdate(array('currentValues' => $oRequestParams->get('current_values'), 'formProperties' => $aFormProperties));
$oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW), 'formProperties' => $aFormProperties));
break;
case 'cancel':
@@ -627,11 +637,11 @@ class ObjectController extends AbstractController
// Preparing fields list regarding the operation
if ($sOperation === 'update')
{
$aRequestedFields = $oRequestParams->get('requested_fields');
$sFormPath = $oRequestParams->get('form_path');
$aRequestedFields = $oApp['request_manipulator']->ReadParam('requested_fields', array(), FILTER_UNSAFE_RAW);
$sFormPath = $oApp['request_manipulator']->ReadParam('form_path', '');
// Checking if the update was on a subform, if so we need to make the rendering for that part only
if ($sFormPath !== null && $sFormPath !== $oFormManager->GetForm()->GetId())
if ( !empty($sFormPath) && $sFormPath !== $oFormManager->GetForm()->GetId() )
{
$oSubForm = $oFormManager->GetForm()->FindSubForm($sFormPath);
$oSubFormRenderer = new BsFormRenderer($oSubForm);
@@ -716,8 +726,8 @@ class ObjectController extends AbstractController
//
// Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
// But it would not be a security issue as it only presets values in the form.
$sActionRulesToken = $oRequest->get('ar_token');
$aActionRules = ($sActionRulesToken !== null) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
$sActionRulesToken = $oApp['request_manipulator']->ReadParam('ar_token', '');
$aActionRules = (!empty($sActionRulesToken)) ? ContextManipulatorHelper::DecodeRulesToken($sActionRulesToken) : array();
// Preparing object
$oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
}
@@ -725,7 +735,7 @@ class ObjectController extends AbstractController
// Updating host object with form data / values
$sFormManagerClass = $aRequestContent['formmanager_class'];
$sFormManagerData = $aRequestContent['formmanager_data'];
if ($sFormManagerClass !== null && $sFormManagerData !== null)
if (!empty($sFormManagerClass) && !empty($sFormManagerData))
{
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
$oFormManager->SetApplication($oApp);
@@ -837,7 +847,7 @@ class ObjectController extends AbstractController
'sTargetAttCode' => $sTargetAttCode,
'sHostObjectClass' => $sHostObjectClass,
'sHostObjectId' => $sHostObjectId,
'sActionRulesToken' => $oRequest->get('ar_token')
'sActionRulesToken' => $oApp['request_manipulator']->ReadParam('ar_token', ''),
);
// Checking security layers
@@ -860,16 +870,15 @@ class ObjectController extends AbstractController
//
// Note : The action rules must be a base64-encoded JSON object, this is just so users are tempted to changes values.
// But it would not be a security issue as it only presets values in the form.
$aActionRules = ($aData['sActionRulesToken'] !== null) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
$aActionRules = !empty($aData['sActionRulesToken']) ? ContextManipulatorHelper::DecodeRulesToken($aData['sActionRulesToken']) : array();
// Preparing object
$oApp['context_manipulator']->PrepareObject($aActionRules, $oHostObject);
}
// Updating host object with form data / values
$oRequestParams = $oRequest->request;
$sFormManagerClass = $oRequestParams->get('formmanager_class');
$sFormManagerData = $oRequestParams->get('formmanager_data');
if ($sFormManagerClass !== null && $sFormManagerData !== null)
$sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', '', FILTER_UNSAFE_RAW);
$sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', '', FILTER_UNSAFE_RAW);
if ( !empty($sFormManagerClass) && !empty($sFormManagerData) )
{
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
$oFormManager->SetApplication($oApp);
@@ -885,18 +894,18 @@ class ObjectController extends AbstractController
}
// Updating host object
$oFormManager->OnUpdate(array('currentValues' => $oRequestParams->get('current_values')));
$oFormManager->OnUpdate(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW)));
$oHostObject = $oFormManager->GetObject();
}
// Retrieving request parameters
$iPageNumber = ($oRequest->get('iPageNumber') !== null) ? $oRequest->get('iPageNumber') : 1;
$iListLength = ($oRequest->get('iListLength') !== null) ? $oRequest->get('iListLength') : static::DEFAULT_LIST_LENGTH;
$bInitalPass = ($oRequest->get('draw') === null) ? true : false;
$sQuery = $oRequest->get('sSearchValue');
$sFormPath = $oRequest->get('sFormPath');
$sFieldId = $oRequest->get('sFieldId');
$aObjectIdsToIgnore = $oRequest->get('aObjectIdsToIgnore');
$iPageNumber = $oApp['request_manipulator']->ReadParam('iPageNumber', static::DEFAULT_PAGE_NUMBER, FILTER_SANITIZE_NUMBER_INT);
$iListLength = $oApp['request_manipulator']->ReadParam('iListLength', static::DEFAULT_LIST_LENGTH, FILTER_SANITIZE_NUMBER_INT);
$bInitalPass = $oApp['request_manipulator']->HasParam('draw') ? false : true;
$sQuery = $oApp['request_manipulator']->ReadParam('sSearchValue', '');
$sFormPath = $oApp['request_manipulator']->ReadParam('sFormPath', '');
$sFieldId = $oApp['request_manipulator']->ReadParam('sFieldId', '');
$aObjectIdsToIgnore = $oApp['request_manipulator']->ReadParam('aObjectIdsToIgnore', null, FILTER_UNSAFE_RAW);
// Building search query
// - Retrieving target object class from attcode
@@ -977,7 +986,7 @@ class ObjectController extends AbstractController
// - Adding query condition
$aInternalParams['this'] = $oHostObject;
if ($sQuery !== null)
if (!empty($sQuery))
{
$oFullExpr = null;
for ($i = 0; $i < count($aAttCodes); $i++)
@@ -1352,9 +1361,9 @@ class ObjectController extends AbstractController
}
// Retrieving ormDocument's host object
$sObjectClass = $oRequest->get('sObjectClass');
$sObjectId = $oRequest->get('sObjectId');
$sObjectField = $oRequest->get('sObjectField');
$sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
$sObjectId = $oApp['request_manipulator']->ReadParam('sObjectId', '');
$sObjectField = $oApp['request_manipulator']->ReadParam('sObjectField', '');
// When reaching to an Attachment, we have to check security on its host object instead of the Attachment itself
if($sObjectClass === 'Attachment')
@@ -1394,8 +1403,7 @@ class ObjectController extends AbstractController
}
else
{
$sCache = $oRequest->get('cache');
$iCacheSec = ($sCache !== null) ? (int) $sCache : 0;
$iCacheSec = $oApp['request_manipulator']->ReadParam('cache', 0, FILTER_SANITIZE_NUMBER_INT);
}
$aHeaders = array();
@@ -1436,16 +1444,16 @@ class ObjectController extends AbstractController
// Retrieving sOperation from request only if it wasn't forced (determined by the route)
if ($sOperation === null)
{
$sOperation = $oRequest->get('operation');
$sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
}
switch ($sOperation)
{
case 'add':
$sFieldName = $oRequest->get('field_name');
$sObjectClass = $oRequest->get('object_class');
$sTempId = $oRequest->get('temp_id');
$sFieldName = $oApp['request_manipulator']->ReadParam('field_name', '');
$sObjectClass = $oApp['request_manipulator']->ReadParam('object_class', '');
$sTempId = $oApp['request_manipulator']->ReadParam('temp_id', '');
if (($sObjectClass === null) || ($sTempId === null))
if (empty($sObjectClass) || empty($sTempId))
{
$aData['error'] = Dict::Format('UI:Error:2ParametersMissing', 'object_class', 'temp_id');
}
@@ -1455,7 +1463,7 @@ class ObjectController extends AbstractController
{
$oDocument = utils::ReadPostedDocument($sFieldName);
$oAttachment = MetaModel::NewObject('Attachment');
$oAttachment->Set('expire', time() + 3600); // one hour...
$oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime')); // one hour...
$oAttachment->Set('temp_id', $sTempId);
$oAttachment->Set('item_class', $sObjectClass);
$oAttachment->SetDefaultOrgId();
@@ -1484,7 +1492,7 @@ class ObjectController extends AbstractController
// - Route
$aRouteParams = array(
'sObjectClass' => 'Attachment',
'sObjectId' => $oRequest->get('sAttachmentId'),
'sObjectId' => $oApp['request_manipulator']->ReadParam('sAttachmentId', null),
'sObjectField' => 'contents',
);
$sRedirectRoute = $oApp['url_generator']->generate('p_object_document_download', $aRouteParams);
@@ -1519,10 +1527,10 @@ class ObjectController extends AbstractController
$aData = array();
// Retrieving parameters
$sObjectClass = $oRequest->Get('sObjectClass');
$aObjectIds = $oRequest->Get('aObjectIds');
$aObjectAttCodes = $oRequest->Get('aObjectAttCodes');
if ($sObjectClass === null || $aObjectIds === null || $aObjectAttCodes === null)
$sObjectClass = $oApp['request_manipulator']->ReadParam('sObjectClass', '');
$aObjectIds = $oApp['request_manipulator']->ReadParam('aObjectIds', array(), FILTER_UNSAFE_RAW);
$aObjectAttCodes = $oApp['request_manipulator']->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
if ( empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes) )
{
IssueLog::Info(__METHOD__ . ' at line ' . __LINE__ . ' : sObjectClass, sObjectId and aObjectAttCodes expected, "' . $sObjectClass . '", "' . $sObjectId . '" given.');
$oApp->abort(500, 'Invalid request data, some informations are missing');
@@ -1590,7 +1598,7 @@ class ObjectController extends AbstractController
if ($oAttDef->IsExternalKey())
{
$aAttData['value'] = $oObject->Get($oAttDef->GetCode() . '_friendlyname');
$aAttData['value'] = $oObject->GetAsHTML($oAttDef->GetCode() . '_friendlyname');
// Checking if user can access object's external key
if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass()))
@@ -1603,9 +1611,22 @@ class ObjectController extends AbstractController
// We skip it
continue;
}
elseif ($oAttDef instanceof AttributeImage)
{
$oOrmDoc = $oObject->Get($oAttDef->GetCode());
if (is_object($oOrmDoc) && !$oOrmDoc->IsEmpty())
{
$sUrl = $oApp['url_generator']->generate('p_object_document_display', array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey(), 'sObjectField' => $oAttDef->GetCode(), 'cache' => 86400));
}
else
{
$sUrl = $oAttDef->Get('default_image');
}
$aAttData['value'] = '<img src="' . $sUrl . '" />';
}
else
{
$aAttData['value'] = $oAttDef->GetValueLabel($oObject->Get($oAttDef->GetCode()));
$aAttData['value'] = $oAttDef->GetAsHTML($oObject->Get($oAttDef->GetCode()));
if ($oAttDef instanceof AttributeFriendlyName)
{

View File

@@ -71,7 +71,7 @@ class UserProfileBrickController extends BrickController
// If this is ajax call, we are just submiting preferences or password forms
if ($oRequest->isXmlHttpRequest())
{
$aCurrentValues = $oRequest->request->get('current_values');
$aCurrentValues = $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW);
$sFormType = $aCurrentValues['form_type'];
if ($sFormType === PreferencesFormManager::FORM_TYPE)
{
@@ -120,10 +120,9 @@ class UserProfileBrickController extends BrickController
public function HandlePreferencesForm(Request $oRequest, Application $oApp, $sFormMode)
{
$aFormData = array();
$oRequestParams = $oRequest->request;
// Handling form
$sOperation = $oRequestParams->get('operation');
$sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
// - Create
if ($sOperation === null)
{
@@ -143,8 +142,8 @@ class UserProfileBrickController extends BrickController
// - Submit
else if ($sOperation === 'submit')
{
$sFormManagerClass = $oRequestParams->get('formmanager_class');
$sFormManagerData = $oRequestParams->get('formmanager_data');
$sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', null, FILTER_UNSAFE_RAW);
$sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', null, FILTER_UNSAFE_RAW);
if ($sFormManagerClass === null || $sFormManagerData === null)
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
@@ -154,7 +153,7 @@ class UserProfileBrickController extends BrickController
// Rebuilding manager from json
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
// Applying modification to object
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oRequestParams->get('current_values')));
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW)));
// Reloading page only if preferences were changed
if (($aFormData['validation']['valid'] === true) && !empty($aFormData['validation']['messages']['success']))
{
@@ -188,10 +187,9 @@ class UserProfileBrickController extends BrickController
public function HandlePasswordForm(Request $oRequest, Application $oApp)
{
$aFormData = array();
$oRequestParams = $oRequest->request;
// Handling form
$sOperation = $oRequestParams->get('operation');
$sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
// - Create
if ($sOperation === null)
{
@@ -206,8 +204,8 @@ class UserProfileBrickController extends BrickController
// - Submit
else if ($sOperation === 'submit')
{
$sFormManagerClass = $oRequestParams->get('formmanager_class');
$sFormManagerData = $oRequestParams->get('formmanager_data');
$sFormManagerClass = $oApp['request_manipulator']->ReadParam('formmanager_class', null, FILTER_UNSAFE_RAW);
$sFormManagerData = $oApp['request_manipulator']->ReadParam('formmanager_data', null, FILTER_UNSAFE_RAW);
if ($sFormManagerClass === null || $sFormManagerData === null)
{
IssueLog::Error(__METHOD__ . ' at line ' . __LINE__ . ' : Parameters formmanager_class and formamanager_data must be defined.');
@@ -217,7 +215,7 @@ class UserProfileBrickController extends BrickController
// Rebuilding manager from json
$oFormManager = $sFormManagerClass::FromJSON($sFormManagerData);
// Applying modification to object
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oRequestParams->get('current_values')));
$aFormData['validation'] = $oFormManager->OnSubmit(array('currentValues' => $oApp['request_manipulator']->ReadParam('current_values', array(), FILTER_UNSAFE_RAW)));
}
else
{
@@ -244,11 +242,10 @@ class UserProfileBrickController extends BrickController
public function HandlePictureForm(Request $oRequest, Application $oApp, $sFormMode)
{
$aFormData = array();
$oRequestParams = $oRequest->request;
$sPictureAttCode = 'picture';
// Handling form
$sOperation = $oRequestParams->get('operation');
$sOperation = $oApp['request_manipulator']->ReadParam('operation', null);
// - No operation specified
if ($sOperation === null)
{

View File

@@ -30,29 +30,37 @@ use Combodo\iTop\Portal\Brick\PortalBrick;
*/
class BrowseBrick extends PortalBrick
{
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-map';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-map fa-2x';
const ENUM_BROWSE_MODE_LIST = 'list';
const ENUM_BROWSE_MODE_TREE = 'tree';
const ENUM_BROWSE_MODE_MOSAIC = 'mosaic';
const ENUM_ACTION_VIEW = 'view';
const ENUM_ACTION_EDIT = 'edit';
const ENUM_ACTION_DRILLDOWN = 'drilldown';
const ENUM_ACTION_CREATE_FROM_THIS = 'create_from_this';
const ENUM_ACTION_ICON_CLASS_VIEW = 'glyphicon glyphicon-list-alt';
const ENUM_ACTION_ICON_CLASS_EDIT = 'glyphicon glyphicon-pencil';
const ENUM_ACTION_ICON_CLASS_DRILLDOWN = 'glyphicon glyphicon-menu-down';
const ENUM_ACTION_ICON_CLASS_CREATE_FROM_THIS = 'glyphicon glyphicon-edit';
const ENUM_FACTORY_TYPE_METHOD = 'method';
const ENUM_FACTORY_TYPE_CLASS = 'class';
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_FULL;
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-map';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-map fa-2x';
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_FULL;
const DEFAULT_LEVEL_NAME_ATT = 'name';
const DEFAULT_BROWSE_MODE = self::ENUM_BROWSE_MODE_LIST;
const DEFAULT_ACTION = self::ENUM_ACTION_DRILLDOWN;
const DEFAULT_ACTION_OPENING_TARGET = self::ENUM_OPENING_TARGET_MODAL;
const DEFAULT_LIST_LENGTH = 20;
static $aBrowseModes = array(self::ENUM_BROWSE_MODE_LIST, self::ENUM_BROWSE_MODE_TREE, self::ENUM_BROWSE_MODE_MOSAIC);
static $aBrowseModes = array(
self::ENUM_BROWSE_MODE_LIST,
self::ENUM_BROWSE_MODE_TREE,
self::ENUM_BROWSE_MODE_MOSAIC
);
static $sRouteName = 'p_browse_brick';

View File

@@ -32,6 +32,9 @@ class FilterBrick extends PortalBrick
{
const DEFAULT_VISIBLE_NAVIGATION_MENU = false;
const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/filter/tile.html.twig';
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-search';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-search fa-2x';
const DEFAULT_TARGET_BRICK_CLASS = 'Combodo\\iTop\\Portal\\Brick\\BrowseBrick';
const DEFAULT_SEARCH_PLACEHOLDER_VALUE = 'Brick:Portal:Filter:SearchInput:Placeholder';
const DEFAULT_SEARCH_SUBMIT_LABEL = 'Brick:Portal:Filter:SearchInput:Submit';

View File

@@ -1,6 +1,6 @@
<?php
// Copyright (C) 2010-2017 Combodo SARL
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
@@ -25,89 +25,151 @@ use DBSearch;
use DOMFormatException;
use MetaModel;
define('MANAGE_BRICK_LAYOUT_PATH', 'itop-portal-base/portal/src/views/bricks/manage/');
class ManageBrick extends PortalBrick
{
const ENUM_ACTION_VIEW = 'view';
const ENUM_ACTION_EDIT = 'edit';
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-pencil-square';
const ENUM_TILE_MODE_TEXT = 'text';
const ENUM_TILE_MODE_BADGE = 'badge';
const ENUM_TILE_MODE_PIE = 'pie-chart';
const ENUM_TILE_MODE_BAR = 'bar-chart';
const ENUM_TILE_MODE_TOP = 'top-list';
const ENUM_DISPLAY_MODE_LIST = 'list';
const ENUM_DISPLAY_MODE_PIE = 'pie-chart';
const ENUM_DISPLAY_MODE_BAR = 'bar-chart';
const ENUM_PAGE_TEMPLATE_PATH_TABLE = 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig';
const ENUM_PAGE_TEMPLATE_PATH_CHART = 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig';
const DEFAULT_DECORATION_CLASS_HOME = 'fa fa-pencil-square';
const DEFAULT_DECORATION_CLASS_NAVIGATION_MENU = 'fa fa-pencil-square fa-2x';
const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig';
const CHART_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig';
const DEFAULT_PAGE_TEMPLATE_PATH = self::ENUM_PAGE_TEMPLATE_PATH_TABLE;
const DEFAULT_OQL = '';
const DEFAULT_OPENING_MODE = self::ENUM_ACTION_EDIT;
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_LAZY;
const DEFAULT_LIST_LENGTH = 20;
const DEFAULT_ZLIST_FIELDS = 'list';
const DEFAULT_SHOW_TAB_COUNTS = false;
const DEFAULT_DISPLAY_MODE = self::ENUM_DISPLAY_MODE_LIST;
const DEFAULT_TILE_MODE = self::ENUM_TILE_MODE_TEXT;
const DEFAULT_GROUP_LIMIT = 0;
const DEFAULT_GROUP_SHOW_OTHERS = true;
const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/tile-default.html.twig';
const DEFAULT_TILE_CONTROLLER_ACTION = 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::TileAction';
static $aDisplayModes = array(
self::ENUM_DISPLAY_MODE_LIST,
self::ENUM_DISPLAY_MODE_PIE,
self::ENUM_DISPLAY_MODE_BAR,
);
static $aTileModes = array(
self::ENUM_TILE_MODE_TEXT,
self::ENUM_TILE_MODE_BADGE,
self::ENUM_TILE_MODE_PIE,
self::ENUM_TILE_MODE_BAR,
self::ENUM_TILE_MODE_TOP,
);
static $aPresentationData = array(
self::ENUM_TILE_MODE_BADGE => array(
'decorationCssClass' => 'fa fa-id-card-o fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-badge.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_TABLE,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_LIST,
'need_details' => true,
),
self::ENUM_TILE_MODE_TOP => array(
'decorationCssClass' => 'fa fa-signal fa-rotate-270 fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-top-list.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_TABLE,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_LIST,
'need_details' => true,
),
self::ENUM_TILE_MODE_PIE => array(
'decorationCssClass' => 'fa fa-pie-chart fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_CHART,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_PIE,
'need_details' => false,
),
self::ENUM_TILE_MODE_BAR => array(
'decorationCssClass' => 'fa fa-bar-chart fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_CHART,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_BAR,
'need_details' => false,
),
self::ENUM_TILE_MODE_TEXT => array(
'decorationCssClass' => 'fa fa-pencil-square fa-2x',
'tileTemplate' => self::DEFAULT_TILE_TEMPLATE_PATH,
'layoutTemplate' => self::ENUM_PAGE_TEMPLATE_PATH_TABLE,
'layoutDisplayMode' => self::ENUM_DISPLAY_MODE_LIST,
'need_details' => true,
),
);
static $sRouteName = 'p_manage_brick';
protected $sOql;
protected $sOpeningMode;
protected $aGrouping;
protected $aFields;
protected $aExportFields;
protected $bShowTabCounts;
/**
* @var string default display mode for the brick's tile
*/
protected $sDisplayType;
protected $aAvailableDisplayModes = array();
protected $sDefaultDisplayMode;
protected $sTileMode;
protected $iGroupLimit;
protected $bGroupShowOthers;
protected $aPresentationData = array(
'badge' => array(
'decorationCssClass' => 'fa fa-id-card-o fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-badge.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig',
'layoutDisplayType' => ManageBrick::DISPLAY_MODE_TABLE,
'need_details' => true,
),
'top-list' => array(
'decorationCssClass' => 'fa fa-signal fa-rotate-270 fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-top-list.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig',
'layoutDisplayType' => ManageBrick::DISPLAY_MODE_TABLE,
'need_details' => true,
),
'pie-chart' => array(
'decorationCssClass' => 'fa fa-pie-chart fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig',
'layoutDisplayType' => ManageBrick::DISPLAY_MODE_PIE,
'need_details' => false,
),
'bar-chart' => array(
'decorationCssClass' => 'fa fa-bar-chart fa-2x',
'tileTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/tile-chart.html.twig',
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-chart.html.twig',
'layoutDisplayType' => ManageBrick::DISPLAY_MODE_BAR,
'need_details' => false,
),
'default' => array(
'decorationCssClass' => 'fa fa-pencil-square fa-2x',
'tileTemplate' => self::DEFAULT_TILE_TEMPLATE_PATH,
'layoutTemplate' => 'itop-portal-base/portal/src/views/bricks/manage/layout-table.html.twig',
'layoutDisplayType' => ManageBrick::DISPLAY_MODE_TABLE,
'need_details' => true,
),
);
protected $aAvailableDisplayModes = array();
const DISPLAY_MODE_TABLE = 'default';
const DISPLAY_MODE_PIE = 'pie-chart';
const DISPLAY_MODE_BAR = 'bar-chart';
const DISPLAY_MODES_ALLOWED = array(
ManageBrick::DISPLAY_MODE_TABLE,
ManageBrick::DISPLAY_MODE_PIE,
ManageBrick::DISPLAY_MODE_BAR
);
/**
* Returns true if the $sDisplayMode need objects details for rendering.
*
* @param string $sDisplayMode
*
* @return bool
*/
static public function AreDetailsNeededForDisplayMode($sDisplayMode)
{
$bNeedDetails = false;
foreach(static::$aPresentationData as $aData)
{
if($aData['layoutDisplayMode'] === $sDisplayMode)
{
$bNeedDetails = $aData['need_details'];
break;
}
}
public function __construct()
return $bNeedDetails;
}
/**
* Returns the page template path for the $sDisplayMode
*
* @param string $sDisplayMode
*
* @return string
*/
static public function GetPageTemplateFromDisplayMode($sDisplayMode)
{
$sTemplate = static::DEFAULT_PAGE_TEMPLATE_PATH;
foreach(static::$aPresentationData as $aData)
{
if($aData['layoutDisplayMode'] === $sDisplayMode)
{
$sTemplate = $aData['layoutTemplate'];
break;
}
}
return $sTemplate;
}
public function __construct()
{
parent::__construct();
@@ -117,6 +179,11 @@ class ManageBrick extends PortalBrick
$this->aFields = array();
$this->aExportFields = array();
$this->bShowTabCounts = static::DEFAULT_SHOW_TAB_COUNTS;
$this->sDefaultDisplayMode = static::DEFAULT_DISPLAY_MODE;
$this->sTileMode = static::DEFAULT_TILE_MODE;
$this->iGroupLimit = static::DEFAULT_GROUP_LIMIT;
$this->bGroupShowOthers = static::DEFAULT_GROUP_SHOW_OTHERS;
// This is hardcoded for now, we might allow area grouping on another attribute in the future
$this->AddGrouping('areas', array('attribute' => 'finalclass'));
@@ -182,35 +249,66 @@ class ManageBrick extends PortalBrick
return $this->bShowTabCounts;
}
/**
* Returns the brick default display mode
*
* @return string
*/
public function GetDefaultDisplayMode()
{
return $this->sDefaultDisplayMode;
}
/**
* Sets the default display mode of the brick
*
* @param string $sDefaultDisplayMode
*
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function SetDefaultDisplayMode($sDefaultDisplayMode)
{
$this->sDefaultDisplayMode = $sDefaultDisplayMode;
return $this;
}
/**
* Returns the tile mode (display)
*
* @return string
*/
public function GetDisplayType()
public function GetTileMode()
{
return $this->sDisplayType;
return $this->sTileMode;
}
/**
* @param string $sDisplayType
* Sets the tile mode (display)
*
* @param string $sTileMode
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function SetDisplayType($sDisplayType)
public function SetTileMode($sTileMode)
{
$this->sDisplayType = $sDisplayType;
$this->sTileMode = $sTileMode;
return $this;
}
/**
* @param string $sDisplayType
* @param string $sTileMode
*
* @return string[] parameters for specified type, default parameters if type is invalid
*/
public function GetPresentationDataForDisplayType($sDisplayType)
public function GetPresentationDataForTileMode($sTileMode)
{
if (isset($this->aPresentationData[$sDisplayType]))
if (isset(static::$aPresentationData[$sTileMode]))
{
return $this->aPresentationData[$sDisplayType];
return static::$aPresentationData[$sTileMode];
}
return $this->aPresentationData['default'];
return static::$aPresentationData[static::DEFAULT_TILE_MODE];
}
/**
@@ -391,7 +489,6 @@ class ManageBrick extends PortalBrick
return $this;
}
/**
* Returns if the brick has grouping tabs or not.
*
@@ -432,12 +529,41 @@ class ManageBrick extends PortalBrick
return (isset($this->aGrouping['areas'])) ? $this->aGrouping['areas'] : false;
}
/**
* @param string $sModeId
*
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function AddAvailableDisplayMode($sModeId)
{
if(!in_array($sModeId, static::$aDisplayModes))
{
throw new Exception('ManageBrick: Display mode "' . $sModeId. '" must be one of the allowed display modes (' . implode(', ', static::$aDisplayModes) . ')');
}
$this->aAvailableDisplayModes[] = $sModeId;
return $this;
}
/**
* Removes $sModeId from the list of availables display modes
*
* @param string $sModeId
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function RemoveAvailableDisplayMode($sModeId)
{
if (isset($this->aAvailableDisplayModes[$sModeId]))
{
unset($this->aAvailableDisplayModes[$sModeId]);
}
return $this;
}
/**
* Returns the available display modes for the brick (page, not tile)
*
* @return string[]
*/
public function GetAvailablesDisplayModes()
@@ -495,9 +621,6 @@ class ManageBrick extends PortalBrick
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
$this->sDisplayType = 'default';
$this->iGroupLimit = 0;
$this->bGroupShowOthers = true;
$bUseListFieldsForExport = false;
// Checking specific elements
@@ -509,7 +632,7 @@ class ManageBrick extends PortalBrick
$sClass = $oBrickSubNode->GetText();
if ($sClass === '')
{
throw new DOMFormatException('ManageBrick : class tag is empty. Must contain Classname', null,
throw new DOMFormatException('ManageBrick: class tag is empty. Must contain Classname', null,
null, $oBrickSubNode);
}
@@ -520,7 +643,7 @@ class ManageBrick extends PortalBrick
$sOql = $oBrickSubNode->GetText();
if ($sOql === '')
{
throw new DOMFormatException('ManageBrick : oql tag is empty. Must contain OQL statement', null,
throw new DOMFormatException('ManageBrick: oql tag is empty. Must contain OQL statement', null,
null, $oBrickSubNode);
}
@@ -531,7 +654,7 @@ class ManageBrick extends PortalBrick
$sOpeningMode = $oBrickSubNode->GetText(static::DEFAULT_OPENING_MODE);
if (!in_array($sOpeningMode, array(static::ENUM_ACTION_VIEW, static::ENUM_ACTION_EDIT)))
{
throw new DOMFormatException('ManageBrick : opening_mode tag value must be edit|view ("'.$sOpeningMode.'" given)',
throw new DOMFormatException('ManageBrick: opening_mode tag value must be edit|view ("'.$sOpeningMode.'" given)',
null, null, $oBrickSubNode);
}
@@ -548,14 +671,14 @@ class ManageBrick extends PortalBrick
{
if (!$oModeNode->hasAttribute('id'))
{
throw new DOMFormatException('ManageBrick : display mode must have a unique ID attribute',
throw new DOMFormatException('ManageBrick: Display mode must have a unique ID attribute',
null, null, $oModeNode);
}
$sModeId = $oModeNode->getAttribute('id');
if (!in_array($sModeId, ManageBrick::DISPLAY_MODES_ALLOWED))
if (!in_array($sModeId, static::$aDisplayModes))
{
throw new DOMFormatException('ManageBrick : display mode has an invalid value',
throw new DOMFormatException('ManageBrick: Display mode has an invalid value. Expected '.implode('/', static::$aDisplayModes.', "'.$sModeId.'" given.'),
null, null, $oModeNode);
}
@@ -563,11 +686,16 @@ class ManageBrick extends PortalBrick
}
break;
case 'default';
$this->sDisplayType = $oDisplayNode->nodeValue;
$aDisplayParameterForType = $this->GetPresentationDataForDisplayType($this->sDisplayType);
$this->SetTileTemplatePath($aDisplayParameterForType['tileTemplate']);
$this->SetPageTemplatePath($aDisplayParameterForType['layoutTemplate']);
case 'default':
$this->SetDefaultDisplayMode($oDisplayNode->GetText(static::DEFAULT_DISPLAY_MODE));
break;
case 'tile';
$this->SetTileMode($oDisplayNode->GetText(static::DEFAULT_TILE_MODE));
$aTileParametersForType = $this->GetPresentationDataForTileMode($this->sTileMode);
$this->SetTileTemplatePath($aTileParametersForType['tileTemplate']);
$this->SetPageTemplatePath($aTileParametersForType['layoutTemplate']);
break;
}
}
@@ -689,21 +817,24 @@ class ManageBrick extends PortalBrick
// Checking if has an oql
if ($this->GetOql() === '')
{
throw new DOMFormatException('BrowseBrick : must have a valid <class|oql> tag', null, null, $oMDElement);
throw new DOMFormatException('ManageBrick: must have a valid <class|oql> tag', null, null, $oMDElement);
}
// Display modes : at least one selected
$sDefaultDetailDisplayMode = (isset($this->sDisplayType))
? $this->aPresentationData[$this->sDisplayType]['layoutDisplayType']
: 'default';
$bHasAvailableDisplayModes = (count($this->GetAvailablesDisplayModes()) > 0);
$bIsDefaultDisplayModeInAvailableModes = in_array($sDefaultDetailDisplayMode,
$this->GetAvailablesDisplayModes());
if (!$bHasAvailableDisplayModes || (!$bIsDefaultDisplayModeInAvailableModes))
{
// legacy : setting to default
$this->AddAvailableDisplayMode($sDefaultDetailDisplayMode);
}
// Checking that the brick has at least a display mode
if (count($this->GetAvailablesDisplayModes()) === 0)
{
$this->AddAvailableDisplayMode(static::DEFAULT_DISPLAY_MODE);
}
// Checking that default display mode in among the availables
if (!in_array($this->sDefaultDisplayMode, $this->aAvailableDisplayModes))
{
throw new DOMFormatException('ManageBrick: Default display mode "' . $this->sDefaultDisplayMode . '" must be one of the available display modes (' . implode(', ', $this->aAvailableDisplayModes) . ')', null, null, $oMDElement);
}
// Checking that tile mode in among the availables
if (!in_array($this->sTileMode, static::$aTileModes))
{
throw new DOMFormatException('ManageBrick: Tile mode "' . $this->sTileMode. '" must be one of the allowed tile modes (' . implode(', ', static::$aTileModes) . ')', null, null, $oMDElement);
}
// Checking if specified fields, if not we put those from the details zlist
if (empty($this->aFields))
@@ -725,9 +856,9 @@ class ManageBrick extends PortalBrick
// Checking the navigation icon
$sDecorationClassNavigationMenu = $this->GetDecorationClassNavigationMenu();
if (empty($sDecorationClassNavigationMenu) && isset($this->aPresentationData[$this->sDisplayType]))
if (empty($sDecorationClassNavigationMenu) && isset(static::$aPresentationData[$this->sTileMode]))
{
$sDecorationClassNavigationMenu = $this->aPresentationData[$this->sDisplayType]['decorationCssClass'];
$sDecorationClassNavigationMenu = static::$aPresentationData[$this->sTileMode]['decorationCssClass'];
if (!empty($sDecorationClassNavigationMenu))
{
$this->SetDecorationClassNavigationMenu($sDecorationClassNavigationMenu);
@@ -738,8 +869,8 @@ class ManageBrick extends PortalBrick
if (empty($sTitle))
{
$sOql = $this->GetOql();
$oSeach = DBSearch::FromOQL($sOql);
$sClassName = MetaModel::GetName($oSeach->GetClass());
$oSearch = DBSearch::FromOQL($sOql);
$sClassName = MetaModel::GetName($oSearch->GetClass());
$this->SetTitleHome($sClassName);
$this->SetTitleNavigationMenu($sClassName);
$this->SetTitle($sClassName);

View File

@@ -35,7 +35,7 @@ abstract class PortalBrick extends AbstractBrick
const ENUM_OPENING_TARGET_SELF = 'self';
const ENUM_OPENING_TARGET_NEW = 'new';
const DEFAULT_WIDTH = 1;
const DEFAULT_WIDTH = 6;
const DEFAULT_HEIGHT = 1;
const DEFAULT_MODAL = false;
const DEFAULT_VISIBLE_HOME = true;

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