Compare commits

..

599 Commits

Author SHA1 Message Date
Eric Espie
f9fc85e763 N°4326 - Fix error in dashboard when no tooltip exist on Pill
(cherry picked from commit 6caf78fdcd)
2021-10-04 11:27:14 +02:00
Eric Espie
1a1439946b N°4338 - Fix indirect links removal 2021-09-29 14:32:01 +02:00
Eric Espie
dade45308c N°4332 - Fix table name 2021-09-29 11:30:07 +02:00
Eric Espie
142699c3b1 N°4332 - Fix table prefix 2021-09-28 17:14:05 +02:00
Molkobain
4c6a7ca30b N°3791 - Fix crash when displaying object with a null state value 2021-09-28 12:51:21 +02:00
Eric Espie
57b08b5b24 N°4328 - Fix XML version 2021-09-28 10:10:10 +02:00
Eric Espie
548d08b1ec N°4332 - Merge multi-LDAP module into iTop 2021-09-28 09:10:26 +02:00
Eric Espie
47fcddc9a6 N°4326 - Fix external keys in dynamic header dashlet 2021-09-27 17:35:52 +02:00
Eric Espie
f67f3eaf74 N°4326 - Fix unknown attribute when no state attcode is given 2021-09-27 14:43:12 +02:00
Molkobain
0d51dc61f9 N°4001 - Fix double title & extra space on object creation failure page 2021-09-26 22:37:41 +02:00
Molkobain
9949b1b11a N°4318 - Improve large field limits visualization with scrollbar's track color 2021-09-26 22:27:18 +02:00
Molkobain
8be141f692 N°3791 - Replace all hard-coded style colors in the DM with corresponding semantic / palette colors 2021-09-26 17:45:18 +02:00
Molkobain
a11bc9ad33 N°3791 - Enable use of SCSS variables in the DM classes / (meta)enum attributes style 2021-09-26 16:53:42 +02:00
Molkobain
3214ae91c7 N°3791 - Panel: Use style defined in the DM for classes
Note that for now, semantic colors cannot be used directly in a <main_color> or <complementary_color> tag. Only valid CSS color (hexa, hsla, ...)
2021-09-26 15:15:34 +02:00
Molkobain
21545da062 Rename Panel / Alert / Button color constants for something more accurate
Done now before their as used too widely. We are most likely to allow dev. to use custom colors soon, so we want to avoid confusing / renaming then. (iTop extensions have been verified, none had to be migrated)
2021-09-26 01:26:42 +02:00
Molkobain
7476b6d059 Button group: Fix button groups always being on a new line instead of staying in the flow 2021-09-26 01:22:53 +02:00
Molkobain
b15c8e30bc N°3791 - Panel: Use style defined in the DM for classes
Note that for now, semantic colors cannot be used directly in a <main_color> or <complementary_color> tag. Only valid CSS color (hexa, hsla, ...)
2021-09-26 01:17:28 +02:00
Molkobain
39d71c9c43 N°3882 - Pill: Fix default color scheme when in a panel 2021-09-26 00:51:04 +02:00
Molkobain
75d913a003 Compiler: Factorize DM classes style and add them to the DM CSS rules 2021-09-25 23:51:52 +02:00
Molkobain
2b38c98183 Compiler: Update unit tests for DM classes / (meta)enum attributes style 2021-09-25 23:27:29 +02:00
Molkobain
ca7f6362bf ormStyle: Fix icon URL including the app. root in the MetaModel to ease usage with load balancers 2021-09-25 17:00:50 +02:00
Molkobain
4d8ac5fee5 PHPDoc 2021-09-25 17:00:50 +02:00
acognet
938de4c71c N°3787 - Use data loader for auto refresh 2021-09-24 17:28:52 +02:00
Pierre Goiffon
3ff117596d 💡 N°4325 add phpdoc 2021-09-24 17:16:59 +02:00
Molkobain
194459e0de Breadcrumbs: Harmonize spacing with other top bar elements 2021-09-24 17:04:55 +02:00
Molkobain
95179b0c18 Dashboard: Fix multi-select field padding on selected values 2021-09-24 17:02:53 +02:00
Molkobain
d76c9cee6f N°3900 - Breadcrumbs: Better fix than 7ae4fe06e 2021-09-24 17:02:53 +02:00
odain
6e45b74665 Composer: Fix required versions => rename cover annotation 2021-09-24 15:01:08 +02:00
acognet
3d8259a083 N°3923 - Polishing: External key 2021-09-24 15:00:32 +02:00
Molkobain
6d8a36e061 N°3882 - Fix null status color when class has no style defined 2021-09-24 14:54:29 +02:00
acognet
814038f5fd recompile styles 2021-09-24 12:12:14 +02:00
acognet
f237b4dd30 N°3705 - Migrate module to new UIBlock system : Kanban board - fix z-index of selectize dropdown list 2021-09-24 12:05:09 +02:00
Pierre Goiffon
4cf4c0e4c3 ♻️ N°4325 refactor CMDBSource mysqli attributes to a separate wrapper class (#237)
In 2.7.5 with N°3513 we added a second mysqli attribute in CMDBSource, so that we can test transactions (see TransactionsTest).

But this wasn't documented, and was really causing confusion !

This refactor wraps both attributes in a dedicated object so that the logic is clearer.
2021-09-24 11:45:39 +02:00
Stephen Abello
86538cf88e Add metadata to legacy search elements 2021-09-24 10:53:02 +02:00
acognet
722cae240e align style of spinner 2021-09-24 10:09:02 +02:00
acognet
f0d3149a1c N°3787 - Use data loader for auto refresh 2021-09-24 10:09:02 +02:00
Stephen Abello
92e315e2c7 Re-dump autoloader since 7c7386a broke it -- with platform check enabled 2021-09-24 10:04:37 +02:00
Stephen Abello
6150881154 Re-dump autoloader since 7c7386a broke it 2021-09-24 09:31:34 +02:00
Molkobain
cf223b583e N°3882 - Protection against ormStyle being null for an object 2021-09-24 09:04:24 +02:00
Molkobain
eb5e5591d7 N°4326 - Improve robustness of DesignerComboField::SetAllowedValues() 2021-09-23 22:25:57 +02:00
Molkobain
dcf4053c30 Setup: Improve components' licenses display 2021-09-23 21:46:36 +02:00
Molkobain
fedc3d4d76 N°3556 - Fix JS dict. entries not available in SetupPage anymore 2021-09-23 21:46:36 +02:00
Molkobain
e93c0123aa PHPDoc 2021-09-23 18:12:52 +02:00
Molkobain
0004586779 N°4327 - Fix JS "ReferenceError" in Application Upgrade 2021-09-23 17:40:48 +02:00
Molkobain
7ae4fe06ed N°3900 - Breadcrumbs: Fix all items being hidden even when they should not (when browser's window is zoomed in/out) 2021-09-23 16:41:12 +02:00
Molkobain
7404599721 N°3882 - Fix unit tests 2021-09-23 14:16:22 +02:00
Molkobain
eb8aed19c2 Code cleanup 2021-09-23 13:10:23 +02:00
Molkobain
3a05e9159d PHPDoc 2021-09-23 12:29:34 +02:00
Molkobain
99026cec1f N°3882 - Compiler: Prepare unit test for DM CSS rules (needs to be completed with the team) 2021-09-23 11:44:12 +02:00
Molkobain
029d2ad526 N°3882 - Compiler: Factorize AttributeEnum/AttributeMetaEnum style compilation 2021-09-23 11:44:12 +02:00
Molkobain
1cb100b010 N°3882 - Header dynamic: Pills now shows the real color from the DM 2021-09-23 11:44:12 +02:00
Molkobain
197864ff83 N°3882 - Object details: Status indicator (dot) now shows the real state color 2021-09-23 11:44:11 +02:00
Molkobain
1e73ee8ccd N°3882 - Refactor display of field badges (meta enums / enums) in lists to match original mockups 2021-09-23 11:44:11 +02:00
Molkobain
e2b73995e1 N°3882 - Move DataModel classes fields' style to theme instead of (duplicated) inline CSS 2021-09-23 11:44:11 +02:00
Molkobain
24cedbdebd PHPDoc 2021-09-23 11:44:11 +02:00
Molkobain
0dc95f93a9 Code conventions 2021-09-23 11:44:10 +02:00
Molkobain
b6bd7fe400 Portal: Remove deprecated message for ItopExtensionsExtraRoutes as there is no alternative yet.
YAML routes can only be declared by the core, not modules.
2021-09-23 11:44:10 +02:00
acognet
03a19ab3f4 N°2573 - Remove MetaModel::GetNextKey 2021-09-23 11:36:10 +02:00
Stephen Abello
50849ae4ea N°4315 Harmonize bulk modify screen with other bulk actions 2021-09-23 11:34:54 +02:00
Stephen Abello
70d7f576f3 N°4001 Remove empty lines from tag set tooltips 2021-09-23 10:31:06 +02:00
acognet
bf491b7298 recompile styles 2021-09-23 09:30:30 +02:00
acognet
40ec7e35fd N°4245 - Using customfield in notifications triggered by TriggerOnObjectUpdate via ApplyStimulus 2021-09-23 09:20:33 +02:00
acognet
ffe5541361 N°2259 - iTop Compilation of branding images ignore real format of source image 2021-09-23 09:19:55 +02:00
acognet
73c55748d7 N°3904 - Polishing: Backup 2021-09-23 09:19:54 +02:00
acognet
0754dda1d9 N°3905 - Polishing: CSV Import 2021-09-23 09:19:54 +02:00
acognet
057bea196e Fix setTitleBlock 2021-09-23 09:19:54 +02:00
acognet
e97a266c44 N°3946 - LogAPI : log config parameters aren't used when logging in the setup wizard context 2021-09-23 09:19:54 +02:00
Eric Espie
b4278a6987 Supportability: Add UserId in EventIssue if 'userinfo' field is empty 2021-09-23 08:25:32 +02:00
Stephen Abello
50f860a0e8 N°3974 When editing an object, align fields synchro icons 2021-09-22 15:05:17 +02:00
Stephen Abello
198c9ed479 N°4296 When switching dashboard, update dashboard title tooltip with new value 2021-09-22 14:03:57 +02:00
Stephen Abello
6e076ae1fa N°3911 Fix HTML content in external key tooltip not showing 2021-09-22 13:49:48 +02:00
Stephen Abello
9d44f0982c N°3911 Fix spacing between attribute label and attribute code 2021-09-22 13:49:44 +02:00
Molkobain
fd933ce49a N°4245 - Temporary partial rollback 2021-09-22 13:34:13 +02:00
odain
ae0c43a099 N°4227 - enhance cli restore feedback when backup file not accessible 2021-09-22 11:42:27 +02:00
Stephen Abello
7c7386afc7 N°3851 Update Emogrifier to a version supporting iTop PHP versions range 2021-09-21 16:45:12 +02:00
bruno-ds
c306c6e30d N°4261 - code review with @PirGoif 2021-09-21 16:38:10 +02:00
Molkobain
d9ccac3aea Composer: Fix required versions of PHP in order to keep our package constraints up-to-date 2021-09-21 16:12:22 +02:00
Eric Espie
d8316734e2 N°4305 - n-n links to same class - Be more robust on original search given to ormLinkSet 2021-09-21 15:46:20 +02:00
Molkobain
7ac5c1bbbb Composer: Fix target PHP version in platform to the min. supported version.
Having this set to the minimum supported version ensure that when packages versions are retrieved/updated, only versions compatible with this PHP version are selected.
2021-09-21 15:22:56 +02:00
Molkobain
37585614ba Branding: Cleanup of old references since refactoring of logos compilation 2021-09-21 15:12:05 +02:00
Molkobain
03b728b394 PHPDoc 2021-09-21 14:38:43 +02:00
Pierre Goiffon
ae2072f4d5 N°3002 Get developer_mode.enabled config param first normally, and if not set from disk 2021-09-21 13:56:15 +02:00
Molkobain
2f6ed8f8af Code conventions 2021-09-21 13:37:35 +02:00
Molkobain
1f5dabf8f6 Code conventions 2021-09-21 13:36:11 +02:00
Stephen Abello
eb5cdb053e Fix unsanitized table id 2021-09-21 12:10:53 +02:00
Pierre Goiffon
25ee577eba 💡 phpdoc 2021-09-21 12:07:37 +02:00
Pierre Goiffon
f0aaf21a79 💚 N°3002 Fix php-mock-objects notices
The error handler now checks if logger is enabled before doing anything
2021-09-21 12:01:42 +02:00
acognet
a906086751 recompile styles 2021-09-21 11:18:40 +02:00
acognet
ab17eaad27 N°4260 - Fix display of log attributes in list 2021-09-21 10:57:47 +02:00
Molkobain
469c1553bf Fix typo in icon filename 2021-09-21 10:45:04 +02:00
Pierre Goiffon
4f72f8be0c N°4158 developer_mode.enabled config param is now always read from disk
If no config file on disk then will return default value
Was failing in the compilation process as during a moment we have in MetaModel a Config instance only containing data from params, and without previous params that were on disk :/
2021-09-21 10:37:17 +02:00
Pierre Goiffon
5f2323f10b N°4092 Symlinks flag : now always update the flag
Before this commit we were only creating/removing the flag is the functionnality was available : but this was causing confusions !
2021-09-21 10:36:07 +02:00
Eric Espie
98013a68a4 N°4305 - n-n links to same class - Fix fatal error when original set is already provided with the target class 2021-09-21 10:23:30 +02:00
Stephen Abello
f55193604c N°3929 Fix carets on dashboard editor's select fields 2021-09-21 10:00:24 +02:00
Stephen Abello
586b8c5c71 N°3929 Fix apply/undo button on specific dashboard editor fields 2021-09-21 09:58:34 +02:00
Molkobain
a771d35197 N°3685 - Fix missing C3 lib in a dashboard menu node 2021-09-21 09:51:25 +02:00
Molkobain
96e4ef68df Code conventions 2021-09-21 09:38:01 +02:00
Molkobain
df40b53b6b Code format 2021-09-21 09:24:26 +02:00
acognet
6d4f919519 Refactor -> create utils::AddParamToUrl - renaming of function + encode parameters 2021-09-20 17:29:50 +02:00
Molkobain
8af116275c Change use of "sizeof()" by "count()" to avoid confusion 2021-09-20 17:02:06 +02:00
Eric Espie
e930d34963 N°4305 - n-n links to same class - Fix aliases in request 2021-09-20 16:09:32 +02:00
Molkobain
4de79afe56 Fix non updated image for contributing guide 2021-09-20 13:19:05 +02:00
bruno-ds
daf24d8cb3 N°4261 - implement feedbacks from the team
plus:
 - the entrypoint is now `LogException()` instead of `FromException()` which sounded more like a factory and less like an active method.
 - merge conflicting commit with @molkobain (CC fix)
 - remove the writing of the exception object in the error.log context (adding it was an error, it's way too verbose!!).
   - Technical note: The context is still used to propagate the exception across several call stack, so it now uses a less generic naming in order to avoid conflicts (see `ExceptionLog::CONTEXT_EXCEPTION`). another solution would have been to add a new parameter to `ExceptionLog::Log()`, but I didn't want to add constraint over the hypothetical evolution of the base class method.
2021-09-20 12:27:39 +02:00
bruno-ds
6e0d570d41 N°4261 - Portal exception logging: Add exception's file and line to the context.
plus:
 - the exception object is no more automatically added to the error.log context (it's way too verbose)
 - code cleanup (use constant instead of repeated strings)
 - ExceptionLog main method is now named `LogException` instead of `FromException`
2021-09-20 12:27:39 +02:00
Molkobain
ce4379920d Update contributing guide with new stickers 👀 2021-09-20 12:17:13 +02:00
acognet
291bbdf3da recompile styles 2021-09-18 23:13:43 +02:00
acognet
61f6c1fe33 N°2259 - iTop Compilation of branding images ignore real format of source image 2021-09-17 16:36:40 +02:00
acognet
a1d6a705ca N°4260 - Fix display of log attributes in list 2021-09-17 16:29:33 +02:00
acognet
b861d45b08 Refactor -> create utils::AddParamToUrl 2021-09-17 16:29:06 +02:00
Molkobain
fc7d2551cd N°4302 - Fix hard-coded image att. code in UserRights::GetUserPictureAbsUrl() 2021-09-17 12:04:13 +02:00
Stephen Abello
b15267d8db N°4239 Update bulk transition/modify mono/multi values indicator style 2021-09-17 11:35:01 +02:00
Stephen Abello
94ce62e424 Remove unintentional function overload committed in e3687b3 2021-09-16 15:16:19 +02:00
Molkobain
6271a33fdb Code review: Fix code conventions 2021-09-16 14:53:40 +02:00
Molkobain
4b6db53e84 Update current version 2021-09-16 13:38:41 +02:00
Eric Espie
540b9f8164 N°3908 - Polishing: Application Upgrade - display the installation progress bar 2021-09-16 11:24:22 +02:00
Eric Espie
5839bab042 N°3908 - Polishing: Application Upgrade - clickable checkbox label, remove history menu, filter history with most recent first, remove unnecessary columns in history 2021-09-16 11:14:50 +02:00
Pierre Goiffon
adc7666dd0 💚 Change setup.css test
Was checking comment presence, but we removed the comments (N°3865)
2021-09-16 10:21:21 +02:00
Stephen Abello
e3687b3cbb N°3685 Use minified versions of jQuery UI timepicker library 2021-09-16 10:09:33 +02:00
Stephen Abello
e81c02b25a N°3685 Fix combodo webfont preloading file name 2021-09-16 10:09:33 +02:00
Pierre Goiffon
28a8a4457e 📦 Update setup.css 2021-09-16 09:38:52 +02:00
Molkobain
eee0f453da N°3685 - Use minified versions of dataTables libs. 2021-09-16 09:20:54 +02:00
Molkobain
164a5501c0 Add missing condition for compiled JS dictionaries 2021-09-15 22:58:09 +02:00
Molkobain
ab3c1ad4af Add TWIG comment 2021-09-15 20:22:30 +02:00
Molkobain
83a3530880 Activity panel: Improve accessibility 2021-09-15 20:16:09 +02:00
Molkobain
0ad23e272b Update precompiled themes 2021-09-15 16:16:20 +02:00
Stephen Abello
b28ef803a3 N°3685 Display text while font is loading 2021-09-15 16:16:20 +02:00
Stephen Abello
b2155c042e N°3685 Fix twig variable name 2021-09-15 16:16:20 +02:00
Stephen Abello
b1b1d25186 N°3685 Preload necessary fonts to speed up display 2021-09-15 16:16:20 +02:00
Stephen Abello
61ee4d6807 N°3685 Replace Raleway truetype font files with woff ones 2021-09-15 16:16:20 +02:00
Molkobain
443292e1f6 PHPDoc 2021-09-15 16:16:20 +02:00
Molkobain
c025a7aa3b N°3685 - Add unit test for WebResourcesHelper 2021-09-15 16:16:20 +02:00
Stephen Abello
86ebb93149 N°3685 Compile SCSS files as a compressed CSS file and fix SCSS copyright comment so that they don't end up in compiled CSS 2021-09-15 16:16:20 +02:00
Molkobain
120670240c PHPDoc 2021-09-15 16:16:20 +02:00
Molkobain
5bd8a25440 N°3685 - Use WebResourcesHelper for D3/C3.js usages 2021-09-15 16:16:20 +02:00
Molkobain
754f755141 N°3685 - Use WebResourcesHelper for D3/C3.js usages 2021-09-15 16:16:20 +02:00
Molkobain
74b7223d44 N°3685 - Introduce WebResourcesHelper and use it for SimpleGraph usages 2021-09-15 16:16:20 +02:00
Stephen Abello
1fb2ff355d N°3685 Optimize CSS size: .ibo-button is extended at multiple places, reducing its use in loops when possible greatly improves compiled selectors 2021-09-15 16:16:20 +02:00
Molkobain
61f3d3aeda N°3685 - Front-end performances: Use only necessary parts of Bulma SCSS 2021-09-15 16:16:20 +02:00
Molkobain
99860e9e20 N°3685 - Front-end performances: Split compatibility files for easier maintenance / inclusion 2021-09-15 16:16:20 +02:00
Molkobain
eb164b47e2 N°3685 - Front-end performances: Update comments 2021-09-15 16:16:20 +02:00
Molkobain
dbd197d9bc N°3685 - Front-end performances: Include advanced search resources only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
e638c65685 N°3685 - Front-end performances: Remove clipboard lib. as it is not used in the backoffice yet 2021-09-15 16:16:20 +02:00
Molkobain
42b779d301 N°3685 - Front-end performances: Include dataTables lib. only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
da3bab9647 N°3685 - Front-end performances: Include jQuery.popup_menu lib. only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
94d53575ae N°3685 - Front-end performances: Include dataTables lib. only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
c26cfad062 Fix JS widget name in comment (decrease false positives in grep) 2021-09-15 16:16:20 +02:00
Molkobain
9cd9087cc7 N°3685 - Front-end performances: Move deprecated JS files to optional compatibility scripts 2021-09-15 16:16:20 +02:00
Molkobain
df7991adeb N°3685 - Front-end performances: Remove JS lib. already included in parent class 2021-09-15 16:16:20 +02:00
Molkobain
2dcfe0e281 N°3685 - Front-end performances: Include ScrollMagic lib. only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
bac584a6b4 N°3685 - Front-end performances: Include graph libs. (Raphael, simple_graph, ...) only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
e4d7bf7dbb N°3685 - Front-end performances: Identify used libs. 2021-09-15 16:16:20 +02:00
Molkobain
2d907f31dd N°3685 - Front-end performances: Use minified versions of libs. 2021-09-15 16:16:20 +02:00
Molkobain
e2001f4585 N°3685 - Front-end performances: Move deprecated JS files to optional compatibility scripts 2021-09-15 16:16:20 +02:00
Molkobain
9810f666fd N°3685 - Front-end performances: Use minified versions of libs. 2021-09-15 16:16:20 +02:00
Molkobain
de5f47d43e N°3685 - UIBlock resources: Add CKEditor resources to RichText 2021-09-15 16:16:20 +02:00
Molkobain
d2662e27e1 N°3685 - Front-end performances: Include BBQ lib. only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
9ac0100d3a N°3685 - Front-end performances: Remove JS lib. already included in parent class 2021-09-15 16:16:20 +02:00
Molkobain
1ea5983464 N°3685 - Front-end performances: Move deprecated JS files to optional compatibility scripts 2021-09-15 16:16:20 +02:00
Molkobain
15081ba82c N°3685 - Front-end performances: Include graph lib. (D3/C3.js) only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
df189bd1f2 N°3685 - Front-end performances: Include advanced search resources only when necessary 2021-09-15 16:16:20 +02:00
Molkobain
06f469faf2 N°3685 - Front-end performances: Add conf. param. to include deprecated JS/CSS files back if necessary 2021-09-15 16:16:20 +02:00
Molkobain
b27aa70a1e N°4247 - Button group: Make sure buttons of a same group always stay on the same line 2021-09-15 15:40:29 +02:00
Molkobain
0e35b8e4fa N°4247 - Activity panel: Make sure that cancel / send buttons always stay on the same line 2021-09-15 15:39:25 +02:00
Molkobain
c0fc62bcb8 N°4247 - Activity panel: All entries are now expanded / collapsed by the corresponding icons 2021-09-15 14:48:17 +02:00
bruno-ds
d1721b0834 N°4261 -Fix CI
Try to repair an odd error in the CI:
> Fatal error: Uncaught Exception: Serialization of 'ReflectionClass' is not allowed

avoid instantiation in the provider, delay them to the actual test
2021-09-15 14:39:00 +02:00
bruno-ds
66e4f369f7 N°4261 -Fix CI
Try to repair an odd error in the CI:
> Fatal error: Uncaught Exception: Serialization of 'ReflectionClass' is not allowed

Maybe it's triggered by the mocks being a property of the test class, so I inlined them.
+ misc. minor modification in order to try to figure out what is causing this behavior.
2021-09-15 13:34:04 +02:00
Pierre Goiffon
c5e1dbce88 💡 PHPDoc for \cmdbAbstractObject::UpdateObjectFromArg 2021-09-14 17:54:28 +02:00
bruno-ds
5d23a250ae N°4261 - New log handler dedicated to Exceptions & use of it in the portal's Exception listener
Such Exceptions are triggered with a Warning level and default the minimum default level in order to write in DB is above, so the behavior is not modified:
 - logs are written in errors.log (with a warning elvel instead of an error)
 - logs are not written in DB unless `log_level_min.write_in_db` is changed
2021-09-14 17:40:11 +02:00
Molkobain
b46357a1bd N°4254 - Fix autocomplete results being hidden below the modal after first opening 2021-09-14 17:31:59 +02:00
Eric Espie
4433cdb21b N°4047 - Fix undefined offset in core\dbobject.class.php - rewrite duplicate check 2021-09-14 16:13:43 +02:00
Eric Espie
45dc79f5af N°4211 - Remove DBObject::Reload calls added in 3.0.0 - Fix Highlight code (computed only for lifecycle and stopwatch) 2021-09-14 15:01:42 +02:00
Eric Espie
2ab0fab051 N°4211 - Remove DBObject::Reload calls added in 3.0.0 - Fix User cache when user not in DB 2021-09-14 15:01:42 +02:00
acognet
be81d1f6c0 N°3901 - Polishing: Menu - count 2021-09-14 12:21:10 +02:00
acognet
ddedce1589 Fix shortcut 2021-09-14 12:01:06 +02:00
Eric Espie
b557f16cfa N°4211 - Remove DBObject::Reload calls added in 3.0.0 - Try Person for User's Contact 2021-09-14 11:06:36 +02:00
Pierre Goiffon
30bf2015cb GitHook install : update create symlink message to add target path 2021-09-14 09:42:23 +02:00
acognet
14b6e903cb N°4245 - Using customfield in notifications triggered by TriggerOnObjectUpdate via ApplyStimulus 2021-09-14 09:41:40 +02:00
acognet
4f6e040346 N°3907 - Polishing: Run query - update of precompiled css 2021-09-14 09:15:22 +02:00
acognet
3e12799d1d N°3907 - Polishing: Run query 2021-09-14 09:10:54 +02:00
acognet
7d0550879f N°3910 - Polishing: data synchro 2021-09-14 09:09:36 +02:00
acognet
6a765fad50 N°3685 - Performance checks on the front end - Avoid reload 2021-09-13 09:19:08 +02:00
Stephen Abello
cbb70c94e5 Update precompiled stylesheets 2021-09-10 10:29:11 +02:00
Stephen Abello
b120488085 N°3922 Restyle one way password inputs, reset buttons and displayed hints 2021-09-10 10:28:38 +02:00
Pierre Goiffon
191891ac35 💬 Pre-commit hook : change message to avoid confusions
"push" is a git verb, so changed it to "add" instead
2021-09-10 08:38:53 +02:00
Molkobain
f171380396 Modals: Fix close "x" not being aligned 2021-09-09 18:01:03 +02:00
Molkobain
f9e54e1dde JSDoc 2021-09-09 18:01:03 +02:00
Eric Espie
15e99a898e N°3985 - Performance checks on the back end - Enhance KPIs 2021-09-09 17:21:57 +02:00
odain
27c397cc1a N°4225/4227: recommit lost restore.php + refactor duplicated code for cli restore/backup in common-cli.execution.php 2021-09-09 16:57:42 +02:00
acognet
616229140e N°3901 - Polishing: Menu 2021-09-09 14:27:36 +02:00
acognet
04e016c919 N°3919 - Polishing: IP Attribute 2021-09-09 14:27:36 +02:00
Eric Espie
a4e43d3f17 N°4225 - Remove Read-only mode for backup 2021-09-09 14:25:19 +02:00
Eric Espie
ed6969be2c N°4227 - Manual iTop restore - maintenance mode 2021-09-09 13:56:07 +02:00
Stephen Abello
8105c4d6cd N°3913 Fix last step double scrollbar on smaller screen 2021-09-09 11:02:18 +02:00
Stephen Abello
3b7073ad4e Remove commented lines 2021-09-09 10:01:52 +02:00
Stephen Abello
dbb84fa4e6 N°4239 Use blocks for bulk modify/transition/delete, harmonize and polish display 2021-09-09 09:52:09 +02:00
Pierre Goiffon
91c7ed9f4d pre-commit hook to disallow commit with SCSS file but without CSS file 2021-09-09 09:28:45 +02:00
Molkobain
e4c818cacb N°4288 - Portal: Update TWIG extensions to match those available in the backoffice 2021-09-08 21:47:05 +02:00
Molkobain
5b6b07af48 N°2007 - Portal: Don't show tooltip if empty on BrowseBrick items 2021-09-08 20:12:03 +02:00
Molkobain
1331bc2139 Add PHPDoc 2021-09-08 15:38:42 +02:00
Molkobain
08946066fb Session management: Add PHPDoc 2021-09-08 14:54:20 +02:00
Molkobain
0de4e62fcd Session management: Revert modification in third-party lib 2021-09-08 14:49:29 +02:00
Molkobain
8edd155351 N°4254 - Fix jQuery dialogs not being stacked correctly 2021-09-08 13:31:58 +02:00
Molkobain
3980ba81e7 Fix typo 2021-09-08 12:33:19 +02:00
acognet
0193868581 N°3901 - Polishing: Menu 2021-09-08 11:48:45 +02:00
Stephen Abello
d2b3de9734 Update precompiled stylesheets 2021-09-08 11:16:58 +02:00
Stephen Abello
d1c39c5e10 N°3929 Fix dashboard properties buttons and change semantics colors/icons 2021-09-08 11:16:06 +02:00
Stephen Abello
e29ae488a1 N°3929 Align dashboard switch button with specifications 2021-09-08 11:16:06 +02:00
Molkobain
ad3e3195a8 Compiler: Add log message in case a profile (user rights) relies on a missing group 2021-09-08 10:58:00 +02:00
Eric Espie
738411005b Fix CI 2021-09-08 10:20:16 +02:00
Pierre Goiffon
40b095e407 📦 Update compiled CSS 2021-09-08 08:04:46 +02:00
Pierre Goiffon
3b7cb9a554 💄 Setup : fix (v2) missing space on backup checkbox
Replaces &nbsp; added in 7890cbd7 with CSS
As the other setup checkboxes seems to be OK (space might already be added, or we might have a line feed in the code between checkbox and label), we are only modifying the backup checkbox
2021-09-07 17:57:25 +02:00
Eric Espie
f474b3de06 Fix CI 2021-09-07 16:58:36 +02:00
Pierre Goiffon
7890cbd701 💄 Setup : fix missing space on backup checkbox 2021-09-07 16:56:47 +02:00
Eric Espie
eef3e9b7ae Merge branch 'develop' into SessionManagement 2021-09-07 16:33:10 +02:00
odain
3c081461b0 fix ci: use env-production instead 2021-09-07 15:22:46 +02:00
Eric Espie
a916df64c9 Merge branch 'develop' into feature/cli-backup-restore 2021-09-07 14:48:06 +02:00
Eric Espie
1d0b96e10f Merge branch 'develop' into feature/cli-backup-restore 2021-09-07 14:45:35 +02:00
Stephen Abello
28070f8682 Update precompiled stylesheets 2021-09-07 14:41:39 +02:00
Stephen Abello
3e428000bd N°3914 Fix "Configure this list" buttons being narrowed when reconstructing modal 2021-09-07 14:40:43 +02:00
Stephen Abello
165b11f948 N°3914 Avoid table header sorting icons from overlapping with header label 2021-09-07 14:40:36 +02:00
acognet
c6495e7212 N°3907 - Polishing: Run query - update of precompiled css 2021-09-07 14:06:28 +02:00
acognet
0fb0b9deab N°3907 - Polishing: Run query 2021-09-07 10:59:14 +02:00
Molkobain
ecb1acf5af Update precompiled stylesheets 2021-09-07 10:54:34 +02:00
Eric Espie
b239e822ef N°4227 / N°4225 - Check cron context in WaitCronTermination 2021-09-07 10:54:27 +02:00
Eric Espie
615aa594f0 N°4227 / N°4225 - Fix cron backup 2021-09-07 10:47:26 +02:00
acognet
2e346a7e80 N°4184 - Alert UI block is collapsible only if there is a title 2021-09-07 10:45:37 +02:00
acognet
f5b557b0bc N°4191 - Fix not selectable caller in NormalChange when only 1 organization 2021-09-07 10:45:37 +02:00
Molkobain
2703075d39 SCSS: Rename variables to match conventions 2021-09-07 10:42:55 +02:00
Molkobain
f6fbd5a7a5 N°3900 - Breadcrumbs: Improve behavior when items are too many for the screen width
When the screen isn't large enough we now put the oldest entries in a dropdown menu like on a browser to access the previous pages.
2021-09-07 10:22:38 +02:00
Molkobain
d1a05f41e5 Button: Fix position to relative to allow absolute positioning of child elements 2021-09-07 10:22:38 +02:00
Stephen Abello
6b67ad93b9 Update precompiled stylesheets 2021-09-07 09:35:53 +02:00
Stephen Abello
884d3c9477 N°3920 On a fullscreen text field and clicking on an image, make popup display on top of current text field 2021-09-07 09:35:53 +02:00
Stephen Abello
26426123b8 N°3920 Move fullscreen button for text field next to field label 2021-09-07 09:35:53 +02:00
Pierre Goiffon
6725a9f1ef N°3807 Fix HTML licenses not displayed correctly 2021-09-07 08:58:00 +02:00
Eric Espie
8e0ae67803 N°4227 / N°4225 - Enhance SetupUtils interface 2021-09-06 17:53:46 +02:00
acognet
979a43edc9 N°3551 - Migrate module to new UIBlock system : Customer Survey - Fix problems of id in checkbox after action like "configure this list" 2021-09-06 15:57:34 +02:00
Eric Espie
2aaac00015 N°3985 - Performance checks on the back end - Fix KPI logs 2021-09-06 14:16:42 +02:00
Stephen Abello
c46cd1c514 Add hover and active style to add caselog entry button 2021-09-03 11:27:37 +02:00
Stephen Abello
74d2a6e46c Activity form can now have extra inputs 2021-09-02 17:27:48 +02:00
Stephen Abello
1d57b4330b Correctly trigger iTop API when adding a caselog entry through quick edit 2021-09-02 17:27:48 +02:00
acognet
034516d0ef Fix on ready script 2021-09-02 15:29:53 +02:00
acognet
72d7758259 N°3905 - Polishing: CSV Import - typo + fix loading js for Panel 2021-09-02 15:26:07 +02:00
acognet
3e9a19b0ea Fix empty page size 2021-09-02 10:11:45 +02:00
Molkobain
6118ee6876 Update precompiled themes 2021-09-01 15:10:15 +02:00
Molkobain
fea3c719af Top bar: Force page title to stay on 1 line 2021-09-01 14:56:21 +02:00
Molkobain
78eda6a2fc N°3800 - Fix text color in code blocks for the activity entries (also update comment to explain both cases) 2021-09-01 14:29:07 +02:00
Molkobain
65db576654 PHPDoc 2021-09-01 09:55:28 +02:00
Pierre Goiffon
69bca189fd N°3800 Update compiled CSS 2021-08-31 16:53:21 +02:00
Pierre Goiffon
5b78820b32 N°3800 Fix color for highlighted code blocks
We want content in a simple code tag to be black
But the color set by Highligh.js must be kept
2021-08-31 16:44:59 +02:00
acognet
79954d3cee N°4259 - Migrate object-copier to itop 3.0 style 2021-08-31 12:07:32 +02:00
Molkobain
8a5c144e3b Fix typo 2021-08-31 09:30:15 +02:00
Stephen Abello
af28a2e01d Update precompiled stylesheets 2021-08-31 09:28:44 +02:00
Stephen Abello
55513b2f4b Move horizontal scroll from tabcontainer to tabs element 2021-08-31 09:26:00 +02:00
Molkobain
e4bf15feb3 Tab container: Hide ellipsis at first to only show if necessary (better UX) 2021-08-30 18:00:03 +02:00
Molkobain
0080caf55f N°2788 - Revert hand-made adjustments for some HTML as it is now handled by .ibo-is-html-content 2021-08-30 17:32:30 +02:00
Molkobain
aed8135d56 N°2788 - Improve how small/large fields value wrap without overlapping the content next to them 2021-08-30 17:32:29 +02:00
Pierre Goiffon
d0ba84068d N°3002 Better log deprecated call location
Now logs file location when called by a function
2021-08-30 16:43:30 +02:00
Pierre Goiffon
d6d1a5cc23 N°3002 Fix undefined index notice in \DeprecatedCallsLog::DeprecatedNoticesErrorHandler 2021-08-30 15:58:55 +02:00
Molkobain
6700de097a N°3712 - Activity panel: Fix origin icon for log entries 2021-08-27 17:28:01 +02:00
Molkobain
cf556de76e Add method to get number of entries of an ormCaseLog 2021-08-27 17:28:01 +02:00
Stephen Abello
72cddf30fb Update demo objects with fresher data 2021-08-27 10:55:36 +02:00
Stephen Abello
5ec6a1cc0c Fix sso buttons alignment with login inputs 2021-08-27 10:55:36 +02:00
Eric
fe0db8f357 N°3985 - Performance checks on the back end - Fix Session helper 2021-08-27 09:08:14 +02:00
Eric
1ab2b9c5d4 N°3985 - Performance checks on the back end - better KPI logs 2021-08-26 17:14:46 +02:00
Eric
67cd5e321e N°3985 - Performance checks on the back end - Fix Session helper 2021-08-26 16:14:14 +02:00
Eric
81d9ea389d N°3985 - Performance checks on the back end - Fix Session helper 2021-08-26 15:33:07 +02:00
Eric
3ee9757a85 🔊 Log when no Twig template is found in controller 2021-08-26 12:06:21 +02:00
Eric
97e0150974 N°3224 - migration audit tool - allow php functions in XPath 2021-08-26 11:54:45 +02:00
acognet
7f37c5912a N°3914 - Polishing: Lists - fix display of datatable and rename attribute for init data 2021-08-26 11:37:17 +02:00
Stephen Abello
2f5a1c99a5 Add variables to class icon size CSS classes 2021-08-26 11:22:31 +02:00
Eric
5ce9b6ca99 Merge branch 'develop' into SessionManagement 2021-08-26 10:28:43 +02:00
Eric
bd9286f903 N°3985 - Performance checks on the back end - Use Session helper 2021-08-26 10:27:26 +02:00
Molkobain
4ae7090a51 N°4062 - Advanced search: Fix visual glitch when submitting the search while the results are sticking 2021-08-25 20:55:58 +02:00
Stephen Abello
335074701f Update precompiled stylesheets 2021-08-25 16:57:26 +02:00
Stephen Abello
76f70d45dd N°4182 Large own caselog entries were not scrolling in activity panel 2021-08-25 16:34:29 +02:00
Stephen Abello
6f147acd76 Update precompiled stylesheets 2021-08-25 16:20:12 +02:00
Stephen Abello
a84d2ce6bb N°3928 Avoid double carret on multiselect 2021-08-25 16:18:56 +02:00
Stephen Abello
ea9c1ab0b3 Add helpers to set size to class icons 2021-08-25 16:18:56 +02:00
Molkobain
e1dc269171 SCSS: Rename variable to something more semantic lik in other blocks 2021-08-25 15:46:08 +02:00
Molkobain
16dc7de8e2 Rename method for consistency across the widget 2021-08-25 15:13:03 +02:00
Stephen Abello
88e210d84d N°3928 Use a more precise selector for tooltips singleton 2021-08-25 14:55:26 +02:00
Stephen Abello
4f6bd5444b N°3928 Create tooltip singleton for impact analysis matching elements (icon/text) 2021-08-25 14:34:40 +02:00
Stephen Abello
0f204f94eb Add helper to create tooltip singleton 2021-08-25 14:34:40 +02:00
Vincent Dumas
f5ae76360e Simple typo in declaration of an entry
No associated bug
2021-08-25 14:18:14 +02:00
vdumas
8d59024d8e N°463 use CDATA on oql tags 2021-08-25 14:13:14 +02:00
Molkobain
32e0031242 N°4254 - Fix items list not visible for external key in modals 2021-08-25 11:25:28 +02:00
Stephen Abello
3fdebdc217 Update precompiled stylesheets 2021-08-25 11:07:14 +02:00
Stephen Abello
7083807319 N°3928 Migrate impact analysis page to 3.0 style 2021-08-25 11:06:26 +02:00
Stephen Abello
0f01dbc3e5 Allow to add an icon to lists display block 2021-08-25 11:06:26 +02:00
Stephen Abello
8289b028cf N°3928 Limit impact analysis size 2021-08-25 11:06:25 +02:00
Stephen Abello
f72a6ee963 Hints that disabled tabs are not clickable 2021-08-25 11:06:25 +02:00
Stephen Abello
08359cdd05 N°3928 Replace legacy loading gif 2021-08-25 11:06:25 +02:00
Stephen Abello
de17439f55 N°3928 Remove function messing up tabs height while giving no real benefit 2021-08-25 11:06:25 +02:00
Stephen Abello
6e555e29d1 N°3928 Fix impact analysis not showing when displaying list first 2021-08-25 11:06:25 +02:00
Stephen Abello
9dae34461b Use tab widget prefix for our slightly modified tabs widgets 2021-08-25 11:06:25 +02:00
Stephen Abello
06b1e581fe N°3928 Use right tab widget in impact analysis and make sure widget is initialized before using it 2021-08-25 11:06:24 +02:00
acognet
d4b515c7b7 Fix id in checkbox 2021-08-25 10:31:33 +02:00
Molkobain
20759eca23 Fix "Configure this list" due to regression in 280afb35 2021-08-25 09:30:53 +02:00
Molkobain
4e420cbcd6 N°4062 - Advanced search: Fix sticky header glitches with pagination 2021-08-24 17:55:41 +02:00
vdumas
0e60c67910 N°463 Add 3 predefined OQL queries 2021-08-24 10:29:33 +02:00
Molkobain
808c1fa571 N°3712 - Activity panel: Fix origin indicator being displayed on all type of entries 2021-08-24 10:17:56 +02:00
Molkobain
dbf8475883 N°4256 - Fix CMDBChange origin to 'csv-interactive' on the CSV Import page 2021-08-24 10:01:44 +02:00
Eric
2c2155a8e0 N°3985 - Performance checks on the back end 2021-08-23 13:57:03 +02:00
Stephen Abello
ffbd94d671 Cleanup 2021-08-20 16:53:38 +02:00
Molkobain
3abcdc9c58 Rename CSS prefixes of autocomplete to match conventions 2021-08-20 16:50:16 +02:00
Molkobain
242f499101 Remove require as the class is handled by the autoloader 2021-08-20 16:50:16 +02:00
Molkobain
6b0106ff73 PHPDoc 2021-08-20 16:50:16 +02:00
acognet
bf991ffb8a N°3685 - Performance checks on the front end - Minimize size of generated javascript 2021-08-20 16:03:22 +02:00
acognet
280afb35a9 Datatable : on init load data for the first page 2021-08-20 15:54:34 +02:00
acognet
e095749c90 Fix typo 2021-08-20 15:54:34 +02:00
Stephen Abello
ced0e7cc8f N°4093 When clicking on a tab before tab widget is loaded, prevent AjaxTabs from redirecting our page to their target 2021-08-20 11:18:13 +02:00
Molkobain
6df98c3d41 N°4248 - Change demo samples icons for services / service families 2021-08-20 09:41:19 +02:00
Stephen Abello
21e16fd2e8 Make extension source clearer in setup and about box 2021-08-19 18:01:40 +02:00
Molkobain
bc2e25be99 N°3918 - Change missing object tooltip text to "Object not found" 2021-08-19 15:02:04 +02:00
Molkobain
0c5ebc3a84 N°1616 - Fix large HTML table overflowing in HTML fields or logs 2021-08-19 15:02:04 +02:00
Stephen Abello
43412b78e3 N°1632 Remove unused autoloader in UnauthenticatedWebPage 2021-08-19 14:08:21 +02:00
Eric
9126635cf2 Allow setup ends correctly when problem occurs 2021-08-19 13:50:16 +02:00
Eric
d0986c048a Allow setup again when problem occurs 2021-08-19 12:14:14 +02:00
Eric
fe0c52bedd themes 2021-08-19 11:48:26 +02:00
Eric
8926c6783a 🐛 scss reference was in bulma not in iTop scss 2021-08-19 11:48:25 +02:00
Stephen Abello
95b6dd0cc3 N°1632 Add a common webpage to be used for unauthenticated/token based actions, guarantees harmony between extensions style and a common API 2021-08-19 11:41:01 +02:00
Eric
121e39b738 fix themes 2021-08-19 11:26:31 +02:00
Eric
48dee9c136 🐛 remove undefined scss reference 2021-08-19 10:55:58 +02:00
Eric
befde44215 🐛 fix non-existing variable 2021-08-19 10:41:36 +02:00
acognet
a827eed59d Restore unwanted changes 2021-08-19 10:41:18 +02:00
Molkobain
1613c1bbdc Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	application/itopwebpage.class.inc.php
#	core/config.class.inc.php
#	setup/setuputils.class.inc.php
2021-08-18 18:55:56 +02:00
Molkobain
909af7f59c JSDoc 2021-08-18 18:53:42 +02:00
vdumas
495b39a7ab N°4246 - Add missing class parameter 2021-08-18 17:05:16 +02:00
vdumas
f9ff66941d N°4079 - FR Dictionnary typo 2021-08-18 17:03:14 +02:00
acognet
29871bc6da Fix precompiled css 2021-08-18 16:49:52 +02:00
Molkobain
1c983e8093 Merge remote-tracking branch 'origin/support/2.6' into support/2.7
# Conflicts:
#	core/config.class.inc.php
#	datamodels/2.x/itop-portal-base/portal/src/controllers/objectcontroller.class.inc.php
#	pages/ajax.render.php
2021-08-18 16:12:22 +02:00
Molkobain
92a9a8c65f N°4129 - Security hardening 2021-08-18 15:57:18 +02:00
Federico Lazcano
a3e1b95c8e Update install.txt
Change old URL for new, linking to iTop Hub.
2021-08-18 14:35:35 +02:00
acognet
bf2d98a1bf N°580 - Jean Dupont - Jean Dupont, auto-complete avec homonyme 2021-08-18 10:41:38 +02:00
acognet
abe103eade N°580 - Jean Dupont - Jean Dupont, auto-complete avec homonyme 2021-08-18 09:32:26 +02:00
acognet
034052cf4b avoid 404 error when loading image in autocomplete 2021-08-18 09:32:26 +02:00
Molkobain
4458f26379 N°3750 - Behat: Add metadata about logs label 2021-08-17 17:49:22 +02:00
Molkobain
007e1ded0d N°3786 - Fix unload XHR call being always sent, even when no draft entries 2021-08-17 16:11:21 +02:00
Molkobain
5651512f68 N°4244 - Add protection against unfortunate massive delete of inline images / attachments when a null temp ID is passed 2021-08-17 15:58:52 +02:00
Molkobain
8c043f137c N°3750 - Behat: Add missing metadata to DesignerFormField 2021-08-17 10:01:22 +02:00
acognet
b3cce54ee9 Fix precompiled css 2021-08-17 10:00:19 +02:00
acognet
e4e4e3b0bf Fix precompiled css 2021-08-17 09:18:58 +02:00
acognet
1cb3b4bd96 Remove lines in double 2021-08-16 21:56:39 +02:00
acognet
97ee6570d2 N°3572 - Migrate backoffice pages to new UIBlock system : Toolkit 2021-08-16 15:17:05 +02:00
acognet
8c8f711fe8 N°3551 - Migrate module to new UIBlock system : Customer Survey 2021-08-16 15:16:08 +02:00
acognet
f140efd95c N°4007 - When a search is on auto submit, auto submit it also on first display - new fix in order to avoid reload in case of OQLMenuNode - rollback some deleted lines 2021-08-16 12:53:27 +02:00
acognet
6b19758654 Add deprecated 2021-08-16 11:24:58 +02:00
acognet
2899c82ef2 Fix Typo 2021-08-16 09:55:40 +02:00
acognet
12c2929f1d Context menu remains in place while scrolling 2021-08-16 09:54:51 +02:00
acognet
9d28e43804 N°4232 - Order By error on query with two classes 2021-08-16 09:53:19 +02:00
Molkobain
27217815d1 N°2510 - Fix expand/collapse buttons of log entries in a list 2021-08-16 09:37:36 +02:00
Molkobain
8e3cc471df N°1836 - Cancel of an object creation form now redirects to the search page of the same class (instead of just showing "operation cancelled") 2021-08-14 23:33:21 +02:00
Molkobain
58e35ea33b N°1047 - Replace hard coded 'iTop' occurrences with ITOP_APPLICATION_SHORT 2021-08-14 22:43:29 +02:00
Stephen Abello
0b81601699 N°3913 Allow iTop Hub iframe to fully display after setup 2021-08-13 17:46:24 +02:00
Stephen Abello
4d3ba6edd0 N°3913 Make setup's collapsable sections' arrow clickable 2021-08-13 17:08:26 +02:00
Stephen Abello
9de014c9cb Add favicon to setup pages 2021-08-13 15:54:43 +02:00
Stephen Abello
75dbaada72 Update favicon style 2021-08-13 15:52:34 +02:00
Stephen Abello
0d4dd0c67b Harmonize iTop's logo and fluent icons 2021-08-13 15:28:17 +02:00
Stephen Abello
fabdef37d2 N°3913 UI improvements for setup, also remove A LOT of <table> 😬 2021-08-13 12:23:52 +02:00
Molkobain
e666631f63 N°3918 - Restore style on broken wiki links (also add a tooltip to explain what it means) 2021-08-13 11:56:40 +02:00
odain
280feca863 Revert "N°4225 - Protect manual backups from cron (and vice versa) - minimal work ie protect manual backup from cron only"
This reverts commit e3dc1b77cc.
2021-08-13 11:16:48 +02:00
DudekArtur
7690e43b39 Update pl.dictionary.itop.core.php 2021-08-13 10:11:16 +02:00
Molkobain
0c66882990 N°3918 - Fix regression on mentions display introduced by 7af10e51 2021-08-12 21:04:50 +02:00
Molkobain
7613ac7a9b Graphviz: Remove generated SVG image after usage as it won't be cached 2021-08-12 19:56:02 +02:00
Molkobain
7af10e5197 N°3918 - Fix wiki syntax "[[CLASS:ID]]" not working in logs 2021-08-12 17:28:50 +02:00
odain
095c975ec6 N°4227 / N°4225 - require cleanup + fix manual restore 2021-08-12 16:25:50 +02:00
odain
9e1e5a8a47 N°4227 - Manual iTop restore - protect any restore from cron or db updates and release readonly mode afterward only if restore set it itself 2021-08-12 16:25:50 +02:00
odain
2c026fa891 N°4225 - Protect manual backups from cron (and vice versa) - protect any backup from cron/db updates and release readonly mode only when backup set it itself 2021-08-12 16:25:50 +02:00
odain
00d65aeb45 N°4227 - Manual iTop restore - create manual CLI restore 2021-08-12 16:25:50 +02:00
odain
5702f603ac N°4227 - Manual iTop restore - move restore mutex management inside in DBRestore->RestoreFromCompressedBackup primitive 2021-08-12 16:25:50 +02:00
odain
e3dc1b77cc N°4225 - Protect manual backups from cron (and vice versa) - minimal work ie protect manual backup from cron only 2021-08-12 16:25:50 +02:00
Stephen Abello
c304f70ff4 Harmonize external key's autocomplete items display with dropdown ones 2021-08-12 14:40:36 +02:00
Stephen Abello
53fd41e748 N°4127 Fix XSS vulnerability in autocomplete lists 2021-08-12 14:21:05 +02:00
Molkobain
7577fbb8bf N°4236 - Fix user with no admin right can't connect in iTop when log_usage set to true 2021-08-12 11:13:59 +02:00
Molkobain
0fc912b357 Rename MetaModel::IsObjectExistsInDb() to MetaModel::IsObjectInDB() 2021-08-12 09:37:24 +02:00
Molkobain
c475e66176 Add documentation 2021-08-12 09:35:14 +02:00
Pierre Goiffon
cd1ba097cb N°3867 Fix error "LEVEL_WARNING of type Identifier is forbidden" error when saving valid config in configuration editor 2021-08-11 18:07:38 +02:00
Molkobain
fdca4d4cc3 N°3786 - Fix object locked hint (on activity panel) not displayed until message removed 2021-08-11 17:41:31 +02:00
Pierre Goiffon
4379b4d908 N°3800 Fix HTML content display for table borders v2 2021-08-11 17:31:08 +02:00
Pierre Goiffon
5b42f67a99 N°3867 Code review: remove condition that is quite too strict (danger !!)
Thanks @odain !
2021-08-11 17:22:21 +02:00
Pierre Goiffon
0b9ccc8e67 N°3800 Fix HTML content display for table borders and code color 2021-08-11 17:08:00 +02:00
Pierre Goiffon
2d98ca2318 N°3867 Fix "Invalid configuration: Stmt_Expression is forbidden in line 10" error when saving valid config in configuration editor
Now this is tested in \ConfigValidator\iTopConfigAstValidatorTest
2021-08-11 16:38:56 +02:00
odain
92add2bbfe add annotation to datasynchro test 2021-08-11 10:17:35 +02:00
Pierre Goiffon
f15bdb75ca N°4173 Fix memory_limit error on target object hyperlink generation 2021-08-10 19:08:00 +02:00
Pierre Goiffon
a66830de17 N°4173 Fix memory_limit error when saving an object pointing to a big AttributeBlob
Check of pointed object was loading the whole object instead of just doing a count
2021-08-10 19:08:00 +02:00
Pierre Goiffon
714294e1b4 N°4173 Fix memory_limit exhausted when having ExtKey widget pointing on class with AttributeBlob
Was failing when opening an object form, and when the list contained target objects with total file size above memory_limit
2021-08-10 18:46:10 +02:00
Molkobain
e3d2c1d761 Advanced search: Restore "more criteria" inputs color 2021-08-10 16:27:08 +02:00
Molkobain
59d95cc14b Field: Change how field's value wrap to fix unwanted "new line" between some elements (lists, ...) 2021-08-10 16:27:08 +02:00
Pierre Goiffon
ddc5bbd1bb N°3867 Fix write config now removes PHP vars
Now the PhpParser tree has a different structure, beginning with a PhpParser\Node\Stmt\Expression
2021-08-10 15:47:01 +02:00
Pierre Goiffon
9bbee0d603 ⬆️ N°3867 Upgrade php-parser from 3.1.5 to latest version 4.12.0
The 3.1.5 version wasn't maintained anymore and was compatible with PHP 7.2 maximum
2021-08-10 15:45:02 +02:00
acognet
be1ef5b452 N°3634 - Feedback alpha 3.0 : finish list - display printable object 2021-08-10 12:10:54 +02:00
Pierre Goiffon
cbdc48b7e1 iTopConfigParserTest now works on Windows 2021-08-10 11:48:13 +02:00
denis.flaven@combodo.com
bb5679959e Fixed unit tests for password_renewed_date, broken by the correction of
n°4095.
2021-08-10 10:40:54 +02:00
Molkobain
cdbb4470fc N°4039 - Add margin below an object details
- Easier read of the fields at the bottom of it
- Better display of a dropdown at the bottom of it
2021-08-09 17:20:56 +02:00
Molkobain
252d752bf5 Navigation menu: Improve french translations for the current user tooltip 2021-08-09 17:20:56 +02:00
denis.flaven@combodo.com
99d0c05c1c N°4095 - Provisions for future implementation of users password
expiration.
2021-08-09 17:13:08 +02:00
Molkobain
5926e9d110 Navigation menu: Add current user's login to the tooltip 2021-08-09 16:28:53 +02:00
Molkobain
c8dd8c3806 Code cleanup: Remove forgotten commented line 2021-08-09 13:54:02 +02:00
Molkobain
70b07721e6 N°2875 - Fix typo in config example 2021-08-09 10:46:42 +02:00
Molkobain
bd050dfe69 N°2875 - Add possibility to configure the marker scope by either a class or an OQL 2021-08-09 10:38:27 +02:00
Molkobain
9eb477ce83 N°2875 - Fix visual glitches on long results
- Medallion could be horizontally compressed
- Title wrapped instead of overflowing, making reading across entries difficult
2021-08-09 10:38:27 +02:00
Pierre Goiffon
a742b6c610 🔊 N°3002 DeprecatedCallsLog error handler : add trigger_error message 2021-08-09 09:06:17 +02:00
Pierre Goiffon
fcf769666e N°3913 setup : in "done" step, add back styling on HTML content 2021-08-06 17:59:42 +02:00
Pierre Goiffon
72e628c5d5 💡 Reference in PHPDoc for \LogAPI::GetMinLogLevel 2021-08-06 17:39:34 +02:00
Pierre Goiffon
bf28602ae6 💡 Improve PHPDoc for \LogAPI::GetMinLogLevel 2021-08-06 17:37:56 +02:00
Pierre Goiffon
b8a0d899f4 Fix \coreExtensions\UserLocalTest::testValidatePassword failing because of DEPRECATED notices 2021-08-06 17:13:24 +02:00
Pierre Goiffon
d335736cfc 🔊 N°3002 Add trigger_error to catch deprecated notices inside DeprecatedCallsLog
They are logged as WARNING level in \DeprecatedCallsLog::ENUM_CHANNEL_PHP_LIBMETHOD channel

Such deprecated notices are generated inside Symfony for example using @trigger_error(..., E_DEPRECATED).
Having such a log will help us migrate to the next lib version !
2021-08-06 17:13:24 +02:00
Molkobain
80f7d07378 N°3592 - Fix alignment of custom fields in the backoffice 2021-08-06 17:04:26 +02:00
Molkobain
0be6a8aef4 N°3592 - Remove part of 146a95bae as it was unnecessary 2021-08-06 17:04:26 +02:00
Stephen Abello
0fe857071d Fix login css glitches with iTop 3.0 2021-08-06 15:41:43 +02:00
acognet
05981c8af8 N°4156 - GUI broken when a external key contains "\" 2021-08-06 11:56:25 +02:00
Molkobain
0abea749fa N°3786 - Fix lock removal when leaving an object details page in read-only with lock on the activity panel 2021-08-06 11:12:56 +02:00
Molkobain
78af6fdc84 N°3944 - Add comment to explain the 2 similar IFs 2021-08-06 11:11:57 +02:00
acognet
1e97b5a8c0 Revert N°3423 - Allow AttributeImage / AttributeDocument content to be cached by the browser + N°4029 - Caching images in chrome does not work 2021-08-06 09:51:59 +02:00
acognet
0214243b63 N°3914 - Polishing: Lists - remove pagination when it is unnecessary 2021-08-06 09:19:31 +02:00
Pierre Goiffon
d30871ac59 Revert "N°3002 Add trigger_error to catch deprecated notices inside DeprecatedCallsLog"
This reverts commit d247ea915d.
Will solve CI build errors... I will work in my own branch to tweak this !
2021-08-05 17:35:17 +02:00
Pierre Goiffon
d247ea915d N°3002 Add trigger_error to catch deprecated notices inside DeprecatedCallsLog
Such notices are generated inside Symfony for example using @trigger_error(..., E_DEPRECATED).
Having such a log will help us migrate to the next version !
2021-08-05 17:18:19 +02:00
acognet
0e0aed1ba4 N°3423 - Allow AttributeImage / AttributeDocument content to be cached by the browser + N°4029 - Caching images in chrome does not work 2021-08-05 16:34:24 +02:00
acognet
bc770ef3d5 N°4007 - When a search is on auto submit, auto submit it also on first display - new fix in order to avoid reload in case of OQLMenuNode -add param in searchFormHandler 2021-08-05 16:20:56 +02:00
Pierre Goiffon
56a4fb0b42 Allow to run all tests from within the IDE
Just :
* configure the config file (test\phpunit.xml.dist)
* add runner parameters : --fail-on-risky --exclude-group beforeSetup
* specify iTop root as custom working directory
2021-08-05 15:21:41 +02:00
acognet
3139628dd8 N°4007 - When a search is on auto submit, auto submit it also on first display - new fix in order to avoid reload in case of OQLMenuNode 2021-08-05 15:08:48 +02:00
acognet
7d9b19cd9e N°4119 - Loose application context in advanced search ajax call 2021-08-05 12:15:47 +02:00
Stephen Abello
b3cb95d2f1 Removed inline background color 2021-08-05 11:56:17 +02:00
Stephen Abello
a6765cdc3a Update precompiled stylesheets 2021-08-05 10:38:40 +02:00
Stephen Abello
97d52fb539 N°3685 Revert most of e4f58b5b changes, replace @extend .ibo-button calls with classes in markup for most cases, except jquery-ui buttons 2021-08-05 10:20:30 +02:00
Pierre Goiffon
ad936d7a2f 🔇 N°3731 Remove DeprecatedCallLog in \MetaModel::BulkUpdate
Method is still called in \ObsolescenceDateUpdater !
2021-08-04 17:37:10 +02:00
Pierre Goiffon
090eb9a560 🔇 N°3731 Remove DeprecatedCallLog in \MetaModel::GetFilterCodeOrigin
Method is still called !
2021-08-04 17:34:13 +02:00
Stephen Abello
e4f58b5b98 N°3685 Greatly optimize SCSS rules to reduce compiled CSS file size (4.7Mo -> 620ko) 2021-08-04 16:38:40 +02:00
acognet
c99a22c9f7 N°4025 - Universal search : cannot select class anymore 2021-08-04 14:41:25 +02:00
Pierre Goiffon
29cf20beaf N°3909 Fix notification configuration help layout 2021-08-04 12:00:48 +02:00
denis.flaven@combodo.com
e325d535ae N°4096 - Add automatic email reprocessing in case of error 2021-08-04 11:24:59 +02:00
Pierre Goiffon
11cfdb2a17 N°3807 Fix container added for all Html components
This was causing issues for example in the setup, as any raw content is sent using \WebPage::add, which create an HTML component.
I reverted the layout update, and created a new factory method to be used when needed : \Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory::MakeHtmlContent
2021-08-04 11:19:35 +02:00
Pierre Goiffon
66720b9731 N°3807 Update theme CSS 2021-08-04 11:19:35 +02:00
acognet
234f46cafa N°1731 - Allow Transitions without unnecessary confirmation 2021-08-04 10:51:04 +02:00
acognet
27c3ce0389 N°3907 - Polishing: Run query - Add dictionary entry 2021-08-04 10:44:26 +02:00
Pierre Goiffon
9d0e2fa64a N°3807 Bring back styling to raw HTML content
Bulma minireset (https://github.com/jgthms/bulma/blob/master/sass/base/minireset.sass) was applied everywhere in iTop. This was causing HTML content without any Bulma or iTop 3.0.* CSS classes to render with no styles anymore, not even the default browser's ones. Especially rendering for content styled in CK Editor was problematic...

This commit creates a new `ibo-is-html-content` CSS class (in css/backoffice/utils/helpers/_misc.scss) that just extends the `content` Bulma class (indirection to reduce framework coupling).
This new iTop CSS class is added in :
* AttributeText and its children when format is HTML
* HTML components
* activity entries in HTML format

The class can also be used elsewhere when needed, for example in modules having custom pages that aren't using yet the iTop 3.0.* UI components or CSS classes.
2021-08-04 10:37:54 +02:00
acognet
e211633fed N°4039 - List contain stays in place when scrolling on a screen 2021-08-04 10:37:02 +02:00
denis.flaven@combodo.com
29967aa41a N°3944 - LogAPI : fix default log level in config when logging using a channel 2021-08-04 10:07:58 +02:00
Pierre Goiffon
3ebb1cfc66 💡 phpDoc 2021-08-03 17:15:38 +02:00
Molkobain
6c2221b8b6 Datatables: Remove extra white space in template 2021-08-03 16:16:21 +02:00
Molkobain
c0b3aed12c N°3712 - Code cleanup thanks to @piR 2021-08-03 16:15:09 +02:00
Stephen Abello
53efc9f75e Update precompiled stylesheets 2021-08-03 15:55:06 +02:00
Stephen Abello
0d566f2e47 Fix panel collapsible toggler display 2021-08-03 15:49:27 +02:00
Stephen Abello
b294e3c734 N°3928 Correctly align impact analysis's filter refresh button 2021-08-03 15:49:27 +02:00
Stephen Abello
6704f9eccf N°3928 Allow user to collapse impact analysis' filter 2021-08-03 15:49:27 +02:00
Molkobain
90cb6e1d49 N°4124 - Extract computations into variables for easier comprehension 2021-08-03 11:37:01 +02:00
Molkobain
c0aa4f2d69 Initialize array properly 2021-08-03 11:11:02 +02:00
Molkobain
01984c24c0 Fix wrong usage of spaceless filter 2021-08-02 20:15:16 +02:00
Molkobain
1da1e0b1bd N°3712 - Activity panel: Improve history entries to have a different icon depending the origin (interactive, webservices, csv, ...) 2021-08-02 19:42:32 +02:00
Molkobain
39bcd3e4cd 🌐 N°3712 - Add dict. entries for CMDBChange->origin values 2021-08-02 19:42:32 +02:00
Molkobain
156cce6098 N°3712 - Extract CMDBChange origin values in a dedicated class 2021-08-02 19:42:32 +02:00
acognet
3130e95f4f N°3907 - Polishing: Run query 2021-08-02 18:56:07 +02:00
Molkobain
e28f704f3e N°4076 - Revert regression in global search from 344cce9fd
Current query was not visible when reaching the results page
2021-08-02 15:20:05 +02:00
Stephen Abello
5c6c59941a N°3901 Correctly hide menu search placeholder 2021-08-02 15:18:06 +02:00
Stephen Abello
bc9d47933e Update precompiled stylesheets 2021-08-02 11:43:28 +02:00
Stephen Abello
07a10e4146 N°3930 Fix dashlet blocker position 2021-08-02 11:42:57 +02:00
Stephen Abello
e8a21870ad Update precompiled stylesheets 2021-08-02 11:28:37 +02:00
Stephen Abello
2e132d5c53 N°3930 Restore dashlet blocker on dashboard editor 2021-08-02 11:28:37 +02:00
Eric
3359609349 Allow mode selection for queries 2021-07-31 15:23:19 +02:00
Molkobain
2966759466 Code cleanup 2021-07-30 17:09:51 +02:00
Molkobain
25395405e5 N°3905 - Remove remaining spaces 2021-07-30 17:09:50 +02:00
Stephen Abello
b589e2d001 Update precompiled stylesheets 2021-07-30 16:48:48 +02:00
Stephen Abello
27f3619cf5 Fix undefined variable used in scss 2021-07-30 16:48:23 +02:00
Stephen Abello
616fc436d0 CSS improvements 2021-07-30 16:40:33 +02:00
acognet
4f2f765207 N°3912 - Polishing: Export - fix page webservices/export.php - revert delete of utils.js 2021-07-30 15:16:51 +02:00
Pierre Goiffon
9931fa1a6b 🎨 N°4092 rename method
Thanks @Molkobain !
2021-07-30 12:09:30 +02:00
Pierre Goiffon
a13f2750ea 🎨 Replace call_user_func call to \cmdbAbstractObject::GetShortcutActions for better IDE recognition
And also :
* removed an obsolete use statement
* fix method phpdoc
2021-07-30 11:41:41 +02:00
Stephen Abello
243d105f59 Fix return value from new method added in 49fd482 2021-07-30 10:54:16 +02:00
Stephen Abello
7783ba570e PHPDoc 2021-07-30 10:37:52 +02:00
acognet
5a9fa2ac32 N°3912 - Polishing: Export - fix page webservices/export.php 2021-07-29 17:59:51 +02:00
Pierre Goiffon
619e3de5a8 N°4092 Setup : add symlinks option in install
Was only available during update
2021-07-29 17:34:24 +02:00
Pierre Goiffon
83064d68c7 N°4092 Setup : symlink option now always displayed if functionality enabled
Previously we were also testing for flag presence.
In consequence the confirmation dialog when unchecking the option is also removed (we can enable back the option using the setup)
2021-07-29 17:24:20 +02:00
Stephen Abello
f3c11e72cf N°3552 Add method to retrieve User object from Person object 2021-07-29 16:37:03 +02:00
Stephen Abello
c9e887a264 N°3552 Allow AddLogEntry to set entry's user_id from another user using a new parameter 2021-07-29 16:37:02 +02:00
Stephen Abello
49fd482389 N°3552 Correctly display case log entry with no iTop user linked (such as mail to ticket entries) 2021-07-29 16:37:02 +02:00
acognet
96de4e1796 N°4117 - Text field display, too many empty lines 2021-07-29 11:52:39 +02:00
acognet
44f413583c N°4124 - Unable to scroll down on some object 2021-07-29 11:38:20 +02:00
acognet
91104002ea N°4037 - Missing close button on list export on portal - rollback commit for N°3746 2021-07-28 16:02:35 +02:00
acognet
f98ba1594c N°3705 - Migrate module to new UIBlock system : Kanban board - css and dictionnary in ajax page 2021-07-28 16:02:35 +02:00
Molkobain
431dc5532b Change comment / test for clarification 2021-07-28 15:59:43 +02:00
Molkobain
df473ae313 PHPDoc 2021-07-28 15:31:22 +02:00
Molkobain
40ce74cffa N°4203 - Activity panel: Add button to load all entries at once 2021-07-28 14:58:15 +02:00
Molkobain
7598c18ad6 N°4203 - Activity panel: Hide "load more entries" button when only "logs" filter is active 2021-07-28 14:55:17 +02:00
Molkobain
d38b655f5f Activity panel: Limit visual glitches on entries on first display by hiding them all before displaying only the relevant.
Otherwise, entries from the other logs / activity tabs are visible for a second before filtering on the entry of the current tab only.
2021-07-28 14:55:17 +02:00
Eric
f4345ef312 N°4199 - Add <code> to EnumSet values to fix XML conversion 2021-07-28 14:40:19 +02:00
Eric
13b548e95d N°4199 - Add <code> to EnumSet values to fix XML conversion 2021-07-28 14:39:52 +02:00
Eric
3a988ab499 N°4175 - Adding a lifecycle to an existing class fails on setup (using xml version < 3.0) 2021-07-28 13:30:39 +02:00
Eric
14a5f87d62 N°4036 - User edition controls : a user cannot remove contact 2021-07-28 13:09:18 +02:00
Eric
8dae459b12 N°4036 - User edition controls : the profiles selection should allow the User modification (when editing your own User) 2021-07-28 11:45:02 +02:00
Eric
8dc10424e8 🎨 cleanup code 2021-07-28 09:59:10 +02:00
Eric
54a6573948 N°4036 - User edition controls : the profiles selection should allow the User modification (when editing your own User) 2021-07-28 09:13:47 +02:00
Eric
1d5e0b6fe9 Temporary ignored test for CI 2021-07-27 18:01:39 +02:00
Eric
acb8a377dd N°4036 - User edition controls : a user cannot change his own status (fix) 2021-07-27 17:40:00 +02:00
odain
d86f489c89 fix provided themes to avoid scss setup compilation 2021-07-27 17:22:24 +02:00
Eric
2c154b601d N°4036 - User edition controls : allowed orgs must contain user org 2021-07-27 15:57:03 +02:00
Eric
7dad079688 N°4036 - User edition controls : a user cannot remove contact 2021-07-27 15:57:03 +02:00
Eric
ace2eb6dab N°4036 - User edition controls : a user cannot change his own status 2021-07-27 15:57:03 +02:00
Eric
25f3c1cbc4 N°4036 - User edition controls : a user cannot add to himself a profile denying the backoffice 2021-07-27 15:57:03 +02:00
Eric
8f7e7c136d Fix links when object edition fails 2021-07-27 15:57:03 +02:00
Eric
71a7e060e4 N°4036 - User edition controls : a user cannot delete himself 2021-07-27 15:57:03 +02:00
Pierre Goiffon
c450f1d02f 💡 aModifierProperties documentation 2021-07-27 15:54:47 +02:00
acognet
4431762c10 N°3592 - Migrate module to new UIBlock system : Customized request forms - precompiled css v2 2021-07-27 15:33:47 +02:00
acognet
97170892e6 N°3905 - Polishing: CSV Import - remove spaces 2021-07-27 15:08:38 +02:00
acognet
5137d634e3 N°3592 - Migrate module to new UIBlock system : Customized request forms - precompiled css 2021-07-27 15:03:04 +02:00
acognet
146a95baec N°3592 - Migrate module to new UIBlock system : Customized request forms - remove space in edit screen 2021-07-27 14:38:44 +02:00
Pierre Goiffon
2b71ea108a Setup memory_limit check : clearer message
Now the current value is displayed as entered in the PHP conf
And the recommended value is displayed in a friendly format (32M instead of raw bytes value)
2021-07-27 11:37:05 +02:00
Molkobain
a7ac9126a2 PHPDoc 2021-07-26 18:07:12 +02:00
Eric
ee544b646d N°4076 - PHPDoc 2021-07-26 17:48:36 +02:00
Eric
344cce9fdd N°4076 - Allow block parameters to change the behaviour of blocks on the page 2021-07-26 17:20:45 +02:00
Stephen Abello
bfcfcdd4cc N°3930 Fix dashlet group by preview not displaying 2021-07-26 14:26:44 +02:00
Molkobain
4410bf7e1f N°4144 - Portal: Fix caselog fullscreen icon too small to be clickable 2021-07-26 12:07:49 +02:00
Eric
d1fda1dbc6 composer classmap changed 2021-07-23 16:47:15 +02:00
Stephen Abello
5c702be641 Update precompiled themes 2021-07-23 11:29:33 +02:00
Stephen Abello
8a9ad78e9c N°3930 Bring back error icons on dashlet property 2021-07-23 11:29:33 +02:00
Molkobain
778be8abce N°4195 - Avoid dictionary entries to be added to the setup page as it breaks the fresh install
In fresh install there is no env-xxx yet, so when the setup tries to load the english dictionary from there it crashes.
In setup pages, all string are hard coded, no dictionary entries are available (yet)
2021-07-23 09:00:19 +02:00
Molkobain
0760adcef6 Object details: Fix status code being set to the color instead of the code 2021-07-22 18:12:11 +02:00
Pierre Goiffon
29ca291860 N°4192 Fix error on Contract classes object details
Was crashing on all classes without state attribute, or with state attribute nullable
2021-07-22 11:07:07 +02:00
Stephen Abello
93d0e4ae7c Update jQuery datepicker z-index 2021-07-22 10:43:22 +02:00
Molkobain
61bc07b598 Add PHPDoc to explain behavior change (as in the migration notes) 2021-07-21 17:46:39 +02:00
Pierre Goiffon
00ee36f938 Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	README.md
2021-07-21 12:22:20 +02:00
Pierre Goiffon
1aa5185c93 Merge remote-tracking branch 'origin/support/2.6' into support/2.7
# Conflicts:
#	README.md
2021-07-21 12:19:52 +02:00
Pierre Goiffon
834ac00d37 📝 README : update latest releases
Was made in #143 but on develop only, but we are still maintaining older branches !
2021-07-21 12:15:22 +02:00
Molkobain
957cb87b8d N°3786 - Fix lock removal when leaving an object details page in edition
The main problem was WebPage::$a_scripts are now printed after the DOM when the mechanism actually prints some JS to redirect to another page immediately.
As there are a LOT of calls to WebPage::add_script(), we kept it to ensure the compatibility; and we add a new WebPage::add_early_script() for JS snippets that explicitly need to be execute before the DOM interpretation.
2021-07-20 20:13:44 +02:00
Eric
d0813f6607 N°4169 - Fix forms properties for designer 2021-07-20 17:49:30 +02:00
Stephen Abello
cf4673d284 Update precompiled stylesheets 2021-07-20 10:21:06 +02:00
Stephen Abello
e3422f5fd9 CSS improvements 2021-07-20 10:20:31 +02:00
Stephen Abello
d7a0878a39 Fix broken variables import in 3.0 themes 2021-07-20 10:04:12 +02:00
Molkobain
076f0e00a7 N°4178 - Stay on the same page when logging again from the "Login again" prompt 2021-07-18 22:58:33 +02:00
Molkobain
eb04ecb5b4 Fix new URL parameter being append after the '#' in CombodoGlobalToolbox.AddParameterToUrl 2021-07-18 22:37:01 +02:00
Molkobain
3323d5532b JSDoc 2021-07-18 22:04:39 +02:00
Molkobain
d68d8204f2 N°4176 - Portal: Deprecate "AddParameterToUrl" function 2021-07-18 22:02:47 +02:00
Molkobain
4a50b95069 N°3924 - Fix misleading orphan activity entries when creating an object 2021-07-17 00:11:16 +02:00
Molkobain
fd9e4f413c N°3924 - Fix missing activity panel on object details when cancelling transition form 2021-07-16 22:53:28 +02:00
Molkobain
dca3fc996c N°3924 - Fix double encoding of the object name in transition page title 2021-07-16 22:39:15 +02:00
Molkobain
c58c1bbf1d N°3924 - Fix notification link in activity panel not opening notifications tab 2021-07-16 22:29:54 +02:00
Molkobain
524919b946 N°4171 - Remove lock message from the log entry form when object is in edition 2021-07-16 20:13:49 +02:00
Molkobain
e15953524a N°3786 - Add pause at the end of the page unload async request to follow the guideline used in N°2763 2021-07-16 20:08:25 +02:00
Molkobain
68f3c53faa N°4171 - Concurrent lock: Fix expiration modal being duplicated sometimes 2021-07-16 16:44:35 +02:00
Molkobain
82e3ddaacd N°4142 - Improve backoffice rendering by showing system avatar no matter what 2021-07-15 21:34:58 +02:00
Molkobain
dfbbee2a1c N°4142 - Add config. param. to hide user avatars in the GUIs -mostly in logs- (backoffice, end-users portal, ...) 2021-07-15 21:23:20 +02:00
Molkobain
4843545171 N°4142 - Fix UserRights::GetUserPictureAbsUrl() returning user's placeholder picture for users with an unknown login 2021-07-15 17:33:38 +02:00
Molkobain
4e58b8616a N°3203 - PHPDoc and small variable refactor 2021-07-15 17:31:30 +02:00
Molkobain
1a79dcd773 N°3203 - Portal: Fix image attribute of an object not authorized if object out of scope 2021-07-15 11:41:07 +02:00
Molkobain
a4104d4315 N°3539 - Remove viewport meta tag as we won't be able to make the backoffice responsive in 3.0 2021-07-15 09:09:47 +02:00
Molkobain
4e1db7d7e2 Merge remote-tracking branch 'origin/support/2.7' into develop 2021-07-13 11:44:42 +02:00
Molkobain
8e6379a112 Merge branch 'support/2.7.5' into support/2.7 2021-07-13 11:42:30 +02:00
Molkobain
da217a1cb3 N°4161 - Fix ManageBrick crash when no item listed 2021-07-12 18:10:22 +02:00
Pierre Goiffon
a683634a05 N°4126 Fix HTML escaped in \SetupUtils::CheckDbServer messages
As content is sent to JS returned to the AJAX request, we need to escape JS string delimiter (single quote)
We had previously a \utils::HtmlEntities call, but this isn't necessary as all content is generated internally, without calling any dict or extensibility interface.
2021-07-12 14:41:26 +02:00
Molkobain
2f1b5d2736 Fix typo in method parameter 2021-07-12 12:47:43 +02:00
Molkobain
c9bf784fce Revert space introduced by 418312bca 2021-07-12 11:37:17 +02:00
Molkobain
4ef10ae7c9 PHPDoc 2021-07-12 10:58:38 +02:00
Molkobain
5174250ff1 N°3836 - Dashlet: Refactor part of the code to match conventions 2021-07-12 10:51:45 +02:00
Pierre Goiffon
7b6ac202c6 N°4518 Add config parameter to set platform as dev env (#220)
developer_mode.enabled, default value null
If true or false then will be used by \utils::IsDevelopmentEnvironment as return value, in all other cases will follow previous behavior
2021-07-12 09:27:36 +02:00
Molkobain
d960183403 Merge remote-tracking branch 'origin/support/3.0.0-beta2' into develop 2021-07-09 16:28:00 +02:00
Eric
ece3e0490d N°3573 - Allow retrieving operation
(cherry picked from commit e6a38a8055)
2021-07-09 15:24:20 +02:00
Eric
1562cb1f38 🎨 Allow tab stickiness for twig based extensions
(cherry picked from commit 1a7755365c)
2021-07-09 15:23:35 +02:00
Molkobain
11a22abfd5 SCSS: Fix horizontal scrollbars being ticker than vertical ones on webkit browsers 2021-07-09 12:52:32 +02:00
Stephen Abello
5254c9a633 N°3930 Fix dashlets select property with a set size 2021-07-08 10:32:41 +02:00
Stephen Abello
f7a35072f5 N°4075 Fix autocompletes in modals 2021-07-07 16:59:50 +02:00
Molkobain
b5f5780f35 Merge remote-tracking branch 'origin/support/3.0.0-beta2' into develop
# Conflicts:
#	datamodels/2.x/itop-structure/precompiled-themes/fullmoon/main.css
#	datamodels/2.x/itop-structure/precompiled-themes/test-red/main.css
2021-07-07 15:08:38 +02:00
Molkobain
f9064084f9 N°4130 - Activity panel: Remove edits and transitions from default filters on logs 2021-07-07 14:11:03 +02:00
Molkobain
67afbd1d8d N°4146 - Fix crash on object deletion 2021-07-07 13:18:10 +02:00
Molkobain
d6b9172e26 N°4131 - Fix multiple "User disconnected" dialogs in the backoffice
(cherry picked from commit d8f36a8aa9)
2021-07-07 09:32:01 +02:00
Molkobain
8e1e71c740 N°4109 - Fix JS message "Gateway timeout" / "Unauthorized" / "" being displayed on object details page
(cherry picked from commit e279799bf8)
2021-07-07 09:31:53 +02:00
Molkobain
ebbf6e56be N°4127 - Security: Fix XSS vulnerability in object attribute's tooltip 2021-07-07 09:28:41 +02:00
Pierre Goiffon
bd67b71f3d Merge remote-tracking branch 'origin/support/2.7' into develop 2021-07-06 11:36:34 +02:00
Pierre Goiffon
69ad10785b 🔧 .editorConfig : disable PHP variables alignement
Still enabled for key/value pairs though ! Example in \UtilsTest::ConvertToBytesProvider
2021-07-05 12:34:20 +02:00
Pierre Goiffon
9aead898e2 Fix Wiki URL to use iTop Hub instead of wiki.openitop
Thanks @Molkobain and @Hipska !
2021-07-05 12:29:07 +02:00
Pierre Goiffon
a48ebfefba N°4126 Change max_allowed_packet error message
Thanks @Molkobain !
2021-07-05 09:54:47 +02:00
Pierre Goiffon
e2a6e6b846 💚 Fix compiled CSS 2021-07-05 09:53:11 +02:00
Molkobain
7ca689e190 Setup: Fix sizes being displayed as bits instead of bytes 2021-07-04 22:49:54 +02:00
Molkobain
d8f36a8aa9 N°4131 - Fix multiple "User disconnected" dialogs in the backoffice 2021-07-04 21:54:56 +02:00
Molkobain
e279799bf8 N°4109 - Fix JS message "Gateway timeout" / "Unauthorized" / "" being displayed on object details page 2021-07-04 16:46:49 +02:00
Eric
a117906ff6 🎨 Add constant for cmdbsource log channel 2021-07-02 17:01:46 +02:00
Pierre Goiffon
c76d4f12fd Fix constant remove in iTopHub:DBBackupSentence ZN dict key
Thanks Hipska for your feedback in the original commit 67e92120 !
2021-07-02 16:30:31 +02:00
Molkobain
b16529e337 Update version.xml to 3.0.0-beta2 2021-07-02 12:28:58 +02:00
Molkobain
67e9212008 Massively update dictionaries 2021-07-02 12:26:51 +02:00
acognet
35b70bfc00 N°3929 - Polishing: Dashboards 2021-07-02 11:29:10 +02:00
acognet
418312bca5 N°3930 - Polishing: Dashlets 2021-07-02 11:22:57 +02:00
Pierre Goiffon
4748717e50 N°4126 Improve max_allowed_packet checks messages 2021-07-02 10:10:35 +02:00
Pierre Goiffon
d90b1a3d82 🐛 N°4020 Fix syntax error for PHP < 7.1
`syntax error, unexpected 'const' (T_CONST), expecting variable (T_VARIABLE) in /var/www/itop274/setup/compiler.class.inc.php on line 61`
Was added in 1059befa
2021-07-02 09:26:03 +02:00
acognet
76a237aad4 N°3930 - Polishing: Dashlets 2021-07-01 17:28:35 +02:00
Pierre Goiffon
3694108f42 N°3870 updateLicenses : fix generating wrong product names on Windows
Was including paths fragments.

Example :
<product scope="datamodels">C:\Dev\wamp64\www\itop-dev\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas</product>

Instead of :
<product scope="datamodels">apereo/phpcas</product>
2021-07-01 17:20:55 +02:00
Eric
1a7755365c 🎨 Allow tab stickiness for twig based extensions 2021-07-01 16:08:03 +02:00
Pierre Goiffon
8cf75f826f 🔨 updateLicenses : add logs and replace rm -f by unlink() 2021-07-01 15:30:33 +02:00
Molkobain
a1da086a64 Revert "Fix and simplify dynamic dictionaries load in ajax pages"
This reverts commit e46743af
2021-07-01 13:38:16 +02:00
Molkobain
5d1d6d07a6 Fix regression introduced in 9048d09bf 2021-07-01 10:55:50 +02:00
Eric
e6a38a8055 N°3573 - Allow retrieving operation 2021-07-01 10:40:04 +02:00
Molkobain
3b3f1806ce Fix resources URL not being printed correctly
If there was any parameters in the URL it would not work as the `&` would have been printed as `&amp;`, see similar issue in the previous commit
2021-07-01 09:55:01 +02:00
Molkobain
e46743af2a Fix and simplify dynamic dictionaries load in ajax pages 2021-07-01 09:55:00 +02:00
Molkobain
9048d09bf6 Fix JS dictionaries not being load in \WebPage 2021-07-01 09:55:00 +02:00
Molkobain
3cd03729b9 Setup: Fix "Reload" button style when config file is read-only 2021-07-01 09:55:00 +02:00
Eric
ff760dacbe N°3573 - Allow disabling buttons 2021-06-30 17:05:03 +02:00
Thomas Casteleyn
94f662c71a Show real next backup date (#196)
* Get the next backup date from what is scheduled in the background task.
* Show different message if cron is not running.
2021-06-30 14:59:36 +02:00
Eric
c7d87ad5b0 🎨 Display human readable sizes in bytes as int instead of float 2021-06-30 11:18:08 +02:00
Pierre Goiffon
ad9726b64c 🔧 .editorConfig : restore old ij_visual_guides value
Was overwritten by mistake in 19505649
2021-06-30 10:24:19 +02:00
Pierre Goiffon
e32e275f40 🎨 Align dataprovider elements 2021-06-29 16:45:34 +02:00
Pierre Goiffon
195056492e 🔧 .editorConfig : enable var alignement 2021-06-29 16:45:18 +02:00
Eric
5691ca0327 Fix CI 2021-05-28 08:48:47 +02:00
1406 changed files with 31279 additions and 73317 deletions

View File

@@ -280,7 +280,7 @@ ij_javascript_while_brace_force = always
ij_javascript_while_on_new_line = false
ij_javascript_wrap_comments = false
[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
[{*.ctp, *.hphp, *.inc, *.module, *.php, *.php4, *.php5, *.phtml}]
indent_style = tab
ij_continuation_indent_size = 4
ij_smart_tabs = true
@@ -289,8 +289,8 @@ ij_php_align_assignments = false
ij_php_align_class_constants = false
ij_php_align_group_field_declarations = false
ij_php_align_inline_comments = false
ij_php_align_key_value_pairs = false
ij_php_align_multiline_array_initializer_expression = false
ij_php_align_key_value_pairs = true
ij_php_align_multiline_array_initializer_expression = true
ij_php_align_multiline_binary_operation = false
ij_php_align_multiline_chained_methods = false
ij_php_align_multiline_extends_list = false

13
.make/git-hooks/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Git hooks for iTop
## ❓ Goal
Those [git hooks](https://git-scm.com/docs/githooks) aims to ease developing on [iTop](https://github.com/Combodo/iTop).
## ☑ Available hooks
* pre-commit : rejects commit if you have at least one SCSS file staged, and no CSS file
## ⚙ Install
Just run install.php !

View File

@@ -0,0 +1,26 @@
<?php
$aHooks = [
'pre-commit.php',
];
$sAppRoot = dirname(__DIR__, 2);
foreach ($aHooks as $sSourceHookFileName) {
echo "Processing for `{$sSourceHookFileName}`...\n";
$sSourceHookPath = __DIR__.DIRECTORY_SEPARATOR.$sSourceHookFileName;
$aPathParts = pathinfo($sSourceHookFileName);
$sTargetHookPath = $sAppRoot.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'hooks'.DIRECTORY_SEPARATOR.$aPathParts['filename'];
if (file_exists($sTargetHookPath) || is_link($sTargetHookPath)) {
echo "Existing $sTargetHookPath ! Removing...";
unlink($sTargetHookPath);
echo "OK !\n";
}
echo "Creating symlink for hook in $sTargetHookPath...";
symlink($sSourceHookPath, $sTargetHookPath);
echo "OK !\n";
}

View File

@@ -0,0 +1,49 @@
#!/usr/bin/php
<?php
/**
* Reject any commit containing .scss files, but no .css file !
*/
echo "Checking files staged...\n";
$sFilesToCommit = shell_exec('git diff --cached --name-only --diff-filter=ACMRT');
$aFilesToCommit = explode("\n", $sFilesToCommit);
$aScssFiles = GetFilesWithExtension('scss', $aFilesToCommit);
if (count($aScssFiles) === 0) {
echo "No scss file : OK to go !\n";
exit(0);
}
$aCssFiles = GetFilesWithExtension('css', $aFilesToCommit);
if (count($aCssFiles) === 0) {
echo "There are SCSS files staged but no CSS file : REJECTING commit.\n";
echo "You must add the compiled SCSS files by running the setup !\n";
exit(1);
}
echo "We have SCSS but also CSS => OK to commit !\n";
exit(0);
function GetFilesWithExtension($sExtension, $aFiles) {
return array_filter(
$aFiles,
function($item) use ($sExtension) {
return (endsWith($item, '.'.$sExtension));
}
);
}
function endsWith( $haystack, $needle ) {
$length = strlen( $needle );
if( !$length ) {
return true;
}
return substr( $haystack, -$length ) === $needle;
}
function exitWithMessage($sMessage, $iCode) {
echo $sMessage;
exit($iCode);
}

View File

@@ -10,10 +10,6 @@
* `curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe`
* this is a Windows port : https://stedolan.github.io/jq/
*
* Known bug on Windows :
* Licenses added from Composer contains a path in the product node (N°3870)
* `<product scope="lib">C:\Dev\wamp64\www\itop-dev\.make\license/../..//lib/symfony/console</product>`
*
* Licenses sources :
* * `composer licenses --format json` (see https://getcomposer.org/doc/03-cli.md#licenses)
* * keep every existing nodes with `/licenses/license[11]/product/@scope` not in ['lib', 'datamodels']
@@ -70,39 +66,83 @@ function get_license_nodes($file_path)
$xp = new DOMXPath($dom);
$licenseList = $xp->query('/licenses/license');
$licenses = iterator_to_array($licenseList);
$licenses = iterator_to_array($licenseList);
usort($licenses, 'sort_by_product');
return $licenses;
}
/** @noinspection SuspiciousAssignmentsInspection */
function fix_product_name(DOMNode &$oProductNode)
{
$sProductNameOrig = $oProductNode->nodeValue;
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//lib/symfony/polyfill-ctype`
$sProductNameFixed = remove_dir_from_string($sProductNameOrig, 'lib/');
// sample : `C:\Dev\wamp64\www\itop-27\.make\license/../..//datamodels/2.x/authent-cas/vendor/apereo/phpcas`
$sProductNameFixed = remove_dir_from_string($sProductNameFixed, 'vendor/');
$oProductNode->nodeValue = $sProductNameFixed;
}
function remove_dir_from_string($sString, $sNeedle)
{
if (strpos($sString, $sNeedle) === false) {
return $sString;
}
$sStringTmp = strstr($sString, $sNeedle);
$sStringFixed = str_replace($sNeedle, '', $sStringTmp);
// DEBUG trace O:)
// echo "$sNeedle = $sString => $sStringFixed\n";
return $sStringFixed;
}
$old_licenses = get_license_nodes($xmlFilePath);
//generate file with updated licenses
$generated_license_file_path = __DIR__."/provfile.xml";
exec("bash " . __DIR__ . "/gen-community-license.sh $iTopFolder > ". $generated_license_file_path);
echo "- Generating licences...";
exec("bash ".__DIR__."/gen-community-license.sh $iTopFolder > ".$generated_license_file_path);
echo "OK!\n";
echo "- Get licenses nodes...";
$new_licenses = get_license_nodes($generated_license_file_path);
exec("rm -f ". $generated_license_file_path);
unlink($generated_license_file_path);
foreach ($old_licenses as $b) {
$aProductNode = get_product_node($b);
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels" )
{
if (get_scope($aProductNode) !== "lib" && get_scope($aProductNode) !== "datamodels") {
$new_licenses[] = $b;
}
}
usort($new_licenses, 'sort_by_product');
echo "OK!\n";
echo "- Overwritting Combodo license file...";
$new_dom = new DOMDocument("1.0");
$new_dom->formatOutput = true;
$root = $new_dom->createElement("licenses");
$new_dom->appendChild($root);
foreach ($new_licenses as $b) {
$node = $new_dom->importNode($b,true);
$root->appendChild($new_dom->importNode($b,true));
$node = $new_dom->importNode($b, true);
// N°3870 fix when running script in Windows
// fix should be in gen-community-license.sh but it is easier to do it here !
if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
$oProductNodeOrig = get_product_node($node);
fix_product_name($oProductNodeOrig);
}
$root->appendChild($node);
}
$new_dom->save($xmlFilePath);
$new_dom->save($xmlFilePath);
echo "OK!\n";

View File

@@ -152,5 +152,9 @@ Stickers' design might change from one year to another. For the first year we wa
* White hat: Find and/or fix a vulnerability
* Contributor: Contribute by finding a bug, making an extension or any other way
* Partner: For Combodo's official partners
* Graduated: Follow a Combodo's iTop training
* Ambassador: Outstanding community contributors
* Beta tester: Test and give feedback on beta releases
* Extension developer: Develop and publish an extension
![](documentation/contributing-guide/contributing-stickers-side-by-side.png)

View File

@@ -24,6 +24,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
@@ -227,6 +228,20 @@ class ApplicationContext
}
return $sContext;
}
/**
* Returns the context an array of input blocks
*
* @return array The context as a sequence of <input type="hidden" /> tags
* @since 3.0.0
*/
public function GetForUIForm()
{
$aContextInputBlocks = [];
foreach ($this->aValues as $sName => $sValue) {
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", htmlentities($sValue, ENT_QUOTES, 'UTF-8'));
}
return $aContextInputBlocks;
}
/**
* Returns the context as sequence of input tags to be inserted inside a <form> tag
@@ -321,7 +336,7 @@ class ApplicationContext
$sPrevious = self::GetUrlMakerClass();
self::$m_sUrlMakerClass = $sClass;
$_SESSION['UrlMakerClass'] = $sClass;
Session::Set('UrlMakerClass', $sClass);
return $sPrevious;
}
@@ -334,9 +349,9 @@ class ApplicationContext
{
if (is_null(self::$m_sUrlMakerClass))
{
if (isset($_SESSION['UrlMakerClass']))
if (Session::IsSet('UrlMakerClass'))
{
self::$m_sUrlMakerClass = $_SESSION['UrlMakerClass'];
self::$m_sUrlMakerClass = Session::Get('UrlMakerClass');
}
else
{
@@ -389,9 +404,9 @@ class ApplicationContext
*/
protected static function LoadPluginProperties()
{
if (isset($_SESSION['PluginProperties']))
if (Session::IsSet('PluginProperties'))
{
self::$m_aPluginProperties = $_SESSION['PluginProperties'];
self::$m_aPluginProperties = Session::Get('PluginProperties');
}
else
{
@@ -411,7 +426,7 @@ class ApplicationContext
if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
Session::Set(['PluginProperties', $sPluginClass, $sProperty], $value);
}
/**

View File

@@ -909,6 +909,8 @@ abstract class ApplicationPopupMenuItem
/**
* Class for adding an item into a popup menu that browses to the given URL
*
* Note: This works only in the backoffice, {@see \URLButtonItem} for the end-user portal
*
* @api
* @package Extensibility
* @since 2.0
@@ -963,6 +965,8 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
/**
* Class for adding an item into a popup menu that triggers some Javascript code
*
* Note: This works only in the backoffice, {@see \JSButtonItem} for the end-user portal
*
* @api
* @package Extensibility
* @since 2.0

View File

@@ -4,10 +4,13 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\Search\SearchForm;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\Button;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\StaticTable;
@@ -18,7 +21,10 @@ use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Title\Title;
@@ -94,6 +100,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
/** @var string */
public const ENUM_INPUT_TYPE_RADIO = 'radio';
/** @var string */
public const ENUM_INPUT_TYPE_CHECKBOX = 'checkbox';
/** @var string */
public const ENUM_INPUT_TYPE_DROPDOWN_RAW = 'dropdown_raw';
/** @var string */
public const ENUM_INPUT_TYPE_DROPDOWN_DECORATED = 'dropdown_decorated'; // now with the JQuery Selectize plugin
@@ -202,8 +210,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sParams .= $sName.'='.urlencode($value).'&'; // Always add a trailing &
}
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1';
$oPage->add_script(
<<<EOF
$oPage->add_early_script(<<<JS
if (!sessionStorage.getItem('$sSessionStorageKey'))
{
sessionStorage.setItem('$sSessionStorageKey', 1);
@@ -213,7 +220,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
sessionStorage.removeItem('$sSessionStorageKey');
}
EOF
JS
);
$oObj->Reload();
@@ -259,15 +266,15 @@ EOF
public static function SetSessionMessage($sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false)
{
$sMessageKey = $sClass.'::'.$iKey;
if (!isset($_SESSION['obj_messages'][$sMessageKey])) {
$_SESSION['obj_messages'][$sMessageKey] = array();
if (!Session::IsSet(['obj_messages', $sMessageKey])) {
Session::Set(['obj_messages', $sMessageKey], []);
}
if (!$bMustNotExist || !array_key_exists($sMessageId, $_SESSION['obj_messages'][$sMessageKey])) {
$_SESSION['obj_messages'][$sMessageKey][$sMessageId] = array(
if (!$bMustNotExist || !Session::IsSet(['obj_messages', $sMessageKey, $sMessageId])) {
Session::Set(['obj_messages', $sMessageKey, $sMessageId], [
'rank' => $fRank,
'severity' => $sSeverity,
'message' => $sMessage,
);
]);
}
}
@@ -662,7 +669,7 @@ HTML
}
$oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false));
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-blocklist--medallion');
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion');
$oPage->AddUiBlock($oClassIcon);
$sDisplayValue = ''; // not used
@@ -740,7 +747,7 @@ HTML
);
}
$oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sTargetClass, false));
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-blocklist--medallion');
$oClassIcon->SetDescription($oAttDef->GetDescription())->AddCSSClass('ibo-block-list--medallion');
$oPage->AddUiBlock($oClassIcon);
$oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false);
$oBlock->Display($oPage, 'rel_'.$sAttCode, $aParams);
@@ -797,7 +804,7 @@ HTML
foreach($aNotificationClasses as $sNotifClass) {
$oClassIcon = new MedallionIcon(MetaModel::GetClassIcon($sNotifClass, false));
$oClassIcon->SetDescription(MetaModel::GetName($sNotifClass))->AddCSSClass('ibo-blocklist--medallion');
$oClassIcon->SetDescription(MetaModel::GetName($sNotifClass))->AddCSSClass('ibo-block-list--medallion');
$oPage->AddUiBlock($oClassIcon);
$oBlock = new DisplayBlock($aNotifSearches[$sNotifClass], 'list', false);
@@ -917,7 +924,7 @@ HTML
$sTip .= "<div class='synchro-source-description'>$sDescription</div>";
}
$sTip = utils::HtmlEntities($sTip);
$sSynchroIcon = '<img id="synchro_'.$sInputId.'" src="../images/transp-lock.png" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true" />';
$sSynchroIcon = '<div id="synchro_'.$sInputId.'" class="ibo-field--comments--synchro ibo-pill ibo-is-frozen" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true"><i class="fas fa-lock"></i></div>';
$sComments = $sSynchroIcon;
}
@@ -1036,9 +1043,32 @@ HTML
*/
public function DisplayDetails(WebPage $oPage, $bEditMode = false, $sMode = self::ENUM_OBJECT_MODE_VIEW)
{
// N°3786: As this can now be call recursively from the self::ReloadAndDisplay(), we need to make sure we don't fall into an infinite loop
static $bBlockReentrance = false;
$sClass = get_class($this);
$iKey = $this->GetKey();
if ($sMode === static::ENUM_OBJECT_MODE_VIEW)
{
// The concurrent access lock makes sense only for already existing objects
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
if ($LockEnabled)
{
$aLockInfo = iTopOwnershipLock::IsLocked($sClass, $iKey);
if ($aLockInfo['locked'] === true && $aLockInfo['owner']->GetKey() == UserRights::GetUserId() && $bBlockReentrance === false)
{
// If the object is locked by the current user, it's worth trying again, since
// the lock may be released by 'onunload' which is called AFTER loading the current page.
//$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId();
$bBlockReentrance = true;
self::ReloadAndDisplay($oPage, $this, array('operation' => 'details'));
return;
}
}
}
// Object's details
$oObjectDetails = ObjectFactory::MakeDetails($this);
@@ -1123,25 +1153,11 @@ HTML
*/
public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
{
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null;
$bViewLink = true;
$sSelectMode = 'none';
$iListId = $sTableId;
$sClassAlias = $oSet->GetClassAlias();
$sClassName = $oSet->GetClass();
$sZListName = 'list';
$aClassAliases = array($sClassAlias => $sClassName);
$aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
$oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId);
$oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList));
$oSettings->iDefaultPageSize = 0;
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink,
$aExtraParams);
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : utils::GetUniqueId();;
$aExtraParams['view_link'] = true;
$aExtraParams['select_mode'] = 'none';
return DataTableUIBlockFactory::MakeForObject($oPage, $sTableId, $oSet, $aExtraParams);
}
/**
@@ -2228,6 +2244,7 @@ EOF
// - Final config
$sConfigJS = json_encode($aConfig);
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
$oPage->add_ready_script(
@@ -2403,6 +2420,7 @@ HTML;
case 'CustomFields':
$sHTMLValue .= '<div id="'.$iId.'_console_form">';
$sHTMLValue .= '<div id="'.$iId.'_field_set">';
$sHTMLValue .= '</div></div>';
$sHTMLValue .= '<div>'.$sReloadSpan.'</div>'; // No validation span for this one: it does handle its own validation!
$sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" value=\"\"/>\n";
@@ -2533,14 +2551,13 @@ EOF
$sDisplayValueForHtml = utils::EscapeHtml($sDisplayValue);
// Adding tooltip so we can read the whole value when its very long (eg. URL)
$sTip = '';
$sTip = '';
if (!empty($sDisplayValue)) {
$sTip = 'data-tooltip-content="'.$sDisplayValueForHtml.'"';
$oPage->add_ready_script(
<<<EOF
$oPage->add_ready_script(<<<JS
$('#{$iId}').on('keyup', function(evt, sFormId){
var sVal = $('#{$iId}').val();
var oTippy = this._tippy;
let sVal = $('#{$iId}').val();
const oTippy = this._tippy;
if(sVal === '')
{
@@ -2553,7 +2570,7 @@ EOF
}
oTippy.setContent(sVal);
});
EOF
JS
);
}
@@ -2766,7 +2783,7 @@ JS
case UR_ALLOWED_YES:
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_NEUTRAL);
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton);
break;
@@ -2825,11 +2842,10 @@ EOF
);
if (isset($aExtraParams['nbBulkObj'])) {
$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$sTitle = Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', $aExtraParams['nbBulkObj'], $sClass, $aExtraParams['nbBulkObj']);
$oTitle = TitleUIBlockFactory::MakeForPageWithIcon($sTitle, $sClassIcon, Title::DEFAULT_ICON_COVER_METHOD, false);
$oObjectDetails = PanelUIBlockFactory::MakeForClass(get_class($this), '');
$oObjectDetails->SetTitleBlock($oTitle);
$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$oObjectDetails = PanelUIBlockFactory::MakeForClass($sClass, $sTitle);
$oObjectDetails->SetIcon($sClassIcon);
$oToolbarButtons->AddCSSClass('ibo-toolbar--button');
} else {
$oObjectDetails = ObjectFactory::MakeDetails($this, $sMode);
@@ -2886,7 +2902,7 @@ EOF
$oPage->add($oAppContext->GetForForm());
// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=cancel&'.$oAppContext->GetForLink();
$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_form&class='.$sClass.'&'.$oAppContext->GetForLink();
$oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').on('click', function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)} );");
$iFieldsCount = count($aFieldsMap);
@@ -2937,6 +2953,93 @@ EOF
}
}
/**
* Select the derived class to create
* @param string $sClass
* @param \WebPage $oP
* @param \ApplicationContext $oAppContext
* @param array $aPossibleClasses
* @param array $aHiddenFields
*
* @return void
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0
*/
public static function DisplaySelectClassToCreate(string $sClass, WebPage $oP, ApplicationContext $oAppContext, array $aPossibleClasses, array $aHiddenFields)
{
$sClassLabel = MetaModel::GetName($sClass);
$sTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);
$oP->set_title($sTitle);
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, $sTitle)
->SetIcon($sClassIconUrl);
$oClassForm = FormUIBlockFactory::MakeStandard();
$oPanel->AddMainBlock($oClassForm);
$oClassForm->AddHtml($oAppContext->GetForForm())
->AddSubBlock(InputUIBlockFactory::MakeForHidden('checkSubclass', '0'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'new'));
foreach ($aHiddenFields as $sKey => $sValue) {
if (is_scalar($sValue)) {
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($sKey, $sValue));
}
}
$aDefaults = utils::ReadParam('default', array(), false, 'raw_data');
foreach ($aDefaults as $key => $value) {
if (is_array($value)) {
foreach ($value as $key2 => $value2) {
if (is_array($value2)) {
foreach ($value2 as $key3 => $value3) {
$sValue = utils::EscapeHtml($value3);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2][$key3]", $sValue));
}
} else {
$sValue = utils::EscapeHtml($value2);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2]", $sValue));
}
}
} else {
$sValue = utils::EscapeHtml($value);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key]", $sValue));
}
}
$oClassForm->AddSubBlock(self::DisplayBlockSelectClassToCreate($sClass, $sClassLabel, $aPossibleClasses));
$oP->AddSubBlock($oPanel);
}
/**
* @param string $sClassLabel
* @param array $aPossibleClasses
* @param string $sClass
*
* @return UIContentBlock
* @throws \CoreException
*/
public static function DisplayBlockSelectClassToCreate( string $sClass, string $sClassLabel,array $aPossibleClasses): UIContentBlock
{
$oBlock= UIContentBlockUIBlockFactory::MakeStandard();
$oBlock->AddSubBlock(HtmlFactory::MakeRaw(Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)));
$oSelect = SelectUIBlockFactory::MakeForSelect('class');
$oBlock->AddSubBlock($oSelect);
asort($aPossibleClasses);
foreach ($aPossibleClasses as $sClassName => $sClassLabel) {
$oSelect->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sClassLabel, ($sClassName == $sClass)));
}
$oToolbar = ToolbarUIBlockFactory::MakeForAction();
$oBlock->AddSubBlock($oToolbar);
$oToolbar->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), null, null, true));
return $oBlock;
}
/**
* @param \WebPage $oPage
* @param string $sClass
@@ -3096,56 +3199,54 @@ EOF
// The list of candidate fields is made of the ordered list of "details" attributes + other attributes
$aAttributes = array();
foreach($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode)
{
foreach ($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) {
$aAttributes[$sAttCode] = true;
}
foreach(MetaModel::GetAttributesList($sClass) as $sAttCode)
{
if (!array_key_exists($sAttCode, $aAttributes))
{
foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) {
if (!array_key_exists($sAttCode, $aAttributes)) {
$aAttributes[$sAttCode] = true;
}
}
// Order the fields based on their dependencies, set the fields for which there is only one possible value
// and perform this in the order of dependencies to avoid dead-ends
$aDeps = array();
foreach($aAttributes as $sAttCode => $trash)
{
foreach ($aAttributes as $sAttCode => $trash) {
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
}
$aList = $this->OrderDependentFields($aDeps);
foreach($aList as $sAttCode)
{
$bExistFieldToDisplay = false;
foreach ($aList as $sAttCode) {
// Consider only the "expected" fields for the target state
if (array_key_exists($sAttCode, $aExpectedAttributes))
{
if (array_key_exists($sAttCode, $aExpectedAttributes)) {
$iExpectCode = $aExpectedAttributes[$sAttCode];
// Prompt for an attribute if
// - the attribute must be changed or must be displayed to the user for confirmation
// - or the field is mandatory and currently empty
if (($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) ||
(($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == '')))
{
(($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == ''))) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aArgs = array('this' => $this);
// If the field is mandatory, set it to the only possible value
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY))
{
if ($oAttDef->IsExternalKey())
{
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY)) {
if ($oAttDef->IsExternalKey()) {
/** @var DBObjectSet $oAllowedValues */
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '',
$this->Get($sAttCode));
if ($oAllowedValues->CountWithLimit(2) == 1)
{
if ($oAllowedValues->CountWithLimit(2) == 1) {
$oRemoteObj = $oAllowedValues->Fetch();
$this->Set($sAttCode, $oRemoteObj->GetKey());
}
}
else
} else
{
if ($oAttDef instanceof \AttributeCaseLog) {
// Add JS files for display caselog
// Dummy collapsible section created in order to get JS files
$oCollapsibleSection = new CollapsibleSection('');
foreach ($oCollapsibleSection->GetJsFilesUrlRecursively(true) as $sJSFile) {
$oPage->add_linked_script($sJSFile);
}
}
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (is_array($aAllowedValues) && count($aAllowedValues) == 1)
{
@@ -3180,8 +3281,7 @@ EOF
$bExcludeRawValue = false;
foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude)
{
if (is_a($sAttDefClass, $sAttDefClassToExclude, true))
{
if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) {
$bExcludeRawValue = true;
break;
}
@@ -3191,88 +3291,106 @@ EOF
$aDetails[] = $aAttrib;
$aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex;
$iFieldIndex++;
$bExistFieldToDisplay = true;
}
}
}
$oPage->set_title($sActionLabel);
$oPage->add(<<<HTML
<!-- Beginning of object-transition -->
<div class="object-transition" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode" data-object-current-state="$sCurrentState" data-object-target-state="$sTargetState">
if ($bExistFieldToDisplay) {
$oPage->set_title($sActionLabel);
$oPage->add(<<<HTML
<!-- Beginning of object-transition -->
<div class="object-transition" data-object-class="$sClass" data-object-id="$iKey" data-object-mode="$sMode" data-object-current-state="$sCurrentState" data-object-target-state="$sTargetState">
HTML
);
);
// Page title and subtitles
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetName()));
if (!empty($sActionDetails)) {
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
}
// Page title and subtitles
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetRawName()));
if (!empty($sActionDetails)) {
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
}
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
$oPage->AddUiBlock($oFormContainer);
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
$oFormContainer->AddSubBlock($oForm);
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
$oPage->AddUiBlock($oFormContainer);
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
$oFormContainer->AddSubBlock($oForm);
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
// Note: Remove the table is we want fields to occupy the whole width of the container
$oForm->AddHtml('<table><tr><td>');
$oForm->AddHtml($oPage->GetDetails($aDetails));
$oForm->AddHtml('</td></tr></table>');
// Note: Remove the table if we want fields to occupy the whole width of the container
$sHtml = '<table><tr><td>';
$sHtml .= $oPage->GetDetails($aDetails);
$sHtml .= '</td></tr></table>';
$oAppContext = new ApplicationContext();
$oForm->AddHtml($oAppContext->GetForForm());
$oAppContext = new ApplicationContext();
$sHtml .= $oAppContext->GetForForm();
$oForm->AddHtml($sHtml);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel');
$oCancelButton->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
$oForm->AddSubBlock($oCancelButton);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel');
$oCancelButton->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
$oForm->AddSubBlock($oCancelButton);
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
$oForm->AddSubBlock($oSubmitButton);
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
$oForm->AddSubBlock($oSubmitButton);
$oPage->add(<<<HTML
<!-- End of object-transition -->
</div>
$oPage->add(<<<HTML
<!-- End of object-transition -->
</div>
HTML
);
);
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oPage->add_script(
<<<EOF
// Initializes the object once at the beginning of the page...
var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus');
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
oWizardHelper.SetFieldsCount($iFieldsCount);
$oPage->add_script(
<<<EOF
// Initializes the object once at the beginning of the page...
var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus');
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
oWizardHelper.SetFieldsCount($iFieldsCount);
EOF
);
$sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(
<<<EOF
// Starts the validation when the page is ready
CheckFields('apply_stimulus', false);
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
);
$sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(
<<<EOF
// Starts the validation when the page is ready
CheckFields('apply_stimulus', false);
$(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
EOF
);
);
if ($sOwnershipToken !== null)
{
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
if ($sOwnershipToken !== null) {
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
}
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
} else {
//we can directly apply the stimuli
$bApplyStimulus = $this->ApplyStimulus($sStimulus); // will write the object in the DB
if (!$bApplyStimulus) {
throw new ApplicationException(Dict::S('UI:FailedToApplyStimuli'));
} else {
if ($sOwnershipToken !== null) {
// Release the concurrent lock, if any
iTopOwnershipLock::ReleaseLock($sClass, $iKey, $sOwnershipToken);
}
return true;
}
}
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
return false;
}
public static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet)
@@ -3587,7 +3705,7 @@ HTML;
* @api
* @overwritable-hook
*
* @param $sFinalClass string The actual class of the objects for which to display the menu
* @param string $sFinalClass The actual class of the objects for which to display the menu
*
* @return array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the
* actions menu
@@ -4121,6 +4239,25 @@ HTML;
/**
* Updates the object from a given page argument
*
* The values are read from parameters (GET or POST, using {@see utils::ReadParam()}).
*
* To pass the arg, either add in HTML :
*
* ```html
* <input type="hidden" name="sArgName[attCode1]" value="...">
* <input type="hidden" name="sArgName[attCode2]" value="...">
* ```
*
* Or directly in the URL :
*
* ```php
* $aObjectArgs = ['attCode1' => ..., 'attCode2' => ...];
* $sQueryString = http_build_query(['sArgName' => $aObjectArgs]);
* ```
*
* @uses utils::ReadParam()
* @uses self::UpdateObjectFromArray
*/
public function UpdateObjectFromArg($sArgName, $aAttList = null, $aAttFlags = array())
{
@@ -4750,8 +4887,8 @@ HTML
$sTip = Dict::S('UI:Component:Field:BulkModify:UnknownValues:Tooltip');
$oDummyObj->Set($sAttCode, null);
$aComments[$sAttCode] = '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'"> ? </div>';
$aComments[$sAttCode] = '<div class="multi_values ibo-field--enable-bulk ibo-pill ibo-is-failure" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'">?';
$aComments[$sAttCode] .= '<input type="checkbox" class="ibo-field--enable-bulk--checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/></div>';
$sReadyScript .= 'ToggleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n";
} else {
$iCount = count($aValues[$sAttCode]);
@@ -4760,13 +4897,13 @@ HTML
reset($aValues[$sAttCode]);
$aKeys = array_keys($aValues[$sAttCode]);
$currValue = $aKeys[0]; // The only value is the first key
//echo "<p>current value for $sAttCode : $currValue</p>";
$oDummyObj->Set($sAttCode, $currValue);
$aComments[$sAttCode] = '';
$sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
$aComments[$sAttCode] .= '<input type="checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$sValueCheckbox .= '<input type="checkbox" class="ibo-field--enable-bulk--checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
}
$aComments[$sAttCode] .= '<div class="mono_value">1</div>';
$aComments[$sAttCode] .= '<div class="mono_value ibo-field--enable-bulk ibo-pill ibo-is-success">1'.$sValueCheckbox.'</div>';
} else {
// Non-homogeneous value
$aMultiValues = $aValues[$sAttCode];
@@ -4812,10 +4949,12 @@ HTML
$oDummyObj->Set($sAttCode, null);
}
$aComments[$sAttCode] = '';
$sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
$aComments[$sAttCode] .= '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
$sValueCheckbox = '<input type="checkbox" class="ibo-field--enable-bulk--checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>';
}
$aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true">'.$iCount.'</div>';
$aComments[$sAttCode] .= '<div class="multi_values ibo-field--enable-bulk ibo-pill ibo-is-failure" id="multi_values_'.$sAttCode.'" data-tooltip-content="'.$sTip.'" data-tooltip-html-enabled="true">'.$iCount.$sValueCheckbox.'</div>';
}
$sReadyScript .= 'ToggleField('.(($iCount == 1) ? 'true' : 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n";
}
@@ -4835,10 +4974,6 @@ HTML
//$oStateAtt = MetaModel::GetAttributeDef($sClass, $sStateAttCode);
//$oDummyObj->Set($sStateAttCode, $oStateAtt->GetDefaultValue());
}
/*$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$sTitle = Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', $iAllowedCount, $sClass, $iAllowedCount);
$oTitle = TitleUIBlockFactory::MakeForPageWithIcon($sTitle, $sClassIcon, Title::DEFAULT_ICON_COVER_METHOD, false);
$oP->AddSubBlock($oTitle);*/
$oP->add("<div class=\"wizContainer\">\n");
$sDisableFields = json_encode($aExcludeAttributes);
@@ -4910,7 +5045,6 @@ EOF
$sHeaderTitle = Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), MetaModel::GetName($sClass));
$sClassIcon = MetaModel::GetClassIcon($sClass, false);
$oTitle = TitleUIBlockFactory::MakeForPageWithIcon($sHeaderTitle, $sClassIcon, Title::DEFAULT_ICON_COVER_METHOD, false);
$oP->set_title(Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), $sClass));
@@ -4953,6 +5087,8 @@ EOF
$oTable->AddOption("bFullscreen", true);
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, '');
$oPanel->SetIcon($sClassIcon);
$oPanel->SetTitle($sHeaderTitle);
$oPanel->AddCSSClass('ibo-datatable-panel');
$oPanel->AddSubBlock($oTable);
@@ -4963,7 +5099,6 @@ EOF
$oForm = FormUIBlockFactory::MakeStandard('')->SetAction($sFormAction);
$oP->AddSubBlock($oForm);
$oForm->AddSubBlock($oPanel);
$oPanel->SetTitleBlock($oTitle);
$oAppContext = new ApplicationContext();
$oP->add($oAppContext->GetForForm());
@@ -4994,7 +5129,6 @@ EOF
}
}
} else {
$oP->AddUiBlock($oTitle);
$oP->AddUiBlock($oPanel);
$oP->AddSubBlock(ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:Button:Done')))->SetOnClickJsCode("window.location.href='$sCancelUrl'")->AddCSSClass('mt-5');
}
@@ -5112,19 +5246,21 @@ EOF
}
$iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects);
$sImpactedTableTitle = '';
$sImpactedTableSubtitle = '';
if ($iImpactedIndirectly > 0)
{
if (count($aObjects) == 1)
{
$oObj = $aObjects[0];
$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly,
$oObj->GetName()));
$sImpactedTableTitle = Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly,
$oObj->GetName());
}
else
{
$oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iImpactedIndirectly));
$sImpactedTableTitle = Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iImpactedIndirectly);
}
$oP->p(Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity'));
$sImpactedTableSubtitle = Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity');
}
if (($iImpactedIndirectly > 0) || $oDeletionPlan->FoundStopper())
@@ -5136,7 +5272,11 @@ EOF
'label' => 'Consequence',
'description' => Dict::S('UI:Delete:Consequence+'),
);
$oP->AddSubBlock(DataTableUIBlockFactory::MakeForForm(preg_replace('/[^a-zA-Z0-9_-]/', '', uniqid('form_', true)), $aDisplayConfig, $aDisplayData));
$oBlock = PanelUIBlockFactory::MakeNeutral($sImpactedTableTitle, $sImpactedTableSubtitle);
$oDataTable = DataTableUIBlockFactory::MakeForForm(utils::Sanitize(uniqid('form_', true), '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER), $aDisplayConfig, $aDisplayData);
$oBlock->AddSubBlock($oDataTable);
$oP->AddUiBlock($oBlock);
}
if ($oDeletionPlan->FoundStopper()) {
@@ -5167,7 +5307,6 @@ EOF
$sSubtitle = Dict::Format('UI:Delect:Confirm_Count_ObjectsOf_Class', count($aObjects),
MetaModel::GetName($sClass));
}
$oP->AddUiBlock(TitleUIBlockFactory::MakeStandard(new Html($sSubtitle)));
foreach ($aObjects as $oObj) {
$aKeys[] = $oObj->GetKey();
@@ -5177,7 +5316,14 @@ EOF
$oSet = new CMDBobjectSet($oFilter);
$oDisplaySet = UIContentBlockUIBlockFactory::MakeStandard("0");
$oP->AddSubBlock($oDisplaySet);
$oDisplaySet->AddSubBlock(CMDBAbstractObject::GetDisplaySetBlock($oP, $oSet, array('display_limit' => false, 'menu' => false)));
$oDisplaySet->AddSubBlock(CMDBAbstractObject::GetDisplaySetBlock($oP, $oSet, array(
'display_limit' => false,
'menu' => false,
'surround_with_panel' => true,
'panel_title' => $sSubtitle,
'panel_icon' => MetaModel::GetClassIcon($sClass, false),
'panel_class' => $sClass,
)));
$oForm = FormUIBlockFactory::MakeStandard('');
$oP->AddSubBlock($oForm);
@@ -5276,18 +5422,16 @@ EOF
$sSubtitle = Dict::Format('UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class', count($aObjects),
MetaModel::GetName($sClass));
}
$oP->AddUiBlock(TitleUIBlockFactory::MakeForPage($sSubtitle));
$aDisplayConfig = array();
$aDisplayConfig['class'] = array('label' => 'Class', 'description' => '');
$aDisplayConfig['object'] = array('label' => 'Object', 'description' => '');
$aDisplayConfig['consequence'] = array('label' => 'Done', 'description' => Dict::S('UI:Delete:Done+'));
$oResultsPanel = PanelUIBlockFactory::MakeForInformation('');
$oResultsPanel = PanelUIBlockFactory::MakeForInformation($sSubtitle);
$oP->AddUiBlock($oResultsPanel);
$oResultsPanel->AddSubBlock(
DataTableUIBlockFactory::MakeForStaticData('', $aDisplayConfig, $aDisplayData)
);
$oDatatable = DataTableUIBlockFactory::MakeForStaticData('', $aDisplayConfig, $aDisplayData);
$oResultsPanel->AddSubBlock($oDatatable);
}
}
}
@@ -5343,6 +5487,20 @@ EOF
$sJSOk = json_encode(Dict::S('UI:Button:Ok'));
$oPage->add_ready_script(
<<<JS
// Prepare reusable modal
const oOwnershipLockModal = $('<div></div>').dialog({
title: $sJSTitle,
modal: true,
autoOpen: false,
minWidth: 600,
buttons:[{
text: $sJSOk,
class: 'ibo-is-alternative',
click: function() { $(this).dialog('close'); }
}],
close: function() { $(this).dialog('close'); }
});
// Start periodic handler
let hOwnershipLockHandlerInterval = window.setInterval(function() {
if (window.bInSubmit || window.bInCancel) return;
@@ -5352,18 +5510,8 @@ EOF
if ($('.lock_owned').length == 0)
{
$('.ui-layout-content').prepend('<div class="header_message message_error lock_owned">'+data.message+'</div>');
$('<div>'+data.popup_message+'</div>').dialog({
title: $sJSTitle,
modal: true,
autoOpen: true,
minWidth: 600,
buttons:[{
text: {$sJSOk},
class: 'ibo-is-alternative',
click: function() { $(this).dialog('close'); }
}],
close: function() { $(this).remove(); }
});
oOwnershipLockModal.text(data.popup_message);
oOwnershipLockModal.dialog('open');
}
$('.object-details form .ibo-toolbar .ibo-button:not([name="cancel"])').prop('disabled', true);
clearInterval(hOwnershipLockHandlerInterval);
@@ -5373,18 +5521,8 @@ EOF
if ($('.lock_owned').length == 0)
{
$('.ui-layout-content').prepend('<div class="header_message message_error lock_owned">'+data.message+'</div>');
$('<div>'+data.popup_message+'</div>').dialog({
title: $sJSTitle,
modal: true,
autoOpen: true,
minWidth: 600,
buttons:[{
text: $sJSOk,
class: 'ibo-is-alternative',
click: function() { $(this).dialog('close'); }
}],
close: function() { $(this).remove(); }
});
oOwnershipLockModal.text(data.popup_message);
oOwnershipLockModal.dialog('open');
}
$('.object-details form .ibo-toolbar .ibo-button:not([name="cancel"])').prop('disabled', true);
clearInterval(hOwnershipLockHandlerInterval);

View File

@@ -543,7 +543,7 @@ EOF
if ($bFromDasboardPage) {
$sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle));
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\">{$sTitleForHTML}</div>";
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\" title=\"{$sTitleForHTML}\">{$sTitleForHTML}</div>";
if ($oPage instanceof iTopWebPage) {
$oTopBar = $oPage->GetTopBarLayout();
$oToolbar = ToolbarUIBlockFactory::MakeStandard();
@@ -552,7 +552,7 @@ EOF
$oToolbar->AddHtml($sHtml);
} else {
$oPage->add_script(<<<JS
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML");
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML").attr("title", "$sTitleForHTML");
JS
);
}

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletContainer;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
@@ -202,6 +203,24 @@ abstract class Dashlet
$this->OnUpdate();
}
/**
* @return array Rel. path to the app. root of the JS files required by the dashlet
* @since 3.0.0
*/
public function GetJSFilesRelPaths(): array
{
return [];
}
/**
* @return array Rel. path to the app. root of the CSS files required by the dashlet
* @since 3.0.0
*/
public function GetCSSFilesRelPaths(): array
{
return [];
}
/**
* @param \WebPage $oPage
* @param bool $bEditMode
@@ -224,6 +243,9 @@ abstract class Dashlet
$oDashletContainer->AddCSSClasses($this->aCSSClasses);
}
$oDashletContainer->AddMultipleJsFilesRelPaths($this->GetJSFilesRelPaths());
$oDashletContainer->AddMultipleCssFilesRelPaths($this->GetCSSFilesRelPaths());
try {
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime') {
$oBlock = $this->Render($oPage, $bEditMode, $aExtraParams);
@@ -604,8 +626,7 @@ class DashletUnknown extends Dashlet
$oDashletContainer = new DashletContainer(null, ['dashlet-content']);
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div><div class="dashlet-ukn-text">'.$sExplainText.'</div>');
return $oDashletContainer;
}
@@ -624,8 +645,7 @@ class DashletUnknown extends Dashlet
$oDashletContainer = new DashletContainer(null, ['dashlet-content']);
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div><div class="dashlet-ukn-text">'.$sExplainText.'</div>');
return $oDashletContainer;
}
@@ -906,18 +926,22 @@ class DashletObjectList extends Dashlet
$sShowMenu = $this->aProperties['menu'] ? '1' : '0';
$oFilter = $this->GetDBSearch($aExtraParams);
$sClass = $oFilter->GetClass();
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle))
->AddCSSClass('ibo-datatable-panel');
//$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle))
// ->AddCSSClass('ibo-datatable-panel');
$oBlock = new DisplayBlock($oFilter, 'list');
$aParams = array(
'menu' => $sShowMenu,
'table_id' => self::APPUSERPREFERENCES_PREFIX.$this->sId,
'surround_with_panel' => false,
'surround_with_panel' => true,
'max_height' => '500px',
"panel_title" => Dict::S($sTitle),
"panel_class" => $sClass,
);
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
//$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
return $oPanel;
}
@@ -984,6 +1008,7 @@ HTML;
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $this->aProperties['query']);
$oField->SetMandatory();
$oField->AddCSSClass("ibo-queryoql");
$oForm->AddField($oField);
$oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
@@ -1020,6 +1045,7 @@ HTML;
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $sOQL);
$oField->SetMandatory();
$oField->AddCSSClass("ibo-queryoql");
$oForm->AddField($oField);
$oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
@@ -1256,15 +1282,21 @@ abstract class DashletGroupBy extends Dashlet
break;
}
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle));
//$oPanel = \Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory::MakeStandard();
//PanelUIBlockFactory::MakeForClass($sClass, Dict::S($sTitle));
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
$oBlock = new DisplayBlock($oFilter, $sType);
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
//$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
$aExtraParams["surround_with_panel"] = true;
$aExtraParams["panel_title"] = Dict::S($sTitle);
$aExtraParams["panel_class"] = $sClass;
$oPanel = $oBlock->GetDisplay($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
if ($bEditMode) {
$oPanel->AddHtml('<div class="dashlet-blocker"></div>');
$oPanel->AddHtml('<div class="ibo-dashlet-blocker dashlet-blocker"></div>');
}
return $oPanel;
}
@@ -1363,10 +1395,10 @@ abstract class DashletGroupBy extends Dashlet
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $this->aProperties['query']);
$oField->SetMandatory();
$oField->AddCSSClass("ibo-queryoql");
$oForm->AddField($oField);
try
{
try {
// Group by field: build the list of possible values (attribute codes + ...)
$aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
@@ -1620,16 +1652,14 @@ abstract class DashletGroupBy extends Dashlet
$oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL);
$oField->SetMandatory();
$oField->AddCSSClass("ibo-queryoql");
$oForm->AddField($oField);
if (!is_null($sOQL))
{
if (!is_null($sOQL)) {
$oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
$aGroupBy = $this->GetGroupByOptions($sOQL);
$oField->SetAllowedValues($aGroupBy);
}
else
{
} else {
// Creating a form for reading parameters!
$oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
}
@@ -1666,6 +1696,28 @@ class DashletGroupByPie extends DashletGroupBy
);
}
/**
* @inheritDoc
*/
public function GetJSFilesRelPaths(): array
{
return array_merge(
parent::GetJSFilesRelPaths(),
WebResourcesHelper::GetJSFilesRelPathsForC3JS()
);
}
/**
* @inheritDoc
*/
public function GetCSSFilesRelPaths(): array
{
return array_merge(
parent::GetCSSFilesRelPaths(),
WebResourcesHelper::GetCSSFilesRelPathsForC3JS()
);
}
/**
* @inheritdoc
*/
@@ -2173,6 +2225,7 @@ class DashletHeaderDynamic extends Dashlet
$oField = new DesignerLongTextField('query', Dict::S('UI:DashletHeaderDynamic:Prop-Query'), $this->aProperties['query']);
$oField->SetMandatory();
$oField->AddCSSClass("ibo-queryoql");
$oForm->AddField($oField);
try

View File

@@ -10,6 +10,7 @@ use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Pill\PillFactory;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
@@ -171,6 +172,8 @@ class DisplayBlock
/**positive or negative*/
'max_height',
/** string Max. height of the list, if not specified will occupy all the available height no matter the pagination */
'localize_values',
/** param for export.php */
], DataTableUIBlockFactory::GetAllowedParams()),
'list_search' => array_merge([
'update_history',
@@ -189,6 +192,8 @@ class DisplayBlock
/** string */
'open',
/** bool open by default the search */
'submit_on_load',
/** bool submit the search on loading page */
'class', /** class name */
'search_header_force_dropdown', /** Html for <select> to choose the class to search */
'this',
@@ -198,6 +203,8 @@ class DisplayBlock
/** string search root class */
'open',
/** bool open the search panel by default */
'submit_on_load',
/** bool submit the search on loading page */
'result_list_outer_selector',
/** string js selector of the search result display */
'search_header_force_dropdown',
@@ -257,6 +264,14 @@ class DisplayBlock
'withJSRefreshCallBack',
/** true if dashboard page */
'from_dashboard_page',
/** bool true if list may be render in panel block */
'surround_with_panel',
/** string title of panel block */
'panel_title',
/** string class for panel block style */
'panel_class',
/** string class for panel block style */
'panel_icon',
];
if (isset($aAllowedParams[$sStyle])) {
@@ -997,9 +1012,10 @@ JS
$aCountsQueryResults[$aCountGroupBySingleResult[0]] = $aCountGroupBySingleResult[1];
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aValues = $oAttDef->GetAllowedValues();
foreach ($aStates as $sStateValue) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aStateLabels[$sStateValue] = $oAttDef->GetAsPlainText($sStateValue);
$aStateLabels[$sStateValue] = $aValues[$sStateValue];
$aCounts[$sStateValue] = (array_key_exists($sStateValue, $aCountsQueryResults))
? $aCountsQueryResults[$sStateValue]
: 0;
@@ -1026,10 +1042,11 @@ JS
$sHyperlink = $aCount['link'];
$sCountLabel = $aCount['label'];
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
->SetUrl($sHyperlink)
->SetTooltip($sStateLabel)
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span>")
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">$sStateLabel</span>");
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span><span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">$sStateLabel</span>");
if ($sHyperlink != '-') {
$oPill->SetUrl($sHyperlink);
}
$oBlock->AddSubBlock($oPill);
}
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
@@ -1173,11 +1190,23 @@ JS
),
);
$sFormat = isset($aExtraParams['format']) ? $aExtraParams['format'] : 'UI:Pagination:HeaderNoSelection';
$sTitle = Dict::Format($sFormat, $iTotalCount);
$aExtraParams['query_params'] = $this->m_oFilter->GetInternalParams();
$aOption['dom'] = 'pl';
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$sTitle = Dict::Format($sFormat, $iTotalCount);
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
$oBlock->AddSubTitleBlock(new Html($sTitle));
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oBlock->SetIcon($aExtraParams["panel_icon"]);
}
$oDataTable = DataTableUIBlockFactory::MakeForStaticData("", $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
$oBlock->AddSubBlock($oDataTable);
} else {
$sTitle = Dict::Format($sFormat, $iTotalCount);
$oBlock = DataTableUIBlockFactory::MakeForStaticData($sTitle, $aAttribs, $aData, null, $aExtraParams, $this->m_oFilter->ToOQL(), $aOption);
}
} else {
// Simply count the number of elements in the set
@@ -1186,7 +1215,15 @@ JS
if (isset($aExtraParams['format'])) {
$sFormat = $aExtraParams['format'];
}
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oBlock = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oBlock->SetIcon($aExtraParams["panel_icon"]);
}
$oBlock->AddSubBlock(new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>'));
} else {
$oBlock = new Html('<p>'.Dict::Format($sFormat, $iCount).'</p>');
}
}
return $oBlock;
@@ -1273,6 +1310,22 @@ JS
$oBlock->bNotAuthorized = true;
}
} else {
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) {
$oBlock->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' => 'fas fa-search',
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
));
}
}
// The list is made of only 1 class of objects, actions on the list are possible
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
$oBlock->AddSubBlock(cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams));
@@ -1299,25 +1352,19 @@ JS
$oBlock->bCreateNew = true;
}
}
}
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) {
$oBlock->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' => 'fas fa-search',
'breadcrumb_icon_type' => iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES,
));
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
}
}
}
}
return $oBlock;
}
@@ -1506,6 +1553,16 @@ JS
$oBlock->sUrl = $sUrl;
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
}
return $oBlock;
}
@@ -1587,6 +1644,16 @@ JS
$oBlock->sURLForRefresh = str_replace("'", "\'", $sUrl);
break;
}
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
}
return $oBlock;
}
@@ -2158,9 +2225,9 @@ class MenuBlock extends DisplayBlock
// Extract favorite actions from their menus
$aFavoriteRegularActions = [];
$aFavoriteTransitionActions = [];
$aCallSpec = [$sClass, 'GetShortcutActions'];
if (is_callable($aCallSpec)) {
$aShortcutActions = call_user_func($aCallSpec, $sClass);
if (is_callable([$sClass, 'GetShortcutActions'])) {
/** @var cmdbAbstractObject $sClass */
$aShortcutActions = $sClass::GetShortcutActions($sClass);
foreach ($aShortcutActions as $key) {
// Regular actions
if (isset($aRegularActions[$key])) {
@@ -2269,7 +2336,7 @@ class MenuBlock extends DisplayBlock
// - Refresh
if ($sRefreshAction != '') {
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
$oActionButton->SetIconClass('fas fa-sync')
$oActionButton->SetIconClass('fas fa-sync-alt')
->SetOnClickJsCode($sRefreshAction)
->SetTooltip(Dict::S('UI:Button:Refresh'))
->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);

View File

@@ -102,12 +102,20 @@ class DesignerForm
$sReturn .= '<fieldset>';
$sReturn .= '<legend>'.$sLabel.'</legend>';
}
/** @var \DesignerFormField $oField */
foreach($aFields as $oField) {
$aRow = $oField->Render($oP, $sFormId);
if ($oField->IsVisible()) {
$sValidation = '<span class="prop_apply ibo-prop--apply">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
$sValidation = '<span class="prop_apply ibo-prop--apply ibo-button ibo-is-alternative">'.$this->GetValidationArea($oField->GetFieldId()).'</span>';
$sField = $aRow['value'].$sValidation;
$aDetails[] = array('label' => $aRow['label'], 'value' => $sField);
$aDetails[] = array(
'label' => $aRow['label'],
'value' => $sField,
'attcode' => $oField->GetCode(),
'attlabel' => $aRow['label'],
'inputid' => $this->GetFieldId($oField->GetCode()),
'inputtype' => $oField->GetInputType(),
);
} else {
$sHiddenFields .= $aRow['value'];
}
@@ -203,51 +211,41 @@ class DesignerForm
$sActionUrl = addslashes($this->sSubmitTo);
$sJSSubmitParams = json_encode($this->aSubmitParams);
$sFormId = $this->GetFormId();
if ($this->oParentForm == null)
{
if ($this->oParentForm == null) {
$sReturn = '<form id="'.$sFormId.'" onsubmit="return false;">';
$sReturn .= '<table class="prop_table">';
$sReturn .= '<thead><tr><th class="prop_header">'.Dict::S('UI:Form:Property').'</th><th class="prop_header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="prop_header">&nbsp;</th></tr></thead><tbody>';
$sReturn .= '<thead><tr><th class="ibo-prop-header">'.Dict::S('UI:Form:Property').'</th><th class="ibo-prop-header">'.Dict::S('UI:Form:Value').'</th><th colspan="2" class="ibo-prop-header">&nbsp;</th></tr></thead><tbody>';
}
$sHiddenFields = '';
foreach($this->aFieldSets as $sLabel => $aFields)
{
foreach ($this->aFieldSets as $sLabel => $aFields) {
$aDetails = array();
if ($sLabel != '')
{
if ($sLabel != '') {
$sReturn .= $this->StartRow().'<th colspan="4">'.$sLabel.'</th>'.$this->EndRow();
}
foreach($aFields as $oField)
{
foreach ($aFields as $oField) {
$aRow = $oField->Render($oP, $sFormId, 'property');
if ($oField->IsVisible())
{
if ($oField->IsVisible()) {
$sFieldId = $this->GetFieldId($oField->GetCode());
$sValidation = $this->GetValidationArea($sFieldId, '<span data-tooltip-content="Apply"><i class="fas fa-check"></i></span>');
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply">'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span data-tooltip-content="Revert"><i class="fas fa-times"></i></span></td>'.$this->EndRow();
$sPath = $this->GetHierarchyPath().'/'.$oField->GetCode();
if (is_null($aRow['label']))
{
$sValidation = $this->GetValidationArea($sFieldId, '<div class="ibo-button ibo-is-alternative ibo-is-success" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></div>');
$sValidationFields = '</td><td class="prop_icon prop_apply ibo-prop--apply" >'.$sValidation.'</td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><div class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-undo"></i></div></span></td>'
.$this->EndRow();
if (is_null($aRow['label'])) {
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value ibo-field--value" colspan="2">'.$aRow['value'];
}
else
{
} else {
$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label ibo-field--label">'.$aRow['label'].'</td><td class="prop_value ibo-field--value">'.$aRow['value'];
}
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField))
{
if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField)) {
$sReturn .= $sValidationFields;
}
$sNotifyParentSelectorJS = is_null($sNotifyParentSelector) ? 'null' : "'".addslashes($sNotifyParentSelector)."'";
$sAutoApply = $oField->IsAutoApply() ? 'true' : 'false';
$sHandlerEquals = $oField->GetHandlerEquals();
$sHandlerGetValue = $oField->GetHandlerGetValue();
$sWidgetClass = $oField->GetWidgetClass();
$sJSExtraParams = '';
if (count($oField->GetWidgetExtraParams()) > 0)
@@ -717,6 +715,19 @@ class DesignerFormField
$this->aWidgetExtraParams = array();
}
/**
* Important, for now we use constants from the \cmdbAbstractObject class, introducing a coupling that should not exist.
* This has been traced under N°4241 and will be discussed during the next modernization batch.
*
* @return string|null Return the input type of the field
* @see \cmdbAbstractObject::ENUM_INPUT_TYPE_XXX
* @since 3.0.0
*/
public function GetInputType(): ?string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_SINGLE_INPUT;
}
/**
* @return string
*/
@@ -1057,6 +1068,14 @@ class DesignerLongTextField extends DesignerTextField
$this->aCSSClasses[] = 'ibo-input-text';
}
/**
* @inheritDoc
*/
public function GetInputType(): string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_TEXTAREA;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
$sId = $this->oForm->GetFieldId($this->sCode);
@@ -1185,9 +1204,27 @@ class DesignerComboField extends DesignerFormField
$this->bAutoApply = true;
$this->bSorted = true; // Sorted by default
}
public function SetAllowedValues($aAllowedValues)
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
if ($this->bMultipleSelection) {
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_MULTIPLE_CHOICES;
}
else {
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_RAW;
}
}
public function SetAllowedValues(?array $aAllowedValues)
{
// Make sure to have an actual array for values
if (is_null($aAllowedValues)) {
$aAllowedValues = [];
}
$this->aAllowedValues = $aAllowedValues;
}
@@ -1264,11 +1301,11 @@ class DesignerComboField extends DesignerFormField
{
if ($this->bMultipleSelection)
{
$sHtml = "<select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
$sHtml = "<span><select $sCSSClasses multiple size=\"8\"id=\"$sId\" name=\"$sName\">";
}
else
{
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\">";
$sHtml = "<span class=\"ibo-input-select-wrapper\"><select $sCSSClasses id=\"$sId\" name=\"$sName\">";
if ($this->sNullLabel != '')
{
$sHtml .= "<option value=\"\">".$this->sNullLabel."</option>";
@@ -1288,7 +1325,7 @@ class DesignerComboField extends DesignerFormField
$sHtmlValue = str_replace(' ', '&nbsp;', htmlentities($sDisplayValue, ENT_QUOTES, 'UTF-8'));
$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>$sHtmlValue</option>";
}
$sHtml .= "</select>";
$sHtml .= "</select></span>";
if ($this->bOtherChoices)
{
$sHtml .= '<br/><input type="checkbox" id="other_chk_'.$sId.'"><label for="other_chk_'.$sId.'">&nbsp;Other:</label>&nbsp;<input type="text" id="other_'.$sId.'" name="other_'.$sName.'" size="30"/>';
@@ -1321,6 +1358,14 @@ class DesignerBooleanField extends DesignerFormField
$this->bAutoApply = true;
$this->aCSSClasses[] = 'ibo-input-checkbox';
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_CHECKBOX;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
@@ -1380,6 +1425,14 @@ class DesignerHiddenField extends DesignerFormField
{
parent::__construct($sCode, $sLabel, $defaultValue);
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function IsVisible()
{
@@ -1407,6 +1460,14 @@ class DesignerIconSelectionField extends DesignerFormField
$this->bAutoApply = true;
$this->sUploadUrl = null;
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return cmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
}
public function SetAllowedValues($aAllowedValues)
{
@@ -1423,28 +1484,32 @@ class DesignerIconSelectionField extends DesignerFormField
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
$idx = 0;
foreach($this->aAllowedValues as $index => $aValue)
{
if ($aValue['value'] == $this->defaultValue)
{
$idxFallback = 0;
foreach ($this->aAllowedValues as $index => $aValue) {
if ($aValue['value'] == $this->defaultValue) {
$idx = $index;
break;
}
//fallback if url of default value contains ../
//for contact, icon is http://localhost/env-production/itop-structure/../../images/icons/icons8-customer.svg => not found http://localhost/images/icons/icons8-customer.svg
if (basename($aValue['value']) == basename($this->defaultValue)) {
$idxFallback = $index;
}
}
if ($idx == 0) {
$idx = $idxFallback;
}
$sJSItems = json_encode($this->aAllowedValues);
$sPostUploadTo = ($this->sUploadUrl == null) ? 'null' : "'{$this->sUploadUrl}'";
if (!$this->IsReadOnly())
{
if (!$this->IsReadOnly()) {
$sDefaultValue = ($this->defaultValue !== '') ? $this->defaultValue : $this->aAllowedValues[$idx]['value'];
$sValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/>";
$sValue = "<span class=\"ibo-input-select-wrapper\"><input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"{$sDefaultValue}\"/></span>";
$oP->add_ready_script(
<<<EOF
<<<EOF
$('#$sId').icon_select({current_idx: $idx, items: $sJSItems, post_upload_to: $sPostUploadTo});
EOF
);
}
else
{
} else {
$sValue = '<span style="display:inline-block;line-height:48px;height:48px;"><span><img style="vertical-align:middle" src="'.$this->aAllowedValues[$idx]['icon'].'" />&nbsp;'.htmlentities($this->aAllowedValues[$idx]['label'], ENT_QUOTES, 'UTF-8').'</span></span>';
}
$sReadOnly = $this->IsReadOnly() ? 'disabled' : '';
@@ -1459,18 +1524,21 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
public function __construct($sCode, $sLabel = '', $defaultValue = '')
{
parent::__construct($sCode, $sLabel, $defaultValue);
$aFolderList = [
APPROOT.'env-'.utils::GetCurrentEnvironment() => utils::GetAbsoluteUrlModulesRoot(),
APPROOT.'images/icons' => utils::GetAbsoluteUrlAppRoot().'images/icons',
];
if (count(self::$aAllIcons) == 0) {
foreach ($aFolderList as $sFolderPath => $sUrlPrefix) {
$aIcons = self::FindIconsOnDisk($sFolderPath);
ksort($aIcons);
if (count(self::$aAllIcons) == 0)
{
self::$aAllIcons = self::FindIconsOnDisk(APPROOT.'env-'.utils::GetCurrentEnvironment());
ksort(self::$aAllIcons);
foreach ($aIcons as $sFilePath) {
self::$aAllIcons[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => $sUrlPrefix.$sFilePath);
}
}
}
$aValues = array();
foreach(self::$aAllIcons as $sFilePath)
{
$aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
}
$this->SetAllowedValues($aValues);
$this->SetAllowedValues(self::$aAllIcons);
}
static protected function FindIconsOnDisk($sBaseDir, $sDir = '')
@@ -1501,26 +1569,29 @@ class RunTimeIconSelectionField extends DesignerIconSelectionField
SetupUtils::builddir(dirname($sCacheFile));
file_put_contents($sCacheFile, $sAvailableIcons, LOCK_EX);
}
return $aFiles;
}
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '')
static protected function _FindIconsOnDisk($sBaseDir, $sDir = '', &$aFilesSpecs = [])
{
$aResult = array();
$aResult = [];
// Populate automatically the list of icon files
if ($hDir = @opendir($sBaseDir.'/'.$sDir))
{
while (($sFile = readdir($hDir)) !== false)
{
if ($hDir = @opendir($sBaseDir.'/'.$sDir)) {
while (($sFile = readdir($hDir)) !== false) {
$aMatches = array();
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
{
if (($sFile != '.') && ($sFile != '..') && ($sFile != 'lifecycle') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile)) {
$sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath));
$aResult = array_merge($aResult, self::_FindIconsOnDisk($sBaseDir, $sDirSubPath, $aFilesSpecs));
}
if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
$sSize = filesize($sBaseDir.'/'.$sDir.'/'.$sFile);
if (isset($aFilesSpecs[$sFile]) && $aFilesSpecs[$sFile] == $sSize) {
continue;
}
if (preg_match("/\.(png|jpg|jpeg|gif|svg)$/i", $sFile, $aMatches)) // png, jp(e)g, gif and svg are considered valid
{
$aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
$aFilesSpecs[$sFile] = $sSize;
}
}
closedir($hDir);
@@ -1566,6 +1637,14 @@ class DesignerSortableField extends DesignerFormField
parent::__construct($sCode, $sLabel, $defaultValue);
$this->aAllowedValues = array();
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function SetAllowedValues($aAllowedValues)
{
@@ -1605,6 +1684,14 @@ class DesignerFormSelectorField extends DesignerFormField
$this->aCSSClasses[] = 'ibo-input-select';
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function IsSorted()
{
return $this->bSorted;
@@ -1645,27 +1732,23 @@ class DesignerFormSelectorField extends DesignerFormField
{
$sId = $this->oForm->GetFieldId($this->sCode);
$sName = $this->oForm->GetFieldName($this->sCode);
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
$this->aCSSClasses[] = 'formSelector';
$sCSSClasses = '';
if (count($this->aCSSClasses) > 0)
{
if (count($this->aCSSClasses) > 0) {
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
}
if ($this->IsSorted())
{
if ($this->IsSorted()) {
uasort($this->aSubForms, array(get_class($this), 'SortOnFormLabel'));
}
if ($this->IsReadOnly())
{
if ($this->IsReadOnly()) {
$sDisplayValue = '';
$sHiddenValue = '';
foreach($this->aSubForms as $iKey => $aFormData)
{
foreach ($this->aSubForms as $iKey => $aFormData) {
if ($iKey == $this->defaultValue) // Default value is actually the index
{
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
@@ -1674,35 +1757,29 @@ class DesignerFormSelectorField extends DesignerFormField
}
}
$sHtml = "<span $sCSSClasses>".$sDisplayValue.$sHiddenValue."</span>";
}
else
{
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
foreach($this->aSubForms as $iKey => $aFormData)
{
} else {
$sHtml = "<span class=\"ibo-input-select-wrapper\"><select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
foreach ($this->aSubForms as $iKey => $aFormData) {
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
$sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
$sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
}
$sHtml .= "</select>";
$sHtml .= "</select></span>";
}
if ($sRenderMode == 'property')
{
$sHtml .= '</td><td class="prop_icon prop_apply"><span title="Apply" class="ui-icon ui-icon-circle-check"/></td><td class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
if ($sRenderMode == 'property') {
$sHtml .= '</td><td class="prop_icon prop_apply ibo-prop--apply"><span><button class="ibo-button ibo-is-alternative ibo-is-success" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Apply').'"><i class="fas fa-check"></i></button></span></td><td class="prop_icon prop_cancel ibo-prop--cancel"><span><button class="ibo-button ibo-is-alternative ibo-is-neutral" data-tooltip-content="'.Dict::Format('UI:DashboardEdit:Revert').'"><i class="fas fa-times"></i></button></span></td></tr>';
}
foreach($this->aSubForms as $sKey => $aFormData)
{
foreach ($this->aSubForms as $sKey => $aFormData) {
$sId = $this->oForm->GetFieldId($this->sCode);
$sStyle = (($sKey == $this->defaultValue) && $this->oForm->IsDisplayed()) ? '' : 'style="display:none"';
$oSubForm = $aFormData['form'];
$oSubForm->SetParentForm($this->oForm);
$oSubForm->CopySubmitParams($this->oForm);
$oSubForm->SetPrefix($this->oForm->GetPrefix().$sKey.'_');
if ($sRenderMode == 'property')
{
if ($sRenderMode == 'property') {
// Note: Managing the visibility of nested subforms had several implications
// 1) Attributes are displayed in a table and we have to group them in as many tbodys as necessary to hide/show the various options depending on the current selection
// 2) It is not possible to nest tbody tags. Therefore, it is not possible to manage the visibility the same way as it is done for the dialog mode (using nested divs).
@@ -1800,6 +1877,14 @@ class DesignerSubFormField extends DesignerFormField
parent::__construct('', $sLabel, '');
$this->oSubForm = $oSubForm;
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
@@ -1844,6 +1929,14 @@ class DesignerStaticTextField extends DesignerFormField
parent::__construct($sCode, $sLabel, $defaultValue);
}
/**
* @inheritDoc
*/
public function GetInputType(): ?string
{
return null;
}
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
{
return array('label' => $this->sLabel, 'value' => $this->defaultValue);

View File

@@ -1,4 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginBasic
*
@@ -20,19 +23,19 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnModeDetection(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']))
if (!Session::IsSet('login_mode'))
{
if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION']))
{
$_SESSION['login_mode'] = 'basic';
Session::Set('login_mode', 'basic');
}
elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
{
$_SESSION['login_mode'] = 'basic';
Session::Set('login_mode', 'basic');
}
elseif (isset($_SERVER['PHP_AUTH_USER']))
{
$_SESSION['login_mode'] = 'basic';
Session::Set('login_mode', 'basic');
}
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -40,10 +43,10 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) || $_SESSION['login_mode'] == 'basic')
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'basic')
{
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
$_SESSION['login_temp_auth_user'] = $sAuthUser;
list($sAuthUser) = $this->GetAuthUserAndPassword();
Session::Set('login_temp_auth_user', $sAuthUser);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -51,10 +54,10 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -65,17 +68,17 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
list($sAuthUser) = $this->GetAuthUserAndPassword();
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
LoginWebPage::HTTP401Error();
}
@@ -84,9 +87,9 @@ class LoginBasic extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'basic')
if (Session::Get('login_mode') == 'basic')
{
$_SESSION['can_logoff'] = true;
Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -4,6 +4,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginDefaultBefore
*/
@@ -23,7 +25,7 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
{
$iErrorCode = LoginWebPage::EXIT_CODE_OK;
unset($_SESSION['login_temp_auth_user']);
Session::Unset('login_temp_auth_user');
// Check if proposed login mode is present and allowed
$aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes();
@@ -32,11 +34,11 @@ class LoginDefaultBefore extends AbstractLoginFSMExtension
if ($index !== false)
{
// Force login mode
$_SESSION['login_mode'] = $sProposedLoginMode;
Session::Set('login_mode', $sProposedLoginMode);
}
else
{
unset($_SESSION['login_mode']);
Session::Unset('login_mode');
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -91,7 +93,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnCredentialsOk(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']))
if (!Session::IsSet('login_mode'))
{
// If no plugin validated the user, exit
self::ResetLoginSession();
@@ -110,7 +112,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnConnected(&$iErrorCode)
{
unset($_SESSION['login_temp_auth_user']);
Session::Unset('login_temp_auth_user');
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -118,11 +120,11 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
private static function ResetLoginSession()
{
LoginWebPage::ResetSession();
foreach (array_keys($_SESSION) as $sKey)
foreach (Session::ListVariables() as $sKey)
{
if (utils::StartsWith($sKey, 'login_'))
{
unset($_SESSION[$sKey]);
Session::Unset($sKey);
}
}
}

View File

@@ -1,5 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginExternal
*
@@ -22,12 +24,12 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnModeDetection(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']))
if (!Session::IsSet('login_mode'))
{
$sAuthUser = $this->GetAuthUser();
if ($sAuthUser && (strlen($sAuthUser) > 0))
{
$_SESSION['login_mode'] = 'external';
Session::Set('login_mode', 'external');
}
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -35,10 +37,10 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
$sAuthUser = $this->GetAuthUser();
if (!UserRights::CheckCredentials($sAuthUser, '', $_SESSION['login_mode'], 'external'))
if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -49,19 +51,19 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
$sAuthUser = $this->GetAuthUser();
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
$_SESSION['can_logoff'] = false;
Session::Set('can_logoff', false);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -69,7 +71,7 @@ class LoginExternal extends AbstractLoginFSMExtension
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'external')
if (Session::Get('login_mode') == 'external')
{
LoginWebPage::HTTP401Error();
}

View File

@@ -5,6 +5,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginForm
*
@@ -29,8 +31,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnReadCredentials(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'form'))
{
if (!Session::IsSet('login_mode') || Session::Get('login_mode') == 'form') {
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if ($this->bForceFormOnError || empty($sAuthUser) || empty($sAuthPwd))
@@ -50,9 +51,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$this->bForceFormOnError = false;
exit;
}
$_SESSION['login_temp_auth_user'] = $sAuthUser;
$_SESSION['login_mode'] = 'form';
Session::Set('login_temp_auth_user', $sAuthUser);
Session::Set('login_mode', 'form');
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -62,11 +62,11 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -80,19 +80,19 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
}
else
{
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
}
// Store 'auth_user' in session for further use
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
@@ -102,7 +102,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
$this->bForceFormOnError = true;
}
@@ -114,9 +114,9 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
*/
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'form')
if (Session::Get('login_mode') == 'form')
{
$_SESSION['can_logoff'] = true;
Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -7,6 +7,7 @@
*/
use Combodo\iTop\Application\Branding;
use Combodo\iTop\TwigExtension;
/**
@@ -238,16 +239,9 @@ class LoginTwigRenderer
public function GetDefaultVars()
{
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
}
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$aVars = array(
'sAppRootUrl' => utils::GetAbsoluteUrlAppRoot(),

View File

@@ -1,5 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
/**
* Class LoginURL
*
@@ -26,13 +28,13 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnModeDetection(&$iErrorCode)
{
if (!isset($_SESSION['login_mode']) && !$this->bErrorOccurred)
if (!Session::IsSet('login_mode') && !$this->bErrorOccurred)
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!empty($sAuthUser) && !empty($sAuthPwd))
{
$_SESSION['login_mode'] = 'url';
Session::Set('login_mode', 'url');
}
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
@@ -40,20 +42,20 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnReadCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$_SESSION['login_temp_auth_user'] = utils::ReadParam('auth_user', '', false, 'raw_data');
Session::Set('login_temp_auth_user', utils::ReadParam('auth_user', '', false, 'raw_data'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnCheckCredentials(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, $_SESSION['login_mode'], 'internal'))
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
@@ -64,17 +66,17 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnCredentialsOK(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', $_SESSION['login_mode']);
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
}
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
protected function OnError(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$this->bErrorOccurred = true;
}
@@ -83,9 +85,9 @@ class LoginURL extends AbstractLoginFSMExtension
protected function OnConnected(&$iErrorCode)
{
if ($_SESSION['login_mode'] == 'url')
if (Session::Get('login_mode') == 'url')
{
$_SESSION['can_logoff'] = true;
Session::Set('can_logoff', true);
return LoginWebPage::CheckLoggedUser($iErrorCode);
}
return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -24,6 +24,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\Helper\Session;
/**
* Web page used for displaying the login form
*/
@@ -139,16 +142,9 @@ class LoginWebPage extends NiceWebPage
public function DisplayLoginHeader($bMainAppLogo = false)
{
$sLogo = 'itop-logo-external.png';
$sBrandingLogo = 'login-logo.png';
$sVersionShort = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
$sIconUrl = Utils::GetConfig()->Get('app_icon_url');
$sDisplayIcon = utils::GetAbsoluteUrlAppRoot().'images/'.$sLogo.'?t='.utils::GetCacheBusterTimestamp();
if (file_exists(MODULESROOT.'branding/'.$sBrandingLogo))
{
$sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/'.$sBrandingLogo.'?t='.utils::GetCacheBusterTimestamp();
}
$sDisplayIcon = Branding::GetLoginLogoAbsoluteUrl();
$this->add("<div id=\"login-logo\"><a href=\"".htmlentities($sIconUrl, ENT_QUOTES,
self::PAGES_CHARSET)."\"><img title=\"$sVersionShort\" src=\"$sDisplayIcon\"></a></div>\n");
}
@@ -392,11 +388,11 @@ class LoginWebPage extends NiceWebPage
public static function ResetSession()
{
// Unset all of the session variables.
unset($_SESSION['auth_user']);
unset($_SESSION['login_state']);
unset($_SESSION['can_logoff']);
unset($_SESSION['archive_mode']);
unset($_SESSION['impersonate_user']);
Session::Unset('auth_user');
Session::Unset('login_state');
Session::Unset('can_logoff');
Session::Unset('archive_mode');
Session::Unset('impersonate_user');
UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
@@ -442,11 +438,11 @@ class LoginWebPage extends NiceWebPage
}
$bLoginDebug = MetaModel::GetConfig()->Get('login_debug');
if (!isset($_SESSION['login_state']) || ($_SESSION['login_state'] == self::LOGIN_STATE_ERROR))
if (Session::Get('login_state') == self::LOGIN_STATE_ERROR)
{
$_SESSION['login_state'] = self::LOGIN_STATE_START;
Session::Set('login_state', self::LOGIN_STATE_START);
}
$sLoginState = $_SESSION['login_state'];
$sLoginState = Session::Get('login_state');
$sSessionLog = '';
if ($bLoginDebug)
@@ -500,7 +496,7 @@ class LoginWebPage extends NiceWebPage
// Every plugin has nothing else to do in this state, go forward
$sLoginState = self::AdvanceLoginFSMState($sLoginState);
$_SESSION['login_state'] = $sLoginState;
Session::Set('login_state', $sLoginState);
}
catch (Exception $e)
{
@@ -526,7 +522,7 @@ class LoginWebPage extends NiceWebPage
if ($bFilterWithMode)
{
$sCurrentLoginMode = isset($_SESSION['login_mode']) ? $_SESSION['login_mode'] : '';
$sCurrentLoginMode = Session::Get('login_mode', '');
}
else
{
@@ -665,8 +661,8 @@ class LoginWebPage extends NiceWebPage
$oLog->DBInsertNoReload();
}
$_SESSION['auth_user'] = $sAuthUser;
$_SESSION['login_mode'] = $sLoginMode;
Session::Set('auth_user', $sAuthUser);
Session::Set('login_mode', $sLoginMode);
UserRights::_InitSessionCache();
}
@@ -681,10 +677,10 @@ class LoginWebPage extends NiceWebPage
*/
public static function CheckLoggedUser(&$iErrorCode)
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
// Already authenticated
$bRet = UserRights::Login($_SESSION['auth_user']); // Login & set the user's language
$bRet = UserRights::Login(Session::Get('auth_user')); // Login & set the user's language
if ($bRet)
{
$iErrorCode = self::EXIT_CODE_OK;
@@ -712,11 +708,11 @@ class LoginWebPage extends NiceWebPage
public static function SetLoginModeAndReload($sNewLoginMode)
{
if (isset($_SESSION['login_mode']) && ($_SESSION['login_mode'] == $sNewLoginMode))
if (Session::Get('login_mode') == $sNewLoginMode)
{
return;
}
$_SESSION['login_mode'] = $sNewLoginMode;
Session::Set('login_mode', $sNewLoginMode);
self::HTTPReload();
}
@@ -829,9 +825,9 @@ class LoginWebPage extends NiceWebPage
{
CMDBObject::SetTrackOrigin('custom-extension');
$sInfo = 'External User provisioning';
if (isset($_SESSION['login_mode']))
if (Session::IsSet('login_mode'))
{
$sInfo .= " ({$_SESSION['login_mode']})";
$sInfo .= " (".Session::Get('login_mode').")";
}
CMDBObject::SetTrackInfo($sInfo);
@@ -883,9 +879,9 @@ class LoginWebPage extends NiceWebPage
{
CMDBObject::SetTrackOrigin('custom-extension');
$sInfo = 'External User provisioning';
if (isset($_SESSION['login_mode']))
if (Session::IsSet('login_mode'))
{
$sInfo .= " ({$_SESSION['login_mode']})";
$sInfo .= " (".Session::Get('login_mode').")";
}
CMDBObject::SetTrackInfo($sInfo);
@@ -924,9 +920,9 @@ class LoginWebPage extends NiceWebPage
// Now synchronize the profiles
$sOrigin = 'External User provisioning';
if (isset($_SESSION['login_mode']))
if (Session::IsSet('login_mode'))
{
$sOrigin .= " ({$_SESSION['login_mode']})";
$sOrigin .= " (".Session::Get('login_mode').")";
}
$aExistingProfiles = self::SynchronizeProfiles($oUser, $aProfiles, $sOrigin);
if ($oUser->IsModified())
@@ -1011,7 +1007,6 @@ class LoginWebPage extends NiceWebPage
$sMessage = self::HandleOperations($operation); // May exit directly
$iRet = self::Login($iOnExit);
if ($iRet == self::EXIT_CODE_OK)
{
if ($bMustBeAdmin && !UserRights::IsAdministrator())
@@ -1091,19 +1086,23 @@ class LoginWebPage extends NiceWebPage
}
else if ($operation == 'change_pwd')
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
$sIssue = Session::Get('pwd_issue');
Session::Unset('pwd_issue');
$bFailedLogin = ($sIssue != null); // Force the "failed login" flag to display the "issue" message
UserRights::Login($sAuthUser); // Set the user's language
$oPage = self::NewLoginWebPage();
$oPage->DisplayChangePwdForm();
$oPage->DisplayChangePwdForm($bFailedLogin, $sIssue);
$oPage->output();
exit;
}
}
else if ($operation == 'check_pwd_policy')
{
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
UserRights::Login($sAuthUser); // Set the user's language
$aPwdMap = array();
@@ -1121,9 +1120,9 @@ class LoginWebPage extends NiceWebPage
}
if ($operation == 'do_change_pwd')
{
if (isset($_SESSION['auth_user']))
if (Session::IsSet('auth_user'))
{
$sAuthUser = $_SESSION['auth_user'];
$sAuthUser = Session::Get('auth_user');
UserRights::Login($sAuthUser); // Set the user's language
$sOldPwd = utils::ReadPostedParam('old_pwd', '', 'raw_data');
$sNewPwd = utils::ReadPostedParam('new_pwd', '', 'raw_data');

View File

@@ -4,6 +4,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
require_once(APPROOT.'/application/utils.inc.php');
@@ -58,6 +59,10 @@ class ApplicationMenu
* @var array
*/
static $aMenusIndex = array();
/**
* @var array
*/
static $aMenusById = [];
/**
* @var string
*/
@@ -166,6 +171,7 @@ class ApplicationMenu
$aBacktrace = debug_backtrace();
$sFile = isset($aBacktrace[2]["file"]) ? $aBacktrace[2]["file"] : $aBacktrace[1]["file"];
self::$aMenusIndex[$index] = array('node' => $oMenuNode, 'children' => array(), 'parent' => $sParentId, 'rank' => $fRank, 'source_file' => $sFile);
self::$aMenusById[$oMenuNode->GetMenuId()] = $index;
}
else
{
@@ -506,17 +512,11 @@ EOF
*/
public static function GetMenuIndexById($sTitle)
{
$index = -1;
/** @var MenuNode[] $aMenu */
foreach(self::$aMenusIndex as $aMenu)
{
if ($aMenu['node']->GetMenuId() == $sTitle)
{
$index = $aMenu['node']->GetIndex();
break;
}
if (isset(self::$aMenusById[$sTitle])) {
return self::$aMenusById[$sTitle];
}
return $index;
return -1;
}
/**
@@ -715,9 +715,10 @@ abstract class MenuNode
{
// Count the entries up to 99
$oSearch = DBSearch::FromOQL($sOQL);
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
DBSearchHelper::AddContextFilter($oSearch);
$oSet = new DBObjectSet($oSearch);
$iCount = $oSet->CountWithLimit(99);
if ($iCount > 99) {
@@ -1125,15 +1126,13 @@ class OQLMenuNode extends MenuNode
{
$sUsageId = utils::GetSafeId($sUsageId);
$oSearch = DBObjectSearch::FromOQL($sOql);
//$sIcon = MetaModel::GetClassIcon($oSearch->GetClass(), false);
if ($bSearchPane) {
$aParams = array_merge(array('open' => $bSearchOpen, 'table_id' => $sUsageId), $aExtraParams);
$aParams = array_merge(['open' => $bSearchOpen, 'table_id' => $sUsageId, 'submit_on_load' => false], $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0);
}
//$oPage->add("<p class=\"page-header\">$sIcon ".utils::HtmlEntities(Dict::S($sTitle))."</p>");
$oPage->add("<div class='sf_results_area' data-target='search_results'>");
$oTitle = TitleUIBlockFactory::MakeForPage($sTitle);
$oPage->AddUiBlock($oTitle);
@@ -1209,7 +1208,8 @@ class SearchMenuNode extends MenuNode
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
$oSearch = new DBObjectSearch($this->sClass);
$aParams = array_merge(array('table_id' => 'Menu_'.utils::GetSafeId($this->GetMenuId())), $aExtraParams);
$sUsageId = 'Menu_'.utils::GetSafeId($this->GetMenuId());
$aParams = array_merge(array('table_id' =>$sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'search', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, 0);
}
@@ -1415,6 +1415,8 @@ class DashboardMenuNode extends MenuNode
$oDashboard = $this->GetDashboard();
if ($oDashboard != null)
{
WebResourcesHelper::EnableC3JSToWebPage($oPage);
$sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier');
$oPage->add('<div id="'.$sDivId.'" class="ibo-dashboard" data-role="ibo-dashboard">');
$aExtraParams['dashboard_div_id'] = $sDivId;

View File

@@ -15,9 +15,12 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\Session;
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/contexttag.class.inc.php');
require_once(APPROOT.'/core/kpi.class.inc.php');
/**
@@ -27,6 +30,9 @@ require_once(APPROOT.'/core/contexttag.class.inc.php');
* @license http://opensource.org/licenses/AGPL-3.0
*/
ExecutionKPI::EnableDuration(1);
ExecutionKPI::EnableMemory(1);
// This storage is freed on error (case of allowed memory exhausted)
$sReservedMemory = str_repeat('*', 1024 * 1024);
register_shutdown_function(function()
@@ -62,14 +68,16 @@ register_shutdown_function(function()
}
}
});
$oKPI = new ExecutionKPI();
Session::Start();
Session::WriteClose();
$oKPI->ComputeAndReport("Session Start");
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))
if (($sSwitchEnv != null) && file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE) &&( Session::Get('itop_env') !== $sSwitchEnv))
{
$_SESSION['itop_env'] = $sSwitchEnv;
Session::Set('itop_env', $sSwitchEnv);
$sEnv = $sSwitchEnv;
$bAllowCache = false;
// Reset the opcache since otherwise the PHP "model" files may still be cached !!
@@ -85,14 +93,14 @@ if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FI
}
// TODO: reset the credentials as well ??
}
else if (isset($_SESSION['itop_env']))
else if (Session::IsSet('itop_env'))
{
$sEnv = $_SESSION['itop_env'];
$sEnv = Session::Get('itop_env');
}
else
{
$sEnv = ITOP_DEFAULT_ENV;
$_SESSION['itop_env'] = ITOP_DEFAULT_ENV;
Session::Set('itop_env', ITOP_DEFAULT_ENV);
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);

View File

@@ -27,6 +27,7 @@ class ThemeHandler
{
const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
/** @var \CompileCSSService */
private static $oCompileCSSService;
public static function GetAppRootWithSlashes()
@@ -315,11 +316,6 @@ class ThemeHandler
// Loading files to import and stylesheet to compile, also getting most recent modification time on overall files
$sTmpThemeScssContent = '';
$oFindStylesheetObject = new FindStylesheetObject();
if (isset($aThemeParameters['variable_imports'])) {
foreach ($aThemeParameters['variable_imports'] as $sImport) {
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
}
}
if (isset($aThemeParameters['utility_imports'])) {
foreach ($aThemeParameters['utility_imports'] as $sImport) {
@@ -337,6 +333,12 @@ class ThemeHandler
$sTmpThemeScssContent .= '@import "'.$sStylesheet.'";'."\n";
}
if (isset($aThemeParameters['variable_imports'])) {
foreach ($aThemeParameters['variable_imports'] as $sImport) {
static::FindStylesheetFile($sImport, $aImportsPaths, $oFindStylesheetObject);
}
}
$iStyleLastModified = $oFindStylesheetObject->GetLastModified();
$aIncludedImages=static::GetIncludedImages($aThemeParametersWithVersion, $oFindStylesheetObject->GetAllStylesheetPaths(), $sThemeId);
@@ -904,7 +906,8 @@ CSS;
foreach($aImportsPaths as $sPath)
{
$sFilePath = $sPath.'/'.$sFileURI;
$sAlterableFileURI = $sFileURI;
$sFilePath = $sPath.'/'.$sAlterableFileURI;
$sImportedFile = realpath($sFilePath);
if ($sImportedFile === false){
// Handle shortcut syntax : @import "typo" ;
@@ -912,7 +915,7 @@ CSS;
$sFilePath2 = "$sFilePath.scss";
$sImportedFile = realpath($sFilePath2);
if ($sImportedFile){
self::FindStylesheetFile("$sFileURI.scss", [ $sPath ], $oFindStylesheetObject, $bImports);
self::FindStylesheetFile("$sAlterableFileURI.scss", [ $sPath ], $oFindStylesheetObject, $bImports);
$sImportedFile = false;
}
}
@@ -922,7 +925,7 @@ CSS;
// file matched: _typo.scss
$sShortCut = substr($sFilePath, strrpos($sFilePath, '/') + 1);
$sFilePath = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sFilePath);
$sFileURI = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sFileURI);
$sAlterableFileURI = static::ReplaceLastOccurrence($sShortCut, "_$sShortCut.scss", $sAlterableFileURI);
$sImportedFile = realpath($sFilePath);
}
@@ -930,14 +933,14 @@ CSS;
&& (!$oFindStylesheetObject->AlreadyFetched($sImportedFile)))
{
if ($bImports){
$oFindStylesheetObject->AddImport($sFileURI, $sImportedFile);
$oFindStylesheetObject->AddImport($sAlterableFileURI, $sImportedFile);
}else{
$oFindStylesheetObject->AddStylesheet($sFileURI, $sImportedFile);
$oFindStylesheetObject->AddStylesheet($sAlterableFileURI, $sImportedFile);
}
$oFindStylesheetObject->UpdateLastModified($sImportedFile);
//Regexp matching on all included scss files : @import 'XXX.scss';
$sDirUri = dirname($sFileURI);
$sDirUri = dirname($sAlterableFileURI);
preg_match_all('/@import \s*[\"\']([^\"\']*)\s*[\"\']\s*;/', file_get_contents($sImportedFile), $aMatches);
if ( (is_array($aMatches)) && (count($aMatches)!==0) ){
foreach ($aMatches[1] as $sImportedFile){

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\Session;
/**
* This class records the pending "transactions" corresponding to forms that have not been
@@ -100,7 +101,8 @@ class privUITransaction
/**
* The original (and by default) mechanism for storing transaction information
* as an array in the $_SESSION variable
* as an array in the _SESSION variable
* @see \Combodo\iTop\Application\Helper\Session
*
*/
class privUITransactionSession
@@ -112,15 +114,15 @@ class privUITransactionSession
*/
public static function GetNewTransactionId()
{
if (!isset($_SESSION['transactions']))
if (!Session::IsSet('transactions'))
{
$_SESSION['transactions'] = array();
Session::Set('transactions', []);
}
// Strictly speaking, the two lines below should be grouped together
// by a critical section
// sem_acquire($rSemIdentified);
$id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime()); //1 + count($_SESSION['transactions']);
$_SESSION['transactions'][$id] = true;
$id = static::GetUserPrefix() . str_replace(array('.', ' '), '', microtime());
Session::Set(['transactions', $id], true);
// sem_release($rSemIdentified);
return (string)$id;
@@ -137,17 +139,17 @@ class privUITransactionSession
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$bResult = false;
if (isset($_SESSION['transactions']))
if (Session::IsSet('transactions'))
{
// Strictly speaking, the eight lines below should be grouped together
// inside the same critical section as above
// sem_acquire($rSemIdentified);
if (isset($_SESSION['transactions'][$id]))
if (Session::IsSet(['transactions', $id]))
{
$bResult = true;
if ($bRemoveTransaction)
{
unset($_SESSION['transactions'][$id]);
Session::Unset(['transactions', $id]);
}
}
// sem_release($rSemIdentified);
@@ -162,14 +164,14 @@ class privUITransactionSession
*/
public static function RemoveTransaction($id)
{
if (isset($_SESSION['transactions']))
if (Session::IsSet('transactions'))
{
// Strictly speaking, the three lines below should be grouped together
// inside the same critical section as above
// sem_acquire($rSemIdentified);
if (isset($_SESSION['transactions'][$id]))
if (Session::IsSet(['transactions', $id]))
{
unset($_SESSION['transactions'][$id]);
Session::Unset(['transactions', $id]);
}
// sem_release($rSemIdentified);
}

View File

@@ -80,30 +80,14 @@ class TwigExtension
// Filter to add itopversion to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_itop_version', function ($sUrl) {
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl."?itopversion=".ITOP_VERSION;
}
else
{
$sUrl = $sUrl."&itopversion=".ITOP_VERSION;
}
$sUrl = utils::AddParameterToUrl($sUrl, 'itopversion', ITOP_VERSION);
return $sUrl;
}));
// Filter to add a module's version to an url
$oTwigEnv->addFilter(new Twig_SimpleFilter('add_module_version', function ($sUrl, $sModuleName) {
$sModuleVersion = utils::GetCompiledModuleVersion($sModuleName);
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl."?moduleversion=".$sModuleVersion;
}
else
{
$sUrl = $sUrl."&moduleversion=".$sModuleVersion;
}
$sUrl = utils::AddParameterToUrl($sUrl, 'moduleversion', $sModuleVersion);
return $sUrl;
}));
@@ -137,4 +121,5 @@ class TwigExtension
return utils::GetAbsoluteUrlModulePage($sModuleName, $sPage);
}));
}
}

View File

@@ -4,6 +4,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once(APPROOT.'/application/displayblock.class.inc.php');
/**
@@ -82,6 +86,12 @@ class UIExtKeyWidget
$aArgs = [], $bSearchMode = false, &$sInputType = ''
)
{
// we will only use key & name, so let's reduce fields loaded !
$aAttToLoad = [
$sClass => [], // nothing, id and friendlyname are automatically added by the API
];
$oAllowedValues->OptimizeColumnLoad($aAttToLoad);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sTargetClass = $oAttDef->GetTargetClass();
$iMaxComboLength = $oAttDef->GetMaximumComboLength();
@@ -194,42 +204,35 @@ class UIExtKeyWidget
$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
//$sHTMLValue .= "<div class=\"field_select_wrapper\">\n";
$aOptions = [];
$sDisplayValue = "";
$aOption = [];
$aOption['value'] = "";
$aOption['label'] = Dict::S('UI:SelectOne');
array_push($aOptions,$aOption);
array_push($aOptions, $aOption);
$oAllowedValues->Rewind();
$bAddingValue=false;
$sClassAllowed = $oAllowedValues->GetClass();
$bAddingValue = false;
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($oAllowedValues->GetClass());
$aComplementAttributeSpec = MetaModel::GetNameSpec($oAllowedValues->GetClass(), FriendlyNameType::COMPLEMENTARY);
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField)>0)
{
$bAddingValue=true;
if (count($aAdditionalField) > 0) {
$bAddingValue = true;
}
while($oObj = $oAllowedValues->Fetch())
{
$aOption=[];
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sClassAllowed);
$bInitValue = false;
while ($oObj = $oAllowedValues->Fetch()) {
$aOption = [];
$aOption['value'] = $oObj->GetKey();
$aOption['label'] = $oObj->GetName();//.'<span class=\"object-ref-icon fas fa-eye-slash object-obsolete fa-1x fa-fw\"></span>';
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true') )
{
if (($oAllowedValues->Count() == 1) && ($bMandatory == 'true')) {
// When there is only once choice, select it by default
$sDisplayValue=$oObj->GetName();
if($value != $oObj->GetKey())
{
$value=$oObj->GetKey();
}
}
else {
if ((is_array($value) && in_array($oObj->GetKey(), $value)) || ($value == $oObj->GetKey())) {
$sDisplayValue = $oObj->GetName();
if ($value != $oObj->GetKey()) {
$value = $oObj->GetKey();
$bInitValue = true;
}
}
if ($oObj->IsObsolete()) {
@@ -242,21 +245,34 @@ class UIExtKeyWidget
}
$aOption['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
}
if (!empty($sObjectImageAttCode)) {
// Try to retrieve image for contact
/** @var \ormDocument $oImage */
$oImage = $oObj->Get($sObjectImageAttCode);
if (!$oImage->IsEmpty()) {
$aOption['picture_url'] = $oImage->GetDisplayURL($sClassAllowed, $oObj->GetKey(), $sObjectImageAttCode);
$aOption['initials'] = '';
} else {
$aOption['initials'] = utils::ToAcronym($oObj->Get('friendlyname'));
}
}
array_push($aOptions, $aOption);
}
$sInputType = CmdbAbstractObject::ENUM_INPUT_TYPE_DROPDOWN_DECORATED;
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\" tabindex=\"0\"></select>";
$sJsonOptions = json_encode($aOptions);
$sJsonOptions = str_replace('\\', '\\\\', json_encode($aOptions));
$oPage->add_ready_script(
<<<EOF
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>";
oACWidget_{$this->iId}.AddSelectize('$sJsonOptions','$value');
$('#$this->iId').on('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange') } );
$('#$this->iId').on('change', function() { $(this).trigger('extkeychange'); } );
EOF
);
if ($bInitValue) {
$oPage->add_ready_script("$('#$this->iId').one('validate', function() { $(this).trigger('change'); } );");
}
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\">";
}
else
@@ -593,7 +609,7 @@ EOF
// the input for the auto-complete
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input-select\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span>";
$sHTMLValue .= "<div class=\"ibo-input-select--action-buttons\"><span class=\"field_input_btn\"><div class=\"mini_button ibo-input-select--action-button\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></div></span></div>";
// another hidden input to store & pass the object's Id
$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"".htmlentities($value, ENT_QUOTES, 'UTF-8')."\" />\n";
@@ -805,21 +821,24 @@ JS
{
case static::ENUM_OUTPUT_FORMAT_JSON:
$aJsonMap = array();
foreach ($aValues as $sKey => $aValue)
{
if ($aValue['additional_field'] != '')
{
$aJsonMap[] = array('value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag'], 'additional_field' => $aValue['additional_field']);
}
else
{
$aJsonMap[] = array('value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag']);
}
}
$aJsonMap = array();
foreach ($aValues as $sKey => $aValue) {
$aElt = ['value' => $sKey, 'label' => $aValue['label'], 'obsolescence_flag' => $aValue['obsolescence_flag']];
if ($aValue['additional_field'] != '') {
$aElt['additional_field'] = $aValue['additional_field'];
}
$oP->SetContentType('application/json');
$oP->add(json_encode($aJsonMap));
if (array_key_exists('initials', $aValue)) {
$aElt['initials'] = $aValue['initials'];
if (array_key_exists('picture_url', $aValue)) {
$aElt['picture_url'] = $aValue['picture_url'];
}
}
$aJsonMap[] = $aElt;
}
$oP->SetContentType('application/json');
$oP->add(json_encode($aJsonMap));
break;
case static::ENUM_OUTPUT_FORMAT_CSV:
@@ -886,26 +905,17 @@ JS
}
}
$sDialogTitle = '';
$oPage->add('<div id="ac_create_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div id="dcr_'.$this->iId.'">');
$oPage->add('<form>');
$sClassLabel = MetaModel::GetName($this->sTargetClass);
$oPage->add('<p>'.Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel));
$oPage->add('<nobr><select name="class">');
asort($aPossibleClasses);
foreach($aPossibleClasses as $sClassName => $sClassLabel)
{
$oPage->add("<option value=\"$sClassName\">$sClassLabel</option>");
}
$oPage->add('</select>');
$oPage->add('&nbsp; <button type="submit" class="action" style="margin-top:15px;"><span>' . Dict::S('UI:Button:Ok') . '</span></button></nobr></p>');
$sDialogTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel);;
$oBlock = UIContentBlockUIBlockFactory::MakeStandard('ac_create_'.$this->iId,['ibo-is-visible']);
$oPage->AddSubBlock($oBlock);
$oClassForm = FormUIBlockFactory::MakeStandard();
$oBlock->AddSubBlock($oClassForm);
$oClassForm->AddSubBlock(cmdbAbstractObject::DisplayBlockSelectClassToCreate( $sClassLabel, $this->sTargetClass, $aPossibleClasses));
$oPage->add('</form>');
$oPage->add('</div></div></div>');
$oPage->add_ready_script("\$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#dcr_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
$oPage->add_ready_script("$('#ac_create_$this->iId').dialog({ width: 'auto', height: 'auto', maxHeight: $(window).height() - 50, autoOpen: false, modal: true, title: '$sDialogTitle'});\n");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').removeAttr('onsubmit');");
$oPage->add_ready_script("$('#ac_create_{$this->iId} form').on('submit.uilinksWizard', oACWidget_{$this->iId}.DoSelectObjectClass);");
}
/**
@@ -976,7 +986,7 @@ JS
public function DisplayHierarchy(WebPage $oPage, $sFilter, $currValue, $oObj)
{
$sDialogTitle = addslashes(Dict::Format('UI:HierarchyOf_Class', MetaModel::GetName($this->sTargetClass)));
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="overflow:auto;background:#fff;margin-bottom:5px;" id="tree_'.$this->iId.'">');
$oPage->add('<div id="dlg_tree_'.$this->iId.'"><div class="wizContainer" style="vertical-align:top;"><div style="margin-bottom:5px;" id="tree_'.$this->iId.'">');
$oPage->add('<table style="width:100%"><tr><td>');
if (is_null($sFilter))
{

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper;
/**
* Class UIHTMLEditorWidget
@@ -83,6 +84,7 @@ class UIHTMLEditorWidget
}
$sConfigJS = json_encode($aConfig);
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
// Please read...

View File

@@ -49,6 +49,8 @@ class UIPasswordWidget
*/
public function Display(WebPage $oPage, $aArgs = array())
{
$oPage->add_dict_entry('UI:Component:Input:Password:DoesNotMatch');
$sCode = $this->sAttCode.$this->sNameSuffix;
$iWidgetIndex = self::$iWidgetIndex;
@@ -57,11 +59,12 @@ class UIPasswordWidget
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword">';
$sHtmlValue .= '<input type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
$sHtmlValue .= '<input type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
$sHtmlValue .= '<span>'.Dict::S('UI:PasswordConfirm').'</span>';
$sHtmlValue .= '<input id="'.$this->iId.'_reset" type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<div class="field_input_zone field_input_onewaypassword ibo-input-wrapper">';
$sHtmlValue .= '<input class="ibo-input" type="password" maxlength="255" name="attr_'.$sCode.'[value]" id="'.$this->iId.'" value="'.htmlentities($sPasswordValue, ENT_QUOTES, 'UTF-8').'"/>';
$sHtmlValue .= '<div class="ibo-input-wrapper ibo-input-wrapper--with-buttons"><input class="ibo-input" type="password" maxlength="255" id="'.$this->iId.'_confirm" value="'.htmlentities($sConfirmPasswordValue, ENT_QUOTES, 'UTF-8').'" name="attr_'.$sCode.'[confirm]"/>';
$sHtmlValue .= '<div class="ibo-input-select--action-buttons"><div class="ibo-input-select--action-button ibo-input-select--action-button--create" data-tooltip-content="'.Dict::S('UI:PasswordConfirm').'"><i class="fas fa-question-circle"></i></div></div></div>';
$sHtmlValue .= '<button id="'.$this->iId.'_reset" class="ibo-button ibo-is-regular ibo-is-neutral" onClick="ResetPwd(\''.$this->iId.'\');">';
$sHtmlValue .= '<span class="ibo-button--icon fas fa-undo"></span><span class="ibo-button--label">'.Dict::S('UI:Button:ResetPassword').'</span></button>';
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
$sHtmlValue .= '</div>';

View File

@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use ScssPhp\ScssPhp\Compiler;
@@ -239,9 +240,9 @@ class utils
public static function InitArchiveMode()
{
if (isset($_SESSION['archive_mode']))
if (Session::IsSet('archive_mode'))
{
$iDefault = $_SESSION['archive_mode'];
$iDefault = Session::Get('archive_mode');
}
else
{
@@ -249,9 +250,9 @@ class utils
}
// Read and record the value for switching the archive mode
$iCurrent = self::ReadParam('with-archive', $iDefault);
if (isset($_SESSION))
if (Session::IsInitialized())
{
$_SESSION['archive_mode'] = $iCurrent;
Session::Set('archive_mode', $iCurrent);
}
// Read and use the value for the current page (web services)
$iCurrent = self::ReadParam('with_archive', $iCurrent, true);
@@ -538,7 +539,7 @@ class utils
*/
public static function ReadMultipleSelection($oFullSetFilter)
{
$aSelectedObj = utils::ReadParam('selectObject[]', array());
$aSelectedObj = utils::ReadParam('selectObject', array());
$sSelectionMode = utils::ReadParam('selectionMode', '');
if ($sSelectionMode != '') {
// Paginated selection
@@ -860,6 +861,8 @@ class utils
}
/**
* @param boolean $bForceGetFromDisk if true then will always read from disk without using instances in memory
*
* @return \Config Get object in the following order :
* <ol>
* <li>from {@link MetaModel::GetConfig} if loaded
@@ -872,25 +875,26 @@ class utils
* @throws \CoreException
*
* @since 2.7.0 N°2478 this method will now always call {@link MetaModel::GetConfig} first, and cache in this class is only set when loading from disk
* @since 3.0.0 N°4158 new $bReadFromDisk parameter
*/
public static function GetConfig()
public static function GetConfig($bForceGetFromDisk = false)
{
$oMetaModelConfig = MetaModel::GetConfig();
if ($oMetaModelConfig !== null)
{
return $oMetaModelConfig;
}
if (!$bForceGetFromDisk) {
$oMetaModelConfig = MetaModel::GetConfig();
if ($oMetaModelConfig !== null) {
return $oMetaModelConfig;
}
if (self::$oConfig !== null)
{
return self::$oConfig;
if (self::$oConfig !== null) {
return self::$oConfig;
}
}
$sCurrentEnvConfigPath = self::GetConfigFilePath();
if (file_exists($sCurrentEnvConfigPath))
{
if (file_exists($sCurrentEnvConfigPath)) {
$oCurrentEnvDiskConfig = new Config($sCurrentEnvConfigPath);
self::SetConfig($oCurrentEnvDiskConfig);
return self::$oConfig;
}
@@ -1210,7 +1214,7 @@ class utils
*/
static function CanLogOff()
{
return (isset($_SESSION['can_logoff']) ? $_SESSION['can_logoff'] : false);
return Session::Get('can_logoff', false);
}
/**
@@ -1219,7 +1223,7 @@ class utils
*/
public static function GetSessionLog()
{
return print_r($_SESSION, true);
return Session::GetLog();
}
static function DebugBacktrace($iLimit = 5)
@@ -1312,14 +1316,7 @@ class utils
*/
public static function GetCurrentEnvironment()
{
if (isset($_SESSION['itop_env']))
{
return $_SESSION['itop_env'];
}
else
{
return ITOP_DEFAULT_ENV;
}
return Session::Get('itop_env', ITOP_DEFAULT_ENV);
}
/**
@@ -1922,7 +1919,7 @@ class utils
public static function CompileCSSFromSASS($sSassContent, $aImportPaths = array(), $aVariables = array())
{
$oSass = new Compiler();
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Expanded');
$oSass->setFormatter('ScssPhp\\ScssPhp\\Formatter\\Compressed');
// Setting our variables
$oSass->setVariables($aVariables);
// Setting our imports paths
@@ -2638,24 +2635,36 @@ class utils
if(!empty($aMentionsAllowedClasses)) {
$aDefaultConf['mentions'] = [];
foreach($aMentionsAllowedClasses as $sMentionChar => $sMentionClass) {
foreach($aMentionsAllowedClasses as $sMentionMarker => $sMentionScope) {
// Retrieve mention class
// - First test if the conf is a simple Datamodel class
if (MetaModel::IsValidClass($sMentionScope)) {
$sMentionClass = $sMentionScope;
}
// - Otherwise it must be a valid OQL
else {
$oTmpSearch = DBSearch::FromOQL($sMentionScope);
$sMentionClass = $oTmpSearch->GetClass();
unset($oTmpSearch);
}
// Note: Endpoints are defaults only and should be overloaded by other GUIs such as the end-users portal
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&target_class='.$sMentionClass.'&needle={encodedQuery}';
$sMentionEndpoint = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=cke_mentions&marker='.$sMentionMarker.'&needle={encodedQuery}';
$sMentionItemUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class='.$sMentionClass.'&id={id}';
$sMentionItemPictureTemplate = (empty(MetaModel::GetImageAttributeCode($sMentionClass))) ? '' : <<<HTML
<span class="ibo-vendors-ckeditor--autocomplete-item-image" style="background-image: url('{picture_url}');">{initials}</span>
<span class="ibo-vendors-ckeditor--autocomplete-item-image" style="{picture_style}">{initials}</span>
HTML;
$sMentionItemTemplate = <<<HTML
<li class="ibo-vendors-ckeditor--autocomplete-item" data-id="{id}">{$sMentionItemPictureTemplate}<span class="ibo-vendors-ckeditor--autocomplete-item-title">{friendlyname}</span></li>
HTML;
$sMentionOutputTemplate = <<<HTML
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionChar}{friendlyname}</a>
<a href="$sMentionItemUrl" data-role="object-mention" data-object-class="{class}" data-object-id="{id}">{$sMentionMarker}{friendlyname}</a>
HTML;
$aDefaultConf['mentions'][] = [
'feed' => $sMentionEndpoint,
'marker' => $sMentionChar,
'marker' => $sMentionMarker,
'minChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'),
'itemTemplate' => $sMentionItemTemplate,
'outputTemplate' => $sMentionOutputTemplate,
@@ -2811,23 +2820,66 @@ HTML;
//----------------------------------------------
/**
* Check if iTop is in a development environment (VCS vs build number)
* Check if iTop is in a development environment
*
* @return bool
* @return bool true if development environment
*
* @since 2.6.0 method creation
* @since 3.0.0 add the `developer_mode.enabled` config parameter
*
* @uses GetDeveloperModeParam
* @uses ITOP_REVISION constant (check 'svn' value)
*/
public static function IsDevelopmentEnvironment()
{
if (! defined('ITOP_REVISION')) {
$bIsDevEnvInConfig = static::GetDeveloperModeParam();
if ($bIsDevEnvInConfig === true) {
return true;
}
if ($bIsDevEnvInConfig === false) {
return false;
}
if (!defined('ITOP_REVISION')) {
//defensive behaviour: by default we are not in dev environment
//can happen even in production (unattended install for example) or with exotic use of iTop
return false;
}
return ITOP_REVISION === 'svn';
}
/**
* @return bool : indicate whether we run under a windows environnement or not
* In the setup there are times when the MetaModel config attribute is loaded but partially (only setup parameters are set, others have the default value)
* So we need to load from disk then !
*
* But in other scenario we want to read from memory : for example when changing the option in a PHPUnit setUp method
*
* This method will first try to get the `developer_mode.enabled` config parameter the standard way (call to GetConfig without modification).
* If we are getting null (not defined parameter), then we will load config from disk only (GetConfig(true))
*
* @return bool|null
* @throws \ConfigException
* @throws \CoreException
*
* @uses developer_mode.enabled config parameter
*/
private static function GetDeveloperModeParam(): ?bool
{
$oConfig = static::GetConfig(false);
$bIsDevEnvInConfig = $oConfig->Get('developer_mode.enabled');
if (!is_null($bIsDevEnvInConfig)) {
return $bIsDevEnvInConfig;
}
$oConfigFromDisk = static::GetConfig(true);
return $oConfigFromDisk->Get('developer_mode.enabled');
}
/**
* @return bool true if we are running under a Windows environment
* @since 2.7.4 : N°3412
*/
public static function IsWindowsEnvironment()
@@ -2993,4 +3045,26 @@ HTML;
return $aMentionedObjects;
}
/**
* @param $sUrl
* @param string $sParamName
* @param string $sParamValue
*
* @return string
* @since 3.0.0
*/
public static function AddParameterToUrl(string $sUrl, string $sParamName, string $sParamValue): string
{
if (strpos($sUrl, '?') === false)
{
$sUrl = $sUrl.'?'.urlencode($sParamName).'='.urlencode($sParamValue);
}
else
{
$sUrl = $sUrl.'&'.urlencode($sParamName).'='.urlencode($sParamValue);
}
return $sUrl;
}
}

View File

@@ -44,11 +44,7 @@ define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
if (function_exists('microtime')) {
$fItopStarted = microtime(true);
} else {
$fItopStarted = 1000 * time();
}
$fItopStarted = microtime(true);
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
require_once APPROOT.'/lib/autoload.php';

View File

@@ -2,7 +2,7 @@
"type": "project",
"license": "AGPLv3",
"require": {
"php": ">=7.1.3",
"php": ">=7.1.3 <8.0.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -11,9 +11,9 @@
"ext-mysqli": "*",
"ext-soap": "*",
"combodo/tcpdf": "6.3.5",
"nikic/php-parser": "^3.1",
"nikic/php-parser": "^4.12.0",
"pear/archive_tar": "1.4.13",
"pelago/emogrifier": "2.1.0",
"pelago/emogrifier": "3.1.0",
"scssphp/scssphp": "1.0.6",
"swiftmailer/swiftmailer": "5.4.12",
"symfony/console": "3.4.*",
@@ -37,24 +37,21 @@
},
"config": {
"platform": {
"php": "7.2.0"
"php": "7.1.3"
},
"vendor-dir": "lib",
"preferred-install": {
"*": "dist"
},
"sort-packages": true,
"classmap-authoritative": true
"classmap-authoritative": true,
"platform-check": true
},
"autoload": {
"classmap": [
"core",
"application",
"sources/application",
"sources/Composer",
"sources/Controller",
"sources/Form",
"sources/Renderer"
"sources"
],
"exclude-from-classmap": [
"core/dbobjectsearch.class.php",

77
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "62e394b1ef30b4e716e3e3e519de11dd",
"content-hash": "fb56686981ee4945791fe5b93735b022",
"packages": [
{
"name": "combodo/tcpdf",
@@ -68,24 +68,25 @@
},
{
"name": "nikic/php-parser",
"version": "v3.1.5",
"version": "v4.12.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce"
"reference": "6608f01670c3cc5079e18c1dab1104e002579143"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
"reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143",
"reference": "6608f01670c3cc5079e18c1dab1104e002579143",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.5"
"php": ">=7.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0|~5.0"
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
@@ -93,7 +94,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-master": "4.9-dev"
}
},
"autoload": {
@@ -115,7 +116,11 @@
"parser",
"php"
],
"time": "2018-02-28T20:30:58+00:00"
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0"
},
"time": "2021-07-21T10:44:31+00:00"
},
{
"name": "paragonie/random_compat",
@@ -406,34 +411,34 @@
},
{
"name": "pelago/emogrifier",
"version": "v2.1.0",
"version": "v3.1.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/emogrifier.git",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f"
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"reference": "40c3d4f475d44ffc7265a760d1dd0e81f579f96f",
"url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"reference": "f6a5c7d44612d86c3901c93f1592f5440e6b2cd8",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0",
"symfony/css-selector": "^3.4.0 || ^4.0.0"
"php": "^5.6 || ~7.0 || ~7.1 || ~7.2 || ~7.3 || ~7.4",
"symfony/css-selector": "^2.8 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.0",
"phpmd/phpmd": "^2.6.0",
"phpunit/phpunit": "^4.8.0",
"squizlabs/php_codesniffer": "^3.3.2"
"friendsofphp/php-cs-fixer": "^2.15.3",
"phpmd/phpmd": "^2.7.0",
"phpunit/phpunit": "^5.7.27",
"squizlabs/php_codesniffer": "^3.5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1.x-dev"
"dev-master": "4.0.x-dev"
}
},
"autoload": {
@@ -446,16 +451,6 @@
"MIT"
],
"authors": [
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
@@ -464,9 +459,19 @@
"name": "Zoli Szabó",
"email": "zoli.szabo+github@gmail.com"
},
{
"name": "John Reeve",
"email": "jreeve@pelagodesign.com"
},
{
"name": "Jake Hotson",
"email": "jake@qzdesign.co.uk"
},
{
"name": "Cameron Brooks"
},
{
"name": "Jaime Prado"
}
],
"description": "Converts CSS styles into inline style attributes in your HTML code",
@@ -476,7 +481,11 @@
"email",
"pre-processing"
],
"time": "2018-12-08T13:55:46+00:00"
"support": {
"issues": "https://github.com/MyIntervals/emogrifier/issues",
"source": "https://github.com/MyIntervals/emogrifier"
},
"time": "2019-12-26T19:37:31+00:00"
},
{
"name": "psr/cache",
@@ -2604,7 +2613,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1.3",
"php": ">=7.1.3 <8.0.0",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -2615,7 +2624,7 @@
},
"platform-dev": [],
"platform-overrides": {
"php": "7.2.0"
"php": "7.1.3"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Core;
use mysqli;
/**
* mysqli object is really hard to mock as it contains lots of attributes & methods ! Thought we need to mock it to test transactions !
*
* To solve this, a new attribute exists and is only used in specific use cases, so there are just few things to mock.
*
* This object adds more readability than previous model with 2 attributes in {@see CMDBSource}.
*
* @used-by \CMDBSource
*
* @since 3.0.0 N°4325 Object creation
* This wrapper handles the 2 {@mysqli myqsli} attributes that were previously in {@see CMDBSource}
* To allow testing we added a second mysqli object (N°3513 in 2.7.5) and code became a bit confusing :/
* With this wrapper everything is in the same place, and we can express the intention more clearly !
*/
class DbConnectionWrapper
{
/** @var mysqli */
protected static $oDbCnxStandard;
/**
* Can contain a genuine mysqli object, or a mock that would emulate {@see mysqli::query()}
*
* @var mysqli
* @used-by \Combodo\iTop\Test\UnitTest\Core\TransactionsTest
*/
protected static $oDbCnxMockableForQuery;
/**
* @param bool $bIsForQuery set to true if using {@see mysqli::query()}
*
* @return \mysqli|null
*/
public static function GetDbConnection(bool $bIsForQuery = false): ?mysqli
{
if ($bIsForQuery) {
return static::$oDbCnxMockableForQuery;
}
return static::$oDbCnxStandard;
}
public static function SetDbConnection(mysqli $oMysqli): void
{
static::$oDbCnxStandard = $oMysqli;
static::SetDbConnectionMockForQuery($oMysqli);
}
/**
* Use this to register a mock that will handle {@see mysqli::query()}
*
* @param \mysqli $oMysqli
*/
public static function SetDbConnectionMockForQuery(mysqli $oMysqli): void
{
static::$oDbCnxMockableForQuery = $oMysqli;
}
}

View File

@@ -66,9 +66,8 @@ class MyHelpers
// getmicrotime()
// format sss.mmmuuupppnnn
public static function getmicrotime()
{
list($usec, $sec) = explode(" ",microtime());
return ((float)$usec + (float)$sec);
{
return microtime(true);
}
/*

View File

@@ -388,7 +388,12 @@ class AsyncSendEmail extends AsyncTask
return "Bug - the email should be sent in synchronous mode";
case EMAIL_SEND_ERROR:
return "Failed: ".implode(', ', $aIssues);
if (is_array($aIssues)) {
$sMessage = "Sending eMail failed: ".implode(', ', $aIssues);
} else {
$sMessage = "Sending eMail failed.";
}
throw new Exception($sMessage);
}
return '';
}

View File

@@ -4123,7 +4123,8 @@ class AttributeText extends AttributeString
{
// Propose a std link to the object
$sClassLabel = MetaModel::GetName($sClass);
$sReplacement = "<span class=\"wiki_broken_link\">$sClassLabel:$sName" . (!empty($sLabel) ? " ($sLabel)" : "") . "</span>";
$sToolTipForHtml = utils::EscapeHtml(Dict::Format('Core:UnknownObjectLabel', $sClass, $sName));
$sReplacement = "<span class=\"wiki_broken_link ibo-is-broken-hyperlink\" data-tooltip-content=\"$sToolTipForHtml\">$sClassLabel:$sName" . (!empty($sLabel) ? " ($sLabel)" : "") . "</span>";
$sText = str_replace($aMatches[0], $sReplacement, $sText);
// Later: propose a link to create a new object
// Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name
@@ -4159,13 +4160,13 @@ class AttributeText extends AttributeString
$sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize);
$sValue = self::RenderWikiHtml($sValue);
return "<div $sStyle>".str_replace("\n", "<br>\n", $sValue).'</div>';
return "<div $sStyle>$sValue</div>";
}
else
{
$sValue = self::RenderWikiHtml($sValue, true /* wiki only */);
return "<div class=\"HTML\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
return "<div class=\"HTML ibo-is-html-content\" $sStyle>".InlineImage::FixUrls($sValue).'</div>';
}
}
@@ -10282,7 +10283,6 @@ abstract class AttributeSet extends AttributeDBFieldVoid
} else {
$sTooltipContent = <<<HTML
<h4>$sLabel</h4>
<br>
<div>$sDescription</div>
HTML;
$sTooltipHtmlEnabled = 'true';
@@ -11573,7 +11573,6 @@ class AttributeTagSet extends AttributeSet
} else {
$sTooltipContent = <<<HTML
<h4>$sTagLabel</h4>
<br>
<div>$sTagDescription</div>
HTML;
$sTooltipHtmlEnabled = 'true';
@@ -12544,10 +12543,12 @@ class AttributeCustomFields extends AttributeDefinition
*/
public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
{
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'),
true);
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
if ($aRawData != null) {
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
} else{
return null;
}
}
public function MakeRealValue($proposedValue, $oHostObject)

View File

@@ -1,22 +1,4 @@
<?php
// Copyright (C) 2010-2021 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/>
/**
* Persistent class (internal) cmdbChange
*
@@ -24,6 +6,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
/**
* A change as requested/validated at once by user, may groups many atomic changes
@@ -53,7 +36,7 @@ class CMDBChange extends DBObject
MetaModel::Init_AddAttribute(new AttributeDateTime("date", array("allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"User", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum('interactive,csv-interactive,csv-import.php,webservice-soap,webservice-rest,synchro-data-source,email-processing,custom-extension'), "sql"=>"origin", "default_value"=>"interactive", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("origin", array("allowed_values"=>new ValueSetEnum(implode(',', [CMDBChangeOrigin::INTERACTIVE, CMDBChangeOrigin::CSV_INTERACTIVE, CMDBChangeOrigin::CSV_IMPORT, CMDBChangeOrigin::WEBSERVICE_SOAP, CMDBChangeOrigin::WEBSERVICE_REST, CMDBChangeOrigin::SYNCHRO_DATA_SOURCE, CMDBChangeOrigin::EMAIL_PROCESSING, CMDBChangeOrigin::CUSTOM_EXTENSION])), "sql"=>"origin", "default_value"=>CMDBChangeOrigin::INTERACTIVE, "is_null_allowed"=>true, "depends_on"=>array())));
}
/**

View File

@@ -24,13 +24,15 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\DbConnectionWrapper;
require_once('MyHelpers.class.inc.php');
require_once(APPROOT.'core/kpi.class.inc.php');
/**
* CMDBSource
* database access wrapper
* database access wrapper
*
* @package iTopORM
*/
@@ -66,11 +68,6 @@ class CMDBSource
*/
protected static $m_sDBTlsCA;
/** @var mysqli $m_oMysqli */
protected static $m_oMysqli;
/** @var mysqli or mock used for test purpose, only used in query() method */
protected static $oMySQLiForQuery;
/**
* @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent
* @since 2.7.0 N°679
@@ -135,8 +132,8 @@ class CMDBSource
self::$m_bDBTlsEnabled = empty($bTlsEnabled) ? false : $bTlsEnabled;
self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA;
self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
self::SetMySQLiForQuery(self::$m_oMysqli);
$oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true);
DbConnectionWrapper::SetDbConnection($oMysqli);
}
/**
@@ -252,7 +249,7 @@ class CMDBSource
* parameters were used.<br>
* This method can be called to ensure that the DB connection really uses TLS.
*
* <p>We're using this object connection : {@link self::$m_oMysqli}
* <p>We're using this object connection : {@see self::$m_oMysqli}
*
* @param \mysqli $oMysqli
*
@@ -345,7 +342,8 @@ class CMDBSource
{
// In case we don't have rights to enumerate the databases
// Let's try to connect directly
return @((bool)self::$m_oMysqli->query("USE `$sSource`"));
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
return @((bool)DbConnectionWrapper::GetDbConnection(true)->query("USE `$sSource`"));
}
}
@@ -361,7 +359,7 @@ class CMDBSource
*/
public static function GetServerInfo()
{
return mysqli_get_server_info ( self::$m_oMysqli );
return mysqli_get_server_info(DbConnectionWrapper::GetDbConnection());
}
/**
@@ -392,9 +390,9 @@ class CMDBSource
*/
public static function SelectDB($sSource)
{
if (!((bool)self::$m_oMysqli->query("USE `$sSource`")))
{
throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
if (!((bool)DbConnectionWrapper::GetDbConnection(true)->query("USE `$sSource`"))) {
throw new MySQLException('Could not select DB', array('db_name' => $sSource));
}
self::$m_sDBName = $sSource;
}
@@ -445,51 +443,29 @@ class CMDBSource
/**
* @return \mysqli
*
* @since 2.5.0 N°1260
*/
public static function GetMysqli()
{
return self::$m_oMysqli;
}
/**
* @return
*/
private static function GetMySQLiForQuery()
{
return self::$oMySQLiForQuery;
}
/**
* Used for test purpose (mysqli mock)
* @param $oMySQLi
*/
private static function SetMySQLiForQuery($oMySQLi)
{
self::$oMySQLiForQuery = $oMySQLi;
return DbConnectionWrapper::GetDbConnection(false);
}
public static function GetErrNo()
{
if (self::$m_oMysqli->errno != 0)
{
return self::$m_oMysqli->errno;
}
else
{
return self::$m_oMysqli->connect_errno;
if (DbConnectionWrapper::GetDbConnection()->errno != 0) {
return DbConnectionWrapper::GetDbConnection()->errno;
} else {
return DbConnectionWrapper::GetDbConnection()->connect_errno;
}
}
public static function GetError()
{
if (self::$m_oMysqli->error != '')
{
return self::$m_oMysqli->error;
}
else
{
return self::$m_oMysqli->connect_error;
if (DbConnectionWrapper::GetDbConnection()->error != '') {
return DbConnectionWrapper::GetDbConnection()->error;
} else {
return DbConnectionWrapper::GetDbConnection()->connect_error;
}
}
@@ -529,7 +505,8 @@ class CMDBSource
// Quote if not a number or a numeric string
if ($bAlways || is_string($value))
{
$value = $cQuoteStyle . self::$m_oMysqli->real_escape_string($value) . $cQuoteStyle;
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$value = $cQuoteStyle.DbConnectionWrapper::GetDbConnection()->real_escape_string($value).$cQuoteStyle;
}
return $value;
}
@@ -590,7 +567,7 @@ class CMDBSource
/**
* Send the query directly to the DB. **Be extra cautious with this !**
*
* Use {@link Query} if you're not sure.
* Use {@see Query} if you're not sure.
*
* @internal
*
@@ -606,32 +583,31 @@ class CMDBSource
{
$sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150);
if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) {
IssueLog::Trace("$sShortSQL", 'cmdbsource');
IssueLog::Trace("$sShortSQL", LogChannels::CMDB_SOURCE);
}
$oKPI = new ExecutionKPI();
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch (mysqli_sql_exception $e)
{
self::LogDeadLock($e);
self::LogDeadLock($e, true);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
}
$oKPI->ComputeStats('Query exec (mySQL)', $sSql);
if ($oResult === false)
{
if ($oResult === false) {
$aContext = array('query' => $sSql);
$iMySqlErrorNo = self::$m_oMysqli->errno;
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection(true)->errno;
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
{
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes)) {
throw new MySQLHasGoneAwayException(self::GetError(), $aContext);
}
$e = new MySQLException('Failed to issue SQL query', $aContext);
self::LogDeadLock($e);
self::LogDeadLock($e, true);
throw $e;
}
@@ -640,23 +616,24 @@ class CMDBSource
/**
* @param \Exception $e
* @param bool $bForQuery to get the proper DB connection
*
* @since 2.7.1
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
*/
private static function LogDeadLock(Exception $e)
private static function LogDeadLock(Exception $e, $bForQuery = false)
{
// checks MySQL error code
$iMySqlErrorNo = self::$m_oMysqli->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK)))
{
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
return;
}
// Get error info
$sUser = UserRights::GetUser();
$oError = self::$m_oMysqli->query('SHOW ENGINE INNODB STATUS');
if ($oError !== false)
{
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oError = DbConnectionWrapper::GetDbConnection(true)->query('SHOW ENGINE INNODB STATUS');
if ($oError !== false) {
$aData = $oError->fetch_all(MYSQLI_ASSOC);
$sInnodbStatus = $aData[0];
}
@@ -695,12 +672,11 @@ class CMDBSource
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
$bHasExistingTransactions = self::IsInsideTransaction();
if (!$bHasExistingTransactions)
{
IssueLog::Trace("START TRANSACTION $sCaller", 'cmdbsource');
if (!$bHasExistingTransactions) {
IssueLog::Trace("START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
self::DBQuery('START TRANSACTION');
} else {
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", 'cmdbsource');
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", LogChannels::CMDB_SOURCE);
}
self::AddTransactionLevel();
@@ -720,21 +696,20 @@ class CMDBSource
{
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
if (!self::IsInsideTransaction())
{
if (!self::IsInsideTransaction()) {
// should not happen !
IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource');
IssueLog::Error("No Transaction COMMIT $sCaller", LogChannels::CMDB_SOURCE);
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
}
self::RemoveLastTransactionLevel();
if (self::IsInsideTransaction())
{
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource');
if (self::IsInsideTransaction()) {
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", LogChannels::CMDB_SOURCE);
return;
}
IssueLog::Trace("COMMIT $sCaller", 'cmdbsource');
IssueLog::Trace("COMMIT $sCaller", LogChannels::CMDB_SOURCE);
self::DBQuery('COMMIT');
}
@@ -755,20 +730,19 @@ class CMDBSource
{
$aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3);
$sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()';
if (!self::IsInsideTransaction())
{
if (!self::IsInsideTransaction()) {
// should not happen !
IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource');
IssueLog::Error("No Transaction ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null);
}
self::RemoveLastTransactionLevel();
if (self::IsInsideTransaction())
{
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource');
if (self::IsInsideTransaction()) {
IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
return;
}
IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource');
IssueLog::Trace("ROLLBACK $sCaller", LogChannels::CMDB_SOURCE);
self::DBQuery('ROLLBACK');
}
@@ -832,7 +806,7 @@ class CMDBSource
public static function GetInsertId()
{
$iRes = self::$m_oMysqli->insert_id;
$iRes = DbConnectionWrapper::GetDbConnection()->insert_id;
if (is_null($iRes))
{
return 0;
@@ -874,7 +848,8 @@ class CMDBSource
$oKPI = new ExecutionKPI();
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -904,17 +879,19 @@ class CMDBSource
/**
* @param string $sSql
* @param int $iMode
*
* @return array
* @throws \MySQLException if query cannot be processed
*/
public static function QueryToArray($sSql)
public static function QueryToArray($sSql, $iMode = MYSQLI_BOTH)
{
$aData = array();
$oKPI = new ExecutionKPI();
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -927,7 +904,7 @@ class CMDBSource
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
while ($aRow = $oResult->fetch_array(MYSQLI_BOTH))
while ($aRow = $oResult->fetch_array($iMode))
{
$aData[] = $aRow;
}
@@ -964,7 +941,8 @@ class CMDBSource
$aData = array();
try
{
$oResult = self::$m_oMysqli->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -996,7 +974,8 @@ class CMDBSource
{
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{
@@ -1021,7 +1000,7 @@ class CMDBSource
public static function AffectedRows()
{
return self::$m_oMysqli->affected_rows;
return DbConnectionWrapper::GetDbConnection()->affected_rows;
}
public static function FetchArray($oResult)
@@ -1139,7 +1118,7 @@ class CMDBSource
*
* We still need to do a case sensitive comparison for enum values !
*
* A better solution would be to generate SQL field definitions ({@link GetFieldSpec} method) based on the DB used... But for
* A better solution would be to generate SQL field definitions ({@see GetFieldSpec} method) based on the DB used... But for
* now (N°2490 / SF #1756 / PR #91) we did implement this simpler solution
*
* @see GetFieldDataTypeAndOptions extracts all info from the SQL field definition
@@ -1484,7 +1463,8 @@ class CMDBSource
$sSql = "SELECT * FROM `$sTable`";
try
{
$oResult = self::GetMySQLiForQuery()->query($sSql);
/** @noinspection NullPointerExceptionInspection this shouldn't be called with un-init DB */
$oResult = DbConnectionWrapper::GetDbConnection(true)->query($sSql);
}
catch(mysqli_sql_exception $e)
{

View File

@@ -113,6 +113,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'log_level_min.write_in_db' => [
'type' => 'array',
'description' => 'Additional configuration that enable "in DB" logs for Exception on compatible code.',
'default' => [ 'Exception' => 'Error', ],
'value' => [ 'Exception' => 'Error', ],
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'app_env_label' => [
'type' => 'string',
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
@@ -1141,6 +1149,38 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'compatibility.include_moved_js_files' => [
'type' => 'bool',
'description' => 'Include back JS files which are now only included when necessary to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'compatibility.include_deprecated_js_files' => [
'type' => 'bool',
'description' => 'Include the deprecated JS files to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'compatibility.include_moved_css_files' => [
'type' => 'bool',
'description' => 'Include back CSS files which are now only included when necessary to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'compatibility.include_deprecated_css_files' => [
'type' => 'bool',
'description' => 'Include the deprecated CSS files to ease usage of not migrated extensions',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'navigation_menu.show_menus_count' => [
'type' => 'bool',
'description' => 'Display count badges for OQL menu entries',
@@ -1237,6 +1277,30 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'activity_panel.prefilter_state_changes_on_logs' => [
'type' => 'bool',
'description' => 'Whether the "State changes" filter should be set by default on all log tabs.',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'activity_panel.prefilter_edits_on_logs' => [
'type' => 'bool',
'description' => 'Whether the "Edits" filter should be set by default on all log tabs.',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'activity_panel.hide_avatars' => [
'type' => 'array',
'description' => 'GUIs IDs ("backoffice", "itop-portal" for the standard end-users portal, ...) in which the user avatars should be hidden and replaced if possible by their initials (eg. array("backoffice", "itop-portal", "another-portal-id"))',
'default' => [],
'value' => [],
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'activity_panel.show_author_name_below_entries' => [
'type' => 'bool',
'description' => 'Whether or not to show the author friendlyname next to the date on the last entry.',
@@ -1263,9 +1327,9 @@ class Config
],
'mentions.allowed_classes' => [
'type' => 'array',
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete (eg. "@" => "Person")',
'description' => 'Classes which can be mentioned through the autocomplete in the caselogs. Key of the array must be a single character that will trigger the autocomplete, value can be either a DM class or a valid OQL (eg. "@" => "Person", "?" => "SELECT FAQ WHERE status = \'published\'")',
'default' => [
'@' => 'Person',
'@' => 'SELECT Person WHERE status = \'active\'',
],
'value' => false,
'source_of_value' => '',
@@ -1391,6 +1455,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.disable_inline_documents_sandbox' => [
'type' => 'bool',
'description' => 'If true then the sandbox for documents displayed in a browser tab will be disabled; enabling scripts and other interactive content. Note that setting this to true will open the application to potential XSS attacks!',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
'type' => 'bool',
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
@@ -1399,6 +1471,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'developer_mode.enabled' => [
'type' => 'bool',
'description' => 'If true then unlocks dev env functionalities, see \utils::IsDevelopmentEnvironment',
'default' => null,
'value' => null,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'theme.enable_precompilation' => [
'type' => 'bool',
'description' => 'If false, theme compilation will not use any precompiled file setup optimization.)',

View File

@@ -4,6 +4,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
/**
* All objects to be displayed in the application (either as a list or as details)
* must implement this interface.
@@ -946,10 +948,9 @@ abstract class DBObject implements iDisplay
*/
protected function ComputeHighlightCode()
{
// First if the state defines a HiglightCode, apply it
$sState = $this->GetState();
if ($sState != '')
if (MetaModel::HasLifecycle(get_class($this)))
{
$sState = $this->GetState();
$sCode = MetaModel::GetHighlightCode(get_class($this), $sState);
$this->SetHighlightCode($sCode);
}
@@ -1277,13 +1278,20 @@ abstract class DBObject implements iDisplay
{
// If the object if not issued from a query but constructed programmatically
// the label may be empty. In this case run a query to get the object's friendly name
$oTmpObj = MetaModel::GetObject($sObjClass, $sObjKey, false);
if (is_object($oTmpObj))
{
$sObjOql = 'SELECT '.$sObjClass.' WHERE id='.$sObjKey;
$oObjFilter = DBSearch::FromOQL($sObjOql);
$oSet = new DBObjectSet($oObjFilter);
// we will only use id and friendlyname, so let's remove other fields !
$aAttToLoad = [
$sObjClass => [],
];
$oSet->OptimizeColumnLoad($aAttToLoad);
$oTmpObj = $oSet->Fetch();
if (is_object($oTmpObj)) {
$sHtmlLabel = $oTmpObj->GetName();
}
else
{
} else {
// May happen in case the target object is not in the list of allowed values for this attribute
$sHtmlLabel = "<em>$sObjClass::$sObjKey</em>";
}
@@ -1518,14 +1526,15 @@ abstract class DBObject implements iDisplay
* Helper to get the friendly name in a safe manner for displaying inside a web page
*
* @internal
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
*
* @return string
* @throws \CoreException
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
* @since 3.0.0 N°580 New $sType parameter
*
*/
public function GetName()
public function GetName($sType = FriendlyNameType::SHORT)
{
return htmlentities($this->GetRawName(), ENT_QUOTES, 'UTF-8');
return htmlentities($this->GetRawName($sType), ENT_QUOTES, 'UTF-8');
}
/**
@@ -1539,11 +1548,17 @@ abstract class DBObject implements iDisplay
* @return string
* @throws \CoreException
* @since 3.0.0 N°4106 This method is now internal. It will be set final in 3.1.0 (N°4107)
* @since 3.0.0 N°580 New $sType parameter
*
*/
public function GetRawName()
public function GetRawName($sType = FriendlyNameType::SHORT)
{
return $this->Get('friendlyname');
if ($sType == FriendlyNameType::SHORT) {
return $this->Get('friendlyname');
} else {
$oExpression = MetaModel::GetNameExpression(get_class($this), $sType);
$this->EvaluateExpression($oExpression);
}
}
/**
@@ -1859,9 +1874,7 @@ abstract class DBObject implements iDisplay
{
/** @var \AttributeExternalKey $oAtt */
$sTargetClass = $oAtt->GetTargetClass();
$oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/);
if (is_null($oTargetObj))
{
if (false === MetaModel::IsObjectInDB($sTargetClass, $toCheck)) {
return "Target object not found ($sTargetClass::$toCheck)";
}
}
@@ -2129,28 +2142,23 @@ abstract class DBObject implements iDisplay
}
if ($oAttDef->DuplicatesAllowed()) {
return true;
return;
}
// To control duplicates go through all the entries and check if the remote has been seen
/** @var \ormLinkSet $value */
$aModifiedLnk = $value->ListModifiedLinks();
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$aExistingRemotesId = $value->GetColumnAsArray($sExtKeyToRemote, true);
$aExistingRemotesFriendlyName = $value->GetColumnAsArray($sExtKeyToRemote.'_friendlyname', true);
$aCurrentRemoteIds = [];
$aDuplicatesFields = [];
foreach ($aModifiedLnk as $oModifiedLnk) {
$iModifiedLnkId = $oModifiedLnk->GetKey();
$iModifiedLnkRemoteId = $oModifiedLnk->Get($sExtKeyToRemote);
$aExistingRemotesIdToCheck = array_filter($aExistingRemotesId, function ($iLnkKey) use ($iModifiedLnkId) {
return ($iLnkKey != $iModifiedLnkId);
}, ARRAY_FILTER_USE_KEY);
if (!in_array($iModifiedLnkRemoteId, $aExistingRemotesIdToCheck, true)) {
continue;
$value->rewind();
while ($oCurrentLnk = $value->current()) {
$iExtKeyToRemote = $oCurrentLnk->Get($sExtKeyToRemote);
if (isset($aCurrentRemoteIds[$iExtKeyToRemote])) {
$aDuplicatesFields[] = $oCurrentLnk->Get($sExtKeyToRemote.'_friendlyname');
} else {
$aCurrentRemoteIds[$iExtKeyToRemote] = true;
}
$iLnkId = $oModifiedLnk->GetKey();
$aDuplicatesFields[] = $aExistingRemotesFriendlyName[$iLnkId];
$value->next();
}
if (!empty($aDuplicatesFields)) {

View File

@@ -63,22 +63,23 @@ else
abstract class DBSearch
{
/** @internal */
/** @internal */
const JOIN_POINTING_TO = 0;
/** @internal */
/** @internal */
const JOIN_REFERENCED_BY = 1;
protected $m_bNoContextParameters = false;
/** @var array For {@see iQueryModifier} impl */
protected $m_aModifierProperties = array();
protected $m_bArchiveMode = false;
protected $m_bShowObsoleteData = true;
/**
* DBSearch constructor.
*
* @api
* @see DBSearch::FromOQL()
*/
/**
* DBSearch constructor.
*
* @api
* @see DBSearch::FromOQL()
*/
public function __construct()
{
$this->Init();

View File

@@ -242,7 +242,7 @@ class DeletionPlan
public function SetDeletionIssues($oObject, $aIssues, $bSecurityIssue)
{
if (count($aIssues) > 0)
if (count($aIssues ?? []) > 0)
{
$sClass = get_class($oObject);
$iId = $oObject->GetKey();

View File

@@ -15,6 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
use Combodo\iTop\Renderer\BlockRenderer;
@@ -464,7 +465,7 @@ class DisplayableNode extends GraphNode
{
$aRootCauses[] = $oRootCause->GetHyperlink();
}
$sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'">&nbsp;'.implode(', ', $aRootCauses).'</p>';
$sHtml .= '<p><img style="max-height: 24px; vertical-align:bottom;" class="ibo-class-icon ibo-is-small" src="'.utils::GetAbsoluteUrlModulesRoot().$aContext['icon'].'" title="'.htmlentities(Dict::S($aContext['dict'])).'">&nbsp;'.implode(', ', $aRootCauses).'</p>';
}
$sHtml .= '<hr/>';
}
@@ -805,7 +806,7 @@ class DisplayableGroupNode extends DisplayableNode
$sHtml .= '<a href="#" onclick="$(\'.itop-simple-graph\').simple_graph(\'show_group\', \'relation_group_'.$iGroupIdx.'\');">'.Dict::Format('UI:RelationGroupNumber_N', (1+$iGroupIdx))."</a>";
$sHtml .= '<hr/>';
$sHtml .= '<table><tbody><tr>';
$sHtml .= '<td style="vertical-align:top;padding-right: 0.5em;"><img src="'.$this->GetProperty('icon_url').'"></td><td style="vertical-align:top">'.MetaModel::GetName($this->GetObjectClass()).'<br/>';
$sHtml .= '<td style="vertical-align:top;padding-right: 0.5em;"><img class="ibo-class-icon ibo-is-small" src="'.$this->GetProperty('icon_url').'"></td><td style="vertical-align:top">'.MetaModel::GetName($this->GetObjectClass()).'<br/>';
$sHtml .= Dict::Format('UI_CountOfObjectsShort', $this->GetObjectCount()).'</td>';
$sHtml .= '</tr></tbody></table>';
return $sHtml;
@@ -1446,9 +1447,9 @@ class DisplayableGraph extends SimpleGraph
$sSftShort = Dict::S('UI:ElementsDisplayed');
$sSearchToggle = Dict::S('UI:Search:Toggle');
$oP->add("<div class=\"not-printable\">\n");
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_CYAN, 'ds_flash');
$oUiSearchBlock = new Panel($sSftShort, [],Panel::ENUM_COLOR_SCHEME_CYAN, 'ds_flash');
$oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]);
$oUiSearchBlock->SetIsCollapsible(true);
$oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html(
<<<EOF
<div id="ds_flash" class="search_box ibo-display-graph--search-box">
@@ -1499,11 +1500,8 @@ EOF
$sDirection = utils::ReadParam('d', 'horizontal');
$iGroupingThreshold = utils::ReadParam('g', 5);
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js');
$oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery.contextMenu.css');
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.contextMenu.js');
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js');
WebResourcesHelper::EnableSimpleGraphInWebPage($oP);
try
{
$this->InitFromGraphviz();

View File

@@ -24,6 +24,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Pelago\Emogrifier\CssInliner;
use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter;
use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
Swift_Preferences::getInstance()->setCharset('UTF-8');
@@ -335,8 +339,9 @@ class EMail
{
if (($sMimeType === 'text/html') && ($sCustomStyles !== null))
{
$emogrifier = new \Pelago\Emogrifier($sBody, $sCustomStyles);
$sBody = $emogrifier->emogrify(); // Adds html/body tags if not already present
$oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument();
HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone();
$sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present
}
$this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType);
$this->m_oMessage->setBody($sBody, $sMimeType);

View File

@@ -222,6 +222,10 @@ class EventIssue extends Event
//
$this->Set('page', @$GLOBALS['_SERVER']['SCRIPT_NAME']);
if (strlen($this->Get('userinfo')) == 0) {
$this->Set('userinfo', UserRights::GetUserId());
}
if (array_key_exists('_GET', $GLOBALS) && is_array($GLOBALS['_GET']))
{
$this->Set('arguments_get', $GLOBALS['_GET']);

View File

@@ -6,6 +6,8 @@
*/
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
@@ -80,38 +82,49 @@ class iTopConfigParser
* @param \PhpParser\Parser $oParser
* @param $sConfig
*
* @return \Combodo\iTop\Config\Validator\ConfigNodesVisitor
* @return void
*/
private function BrowseFile(\PhpParser\Parser $oParser, $sConfig)
private function BrowseFile(Parser $oParser, $sConfig)
{
$prettyPrinter = new Standard();
try
{
try {
$aNodes = $oParser->parse($sConfig);
}
catch (\Error $e)
{
catch (\Error $e) {
$sMessage = Dict::Format('config-parse-error', $e->getMessage(), $e->getLine());
$this->oException = new \Exception($sMessage, 0, $e);
}
foreach ($aNodes as $oAssignation)
{
if (! $oAssignation instanceof Assign)
{
foreach ($aNodes as $sKey => $oNode) {
// With PhpParser 3 we had an Assign node at root
// In PhpParser 4 the root node is now an Expression
if (false === ($oNode instanceof \PhpParser\Node\Stmt\Expression)) {
continue;
}
/** @var \PhpParser\Node\Stmt\Expression $oNode */
if (false === ($oNode->expr instanceof Assign)) {
continue;
}
/** @var Assign $oAssignation */
$oAssignation = $oNode->expr;
if (false === ($oAssignation->var instanceof Variable)) {
continue;
}
if (false === ($oAssignation->expr instanceof PhpParser\Node\Expr\Array_)) {
continue;
}
$sCurrentRootVar = $oAssignation->var->name;
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap))
{
if (!array_key_exists($sCurrentRootVar, $this->aVarsMap)) {
continue;
}
$aCurrentRootVarMap =& $this->aVarsMap[$sCurrentRootVar];
foreach ($oAssignation->expr->items as $oItem)
{
foreach ($oAssignation->expr->items as $oItem) {
$sValue = $prettyPrinter->prettyPrintExpr($oItem->value);
$aCurrentRootVarMap[$oItem->key->value] = $sValue;
}

View File

@@ -229,7 +229,7 @@ class InlineImage extends DBObject
*
* @param string $sTempId
*
* @return void
* @return bool True if cleaning was successful, false if anything aborted it
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
@@ -239,8 +239,19 @@ class InlineImage extends DBObject
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public static function OnFormCancel($sTempId)
public static function OnFormCancel($sTempId): bool
{
// Protection against unfortunate massive delete of inline images when a null temp ID is passed
if (strlen($sTempId) === 0) {
IssueLog::Trace('OnFormCancel "error" $sTempId is null or empty', LogChannels::INLINE_IMAGE, array(
'$sTempId' => $sTempId,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
return false;
}
// Delete all "pending" InlineImages for this form
$sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
@@ -257,6 +268,8 @@ class InlineImage extends DBObject
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
return true;
}
/**
@@ -577,7 +590,7 @@ JS
oEditor.on( 'instanceReady', function() {
if(!CKEDITOR.env.iOS && $('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').length == 0)
{
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen" style="background-image:url(\\'$sAbsoluteUrlAppRoot/images/full-screen.png\\')">&nbsp;</span>');
$('#'+oEditor.id+'_toolbox').append('<span class="ibo-vendors-ckeditor--toolbar-fullscreen-button editor-fullscreen-button" data-role="ibo-vendors-ckeditor--toolbar-fullscreen-button" title="$sToggleFullScreen">&nbsp;</span>');
$('#'+oEditor.id+'_toolbox .ibo-vendors-ckeditor--toolbar-fullscreen-button').on('click', function() {
oEditor.execCommand('maximize');
if ($(this).closest('.cke_maximized').length != 0)

View File

@@ -31,29 +31,38 @@ class ExecutionKPI
static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*';
static protected $m_aStats = array(); // Recurrent operations
static protected $m_aExecData = array(); // One shot operations
static protected $m_aStats = []; // Recurrent operations
static protected $m_aExecData = []; // One shot operations
/**
* @var array[ExecutionKPI]
*/
static protected $m_aExecutionStack = []; // embedded execution stats
protected $m_fStarted = null;
protected $m_fChildrenDuration = 0; // Count embedded
protected $m_iInitialMemory = null;
static public function EnableDuration($iLevel)
{
if ($iLevel > 0)
{
if ($iLevel > 0) {
self::$m_bEnabled_Duration = true;
if ($iLevel > 1)
{
if ($iLevel > 1) {
self::$m_bBlameCaller = true;
} else {
self::$m_bBlameCaller = false;
}
} else {
self::$m_bEnabled_Duration = false;
self::$m_bBlameCaller = false;
}
}
static public function EnableMemory($iLevel)
{
if ($iLevel > 0)
{
if ($iLevel > 0) {
self::$m_bEnabled_Memory = true;
} else {
self::$m_bEnabled_Memory = false;
}
}
@@ -103,16 +112,17 @@ class ExecutionKPI
$sTableStyle = 'background-color: #ccc; margin: 10px;';
self::Report("<hr/>");
self::Report("<div style=\"background-color: grey; padding: 10px;\">");
self::Report("<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>");
self::Report("<p>".date('Y-m-d H:i:s', $fItopStarted)."</p>");
self::Report("<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>");
self::Report("<div>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>");
self::Report("</thead>");
$sHtml = "<hr/>";
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>";
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
$sHtml .= "<p>".$oStarted->format('Y-m-d H:i:s.u')."</p>";
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
$sHtml .= "<div>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation</th><th>Begin</th><th>End</th><th>Duration</th><th>Memory start</th><th>Memory end</th><th>Memory peak</th>";
$sHtml .= "</thead>";
foreach (self::$m_aExecData as $aOpStats)
{
$sOperation = $aOpStats['op'];
@@ -134,12 +144,12 @@ class ExecutionKPI
}
}
self::Report("<tr>");
self::Report(" <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>");
self::Report("</tr>");
$sHtml .= "<tr>";
$sHtml .= " <td>$sOperation</td><td>$sBegin</td><td>$sEnd</td><td>$sDuration</td><td>$sMemBegin</td><td>$sMemEnd</td><td>$sMemPeak</td>";
$sHtml .= "</tr>";
}
self::Report("</table>");
self::Report("</div>");
$sHtml .= "</table>";
$sHtml .= "</div>";
$aConsolidatedStats = array();
foreach (self::$m_aStats as $sOperation => $aOpStats)
@@ -175,11 +185,11 @@ class ExecutionKPI
);
}
self::Report("<div>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>");
self::Report("</thead>");
$sHtml .= "<div>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th><th>Avg</th>";
$sHtml .= "</thead>";
foreach ($aConsolidatedStats as $sOperation => $aOpStats)
{
$sOperation = '<a href="#'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
@@ -189,22 +199,24 @@ class ExecutionKPI
$sMax = '<a href="#'.md5($sExecId.$aOpStats['max_args']).'">'.round($aOpStats['max'], 3).'</a>';
$sAvg = round($aOpStats['avg'], 3);
self::Report("<tr>");
self::Report(" <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>");
self::Report("</tr>");
$sHtml .= "<tr>";
$sHtml .= " <td>$sOperation</td><td>$sCount</td><td>$sDuration</td><td>$sMin</td><td>$sMax</td><td>$sAvg</td>";
$sHtml .= "</tr>";
}
self::Report("</table>");
self::Report("</div>");
$sHtml .= "</table>";
$sHtml .= "</div>";
self::Report("</div>");
$sHtml .= "</div>";
self::Report("<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>");
$sHtml .= "<p><a href=\"#end-".md5($sExecId)."\">Next page stats</a></p>";
$fSlowQueries = MetaModel::GetConfig()->Get('log_kpi_slow_queries');
self::Report($sHtml);
// Report operation details
foreach (self::$m_aStats as $sOperation => $aOpStats)
{
$sHtml = '';
$bDisplayHeader = true;
foreach ($aOpStats as $sArguments => $aEvents)
{
@@ -250,31 +262,59 @@ class ExecutionKPI
if ($bDisplayHeader)
{
$sOperationHtml = '<a name="'.md5($sExecId.$sOperation).'">'.$sOperation.'</a>';
self::Report("<h4>$sOperationHtml</h4>");
self::Report("<table border=\"1\" style=\"$sTableStyle\">");
self::Report("<thead>");
self::Report(" <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>");
self::Report("</thead>");
$sHtml .= "<h4>$sOperationHtml</h4>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
$sHtml .= "<thead>";
$sHtml .= " <th>Operation details (+ blame caller if log_kpi_duration = 2)</th><th>Count</th><th>Duration</th><th>Min</th><th>Max</th>";
$sHtml .= "</thead>";
$bDisplayHeader = false;
}
self::Report("<tr>");
self::Report(" <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>");
self::Report("</tr>");
$sHtml .= "<tr>";
$sHtml .= " <td>$sHtmlArguments</td><td>$iCountInter</td><td>$sTotalInter</td><td>$sMinInter</td><td>$sMaxInter</td>";
$sHtml .= "</tr>";
}
}
if (!$bDisplayHeader)
{
self::Report("</table>");
self::Report("<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>");
$sHtml .= "</table>";
$sHtml .= "<p><a href=\"#".md5($sExecId)."\">Back to page stats</a></p>";
}
self::Report($sHtml);
}
self::Report('<a name="end-'.md5($sExecId).'">&nbsp;</a>');
$sHtml = '<a name="end-'.md5($sExecId).'">&nbsp;</a>';
self::Report($sHtml);
}
public function __construct()
{
$this->ResetCounters();
self::Push($this);
}
/**
* Stack executions to remove children duration from stats
*
* @param \ExecutionKPI $oExecutionKPI
*/
private static function Push(ExecutionKPI $oExecutionKPI)
{
array_push(self::$m_aExecutionStack, $oExecutionKPI);
}
/**
* Pop current child and count its duration in its parent
*
* @param float|int $fChildDuration
*/
private static function Pop(float $fChildDuration = 0)
{
array_pop(self::$m_aExecutionStack);
// Update the parent's children duration
$oPrevExecutionKPI = end(self::$m_aExecutionStack);
if ($oPrevExecutionKPI) {
$oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
}
}
// Get the duration since startup, and reset the counter for the next measure
@@ -285,13 +325,12 @@ class ExecutionKPI
$aNewEntry = null;
if (self::$m_bEnabled_Duration)
{
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$aNewEntry = array(
'op' => $sOperationDesc,
'op' => $sOperationDesc,
'time_begin' => $this->m_fStarted - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
'time_end' => $fStopped - $fItopStarted,
);
// Reset for the next operation (if the object is recycled)
$this->m_fStarted = $fStopped;
@@ -323,31 +362,30 @@ class ExecutionKPI
public function ComputeStats($sOperation, $sArguments)
{
if (self::$m_bEnabled_Duration)
{
$fDuration = 0;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted;
if (self::$m_bBlameCaller)
{
$fSelfDuration = $fDuration - $this->m_fChildrenDuration;
if (self::$m_bBlameCaller) {
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration,
'time' => $fSelfDuration,
'callers' => MyHelpers::get_callstack(1),
);
}
else
{
} else {
self::$m_aStats[$sOperation][$sArguments][] = array(
'time' => $fDuration
'time' => $fSelfDuration,
);
}
}
self::Pop($fDuration);
}
protected function ResetCounters()
{
if (self::$m_bEnabled_Duration)
{
$this->m_fStarted = MyHelpers::getmicrotime();
$this->m_fStarted = microtime(true);
}
if (self::$m_bEnabled_Memory)

View File

@@ -545,6 +545,7 @@ class LogChannels
public const DEADLOCK = 'DeadLock';
public const INLINE_IMAGE = 'InlineImage';
public const PORTAL = 'portal';
public const CMDB_SOURCE = 'cmdbsource';
}
@@ -586,6 +587,9 @@ abstract class LogAPI
static::$m_oFileLog = new FileLog($sTargetFile);
}
/**
* @internal uses only for testing purpose.
*/
public static function MockStaticObjects($oFileLog, $oMetaModelConfig = null)
{
static::$m_oFileLog = $oFileLog;
@@ -652,9 +656,9 @@ abstract class LogAPI
* @throws \ConfigException if log wrongly configured
* @uses GetMinLogLevel
*/
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel): bool
final public static function IsLogLevelEnabled(string $sLevel, string $sChannel, string $sCode = 'log_level_min'): bool
{
$sMinLogLevel = self::GetMinLogLevel($sChannel);
$sMinLogLevel = self::GetMinLogLevel($sChannel, $sCode);
if ($sMinLogLevel === false || $sMinLogLevel === 'false') {
return false;
@@ -674,22 +678,34 @@ abstract class LogAPI
/**
* @param string $sChannel
* @param string $sCode
*
* @return string one of the LEVEL_* const value : the one configured it if exists, otherwise default log level for this channel
* Config can be done globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
* Or per channel : `'log_level_min' => ['InlineImage' => LogAPI::LEVEL_TRACE, 'UserRequest' => LogAPI::LEVEL_TRACE],`
* Config can be set :
* * globally : `'log_level_min' => LogAPI::LEVEL_TRACE,`
* * per channel :
* ```
* 'log_level_min' => [
* '' => LogAPI::LEVEL_ERROR, // default log level for channels not listed below
* 'InlineImage' => LogAPI::LEVEL_TRACE,
* 'UserRequest' => LogAPI::LEVEL_TRACE
* ],
* ```
*
* @uses \LogAPI::GetConfig()
* @uses `log_level_min` config parameter
* @uses \LogAPI::GetLevelDefault
*
* @link https://www.itophub.io/wiki/page?id=3_0_0%3Aadmin%3Alog iTop log reference
*/
protected static function GetMinLogLevel($sChannel)
protected static function GetMinLogLevel($sChannel, $sCode = 'log_level_min')
{
$oConfig = static::GetConfig();
if (!$oConfig instanceof Config) {
return static::GetLevelDefault();
}
$sLogLevelMin = $oConfig->Get('log_level_min');
$sLogLevelMin = $oConfig->Get($sCode);
if (empty($sLogLevelMin)) {
return static::GetLevelDefault();
@@ -704,7 +720,12 @@ abstract class LogAPI
}
if (isset($sLogLevelMin[static::CHANNEL_DEFAULT])) {
return $sLogLevelMin[$sChannel];
return $sLogLevelMin[static::CHANNEL_DEFAULT];
}
// Even though the *self*::CHANNEL_DEFAULT is set to '' in the current class (LogAPI), the test below is necessary as the CHANNEL_DEFAULT constant can be (and is!) overloaded in derivated classes, don't remove this test to factorize it with the previous one.
if (isset($sLogLevelMin[''])) {
return $sLogLevelMin[''];
}
return static::GetLevelDefault();
@@ -716,7 +737,7 @@ abstract class LogAPI
*/
protected static function GetConfig(): ?Config
{
return static::$m_oMockMetaModelConfig ?? \MetaModel::GetConfig();
return static::$m_oMockMetaModelConfig ?? \utils::GetConfig();
}
/**
@@ -824,6 +845,7 @@ class DeadLockLog extends LogAPI
class DeprecatedCallsLog extends LogAPI
{
public const ENUM_CHANNEL_PHP_METHOD = 'deprecated-php-method';
public const ENUM_CHANNEL_PHP_LIBMETHOD = 'deprecated-php-libmethod';
public const ENUM_CHANNEL_FILE = 'deprecated-file';
public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD;
@@ -832,12 +854,112 @@ class DeprecatedCallsLog extends LogAPI
/** @var \FileLog we want our own instance ! */
protected static $m_oFileLog = null;
/**
* Indirection to {@see \LogAPI::IsLogLevelEnabled()} that is handling possible {@see ConfigException}
*
* @param string $sLevel
* @param string $sChannel
*
* @return bool if exception occurs, then returns false
*
* @uses \LogAPI::IsLogLevelEnabled()
*/
protected static function IsLogLevelEnabledSafe($sLevel, $sChannel): bool
{
try {
$bIsLogLevelEnabled = static::IsLogLevelEnabled(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD);
}
catch (ConfigException $e) {
$bIsLogLevelEnabled = false;
}
return $bIsLogLevelEnabled;
}
/**
* @param string|null $sTargetFile
*
* @uses \set_error_handler() to catch deprecated notices
*
* @since 3.0.0 N°3002 logs deprecated notices in called code
*/
public static function Enable($sTargetFile = null): void
{
if (empty($sTargetFile)) {
$sTargetFile = APPROOT.'log/deprecated-calls.log';
}
parent::Enable($sTargetFile);
if (static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler']);
}
}
/**
* This will catch a message for all E_DEPRECATED and E_USER_DEPRECATED errors.
* This handler is set in DeprecatedCallsLog::Enable
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
*
* @return bool
* @since 3.0.0 N°3002
* @noinspection SpellCheckingInspection
*/
public static function DeprecatedNoticesErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool
{
if (
(\E_USER_DEPRECATED !== $errno)
&& (\E_DEPRECATED !== $errno)
) {
return false;
}
if (false === static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)) {
// returns true so that nothing is throwned !
return true;
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
// We are generating a trigger_error ourselves, we don't want to trace them !
return false;
}
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 3 = caller of the deprecated method
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'] ?? null;
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'] ?? null;
$sMessage .= ' (';
if (!is_null($sCallerObject)) {
$sMessage .= "{$sCallerObject}::{$sCallerMethod}";
} else {
$sCallerMethodFile = $aStack[$iStackCallerMethodLevel]['file'];
$sCallerMethodLine = $aStack[$iStackCallerMethodLevel]['line'];
if (!is_null($sCallerMethod)) {
$sMessage .= "call to {$sCallerMethod}() in {$sCallerMethodFile}#L{$sCallerMethodLine}";
} else {
$sMessage .= "{$sCallerMethodFile}#L{$sCallerMethodLine}";
}
}
$sMessage .= ')';
}
if (!empty($errstr)) {
$sMessage .= ' : '.$errstr;
}
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_LIBMETHOD);
return true;
}
protected static function GetLevelDefault(): string
@@ -897,16 +1019,17 @@ class DeprecatedCallsLog extends LogAPI
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$sDeprecatedObject = $aStack[1]['class'];
$sDeprecatedMethod = $aStack[1]['function'];
$sCallerFile = $aStack[1]['file'];
$sCallerLine = $aStack[1]['line'];
$iStackDeprecatedMethodLevel = 1; // level 0 = current method, level 1 = method containing the `NotifyDeprecatedPhpMethod` call
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
if (array_key_exists(2, $aStack)) {
$sCallerObject = $aStack[2]['class'];
$sCallerMethod = $aStack[2]['function'];
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 2 = caller of the deprecated method
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'];
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'];
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
}
@@ -914,12 +1037,12 @@ class DeprecatedCallsLog extends LogAPI
$sMessage .= ' : '.$sAdditionalMessage;
}
static::Warning($sMessage, static::ENUM_CHANNEL_PHP_METHOD);
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
}
public static function Log($sLevel, $sMessage, $sChannel = null, $aContext = array()): void
{
if (utils::IsDevelopmentEnvironment()) {
if (true === utils::IsDevelopmentEnvironment()) {
trigger_error($sMessage, E_USER_DEPRECATED);
}
@@ -1001,3 +1124,160 @@ class LogFileRotationProcess implements iScheduledProcess
throw new ProcessException(self::class.' : The configured filename builder is invalid (log_filename_builder_impl="'.$sLogFileNameBuilder.'")');
}
}
/**
* Log exceptions using dedicated API and logic.
*
* Please use {@see ExceptionLog::LogException()} to log exceptions
*
* @since 3.0.0
*/
class ExceptionLog extends LogAPI
{
const CHANNEL_DEFAULT = 'Exception';
const CONTEXT_EXCEPTION = '__exception';
private static $oLastEventIssue = null;
protected static $m_oFileLog = null;
/**
* This method should be used to write logs.
*
* As it encapsulate the operations performed using the Exception, you should prefer it to the standard API inherited from LogApi `ExceptionLog::Error($oException->getMessage(), get_class($oException), ['__exception' => $oException]);`
* The parameter order is not standard, but in our use case, the resulting API is way more convenient this way.
*/
public static function LogException(Exception $oException, $aContext = array(), $sLevel = self::LEVEL_WARNING)
{
if (empty($aContext[self::CONTEXT_EXCEPTION])) {
$aContext[self::CONTEXT_EXCEPTION] = $oException;
}
if (empty($aContext['exception class'])) {
$aContext['exception class'] = get_class($oException);
}
if (empty($aContext['file'])) {
$aContext['file'] = $oException->getFile();
}
if (empty($aContext['line'])) {
$aContext['line'] =$oException->getLine();
}
self::Log($sLevel, $oException->getMessage(), get_class($oException), $aContext);
}
/**
* @inheritDoc
* @throws \ConfigException if log wrongly configured
*/
public static function Log($sLevel, $sMessage, $sClass = null, $aContext = array())
{
if (!static::$m_oFileLog) {
return;
}
if (!isset(self::$aLevelsPriority[$sLevel])) {
IssueLog::Error("invalid log level '{$sLevel}'");
return;
}
$sChannel = self::FindClassChannel($sClass);
if (static::IsLogLevelEnabled($sLevel, $sChannel)) {
static::$m_oFileLog->$sLevel($sMessage, $sChannel, array_diff_key($aContext, [self::CONTEXT_EXCEPTION => null])); //The exception should not be included in the error.log because of its verbosity.
}
$sDbChannel = self::FindClassChannel($sClass, 'log_level_min.write_in_db');
if (static::IsLogLevelEnabled($sLevel, $sDbChannel, 'log_level_min.write_in_db')) {
self::WriteToDb($aContext);
}
}
protected static function FindClassChannel($sClass, $sCode = 'log_level_min')
{
$oConfig = static::GetConfig();
if (!$oConfig instanceof Config) {
return static::GetLevelDefault();
}
$sLogLevelMin = $oConfig->Get($sCode);
if (empty($sLogLevelMin)) {
return $sClass;
}
if (!is_array($sLogLevelMin)) {
return $sClass;
}
$sParentClass = $sClass;
while (
(!isset($sLogLevelMin[$sParentClass]))
&&
($sParentClass !== false)
)
{
$sParentClass = get_parent_class($sParentClass);
}
if (isset($sLogLevelMin[$sParentClass])) {
return $sParentClass;
}
return $sClass;
}
/**
* @inheritDoc
*/
public static function Enable($sTargetFile = null)
{
if (empty($sTargetFile))
{
$sTargetFile = APPROOT.'log/error.log';
}
parent::Enable($sTargetFile);
}
private static function WriteToDb(array $aContext): void
{
$oContextException = $aContext[self::CONTEXT_EXCEPTION];
unset($aContext[self::CONTEXT_EXCEPTION]);
if (MetaModel::IsLogEnabledIssue()) {
if (MetaModel::IsValidClass('EventIssue')) {
try {
self::$oLastEventIssue = new EventIssue();
$sIssue = ($oContextException instanceof CoreException) ? $oContextException->GetIssue() : 'PHP Exception';
$sErrorStackTrace = ($oContextException instanceof CoreException) ? $oContextException->getFullStackTraceAsString() : $oContextException->getTraceAsString();
$aContextData = ($oContextException instanceof CoreException) ? $oContextException->getContextData() : [];
self::$oLastEventIssue->Set('message', $oContextException->getMessage());
self::$oLastEventIssue->Set('userinfo', '');
self::$oLastEventIssue->Set('issue', $sIssue);
self::$oLastEventIssue->Set('impact', '');
self::$oLastEventIssue->Set('callstack', $sErrorStackTrace);
self::$oLastEventIssue->Set('data', array_merge($aContextData, $aContext));
self::$oLastEventIssue->DBInsertNoReload();
}
catch (Exception $e) {
IssueLog::Error("Failed to log issue into the DB");
}
}
}
}
/**
* @internal Used by the tests
*/
private static function GetLastEventIssue()
{
return self::$oLastEventIssue;
}
}

View File

@@ -17,6 +17,8 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once APPROOT.'core/modulehandler.class.inc.php';
require_once APPROOT.'core/querymodifier.class.inc.php';
require_once APPROOT.'core/metamodelmodifier.inc.php';
@@ -443,10 +445,10 @@ abstract class MetaModel
/**
* @param string $sClass
* @param bool $bImgTag
* @param string $sMoreStyles
* @param bool $bImgTag Whether to surround the icon URL with an HTML IMG tag or not
* @param string $sMoreStyles Additional inline CSS style to add to the IMG tag. Only used if $bImgTag is set to true
*
* @return string
* @return string Absolute URL the class icon
* @throws \CoreException
*/
final public static function GetClassIcon($sClass, $bImgTag = true, $sMoreStyles = '')
@@ -457,7 +459,7 @@ abstract class MetaModel
if (array_key_exists('style', self::$m_aClassParams[$sClass])) {
/** @var ormStyle $oStyle */
$oStyle = self::$m_aClassParams[$sClass]['style'];
$sIcon = $oStyle->GetIcon();
$sIcon = $oStyle->GetIconAsAbsUrl();
}
if (strlen($sIcon) == 0) {
$sParentClass = self::GetParentPersistentClass($sClass);
@@ -492,7 +494,7 @@ abstract class MetaModel
$oStyle = new ormStyle("ibo-class-style--$sClass", "ibo-class-style-alt--$sClass");
}
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIcon()) > 0)) {
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) {
// all the parameters are set, no need to search in the parent classes
return $oStyle;
}
@@ -510,10 +512,10 @@ abstract class MetaModel
$oStyle->SetComplementaryColor($oParentStyle->GetComplementaryColor());
$oStyle->SetAltStyleClass($oParentStyle->GetAltStyleClass());
}
if (strlen($oStyle->GetIcon()) == 0) {
$oStyle->SetIcon($oParentStyle->GetIcon());
if (strlen($oStyle->GetIconAsRelPath()) == 0) {
$oStyle->SetIcon($oParentStyle->GetIconAsRelPath());
}
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIcon()) > 0)) {
if ((strlen($oStyle->GetMainColor()) > 0) && (strlen($oStyle->GetComplementaryColor()) > 0) && (strlen($oStyle->GetIconAsRelPath()) > 0)) {
// all the parameters are set, no need to search in the parent classes
return $oStyle;
}
@@ -521,7 +523,7 @@ abstract class MetaModel
$sParentClass = self::GetParentPersistentClass($sParentClass);
}
if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIcon()) == 0)) {
if ((strlen($oStyle->GetMainColor()) == 0) && (strlen($oStyle->GetComplementaryColor()) == 0) && (strlen($oStyle->GetIconAsRelPath()) == 0)) {
return null;
}
@@ -752,17 +754,56 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return array
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetNameSpec($sClass)
final public static function GetNameSpec($sClass, $sType = FriendlyNameType::SHORT)
{
self::_check_subclass($sClass);
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
switch ($sType) {
case FriendlyNameType::COMPLEMENTARY:
if (!isset(self::$m_aClassParams[$sClass]["complementary_name_attcode"])) {
return [$sClass, []];
}
$nameRawSpec = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
$sDictName = 'ComplementaryName';
break;
case FriendlyNameType::LONG:
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
if (!isset(self::$m_aClassParams[$sClass]["complementary_name_attcode"])) {
return self::GetNameSpec($sClass, FriendlyNameType::SHORT);
}
$complementaryNameRawSpec = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
if (is_array($nameRawSpec)) {
if (is_array($complementaryNameRawSpec)) {
$nameRawSpec = merge($nameRawSpec, $complementaryNameRawSpec);
} elseif (!empty($nameRawSpec)) {
$nameRawSpec = merge($nameRawSpec, [$complementaryNameRawSpec]);
}
} elseif (empty($nameRawSpec)) {
$nameRawSpec = $complementaryNameRawSpec;
} else {
if (is_array($complementaryNameRawSpec)) {
$nameRawSpec = merge([$nameRawSpec], $complementaryNameRawSpec);
} elseif (!empty($nameRawSpec)) {
$nameRawSpec = [$nameRawSpec, $complementaryNameRawSpec];
}
}
$sDictName = 'LongName';
break;
default:
$nameRawSpec = self::$m_aClassParams[$sClass]["name_attcode"];
$sDictName = 'Name';
}
if (is_array($nameRawSpec)) {
$sFormat = Dict::S("Class:$sClass/Name", '');
$sFormat = Dict::S("Class:$sClass/$sDictName", '');
if (strlen($sFormat) == 0) {
// Default to "%1$s %2$s..."
for ($i = 1; $i <= count($nameRawSpec); $i++) {
@@ -774,12 +815,12 @@ abstract class MetaModel
}
}
return array($sFormat, $nameRawSpec);
return [$sFormat, $nameRawSpec];
} elseif (empty($nameRawSpec)) {
return array($sClass, array());
return [$sClass, []];
} else {
// string -> attcode
return array('%1$s', array($nameRawSpec));
return ['%1$s', [$nameRawSpec]];
}
}
@@ -787,24 +828,38 @@ abstract class MetaModel
*
* @param string $sClass
* @param bool $bWithAttributeDefinition
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return array of attribute codes used by friendlyname
* @throws \CoreException
* @since 3.0.0
*/
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false): array
final public static function GetNameAttributes(string $sClass, $bWithAttributeDefinition = false, $sType = FriendlyNameType::SHORT): array
{
self::_check_subclass($sClass);
$rawNameAttCodes = self::$m_aClassParams[$sClass]["name_attcode"];
$aNameAttCodes = [];
if (!is_array($rawNameAttCodes)) {
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
$aNameAttCodes[] = $rawNameAttCodes;
if ($sType == FriendlyNameType::SHORT || FriendlyNameType::LONG) {
$rawNameAttCodes = self::$m_aClassParams[$sClass]["name_attcode"];
if (!is_array($rawNameAttCodes)) {
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
$aNameAttCodes[] = $rawNameAttCodes;
}
} else {
$aNameAttCodes = $rawNameAttCodes;
}
}
if ($sType == FriendlyNameType::COMPLEMENTARY || FriendlyNameType::LONG) {
$rawNameAttCodes = self::$m_aClassParams[$sClass]["complementary_name_attcode"];
if (!isEmpty($rawNameAttCodes)) {
if (!is_array($rawNameAttCodes)) {
if (self::IsValidAttCode($sClass, $rawNameAttCodes)) {
$aNameAttCodes[] = array_merge($aNameAttCodes, [$rawNameAttCodes]);
}
} else {
$aNameAttCodes = array_merge($rawNameAttCodes, $rawNameAttCodes);
}
}
} else {
$aNameAttCodes = $rawNameAttCodes;
}
if ($bWithAttributeDefinition) {
$aResults = [];
foreach ($aNameAttCodes as $sAttCode) {
@@ -842,68 +897,21 @@ abstract class MetaModel
return $aResults;
}
/**
* @param string $sClass
*
* @return array
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
final static public function GetComplementAttributeSpec($sClass)
{
self::_check_subclass($sClass);
if (!isset(self::$m_aClassParams[$sClass]["name_complement_for_select"]))
{
$sParentClass = static::GetParentClass($sClass);
if (is_null($sParentClass)) {
return array($sClass, array());
} else {
return static::GetComplementAttributeSpec($sParentClass);
}
}
$nameRawSpec = self::$m_aClassParams[$sClass]["name_complement_for_select"];
if (is_array($nameRawSpec))
{
$sFormat = Dict::S("Class:$sClass/ComplementForSelect", '');
if (strlen($sFormat) == 0)
{
// Default to "%1$s %2$s..."
for($i = 1; $i <= count($nameRawSpec); $i++)
{
if (empty($sFormat))
{
$sFormat .= '%'.$i.'$s';
}
else
{
$sFormat .= ' %'.$i.'$s';
}
}
}
return array($sFormat, $nameRawSpec);
}
elseif (empty($nameRawSpec))
{
return array($sClass, array());
}
else
{
// string -> attcode
return array('%1$s', array($nameRawSpec));
}
}
/**
* Get the friendly name expression for a given class
*
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return Expression
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetNameExpression($sClass)
final public static function GetNameExpression($sClass, $sType = FriendlyNameType::SHORT)
{
$aNameSpec = self::GetNameSpec($sClass);
$aNameSpec = self::GetNameSpec($sClass, $sType);
$sFormat = $aNameSpec[0];
$aAttributes = $aNameSpec[1];
@@ -935,14 +943,17 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return string The friendly name IIF it is equivalent to a single attribute
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetFriendlyNameAttributeCode($sClass)
final public static function GetFriendlyNameAttributeCode($sClass, $sType = FriendlyNameType::SHORT)
{
$aNameSpec = self::GetNameSpec($sClass);
$aNameSpec = self::GetNameSpec($sClass, $sType);
$sFormat = trim($aNameSpec[0]);
$aAttributes = $aNameSpec[1];
if (($sFormat != '') && ($sFormat != '%1$s')) {
@@ -958,13 +969,16 @@ abstract class MetaModel
/**
* Returns the list of attributes composing the friendlyname
*
* @param $sClass
* @param string $sClass
* @param string $sType {@see \Combodo\iTop\Core\MetaModel\FriendlyNameType}
*
* @return array
*
* @since 3.0.0 N°580 New $sType parameter
*/
final public static function GetFriendlyNameAttributeCodeList($sClass)
final public static function GetFriendlyNameAttributeCodeList($sClass, $sType = FriendlyNameType::SHORT)
{
$aNameSpec = self::GetNameSpec($sClass);
$aNameSpec = self::GetNameSpec($sClass, $sType);
$aAttributes = $aNameSpec[1];
return $aAttributes;
@@ -1116,7 +1130,6 @@ abstract class MetaModel
*/
final public static function GetFilterCodeOrigin($sClass, $sAttCode)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
self::_check_subclass($sClass);
return self::$m_aFilterOrigins[$sClass][$sAttCode];
@@ -1134,7 +1147,8 @@ abstract class MetaModel
{
self::_check_subclass($sClass);
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
return $oAtt->GetPrerequisiteAttributes();
return $oAtt->GetPrerequisiteAttributes($sClass);
}
/**
@@ -5115,7 +5129,7 @@ abstract class MetaModel
foreach($aErrors as $sClass => $aMessages)
{
echo "<p>Wrong declaration for class <b>$sClass</b></p>\n";
echo "<ul class=\"treeview\">\n";
echo "<ul >\n";
$i = 0;
foreach($aMessages as $sMsg)
{
@@ -6510,6 +6524,7 @@ abstract class MetaModel
*/
public static function LoadConfig($oConfiguration, $bAllowCache = false)
{
$oKPI = new ExecutionKPI();
self::$m_oConfig = $oConfiguration;
// N°2478 utils has his own private attribute
@@ -6530,6 +6545,7 @@ abstract class MetaModel
ToolsLog::Enable(APPROOT.'log/tools.log');
DeadLockLog::Enable();
DeprecatedCallsLog::Enable();
ExceptionLog::Enable();
}
else
{
@@ -6586,6 +6602,7 @@ abstract class MetaModel
$sSource = self::$m_oConfig->Get('db_name');
$sTablePrefix = self::$m_oConfig->Get('db_subname');
$oKPI->ComputeAndReport('Load config');
if (self::$m_bUseAPCCache)
{
@@ -6957,7 +6974,7 @@ abstract class MetaModel
* @param int $iKey id value of the object to retrieve
* @param bool $bMustBeFound see throws ArchivedObjectException
* @param bool $bAllowAllData if true then user rights will be bypassed - use with care!
* @param null $aModifierProperties
* @param array $aModifierProperties properties for {@see iQueryModifier} impl
*
* @return \DBObject null if : (the object is not found) or (archive mode disabled and object is archived and
* $bMustBeFound=false)
@@ -6977,12 +6994,9 @@ abstract class MetaModel
if (!utils::IsArchiveMode() && $oObject->IsArchived())
{
if ($bMustBeFound)
{
if ($bMustBeFound) {
throw new ArchivedObjectException("The object $sClass::$iKey is archived");
}
else
{
} else {
return null;
}
}
@@ -6990,6 +7004,35 @@ abstract class MetaModel
return $oObject;
}
/**
* @param string $sClass
* @param int $iKey
*
* @return bool True if the object of $sClass and $iKey exists in the DB -no matter the current user restrictions-, false otherwise meaning:
* - It could be in memory for now and is not persisted yet
* - It is neither in memory nor DB
*
* @throws \CoreException
* @throws \MySQLException
* @throws \MySQLQueryHasNoResultException
* @since 3.0.0 N°4173
*/
public static function IsObjectInDB(string $sClass, int $iKey): bool
{
// Note: We take the root class to ensure that there is a corresponding table in the DB
// as some intermediate classes can have no table in the DB.
$sRootClass = MetaModel::GetRootClass($sClass);
$sTable = MetaModel::DBGetTable($sRootClass);
$sKeyCol = MetaModel::DBGetKey($sRootClass);
$sEscapedKey = CMDBSource::Quote($iKey);
$sQuery = "SELECT count(*) FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
$iCount = (int) CMDBSource::QueryToScalar($sQuery);
return $iCount === 1;
}
/**
* Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4
* module compatibility, see N.1108)
@@ -7190,21 +7233,6 @@ abstract class MetaModel
return $oRet;
}
/**
* @deprecated 2.7.0 N°1627, use ItopCounter::incRootClass($sClass) instead
*
* @param string $sClass
*
* @return int
* @throws \CoreException
*/
public static function GetNextKey($sClass)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use ItopCounter::incRootClass($sClass) instead');
return ItopCounter::IncClass($sClass);
}
/**
* Deletion of records, bypassing {@link DBObject::DBDelete} !!!
* It is NOT recommended to use this shortcut
@@ -7244,7 +7272,6 @@ abstract class MetaModel
*/
public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('do not use : dead code, will be removed in the future');
// $aValues is an array of $sAttCode => $value
$sSQL = $oFilter->MakeUpdateQuery($aValues);
if (!self::DBIsReadOnly()) {
@@ -7691,14 +7718,18 @@ abstract class MetaModel
/**
* @param string $sClass
* @param string $sAttCode
* @param string $sValue
* @param string|null $sValue Code of the state value, can be null if allowed by the attribute definition
*
* @return \ormStyle|null
* @throws \Exception
* @throws \CoreException
*/
public static function GetEnumStyle(string $sClass, string $sAttCode, string $sValue = ''): ?ormStyle
public static function GetEnumStyle(string $sClass, string $sAttCode, ?string $sValue = ''): ?ormStyle
{
if (strlen($sAttCode) === 0) {
return null;
}
$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
if (!$oAttDef instanceof AttributeEnum) {
throw new CoreException("MetaModel::GetEnumStyle() Attribute $sAttCode of class $sClass is not an AttributeEnum\n");

View File

@@ -7,41 +7,50 @@
/**
* Class ormStyle
*
* @since 3.0
* @since 3.0.0
*/
class ormStyle
{
/** @var string */
/** @var string|null */
protected $sMainColor;
/** @var string */
/** @var string|null */
protected $sComplementaryColor;
/** @var string CSS class with color and background-color */
/** @var string|null CSS class with color and background-color */
protected $sStyleClass;
/** @var string CSS class with only color */
/** @var string|null CSS class with only color */
protected $sAltStyleClass;
/** @var string */
/** @var string|null */
protected $sDecorationClasses;
/** @var string */
/** @var string|null Relative path (from current environment) to the icon */
protected $sIcon;
/**
* ormStyle constructor.
*
* @param string $sStyleClass
* @param string $sAltStyleClass
* @param string|null $sStyleClass
* @param string|null $sAltStyleClass
* @param string|null $sMainColor
* @param string|null $sComplementaryColor
* @param string|null $sDecorationClasses
* @param string|null $sIcon
*/
public function __construct(string $sStyleClass, string $sAltStyleClass, string $sMainColor = null, string $sComplementaryColor = null, string $sDecorationClasses = null, string $sIcon = null)
public function __construct(?string $sStyleClass = null, ?string $sAltStyleClass = null, ?string $sMainColor = null, ?string $sComplementaryColor = null, ?string $sDecorationClasses = null, ?string $sIcon = null)
{
$this->sMainColor = $sMainColor;
$this->sComplementaryColor = $sComplementaryColor;
$this->sStyleClass = $sStyleClass;
$this->sAltStyleClass = $sAltStyleClass;
$this->sDecorationClasses = $sDecorationClasses;
$this->sIcon = $sIcon;
$this->SetMainColor($sMainColor);
$this->SetComplementaryColor($sComplementaryColor);
$this->SetStyleClass($sStyleClass);
$this->SetAltStyleClass($sAltStyleClass);
$this->SetDecorationClasses($sDecorationClasses);
$this->SetIcon($sIcon);
}
/**
* @see static::$sMainColor
* @return bool
*/
public function HasMainColor(): bool
{
return strlen($this->sMainColor) > 0;
}
/**
@@ -59,10 +68,19 @@ class ormStyle
*/
public function SetMainColor(?string $sMainColor)
{
$this->sMainColor = $sMainColor;
$this->sMainColor = (strlen($sMainColor) === 0) ? null : $sMainColor;
return $this;
}
/**
* @see static::$sComplementaryColor
* @return bool
*/
public function HasComplementaryColor(): bool
{
return strlen($this->sComplementaryColor) > 0;
}
/**
* @return string
*/
@@ -78,14 +96,33 @@ class ormStyle
*/
public function SetComplementaryColor(?string $sComplementaryColor)
{
$this->sComplementaryColor = $sComplementaryColor;
$this->sComplementaryColor = (strlen($sComplementaryColor) === 0) ? null : $sComplementaryColor;
return $this;
}
/**
* @see static::$sMainColor
* @see static::$sComplementaryColor
* @return bool
*/
public function HasAtLeastOneColor(): bool
{
return $this->HasMainColor() || $this->HasComplementaryColor();
}
/**
* @see static::$sStyleClass
* @return bool
*/
public function HasStyleClass(): bool
{
return strlen($this->sStyleClass) > 0;
}
/**
* @return string
*/
public function GetStyleClass(): string
public function GetStyleClass(): ?string
{
return $this->sStyleClass;
}
@@ -95,16 +132,25 @@ class ormStyle
*
* @return $this
*/
public function SetStyleClass(string $sStyleClass)
public function SetStyleClass(?string $sStyleClass)
{
$this->sStyleClass = $sStyleClass;
$this->sStyleClass = (strlen($sStyleClass) === 0) ? null : $sStyleClass;
return $this;
}
/**
* @see static::$sAltStyleClass
* @return bool
*/
public function HasAltStyleClass(): bool
{
return strlen($this->sAltStyleClass) > 0;
}
/**
* @return string
*/
public function GetAltStyleClass(): string
public function GetAltStyleClass(): ?string
{
return $this->sAltStyleClass;
}
@@ -114,12 +160,21 @@ class ormStyle
*
* @return $this
*/
public function SetAltStyleClass(string $sAltStyleClass)
public function SetAltStyleClass(?string $sAltStyleClass)
{
$this->sAltStyleClass = $sAltStyleClass;
$this->sAltStyleClass = (strlen($sAltStyleClass) === 0) ? null : $sAltStyleClass;
return $this;
}
/**
* @see static::$sDecorationClasses
* @return bool
*/
public function HasDecorationClasses(): bool
{
return strlen($this->sDecorationClasses) > 0;
}
/**
* @return string
*/
@@ -135,16 +190,17 @@ class ormStyle
*/
public function SetDecorationClasses(?string $sDecorationClasses)
{
$this->sDecorationClasses = $sDecorationClasses;
$this->sDecorationClasses = (strlen($sDecorationClasses) === 0) ? null : $sDecorationClasses;
return $this;
}
/**
* @return string
* @see static::$sIcon
* @return bool
*/
public function GetIcon(): ?string
public function HasIcon(): bool
{
return $this->sIcon;
return strlen($this->sIcon) > 0;
}
/**
@@ -154,8 +210,30 @@ class ormStyle
*/
public function SetIcon(?string $sIcon)
{
$this->sIcon = $sIcon;
$this->sIcon = (strlen($sIcon) === 0) ? null : $sIcon;
return $this;
}
/**
* @see static::$sIcon
* @return string|null Relative path (from the current environment) of the icon
*/
public function GetIconAsRelPath(): ?string
{
return $this->sIcon;
}
/**
* @see static::$sIcon
* @return string|null Absolute URL of the icon
* @throws \Exception
*/
public function GetIconAsAbsUrl(): ?string
{
if (is_null($this->sIcon)) {
return null;
}
return utils::GetAbsoluteUrlModulesRoot().$this->sIcon;
}
}

View File

@@ -16,6 +16,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSectionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Renderer\BlockRenderer;
define('CASELOG_VISIBLE_ITEMS', 2);
define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
@@ -168,7 +174,6 @@ class ormCaseLog {
return $aEntries;
}
/**
* Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
* @return string
@@ -201,6 +206,15 @@ class ormCaseLog {
{
return ($this->m_sLog === null);
}
/**
* @return int The number of entries in this log
* @since 3.0.0
*/
public function GetEntryCount(): int
{
return count($this->m_aIndex);
}
public function ClearModifiedFlag()
{
@@ -389,7 +403,7 @@ class ormCaseLog {
{
$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
$sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
$oBlock = UIContentBlockUIBlockFactory::MakeStandard(null, ['ibo-caselog-list']);
$iPos = 0;
$aIndex = $this->m_aIndex;
if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
@@ -403,20 +417,16 @@ class ormCaseLog {
{
if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
{
$sOpen = '';
$sDisplay = 'style="display:none;"';
$bIsOpen = false;
}
else
{
$sOpen = ' open';
$sDisplay = '';
$bIsOpen = true;
}
$iPos += $aIndex[$index]['separator_length'];
$sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
$sCSSClass= 'caselog_entry_html';
if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
{
$sCSSClass= 'caselog_entry';
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
if (!is_null($aTransfoHandler))
{
@@ -433,7 +443,6 @@ class ormCaseLog {
}
$iPos += $aIndex[$index]['text_length'];
$sEntry = '<div class="caselog_header'.$sOpen.'">';
// Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
// therefore we have changed the format. To preserve the compatibility with existing
// installations of iTop, both format are allowed:
@@ -456,14 +465,11 @@ class ormCaseLog {
$sDate = '';
}
}
$sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
$sEntry .= '</div>';
$sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
$sEntry .= $sTextEntry;
$sEntry .= '</div>';
$sHtml = $sHtml.$sEntry;
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']));
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
$oCollapsibleBlock->SetOpenedByDefault($bIsOpen);
$oBlock->AddSubBlock($oCollapsibleBlock);
}
// Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (strlen($this->m_sLog) - 1))
{
@@ -477,48 +483,71 @@ class ormCaseLog {
if (count($this->m_aIndex) == 0)
{
$sHtml .= '<div class="caselog_entry open">';
$sHtml .= $sTextEntry;
$sHtml .= '</div>';
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( '');
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
$oCollapsibleBlock->SetOpenedByDefault(true);
$oBlock->AddSubBlock($oCollapsibleBlock);
}
else
{
if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
{
$sOpen = '';
$sDisplay = 'style="display:none;"';
$bIsOpen = false;
}
else
{
$sOpen = ' open';
$sDisplay = '';
$bIsOpen = true;
}
$sHtml .= '<div class="caselog_header'.$sOpen.'">';
$sHtml .= Dict::S('UI:CaseLog:InitialValue');
$sHtml .= '</div>';
$sHtml .= '<div class="caselog_entry"'.$sDisplay.'>';
$sHtml .= $sTextEntry;
$sHtml .= '</div>';
$oCollapsibleBlock = CollapsibleSectionUIBlockFactory::MakeStandard( Dict::S('UI:CaseLog:InitialValue'));
$oCollapsibleBlock->AddSubBlock(new Html($sTextEntry));
$oCollapsibleBlock->SetOpenedByDefault($bIsOpen);
}
}
$sHtml .= '</td></tr></table>';
return $sHtml;
$oBlockRenderer = new BlockRenderer($oBlock);
$sHtml = $oBlockRenderer->RenderHtml();
$sScript = $oBlockRenderer->RenderJsInlineRecursively($oBlock,iUIBlock::ENUM_JS_TYPE_ON_READY);
if ($sScript!=''){
if ($oP == null) {
$sScript = '<script>'.$sScript.'</script>';
$sHtml .= $sScript;
} else {
$oP->add_ready_script($sScript);
}
}
return $sHtml;
}
/**
* Add a new entry to the log or merge the given text into the currently modified entry
* Add a new entry to the log or merge the given text into the currently modified entry
* and updates the internal index
* @param $sText string The text of the new entry
*
* @param string $sText The text of the new entry
* @param string $sOnBehalfOf Display this name instead of current user name
* @param null|int $iOnBehalfOfId Use this UserId to author this Entry. If $sOnBehalfOf equals '', it'll be replaced by this User friendlyname
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*
* @since 3.0.0 New $iOnBehalfOfId parameter
* @since 3.0.0 May throw \ArchivedObjectException exception
*/
public function AddLogEntry($sText, $sOnBehalfOf = '')
public function AddLogEntry(string $sText, $sOnBehalfOf = '', $iOnBehalfOfId = null)
{
$sText = HTMLSanitizer::Sanitize($sText);
$sDate = date(AttributeDateTime::GetInternalFormat());
if ($sOnBehalfOf == '')
{
if ($sOnBehalfOf == '' && $iOnBehalfOfId === null) {
$sOnBehalfOf = UserRights::GetUserFriendlyName();
$iUserId = UserRights::GetUserId();
}
elseif ($iOnBehalfOfId !== null) {
$iUserId = $iOnBehalfOfId;
/* @var User $oUser */
$oUser = MetaModel::GetObject('User', $iUserId, false, true);
if ($oUser !== null && $sOnBehalfOf === '') {
$sOnBehalfOf = $oUser->GetFriendlyName();
}
}
else
{
$iUserId = null;
@@ -553,7 +582,6 @@ class ormCaseLog {
$this->m_bModified = true;
}
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
if (isset($oJson->user_id))
@@ -630,7 +658,6 @@ class ormCaseLog {
$this->m_bModified = true;
}
public function GetModifiedEntry($sFormat = 'text')
{
$sModifiedEntry = '';
@@ -709,4 +736,3 @@ class ormCaseLog {
return $sText;
}
}
?>

View File

@@ -803,6 +803,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
$oLinkSearch = $this->GetFilter();
if ($oAttDef->IsIndirect())
{
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
/** @var \AttributeExternalKey $oLinkingAttDef */
$oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote);
@@ -810,12 +811,12 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
// N°2334 add pointed class (SELECT L,R) to have all fields (lnk + remote) in display
// the pointed class is always present in the search, as generated by \AttributeLinkedSet::GetDefaultValue
$sTargetClass = $oLinkingAttDef->GetTargetClass();
$oRemoteClassSearch = new DBObjectSearch($sTargetClass);
$oRemoteClassSearch = new DBObjectSearch($sTargetClass, self::REMOTE_ALIAS);
if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass))
{
$oNotObsolete = new BinaryExpression(
new FieldExpression('obsolescence_flag', $sTargetClass),
new FieldExpression('obsolescence_flag', self::REMOTE_ALIAS),
'=',
new ScalarExpression(0)
);
@@ -825,7 +826,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
if (!utils::IsArchiveMode() && MetaModel::IsArchivable($sTargetClass))
{
$oNotArchived = new BinaryExpression(
new FieldExpression('archive_flag', $sTargetClass),
new FieldExpression('archive_flag', self::REMOTE_ALIAS),
'=',
new ScalarExpression(0)
);
@@ -833,9 +834,14 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
$oRemoteClassSearch->AddConditionExpression($oNotArchived);
}
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote);
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
$oLinkSearch->RenameAlias($sTargetClass, self::REMOTE_ALIAS);
$aReAliasingMap = [];
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote, TREE_OPERATOR_EQUALS, $aReAliasingMap);
if (array_key_exists(self::REMOTE_ALIAS, $aReAliasingMap)) {
// If 'Remote' alias has been renamed, change it back.
if ($aReAliasingMap[self::REMOTE_ALIAS][0] != self::REMOTE_ALIAS) {
$oLinkSearch->RenameAlias($aReAliasingMap[self::REMOTE_ALIAS][0], self::REMOTE_ALIAS);
}
}
$oLinkSearch->SetSelectedClasses([self::LINK_ALIAS, self::REMOTE_ALIAS]);
}
$oLinkSet = new DBObjectSet($oLinkSearch);

View File

@@ -388,15 +388,18 @@ class SQLObjectQuery extends SQLQuery
{
if (count($this->__aSelectedIdFields) > 0)
{
$aCountFields = array();
foreach ($this->__aSelectedIdFields as $sFieldExpr)
{
$aCountFields[] = "COALESCE($sFieldExpr, 0)"; // Null values are excluded from the count
$aCountFields = [];
$aCountI = [];
$i = 0;
foreach ($this->__aSelectedIdFields as $sFieldExpr) {
$aCountFields[] = "COALESCE($sFieldExpr, 0) AS idCount$i"; // Null values are excluded from the count
$aCountI[] = 'idCount'.$i++;
}
$sCountFields = implode(', ', $aCountFields);
$sCountI = implode('+ ', $aCountI);
// 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 _alderaan_";
$sSQL = "SELECT COUNT(*) AS COUNT FROM (SELECT$sLineSep DISTINCT $sCountFields $sLineSep FROM $sFrom$sLineSep WHERE $sWhere $sLimit) AS _alderaan_ WHERE $sCountI>0";
}
else
{

View File

@@ -1,4 +1,7 @@
<?php
use Combodo\iTop\Application\Helper\Session;
define('UR_ALLOWED_NO', 0);
define('UR_ALLOWED_YES', 1);
define('UR_ALLOWED_DEPENDS', 2);
@@ -318,7 +321,14 @@ abstract class User extends cmdbAbstractObject
{
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
{
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
$this->oContactObject = null;
// The User Contact is generally a Person, so try it first
if (MetaModel::IsValidClass('Person')) {
$this->oContactObject = MetaModel::GetObject('Person', $this->Get('contactid'), false);
}
if (is_null($this->oContactObject)) {
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
}
}
}
return $this->oContactObject;
@@ -333,77 +343,135 @@ abstract class User extends cmdbAbstractObject
{
parent::DoCheckToWrite();
// Note: This MUST be factorized later: declare unique keys (set of columns) in the data model
$oAddon = UserRights::GetModuleInstance();
$aChanges = $this->ListChanges();
if (array_key_exists('login', $aChanges))
{
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0)
{
if (array_key_exists('login', $aChanges)) {
// Check login uniqueness
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0) {
$sNewLogin = $aChanges['login'];
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
if (!$this->IsNew())
{
if (!$this->IsNew()) {
$oSearch->AddCondition('id', $this->GetKey(), '!=');
}
$oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin));
if ($oSet->Count() > 0)
{
if ($oSet->Count() > 0) {
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin);
}
}
}
// Check that this user has at least one profile assigned when profiles have changed
if (array_key_exists('profile_list', $aChanges))
{
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
// A User cannot disable himself
if ($this->IsCurrentUser()) {
if (isset($aChanges['status']) && ($this->Get('status') == 'disabled')) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:StatusChangeIsNotAllowed');
}
}
// Check that this user has at least one profile assigned when profiles have changed
if (array_key_exists('profile_list', $aChanges)) {
/** @var \DBObjectSet $oSet */
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
}
// A user cannot add to themself a profile denying the access to the backoffice
$aForbiddenProfiles = PortalDispatcherData::GetData('backoffice')['deny'];
if ($this->IsCurrentUser()) {
$oSet->Rewind();
$aProfiles = [];
while ($oUserProfile = $oSet->Fetch()) {
$sProfile = $oUserProfile->Get('profile');
if (in_array($sProfile, $aForbiddenProfiles)) {
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:ProfileNotAllowed', $sProfile);
}
$aProfiles[$oUserProfile->Get('profileid')] = $sProfile;
}
if (!in_array(ADMIN_PROFILE_NAME, $aProfiles)) {
// Check if the user is yet allowed to modify Users
if (method_exists($oAddon, 'ResetCache')) {
$aCurrentProfiles = Session::Get('profile_list');
// Set the current profiles into a session variable (not yet in the database)
Session::Set('profile_list', $aProfiles);
$oAddon->ResetCache();
if (!$oAddon->IsActionAllowed($this, 'User', UR_ACTION_MODIFY, null)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:CurrentProfilesHaveInsufficientRights');
}
$oAddon->ResetCache();
if (is_null($aCurrentProfiles)) {
Session::IsSet('profile_list');
} else {
Session::Set('profile_list', $aCurrentProfiles);
}
}
}
}
}
// Only administrators can manage administrators
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator())
{
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator()) {
$this->m_aCheckIssues[] = Dict::S('UI:Login:Error:AccessRestricted');
}
if (!UserRights::IsAdministrator())
{
$oUser = UserRights::GetUserObject();
$oAddon = UserRights::GetModuleInstance();
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs'))
{
if ((empty($this->GetOriginal('contactid')) && !($this->IsNew())) || empty($this->Get('contactid')))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
// A contact is mandatory (an administrator can bypass it but not for himself)
if ((!UserRights::IsAdministrator() || $this->IsCurrentUser())
&& !$this->IsNew()
&& isset($aChanges['contactid'])
&& empty($this->Get('contactid'))) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
}
// Allowed orgs must contains the user org (if any)
if (!empty($this->Get('org_id')) && !UserRights::IsAdministrator($this)) {
// Get the user org and all its parent orgs
$aUserOrgs = [$this->Get('org_id')];
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
if ($sHierarchicalKeyCode !== false) {
$sOrgQuery = 'SELECT Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' ABOVE Root.id WHERE Root.id = :id';
$oOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sOrgQuery), [], ['id' => $this->Get('org_id')]);
while ($aRow = $oOrgSet->FetchAssoc()) {
$oOrg = $aRow['Org'];
$aUserOrgs[] = $oOrg->GetKey();
}
else
{
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
if (count($aOrgs) > 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::S('Class:User/Error:UserOrganizationNotAllowed');
}
// Check users with restricted organizations when allowed organizations have changed
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges))
{
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
}
else
{
$aModifiedLinks = $oSet->ListModifiedLinks();
foreach ($aModifiedLinks as $oLink)
{
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
}
}
// Check the allowed orgs list
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() > 0) {
$bFound = false;
while ($oOrg = $oSet->Fetch()) {
if (in_array($oOrg->Get('allowed_org_id'), $aUserOrgs)) {
$bFound = true;
break;
}
}
if (!$bFound) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AllowedOrgsMustContainUserOrg');
}
}
}
if (!UserRights::IsAdministrator()) {
$oUser = UserRights::GetUserObject();
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs')) {
$aOrgs = $oAddon->GetUserOrgs($oUser, ''); // Modifier allowed orgs
if (count($aOrgs) > 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::S('Class:User/Error:UserOrganizationNotAllowed');
}
// Check users with restricted organizations when allowed organizations have changed
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges)) {
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() == 0) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
} else {
$aModifiedLinks = $oSet->ListModifiedLinks();
foreach ($aModifiedLinks as $oLink) {
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs)) {
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
}
}
}
@@ -413,14 +481,26 @@ abstract class User extends cmdbAbstractObject
}
}
/**
* @inheritDoc
* @since 3.0.0
*/
public function DoCheckToDelete(&$oDeletionPlan)
{
parent::DoCheckToDelete($oDeletionPlan);
// A user cannot suppress himself
if ($this->IsCurrentUser()) {
$this->m_bSecurityIssue = true;
$this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete');
}
}
function GetGrantAsHtml($sClass, $iAction)
{
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this))
{
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this)) {
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
}
else
{
} else {
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
@@ -528,6 +608,19 @@ abstract class User extends cmdbAbstractObject
}
parent::DBDeleteSingleObject();
}
/**
* @return bool
* @throws \OQLException
* @since 3.0.0
*/
protected function IsCurrentUser(): bool
{
if (is_null(UserRights::GetUserId())) {
return false;
}
return UserRights::GetUserId() == $this->GetKey();
}
}
/**
@@ -757,10 +850,10 @@ class UserRights
}
self::$m_oUser = $oUser;
if (isset($_SESSION['impersonate_user']))
if (Session::IsSet('impersonate_user'))
{
self::$m_oRealUser = self::$m_oUser;
self::$m_oUser = self::FindUser($_SESSION['impersonate_user']);
self::$m_oUser = self::FindUser(Session::Get('impersonate_user'));
}
Dict::SetUserLanguage(self::GetUserLanguage());
@@ -864,15 +957,15 @@ class UserRights
{
$bRet = false;
}
elseif (isset($_SESSION['archive_allowed']))
elseif (Session::IsSet('archive_allowed'))
{
$bRet = $_SESSION['archive_allowed'];
$bRet = Session::Get('archive_allowed');
}
else
{
// As of now, anybody can switch to the archive mode as soon as there is an archivable class
$bRet = (count(MetaModel::EnumArchivableClasses()) > 0);
$_SESSION['archive_allowed'] = $bRet;
Session::Set('archive_allowed', $bRet);
}
return $bRet;
}
@@ -958,7 +1051,7 @@ class UserRights
// Do impersonate!
self::$m_oUser = $oUser;
Dict::SetUserLanguage(self::GetUserLanguage());
$_SESSION['impersonate_user'] = $sLogin;
Session::Set('impersonate_user', $sLogin);
self::_ResetSessionCache();
}
}
@@ -974,7 +1067,7 @@ class UserRights
{
self::$m_oUser = self::$m_oRealUser;
Dict::SetUserLanguage(self::GetUserLanguage());
unset($_SESSION['impersonate_user']);
Session::Unset('impersonate_user');
self::_ResetSessionCache();
}
}
@@ -1011,6 +1104,30 @@ class UserRights
}
}
/**
* @param Person $oPerson Person we try to match against Users contact (also Person objects)
* @param bool $bMustBeUnique If true, return null when 2+ Users matching this Person were found. Otherwise return the first one
*
* @return \DBObject|null
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @since 3.0.0
*/
public static function GetUserFromPerson(Person $oPerson, bool $bMustBeUnique = true): ?DBObject
{
$sUserSearch = 'SELECT User WHERE contactid = :id';
$oUserSearch = DBObjectSearch::FromOQL($sUserSearch);
$oUserSearch->AllowAllData();
$oUserSet = new DBObjectSet($oUserSearch, array(), array('id' => $oPerson->GetKey()));
if($oUserSet->Count() > 0 && !($oUserSet->Count() > 1 && $bMustBeUnique)){
return $oUserSet->Fetch();
}
return null;
}
/**
* @return string
*/
@@ -1086,17 +1203,26 @@ class UserRights
// Then check if the user has a contact attached and if it has an picture defined
$sContactId = UserRights::GetContactId($sLogin);
if (!empty($sContactId)) {
$oContact = MetaModel::GetObject('Contact', $sContactId, false, true);
$oContact = null;
// Picture if generally for Person, so try it first
if (MetaModel::IsValidClass('Person')) {
$oContact = MetaModel::GetObject('Person', $sContactId, false, true);
}
if (is_null($oContact)) {
$oContact = MetaModel::GetObject('Contact', $sContactId, false, true);
}
$sContactClass = get_class($oContact);
// Check that Contact object still exists and that Contact class has a picture attribute
if (!is_null($oContact) && MetaModel::IsValidAttCode($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE)) {
// - Try to get the semantic image attribute, or try to fallback on the default one if none defined
$sContactPictureAttCode = MetaModel::HasImageAttributeCode($sContactClass) ? MetaModel::GetImageAttributeCode($sContactClass) : static::DEFAULT_CONTACT_PICTURE_ATTCODE;
if (!is_null($oContact) && MetaModel::IsValidAttCode($sContactClass, $sContactPictureAttCode)) {
/** @var \ormDocument $oPicture */
$oPicture = $oContact->Get(static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$oPicture = $oContact->Get($sContactPictureAttCode);
if ($oPicture->IsEmpty()) {
if ($bAllowDefaultPicture === true) {
/** @var \AttributeImage $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$oAttDef = MetaModel::GetAttributeDef($sContactClass, $sContactPictureAttCode);
$sPictureUrl = $oAttDef->Get('default_image');
} else {
$sPictureUrl = null;
@@ -1104,19 +1230,21 @@ class UserRights
} else {
if (ContextTag::Check(ContextTag::TAG_PORTAL)) {
$sSignature = $oPicture->GetSignature();
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.static::DEFAULT_CONTACT_PICTURE_ATTCODE.'?cache=86400&s='.$sSignature.'&exec_module=itop-portal-base&exec_page=index.php&portal_id='.PORTAL_ID;
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.$sContactPictureAttCode.'?cache=86400&s='.$sSignature.'&exec_module=itop-portal-base&exec_page=index.php&portal_id='.PORTAL_ID;
} else {
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), $sContactPictureAttCode);
}
}
}
} // If no contact, check if user has a placeholder in they preferences
else {
}
// If no contact & empty login, check if current user has a placeholder in they preferences
elseif ('' === $sLogin) {
$sPlaceholderPictureFilename = appUserPreferences::GetPref($sUserPicturePlaceholderPrefKey, null, static::GetUserId($sLogin));
if (!empty($sPlaceholderPictureFilename)) {
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().$sUserPicturesFolder.$sPlaceholderPictureFilename;
}
}
// Else, no contact and no login, then it's for an unknown origin (system, extension, ...)
// Update cache
static::$m_aCacheContactPictureAbsUrl[$sLogin] = $sPictureUrl;
@@ -1646,9 +1774,9 @@ class UserRights
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
{
// Data about the current user can be found into the session data
if (array_key_exists('profile_list', $_SESSION))
if (Session::IsSet('profile_list'))
{
$aProfiles = $_SESSION['profile_list'];
$aProfiles = Session::Get('profile_list');
}
}
@@ -1683,11 +1811,6 @@ class UserRights
self::$m_aAdmins = array();
self::$m_aPortalUsers = array();
}
if (!isset($_SESSION) && !utils::IsModeCLI())
{
session_name('itop-'.md5(APPROOT));
session_start();
}
self::_ResetSessionCache();
if (self::$m_oAddOn)
{
@@ -1722,7 +1845,7 @@ class UserRights
self::$m_aCacheUsers = array('internal' => array(), 'external' => array());
}
if (!isset(self::$m_aCacheUsers[$sAuthentication][$sLogin]))
if (!array_key_exists($sLogin, self::$m_aCacheUsers[$sAuthentication]))
{
switch($sAuthentication)
{
@@ -1772,10 +1895,7 @@ class UserRights
public static function _InitSessionCache()
{
// Cache data about the current user into the session
if (isset($_SESSION))
{
$_SESSION['profile_list'] = self::ListProfiles();
}
Session::Set('profile_list', self::ListProfiles());
$oConfig = MetaModel::GetConfig();
$bSessionIdRegeneration = $oConfig->Get('regenerate_session_id_enabled');
@@ -1797,14 +1917,8 @@ class UserRights
public static function _ResetSessionCache()
{
if (isset($_SESSION['profile_list']))
{
unset($_SESSION['profile_list']);
}
if (isset($_SESSION['archive_allowed']))
{
unset($_SESSION['archive_allowed']);
}
Session::Unset('profile_list');
Session::Unset('archive_allowed');
}
/**

View File

@@ -24,6 +24,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
require_once('MyHelpers.class.inc.php');
/**
@@ -379,41 +381,35 @@ class ValueSetObjects extends ValueSetDefinition
$this->m_aValues = array();
if ($this->m_bAllowAllData)
{
if ($this->m_bAllowAllData) {
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
}
else
{
} else {
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
}
if (!$oFilter) return false;
if (!is_null($this->m_oExtraCondition))
{
if (!$oFilter) {
return false;
}
if (!is_null($this->m_oExtraCondition)) {
$oFilter = $oFilter->Intersect($this->m_oExtraCondition);
}
foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
{
foreach ($aProperties as $sProperty => $value)
{
foreach ($this->m_aModifierProperties as $sPluginClass => $aProperties) {
foreach ($aProperties as $sProperty => $value) {
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
//$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
$sClass = $oFilter->GetClass();
$sClassAlias = $oFilter->GetClassAlias();
switch ($sOperation)
{
switch ($sOperation) {
case 'equals':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains);
foreach($aAttributes as $sAttribute)
{
foreach ($aAttributes as $sAttribute) {
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
@@ -425,7 +421,6 @@ class ValueSetObjects extends ValueSetDefinition
break;
case 'start_with':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains.'%');
foreach($aAttributes as $sAttribute)
@@ -442,63 +437,67 @@ class ValueSetObjects extends ValueSetDefinition
default:
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNameExpr = new FieldExpression('friendlyname', $sClassAlias);
$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));
if (empty($this->m_sValueAttCode)) {
$aAttToLoad = ['friendlyname'];
} else {
$aAttToLoad = [$this->m_sValueAttCode];
}
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($sClass);
$sImageAttr = MetaModel::GetImageAttributeCode($sClass);
if (!empty($sImageAttr)) {
$aAttToLoad [] = $sImageAttr;
}
$aComplementAttributeSpec = MetaModel::GetNameSpec($sClass, FriendlyNameType::COMPLEMENTARY);
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField)>0)
{
$aAttToLoad = array_merge ($aAttToLoad, [$oFilter->GetClassAlias() => $aAdditionalField]);
if (count($aAdditionalField) > 0) {
if (is_array($aAdditionalField)) {
$aAttToLoad = array_merge($aAttToLoad, $aAdditionalField);
} else {
$aAttToLoad [] = $aAdditionalField;
}
}
$oObjects->OptimizeColumnLoad($aAttToLoad);
while ($oObject = $oObjects->Fetch())
{
$aData=[];
if (empty($this->m_sValueAttCode))
{
$oObjects->OptimizeColumnLoad([$sClassAlias => $aAttToLoad]);
while ($oObject = $oObjects->Fetch()) {
$aData = [];
if (empty($this->m_sValueAttCode)) {
$aData['label'] = $oObject->GetName();
}
else
{
} else {
$aData['label'] = $oObject->Get($this->m_sValueAttCode);
}
if($oObject->IsObsolete())
{
$aData['obsolescence_flag']='1';
if ($oObject->IsObsolete()) {
$aData['obsolescence_flag'] = '1';
} else {
$aData['obsolescence_flag'] = '0';
}
else
{
$aData['obsolescence_flag']='0';
}
if (count($aAdditionalField)>0)
{
if (count($aAdditionalField) > 0) {
$aArguments = [];
foreach ($aAdditionalField as $sAdditionalField)
{
array_push ($aArguments,$oObject->Get($sAdditionalField));
foreach ($aAdditionalField as $sAdditionalField) {
array_push($aArguments, $oObject->Get($sAdditionalField));
}
$aData['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
} else {
$aData['additional_field'] = '';
}
else
{
$aData['additional_field']='';
if (!empty($sImageAttr)) {
/** @var \ormDocument $oImage */
$oImage = $oObject->Get($sImageAttr);
if (!$oImage->IsEmpty()) {
$aData['picture_url'] = $oImage->GetDisplayURL($sClass, $oObject->GetKey(), $sImageAttr);
$aData['initials'] = '';
} else {
$aData['initials'] = utils::ToAcronym($aData['label']);
}
}
$this->m_aValues[$oObject->GetKey()] = $aData;
}

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
//==========================================================================

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "display-block/all";

View File

@@ -1,6 +1,7 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "block-csv";
@import "block-list";

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-block-csv--textarea--width: 100% !default;
$ibo-block-csv--textarea--min-height: 10em !default;
$ibo-block-csv--textarea--margin-top: 10px !default;
@@ -8,10 +13,4 @@ $ibo-block-csv--textarea--margin-top: 10px !default;
min-height: $ibo-block-csv--textarea--min-height;
margin-top: $ibo-block-csv--textarea--margin-top;
}
}
.ibo-block-csv--download-link{
@extend .ibo-button;
@extend .ibo-is-alternative;
@extend .ibo-is-primary;
}

View File

@@ -0,0 +1,14 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-block-list--create-icon--margin-right: 0.5rem !default;
.ibo-block-list--empty-text, .ibo-block-list--create-action{
text-align: center;
}
.ibo-block-list--create-icon {
margin-right: $ibo-block-list--create-icon--margin-right;
}

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "tabular-fields-selector";

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "base";

View File

@@ -1,23 +1,12 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-scrollbar--scrollbar-width: 8px !default;
$ibo-scrollbar--scrollbar-height: $ibo-scrollbar--scrollbar-width !default; /* For horizontal scrollbars */
$ibo-scrollbar--scrollbar-track-background-color: $ibo-color-transparent !default;
$ibo-scrollbar--scrollbar-track-border-radius: $ibo-border-radius-500 !default;
$ibo-scrollbar--scrollbar-thumb-background-color: $ibo-color-grey-300 !default;
$ibo-scrollbar--scrollbar-thumb-border: none !default;
$ibo-scrollbar--scrollbar-thumb-border-radius: $ibo-border-radius-500 !default;
@@ -34,7 +23,9 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
/* CSS variables */
:root{
--ibo-scrollbar--scrollbar-width: #{$ibo-scrollbar--scrollbar-width};
--ibo-scrollbar--scrollbar-height: #{$ibo-scrollbar--scrollbar-height};
--ibo-scrollbar--scrollbar-track-background-color: #{$ibo-scrollbar--scrollbar-track-background-color};
--ibo-scrollbar--scrollbar-track-border-radius: #{$ibo-scrollbar--scrollbar-track-border-radius};
--ibo-scrollbar--scrollbar-thumb-background-color: #{$ibo-scrollbar--scrollbar-thumb-background-color};
--ibo-scrollbar--scrollbar-thumb-border: #{$ibo-scrollbar--scrollbar-thumb-border};
--ibo-scrollbar--scrollbar-thumb-border-radius: #{$ibo-scrollbar--scrollbar-thumb-border-radius};
@@ -60,9 +51,11 @@ $ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
/* - For Chrome/Edge/Safari */
&::-webkit-scrollbar {
width: var(--ibo-scrollbar--scrollbar-width);
height: var(--ibo-scrollbar--scrollbar-height);
}
&::-webkit-scrollbar-track {
background-color: var(--ibo-scrollbar--scrollbar-track-background-color);
border-radius: var(--ibo-scrollbar--scrollbar-track-border-radius);
}
::-webkit-scrollbar-thumb {
background-color: var(--ibo-scrollbar--scrollbar-thumb-background-color);

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* This is an overload of the default lib. stylesheet to use local fonts instead of the CDN */
@@ -21,125 +8,143 @@
font-family: Raleway;
font-weight: 100;
font-style: normal;
font-display: swap;
src: local('Raleway Thin'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Thin.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-100-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 100;
font-style: italic;
font-display: swap;
src: local('Raleway Thin'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Thin-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-100-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 200;
font-style: normal;
font-display: swap;
src: local('Raleway ExtraLight'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraLight.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-200-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 200;
font-style: italic;
font-display: swap;
src: local('Raleway ExtraLight'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraLight-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-200-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 300;
font-style: normal;
font-display: swap;
src: local('Raleway Light'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Light.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-300-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 300;
font-style: italic;
font-display: swap;
src: local('Raleway Light'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Light-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-300-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 400;
font-style: normal;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Regular.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-400-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 400;
font-style: italic;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Regular-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-400-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 500;
font-style: normal;
font-display: swap;
src: local('Raleway Medium'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Medium.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-500-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 500;
font-style: italic;
font-display: swap;
src: local('Raleway Medium'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Medium-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-500-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 600;
font-style: normal;
font-display: swap;
src: local('Raleway SemiBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-SemiBold.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-600-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 600;
font-style: italic;
font-display: swap;
src: local('Raleway SemiBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-SemiBold-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-600-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 700;
font-style: normal;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Bold.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-700-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 700;
font-style: italic;
font-display: swap;
src: local('Raleway'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Bold-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-700-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 800;
font-style: normal;
font-display: swap;
src: local('Raleway ExtraBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraBold.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-800-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 800;
font-style: italic;
font-display: swap;
src: local('Raleway ExtraBold'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-ExtraBold-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-800-italic.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 900;
font-style: normal;
font-display: swap;
src: local('Raleway Black'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Black.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-900-normal.woff') format('woff'),
}
@font-face {
font-family: Raleway;
font-weight: 900;
font-style: italic;
font-display: swap;
src: local('Raleway Black'),
url($approot-relative + 'node_modules/raleway-webfont/fonts/Raleway-Black-Italic.ttf') format('truetype'),
url($approot-relative + 'node_modules/@fontsource/raleway/files/raleway-all-900-italic.woff') format('woff'),
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -13,5 +13,7 @@
@import "object-details-with-tab-container";
@import "medallion-with-blocklist";
@import "input-within-datatable";
@import "field-badge-within-datatable";
@import "jquery-blockui-within-dialog";
@import "jquery-blockui-within-datatable";
@import "jquery-blockui-within-datatable";
@import "collapsible-section-within-caselog-list";

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -0,0 +1,29 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-caselog-entry-in-collapsible-section--body--background-color: transparentize($ibo-color-grey-100,0.5) !default;
$ibo-caselog-entry-in-collapsible-section--body--padding: 8px !default;
$ibo-caselog-entry-in-collapsible-section--body--color: $ibo-color-grey-900 !default;
/* - caselog display in ormcaselog */
.ibo-caselog-list {
.ibo-collapsible-section {
margin: 0;
min-width: 22em;
.ibo-collapsible-section--header .ibo-collapsible-section--title {
@extend %ibo-font-size-100;
}
.ibo-collapsible-section--body {
@extend %ibo-font-size-100;
color: $ibo-caselog-entry-in-collapsible-section--body--color;
padding: $ibo-caselog-entry-in-collapsible-section--body--padding;
background-color: $ibo-caselog-entry-in-collapsible-section--body--background-color;
}
}
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -0,0 +1,40 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-field-badge-within-datatable--ibo-field-badge--margin: 0 !default;
$ibo-field-badge-within-datatable--ibo-field-badge--padding: 0 !default;
$ibo-field-badge-within-datatable--ibo-field-badge--text-color: unset !default;
$ibo-field-badge-within-datatable--ibo-field-badge--background-color: unset !default;
$ibo-field-badge-within-datatable--ibo-field-badge-dot--size: 10px !default;
$ibo-field-badge-within-datatable--ibo-field-badge-dot--spacing-x: $ibo-field-badge--label--spacing-x !default;
$ibo-field-badge-within-datatable--ibo-field-badge-dot--background-color: $ibo-color-white-100 !default;
.ibo-datatable {
.ibo-field-badge {
margin: $ibo-field-badge-within-datatable--ibo-field-badge--margin;
padding: $ibo-field-badge-within-datatable--ibo-field-badge--padding;
color: $ibo-field-badge-within-datatable--ibo-field-badge--text-color;
background-color: $ibo-field-badge-within-datatable--ibo-field-badge--background-color;
&::before {
content: "";
display: inline-flex;
margin-right: $ibo-field-badge-within-datatable--ibo-field-badge-dot--spacing-x;
width: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
height: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
min-width: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
min-height: $ibo-field-badge-within-datatable--ibo-field-badge-dot--size;
background-color: var(--ibo-main-color);
@extend %ibo-border-radius-full;
}
.ibo-field-badge--decoration {
display: none;
+ .ibo-field-badge--label {
margin-left: unset;
}
}
}
}

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-within-datatable--attribute-set-item--padding-x: $ibo-input-set--item--padding-x !default;
$ibo-input-within-datatable--attribute-set-item--box-shadow: $ibo-elevation-100 !default;

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-vendors-blockui--blockoverlay--within--datatable--background-color: $ibo-color-white-100 !default;
.ibo-datatable .blockUI.blockOverlay{

View File

@@ -1,7 +1,8 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-vendors-blockui--blockoverlay--within--dialog--background-color: $ibo-vendors-jqueryui--ui-dialog--background-color !default;
.ui-dialog .blockUI.blockOverlay{

View File

@@ -1,7 +1,9 @@
.ibo-blocklist--empty-text, .ibo-blocklist--create-new{
text-align: center;
}
.ibo-blocklist--medallion{
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-block-list--medallion{
flex-direction: column;
align-items: center;
> .ibo-medallion-icon--image{

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-panel .ibo-panel--body{
.ibo-datatable{
width: 100%

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,8 +1,10 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-panel-within-main-content--margin-bottom: 200px !default;
$ibo-panel-within-main-content--sticky-sentinel-top--top: -1 * $ibo-main-content--padding-top !default;
$ibo-panel-within-main-content--sticky-sentinel-top--height: $ibo-main-content--padding-top !default;
@@ -17,6 +19,8 @@ $ibo-panel-within-main-content--header--top--is-sticky: -1 * $ibo-main-content--
* - Unlike in JS, there no easy way to find the closest descendant
*/
.ibo-panel.ibo-has-sticky-header {
margin-bottom: $ibo-panel-within-main-content--margin-bottom; /* Add a margin below the panel so the dropdown lists can open without problem (N°4039) */
/* Stickable header rules */
> .ibo-sticky-sentinel-top {
top: $ibo-panel-within-main-content--sticky-sentinel-top--top;

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */

View File

@@ -1,19 +1,19 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "alert";
@import "button";
@import "button-group";
@import "breadcrumbs";
@import "collapsible-section";
@import "quick-create";
@import "global-search";
@import "popover-menu/popover-menu";
@import "popover-menu/popover-menu-item";
@import "newsroom-menu";
@import "panel";
@import "collapsible-section";
@import "modal";
@import "dashlet/all";
@import "input/all";

View File

@@ -1,21 +1,10 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-breadcrumbs--margin-right: 32px !default;
$ibo-breadcrumbs--item--text-color: $ibo-color-grey-800 !default;
$ibo-breadcrumbs--item-icon--margin-x: 8px !default;
@@ -27,8 +16,26 @@ $ibo-breadcrumbs--item-label--max-width: 100px !default;
$ibo-breadcrumbs--item-separator--margin-x: 12px !default;
$ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
$ibo-breadcrumbs--previous-items-list-toggler--text-color: $ibo-color-grey-700 !default;
$ibo-breadcrumbs--previous-items-list-toggler--margin-right: 2 * $ibo-breadcrumbs--item-separator--margin-x !default;
$ibo-breadcrumbs--previous-items-list--top: 37px !default;
$ibo-breadcrumbs--previous-items-list--padding-y: 8px !default;
$ibo-breadcrumbs--previous-items-list--background-color: $ibo-color-white-100 !default;
$ibo-breadcrumbs--previous-item--text-color: $ibo-breadcrumbs--item--text-color !default;
$ibo-breadcrumbs--previous-item--padding-x: 12px !default;
$ibo-breadcrumbs--previous-item--padding-y: 12px !default;
$ibo-breadcrumbs--previous-item-label--max-width: 200px !default;
.ibo-breadcrumbs{
@extend %ibo-full-height-content;
position: relative;
margin-right: $ibo-breadcrumbs--margin-right;
@extend %ibo-full-height-content;
&.ibo-is-overflowing {
justify-content: right;
}
* {
display: flex;
@@ -40,13 +47,6 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
@extend %ibo-font-ral-med-100;
&:not(:last-child){
&::after{
content: '\f054';
margin: 0 $ibo-breadcrumbs--item-separator--margin-x;
color: $ibo-breadcrumbs--item-separator--text-color;
@extend %fa-solid-base;
}
&:hover{
.ibo-breadcrumbs--item-icon{
> *{
@@ -78,3 +78,50 @@ $ibo-breadcrumbs--item-separator--text-color: $ibo-color-grey-500 !default;
max-width: $ibo-breadcrumbs--item-label--max-width;
@extend %ibo-text-truncated-with-ellipsis;
}
.ibo-breadcrumbs--item,
.ibo-breadcrumbs--previous-items-list-toggler {
&:not(:last-child){
&::after{
content: '\f054';
margin: 0 $ibo-breadcrumbs--item-separator--margin-x;
color: $ibo-breadcrumbs--item-separator--text-color;
@extend %fa-solid-base;
}
}
}
.ibo-breadcrumbs--previous-items-list-toggler {
margin-right: $ibo-breadcrumbs--previous-items-list-toggler--margin-right;
color: $ibo-breadcrumbs--previous-items-list-toggler--text-color !important;
@extend %ibo-font-ral-med-100;
&:not(:last-child) {
&::after {
position: absolute; /* To be outside of the button */
right: -1 * $ibo-breadcrumbs--previous-items-list-toggler--margin-right;
}
}
}
.ibo-breadcrumbs--previous-items-list {
display: flex;
flex-direction: column;
align-items: stretch; /* For the items to occupy the full width */
position: fixed;
top: $ibo-breadcrumbs--previous-items-list--top;
padding: $ibo-breadcrumbs--previous-items-list--padding-y 0;
background-color: $ibo-breadcrumbs--previous-items-list--background-color;
@extend %ibo-elevation-300;
}
.ibo-breadcrumbs--previous-item {
color: $ibo-breadcrumbs--previous-item--text-color;
@extend %ibo-font-ral-med-100;
padding: $ibo-breadcrumbs--previous-item--padding-y $ibo-breadcrumbs--previous-item--padding-x;
.ibo-breadcrumbs--item-label {
max-width: 200px;
}
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -13,6 +13,9 @@ $ibo-button-group--elements-separator--width: 1px !default;
$ibo-button-group--elements-separator--border-left: 1px solid transparent !default;
.ibo-button-group {
display: inline-flex; /* Ensure that buttons of a same group always stay on the same line */
flex-wrap: nowrap;
.ibo-button {
position: relative;
}
@@ -45,8 +48,10 @@ $ibo-button-group--elements-separator--border-left: 1px solid transparent !defau
border-left: $ibo-button-group--elements-separator--border-left;
}
/* Note: Selector cannot be simplified in the one above as the "::before" must be after the different classes */
.ibo-button + .ibo-button {
/* Note: Selector cannot be simplified in the one above as the "::before" must be after the different classes.
* A more precise selector could be .ibo-button + .ibo-button, however .ibo-button is extended by multiple element and in a multiple loop it creates heavy selectors.
*/
> * + * {
@each $sType, $aColors in $ibo-button-colors {
@each $sColor, $aPseudoclasses in $aColors {
@each $sPseudoclass, $aAttributes in $aPseudoclasses {

View File

@@ -1,3 +1,8 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-button--sibling-spacing: 5px !default;
$ibo-button--padding-y: 6px !default;
$ibo-button--padding-x: 9px !default;
@@ -461,6 +466,7 @@ $ibo-button-colors: (
}
.ibo-button {
position: relative;
display: inline-block; /* Used to allow truncated text on .ibo-button--label */
padding: $ibo-button--padding-y $ibo-button--padding-x;
border: $ibo-button--border;
@@ -470,7 +476,7 @@ $ibo-button-colors: (
white-space: nowrap; /* To force sub elements to be on 1 line */
@extend %ibo-font-ral-sembol-100;
& ~ .ibo-button {
& + .ibo-button {
margin-left: $ibo-button--sibling-spacing;
}

View File

@@ -1,37 +1,24 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-collapsible-section--margin-top: 3rem;
$ibo-collapsible-section--title--color: $ibo-color-grey-900 !default;
$ibo-collapsible-section--body--background-color: $ibo-color-white-100 !default;
$ibo-collapsible-section--margin-top: 3rem !default;
$ibo-collapsible-section--title--color: $ibo-panel--title--color !default;
$ibo-collapsible-section--body--background-color: $ibo-panel--body--background-color !default;
$ibo-collapsible-section--highlight--height: 8px !default;
$ibo-collapsible-section--maximize-minimize-button--right: 5px !default;
$ibo-collapsible-section--maximize-minimize-button--color: $ibo-panel--collapsible-toggler--color !default;
$ibo-collapsible-section--maximize-minimize-button--right: $ibo-panel--collapsible-toggler--margin-right !default;
$ibo-collapsible-section--body--padding-bottom: 16px !default;
$ibo-collapsible-section--body--padding-top: $ibo-collapsible-section--body--padding-bottom + $ibo-collapsible-section--highlight--height !default;
$ibo-collapsible-section--body--padding-x: 16px !default;
$ibo-collapsible-section--body--border-radius: $ibo-border-radius-500 !default;
$ibo-collapsible-section--body--border-radius: $ibo-panel--body--border-radius !default;
$ibo-collapsible-section--body--border-size: 1px !default;
$ibo-collapsible-section--body--border-color: $ibo-color-grey-400 !default;
$ibo-collapsible-section--body--border-color: $ibo-panel--base-border-color !default;
/* Rules */
@@ -78,6 +65,7 @@ $ibo-collapsible-section--body--border-color: $ibo-color-grey-400 !default;
.ibo-collapsible-section--action-button {
&.ibo-collapsible-section--maximize-button, &.ibo-collapsible-section--minimize-button {
color: $ibo-collapsible-section--maximize-minimize-button--color;
margin-right: $ibo-collapsible-section--maximize-minimize-button--right;
}
}

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -77,11 +77,6 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
overflow-y: auto;
}
.ibo-datatable .ibo-field-badge {
margin: 0;
padding: 0px 4px;
}
// Datatables configure this list dialog
// Could be in a separate component, but is only used in datatables as of now

View File

@@ -1,6 +1,6 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-field-badge--margin: 0 !default;
@@ -10,10 +10,15 @@ $ibo-field-badge--border-radius: $ibo-border-radius-300 !default;
$ibo-field-badge--label--spacing-x: 0.5rem !default;
.ibo-field-badge {
display: inline-flex;
align-items: baseline;
margin: $ibo-field-badge--margin;
padding: $ibo-field-badge--padding;
border-radius: $ibo-field-badge--border-radius;
display: inline;
/* Mind the use of these "generic" variables that allow to automatically use the right colors for a field and its value depending on which class will be append to it (see those defined in the DM) */
background-color: var(--ibo-main-color);
color: var(--ibo-complementary-color);
}
.ibo-field-badge--decoration + .ibo-field-badge--label {

View File

@@ -1,4 +1,4 @@
/*!
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -23,24 +23,40 @@ $ibo-field--fullscreen-toggler--size: 20px !default;
$ibo-field--fullscreen-toggler--border-radius: $ibo-border-radius-500 !default;
$ibo-field--fullscreen-toggler--background-color--on-hover: $ibo-color-white-200 !default;
$ibo-field--value--scrollbar-track-background-color: $ibo-color-white-200 !default;
$ibo-field--value--padding-x--is-fullscreen: $ibo-field--label--padding-x--is-fullscreen !default;
$ibo-field--value--padding-top--is-fullscreen: $ibo-field--label--padding-y--is-fullscreen + 32px !default;
$ibo-field--value--padding-bottom--is-fullscreen: $ibo-field--label--padding-y--is-fullscreen !default;
$ibo-field--value-decoration--spacing-x: 0.5rem !default;
$ibo-field--enable-bulk--padding-y: 2px !default;
$ibo-field--enable-bulk--padding-x: 5px !default;
$ibo-field--enable-bulk--margin-left: 5px !default;
$ibo-field--enable-bulk--height: calc(100% - #{$ibo-field--enable-bulk--padding-x}) !default;
$ibo-field--enable-bulk--border-radius: $ibo-border-radius-500 !default;
$ibo-field--enable-bulk--checkbox--margin-left: 8px !default;
/* SCSS rules */
.ibo-field {
@extend %ibo-font-size-150;
/* Avoid value to overflow from its container with very long strings (typically URLs) */
/* Note: Some types of attribute must be excluding as it can alter their rendering */
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not(.ibo-input-file-select--container) {
/* We need the rule to apply for the class and all its descendants */
&:not([data-attribute-type="AttributeBlob"]):not([data-attribute-type="AttributeFile"]):not([data-attribute-type="AttributeImage"]):not([data-attribute-type="AttributeCustomFields"]):not(.ibo-input-file-select--container) {
/* We need the rule to apply for the class and all its descendants, hence the "&, & *" */
.ibo-field--value {
* {
&, & * {
word-break: break-word;
white-space: pre-wrap;
white-space: inherit; /* Here we don't put break-spaces as it would put ".ibo-field-small .ibo-field-value" on a new line due to the spaces/indentation of the HTML templates. For now we rather have this rule than diminish the templates readability/maintenability */
}
}
&.ibo-field-large {
.ibo-field--value {
&, & * {
white-space: break-spaces; /* For large fields we don't have the issue stated above */
}
}
}
}
@@ -57,7 +73,6 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
.ibo-field--label {
position: relative; /* Necessary for fullscreen toggler */
display: flex;
justify-content: space-between;
align-items: center;
max-width: initial;
width: 100%;
@@ -73,6 +88,11 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
width: 30%;
}
/* N°4318 - Visible scrollbar background for large fields overflowing to ease "limits" visualization by the user */
.ibo-field--value > * {
--ibo-scrollbar--scrollbar-track-background-color: $ibo-field--value--scrollbar-track-background-color;
}
/* Fullscreen mode */
&.ibo-is-fullscreen {
background-color: $ibo-field--background-color--is-fullscreen;
@@ -113,7 +133,7 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
@extend %ibo-hyperlink-inherited-colors;
@extend %ibo-fully-centered-content;
@extend %ibo-font-size-100;
@extend %ibo-font-size-50;
&:hover {
background-color: $ibo-field--fullscreen-toggler--background-color--on-hover;
@@ -138,6 +158,9 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
}
}
}
.ibo-field--label-small .ibo-field--label{
width: 20em;
}
.ibo-field--value {
display: table;
width: 100%;
@@ -155,14 +178,13 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
.ibo-field--comments {
display: table-cell;
vertical-align: top;
padding-right: 10px;
> input[type="checkbox"] {
margin-left: 5px;
float: right;
}
> .multi_values, > .mono_value {
> .multi_values, > .mono_value, > .ibo-field--comments--synchro{
float: right;
}
}
@@ -187,21 +209,20 @@ $ibo-field--value-decoration--spacing-x: 0.5rem !default;
}
}
.multi_values, .mono_value {
display: inline-block;
padding: 1px 3px;
margin-left: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
color: #fff;
.ibo-field--enable-bulk, .ibo-field--comments--synchro {
display: inline;
padding: $ibo-field--enable-bulk--padding-y $ibo-field--enable-bulk--padding-x;
margin: 0 0 0 $ibo-field--enable-bulk--margin-left;
height: $ibo-field--enable-bulk--height;
border-radius: $ibo-field--enable-bulk--border-radius;
font-weight: bold;
white-space: nowrap;
}
.mono_value {
background-color: #3c3;
.ibo-field--enable-bulk--checkbox {
margin-left: $ibo-field--enable-bulk--checkbox--margin-left;
}
.multi_values {
background-color: #c33;
}
.form_field ~ .form_field {
margin-top: $ibo-field--sibling-spacing;
}

View File

@@ -1,6 +1,6 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-fieldset--sibling-spacing: 48px !default;

View File

@@ -1,6 +1,6 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-input-file-select--file-name-margin-left: 10px !default;

View File

@@ -1,4 +1,9 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-prop-header {
@extend %ibo-font-size-150;
padding-bottom: 14px;
}

View File

@@ -1,4 +1,4 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,4 +1,4 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
@@ -22,7 +9,7 @@ $ibo-global-search--head--background-color: $ibo-color-white-100 !default;
$ibo-global-search--icon-padding-x: 16px !default;
$ibo-global-search--icon-padding-y: 0 !default;
$ibo-global-search--input--padding: 0 default;
$ibo-global-search--input--padding: 0 !default;
$ibo-global-search--input--padding-x--is-opened: 8px !default;
$ibo-global-search--input--padding-y--is-opened: 8px !default;
$ibo-global-search--input--width: 0 !default;
@@ -102,6 +89,7 @@ $ibo-global-search--compartment--placeholder-hint--text-color: $ibo-color-grey-7
padding: $ibo-global-search--input--padding;
width: $ibo-global-search--input--width;
color: $ibo-global-search--input--text-color;
background-color: transparent;
@extend %ibo-font-ral-nor-300;
border: none;

View File

@@ -1,7 +1,8 @@
/*!
* copyright Copyright (C) 2010-2021 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-medallion-icon--padding-y: 13px !default;
$ibo-medallion-icon--padding-x: 0 !default;

View File

@@ -0,0 +1,4 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/

View File

@@ -1,19 +1,6 @@
/*!
* Copyright (C) 2013-2021 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
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */

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