Compare commits

...

726 Commits

Author SHA1 Message Date
Eric
fc24746862 N°2847 - Fix "Add some criterion..." message on OQL menus 2020-11-24 15:38:04 +01:00
Eric
d0baf298be N°2847 - Datatable Fix n-n links 2020-11-24 14:54:32 +01:00
acognet
fb70026966 N°2847 - Tranform old itop datatable to jquery DataTable - Fix management of button "Add" step 2 2020-11-24 14:21:28 +01:00
Molkobain
ad19a89fc5 N°2847 - Fix iTopWebPage's header / footer API layout 2020-11-24 13:30:29 +01:00
Eric
7b937016d0 N°2847 - Datatable sortable columns tooltip 2020-11-24 11:37:23 +01:00
Eric
e8f7c7dafe N°2847 - Datatable cursor on sortable columns 2020-11-24 11:32:53 +01:00
Eric
d9264e5181 N°2847 - Datatable Fix external keys "undefined" 2020-11-24 11:25:26 +01:00
Pierre Goiffon
f893603332 🔧 editor config : else on same line in JS 2020-11-24 11:03:59 +01:00
Stephen Abello
a49a56def8 N°2847 Correctly center external key value on edition 2020-11-24 09:35:09 +01:00
Molkobain
2f138db4ac N°2847 - Work on object details structure 2020-11-23 23:17:12 +01:00
Molkobain
8d19958d6d N°2847 - ActivityPanel: Rework for new UX in edition/creation
- Hide main action buttons
- Remove activity tab entry form
- Send caselogs value with global form
2020-11-23 23:17:12 +01:00
acognet
11b6429e3c N°2847 - Tranform old itop datatable to jquery DataTable - Fix management of button "Add" 2020-11-23 19:59:55 +01:00
Eric
dccffc91b1 N°2847 - Datatable Fix preferences 2020-11-23 17:54:00 +01:00
Pierre Goiffon
6c5e1c429f 🎨 Remove auto closing tags for CSS
We have an HTML 5 Doctype, we're not using XHTML anymore
2020-11-23 15:09:45 +01:00
Molkobain
7d0f1f46d3 N°2847 - ActivityPanel: Rework for new UX
- Add MetaModel::GetCaseLogs($sClass) function
- Rename ActivityNewEntryForm to CaseLogEntryForm
- Rework ActivityPanel and CaseLogEntryForm markup / CSS
- Change for 1 CaseLogEntryForm per tab (caselogs and activity) with specific "Add entry..." choices
2020-11-23 11:11:05 +01:00
Molkobain
1e7d4e5c31 N°2847 - PopoverMenu: Fix quotes not being escaped in JS menu items 2020-11-23 10:08:21 +01:00
Molkobain
b12dadb0dc N°2847 - Button: Fix icon right margin and missing data role in markup 2020-11-23 10:08:20 +01:00
Molkobain
c6506c9f13 N°2847 - UIContentBlock: Rename AddDataAttributes method and fix PHPDoc 2020-11-23 10:08:18 +01:00
Molkobain
af11577450 N°2847 - iTopWebPage: Remove deprecated JS FixPaneVis() function (not used anymore since 3.0.0) 2020-11-23 10:08:17 +01:00
Molkobain
91ab66453b Remove jQuery Migrate "traces" logs, keeping only "warnings" 2020-11-23 10:08:16 +01:00
Stephen Abello
b1102bc68c N°2847 Re-generate precompiled css files 2020-11-20 10:45:16 +01:00
Stephen Abello
0443197c04 N°2847 Move Advance search css from light-grey to a dedicated file and tweak its style for 3.0 2020-11-20 10:32:52 +01:00
Eric
32e0c8f9bf N°2847 - Datatable modal dialogs 2020-11-19 17:45:46 +01:00
Eric
26d912f059 Fix breadcrumb 2020-11-19 16:02:11 +01:00
Molkobain
3cb5f3d07e N°2847 - Improve fatal error page message 2020-11-19 14:53:23 +01:00
Molkobain
675a408ab9 N°2847 - Activity panel: Pass host object display mode 2020-11-19 13:45:10 +01:00
Stephen Abello
2f938814e0 N°2847 Forgot to include aef77d8 illustration file in fatal errors alpha/development message 2020-11-19 13:43:05 +01:00
Stephen Abello
8042afa20f N°2847 Light fix to setup style 2020-11-19 13:39:27 +01:00
Stephen Abello
aef77d8f93 N°2847 Add message on alpha/development fatal error pages 🤫 2020-11-19 13:39:04 +01:00
Stephen Abello
05c53d35b7 N°2847 Update login screen style to better fit with iTop 3.0 2020-11-19 11:00:41 +01:00
Stephen Abello
ea169e8358 N°2847 Fix User menu not displaying on carret click 2020-11-19 10:45:39 +01:00
Stephen Abello
28e80e5568 N°2847 Finish style and refactor SCSS for datatables 2020-11-19 10:36:01 +01:00
BenGrenoble
487e0ad5a7 N°3440 Datamodel correction following Eric input 2020-11-18 16:19:16 +01:00
acognet
106dfc63d5 N°2847 - Tranform old itop datatable to jquery DataTable - manage popup "Configure this list" 2020-11-18 12:31:42 +01:00
acognet
5115db1b01 N°2847 - Tranform old itop datatable to jquery DataTable - add new style for MenuBlock 2020-11-17 15:18:05 +01:00
acognet
9621ff4d5e N°2847 - Tranform old itop datatable to jquery DataTable - fix diplay 2020-11-17 11:54:52 +01:00
Eric
1c5cb4e125 Fix datamodel 2020-11-17 10:50:49 +01:00
Stephen Abello
dc6a900f6d N°2847 Add css class to TagSet and Password input error message 2020-11-17 10:43:28 +01:00
Stephen Abello
0d23faba1c N°2847 Fix select input and duration input elements going to newline 2020-11-17 10:42:55 +01:00
Stephen Abello
c861941e92 N°2847 Fix input background color on error 2020-11-17 10:41:10 +01:00
Eric
313df4551f N°2847 - Datatable Fix selection 2020-11-17 09:42:26 +01:00
Pierre Goiffon
07cc8bf508 N°3436 fix Setup
Was blocked because using iTop 3.0.0 with datamodels/2.x dir : \SetupUtils::GetCompatibleDataModelDir wasn't ok with that :o)
This method is no more present, and we're calling \SetupUtils::GetLatestDataModelDir only
In other words, we're picking the latest datamodel/*.x dir (greatest number)
2020-11-16 17:51:52 +01:00
Pierre Goiffon
c2e31de263 🎨 SetupUtils fix methods modifiers + formatting 2020-11-16 17:39:32 +01:00
Eric
103b5bfd22 Merge remote-tracking branch 'origin/datatable' into feature/backoffice-full-moon-design 2020-11-16 15:24:07 +01:00
Eric
4c78ecab91 N°2847 - Datatables WIP 2020-11-16 15:02:02 +01:00
Pierre Goiffon
48bf954387 🔊 DisplayBlock add error message on error if user is admin
Previously only an IssueLog was made, which was confusing
The log is limited to admin users as it contains the exception message, which could expose sensitive info
2020-11-16 11:21:20 +01:00
Eric
89c35f0345 N°2847 - Datatables Fix external keys search 2020-11-16 11:04:16 +01:00
Eric
434c829f7e Fix update objects 2020-11-16 11:04:16 +01:00
Eric
4c8a8fc510 N°2847 - Datatables Fix external keys search 2020-11-16 11:04:16 +01:00
acognet
0a02c10287 N°2847 - Tranform old itop datatable to jquery DataTable - fix diplay 2020-11-16 10:40:35 +01:00
Molkobain
d4516c214c N°2847 - Input : Fix vertical alignment and horizontal padding 2020-11-15 17:56:32 +01:00
Molkobain
931225ed5a N°2847 - Activity panel: Work on the global UX
- Fix collapse/expand icon not toggling
2020-11-15 17:56:32 +01:00
Stephen Abello
18ed151e50 N°2847 Add placeholder to menu filter 2020-11-13 10:05:48 +01:00
acognet
0046834b2c N°2847 - Tranform old itop datatable to jquery DataTable - add sort 2020-11-13 08:51:16 +01:00
acognet
6835ebd11d N°2847 - Tranform old itop datatable to jquery DataTable - show + button in search box 2020-11-12 12:35:11 +01:00
Eric
62880b04eb N°2847 - Datatables pagination CSS 2020-11-12 11:59:56 +01:00
Eric
6d82a85b12 N°2847 - Datatables for forms 2020-11-12 11:44:06 +01:00
Stephen Abello
64134ff428 N°2847 Add silo selector to navigation menu 2020-11-12 11:00:59 +01:00
Stephen Abello
c7661bcfcf Update TODO format 2020-11-12 11:00:59 +01:00
Eric
291041610b set_time_limit accept only int in PHP7.4 2020-11-12 10:57:13 +01:00
Molkobain
a729c6c1e3 Fix typo (thanks @steffunky) 2020-11-12 09:42:39 +01:00
Eric
23452804aa N°2847 - Datatables for static data 2020-11-10 17:30:53 +01:00
Molkobain
1098182720 Update pre-compiled themes 2020-11-10 16:28:17 +01:00
Molkobain
77ecd055b4 N°2847 - Fix crash when object with state attribute has no state defined 2020-11-10 15:55:20 +01:00
Molkobain
38fbc49e51 N°2844 - Fix layout for object creation and edition pages (activity panel, title, ...) 2020-11-10 15:41:33 +01:00
Molkobain
f9b905403d N°3389 - Add integration test (XML version and app. core version must be aligned from now on) 2020-11-10 10:33:22 +01:00
Molkobain
96130735c4 N°3287 - Add french translations 2020-11-10 09:46:00 +01:00
Molkobain
448afc640d Internal: Change XML version from 1.8 to 3.0 as it will now follow iTop core version numbering 2020-11-09 19:48:28 +01:00
Molkobain
f2ff5a4e83 N°3203 - Datamodel: Add semantic for image & state attributes Part. II 2020-11-09 15:44:45 +01:00
Pierre Goiffon
b44e6a4a53 Fix ThemeHandlerTest
Regression introduced by f718b26b : the test expected SCSS file was modified in sync with modifications done in iTop SCSS files. No need to do that as this file is only used in this test.
2020-11-09 15:15:12 +01:00
Eric
866a93c0c8 Fix unit tests 2020-11-09 11:57:56 +01:00
Eric
d7b1e60219 Merge remote-tracking branch 'origin/feature/backoffice-full-moon-design' into datatable 2020-11-09 11:33:48 +01:00
Pierre Goiffon
63d52787f0 Commit CSS modified by d76e425 2020-11-09 09:37:29 +01:00
Eric
bc809b0f3b N°2847 - cleanup code 2020-11-09 08:00:26 +01:00
Eric
f64cad75cd Fix error when history contains a now invalid attribute 2020-11-06 14:55:59 +01:00
Eric
6237b5565c N°2847 - Fix panel toolbars 2020-11-06 09:08:12 +01:00
Molkobain
d76e4255f2 N°2950 - Fix syntax highlighting (CKEditor) not working on AttributeHTML
- Fix for not supported attributes (HTML)
- Migrate selectors for new backoffice markup
2020-11-04 17:25:25 +01:00
Molkobain
fa0bb2b466 N°3203 - Fix regression: PHP notice on object with no class icon 2020-11-04 16:15:18 +01:00
Molkobain
8954d02796 PHPDoc & warnings suppression 2020-11-04 16:15:17 +01:00
Eric
87a2d30c78 N°2847 - revert fieldset style for extensions before 3.0.0 2020-11-04 14:21:28 +01:00
acognet
f718b26b7b N°2847 - Tranform old itop datatable to jquery DataTable 2020-11-04 13:36:20 +01:00
Eric
7cfd888516 N°2847 - fieldset style for extensions before 3.0.0 2020-11-04 12:02:49 +01:00
Eric
cd10c41362 N°2847 - fieldset style for extensions before 3.0.0 2020-11-04 11:50:38 +01:00
Eric
eb391d52fc N°2847 - fieldset style for extensions before 3.0.0 2020-11-04 11:30:46 +01:00
Eric
1fc54edf21 N°2847 - Fix ajax tabs 2020-11-04 10:28:40 +01:00
Eric
cfbfaad154 N°2847 - Dashboards edition 2020-11-03 16:15:03 +01:00
Molkobain
b7543b54e8 N°2847 - Field: Restore HTML metadata lost during migration, add missing ID in template 2020-11-03 15:10:15 +01:00
Molkobain
10afd1cede N°2847 - Panel: Fix top padding being too small since integration with tab containers 2020-11-02 18:04:03 +01:00
Molkobain
b454958742 N°2847 - Refactor Button block for a better use of semantic colors, also change "primary" action color to something less flashy 2020-11-02 17:39:10 +01:00
Molkobain
680f522065 N°2847 - Refactor color palettes SCSS files for a better comprehension 2020-11-02 16:38:01 +01:00
Molkobain
50421f4753 N°3203 - Datamodel: Add semantic for image & state attributes Part. I 2020-11-02 14:51:25 +01:00
Pierre Goiffon
7864461d85 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	core/cmdbobject.class.inc.php
#	datamodels/2.x/itop-bridge-cmdb-ticket/datamodel.itop-bridge-cmdb-ticket.xml
#	datamodels/2.x/itop-structure/datamodel.itop-structure.xml
2020-11-02 10:30:44 +01:00
Eric
be338e385b N°2847 - Dashboards 2020-11-02 09:59:31 +01:00
Pierre Goiffon
235b4be790 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-10-30 18:09:44 +01:00
BenGrenoble
dfcc3b22a6 Location/physicaldevice_list from structure to bridge. 2020-10-30 16:47:19 +01:00
Eric
f3e47a15f6 N°2847 - Dashlet fix header dynamic 2020-10-30 10:39:58 +01:00
Stephen Abello
a70ed7fcc2 N°2847 Lighter error display on inputs firts validation
Note: Doesn't work with tagsets and enumsets
2020-10-30 10:13:59 +01:00
Eric
c43d52277c N°2847 - Dashlet header static and text 2020-10-29 18:06:04 +01:00
Eric
8a1d17551b N°2847 - Dashlet badge 2020-10-29 17:08:03 +01:00
Eric
6e584cded4 N°2847 - Dashlet group by 2020-10-29 16:15:24 +01:00
Eric
6c7efb2448 N°2847 - Dashlet header dynamic and badges 2020-10-29 15:20:44 +01:00
Stephen Abello
3548d5e264 N°2847 Fix inputs border color 2020-10-29 10:10:35 +01:00
Eric
bd606ca3f9 N°2847 - Move dashboard title and menu to the top bar 2020-10-28 15:43:42 +01:00
BenGrenoble
6c5c71a0ee Contracts_list/Services_list from structure to bridge cmdb ticket 2020-10-28 14:08:00 +01:00
BenGrenoble
dfd9dd3352 Move cis_list from DocumentNote, DocumentWeb, DocumentFile to bridge cmdb ticket 2020-10-28 10:01:55 +01:00
BenGrenoble
6583c9495a Translation Fieldset person 2020-10-28 09:17:58 +01:00
BenGrenoble
93132dc3b4 Person_list in location 2020-10-28 08:49:05 +01:00
BenGrenoble
f257dcfb85 ConfigManagementCI and CI+ in structure dict 2020-10-28 08:48:46 +01:00
Pierre Goiffon
776f32dbe1 🎨 Block ID generation : less risk of id collision
Reported by NonSecureUniqidUsageInspection : when calling \uniqid we should provide true as second parameter.

But was causing issues as the generated string could contain a dot, and this is used in CSS and JQuery selectors (for example a "my.id" cannot be used in #my.id selector)

So we're just replacing dots with hyphens !

Was discussed with @Molkobain in 0119f6c395
2020-10-27 16:58:25 +01:00
Eric
8502fa7721 N°3294 - Display and refresh counters in OQL menu entries 2020-10-27 15:11:30 +01:00
Eric
48b3716278 N°3294 - Fix CSS typo 2020-10-27 11:43:31 +01:00
Pierre Goiffon
5f9cdc0431 Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	test/integration/iTopModulesPhpVersionChecklistTest.php
2020-10-27 10:48:39 +01:00
Pierre Goiffon
6df74073a7 🐛 Fix \Combodo\iTop\Application\UI\UIBlock::GenerateId 2020-10-27 08:42:25 +01:00
Stephen Abello
b59c40570b N°2847 WIP First iteration on form inputs 2020-10-26 17:05:57 +01:00
Pierre Goiffon
444f60c251 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	core/cmdbobject.class.inc.php
2020-10-26 14:31:49 +01:00
Pierre Goiffon
2425ccb8f2 Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	css/css-variables.scss
#	datamodels/2.x/version.xml
2020-10-26 14:30:33 +01:00
Eric
4fd65a8a62 N°3294 - WIP: Introduce counter in OQL menu entries 2020-10-26 13:37:32 +01:00
Eric
664d3251cc N°3294 - WIP: Introduce counter in OQL menu entries 2020-10-26 12:07:09 +01:00
Molkobain
f31f991365 N°2836 - Portal: Fix image coverage of the medallion 2020-10-25 15:37:07 +01:00
Molkobain
bb1a8a92dd N°2847 - Fix Quick create / Global search drawer position when closed and history full (10 items) 2020-10-25 00:01:58 +02:00
Molkobain
27e1f3d64b N°2836 - Portal: Introduce bubbles conversation as default caselog rendering 2020-10-24 15:22:42 +02:00
Molkobain
95a2ffa0f2 N°2224 - Portal: Enable tooltips for object's attributes description in dynamically added content as well (AJAX, forms, ...) 2020-10-22 21:29:53 +02:00
Molkobain
b1e29cbd81 Fix call to Field::AddMetadata() to remove $sValue as null (regression introduced in cfd9dba6) 2020-10-22 21:28:52 +02:00
Molkobain
2e97b3b593 N°2847 - Add reminders / info message for refactoring 2020-10-22 16:11:58 +02:00
Molkobain
e419060e8a Add type hinting to methods parameters 2020-10-22 16:06:00 +02:00
Molkobain
cfd9dba66e N°2224 - Portal: Enable tooltips for object's attributes description 2020-10-22 16:05:59 +02:00
Molkobain
f72ddd72f3 N°2847 - Tooltip: Add new "Tippy" and "Popper" libs to the portal 2020-10-22 16:05:59 +02:00
Molkobain
d85d29155f N°2847 - Tooltip: Improve mechanism to instantiate them only once 2020-10-22 16:05:59 +02:00
Molkobain
91ee5ebfeb N°2847 - Tooltip: Move new wrappers from CombodoBackofficeToolbox into CombodoGlobalToolbox so it can be used in any GUIs 2020-10-22 16:05:59 +02:00
Molkobain
a6ea75f5fe N°2847 - Rename backoffice.js into backoffice/toolbox.js for better consistency with other toolboxes 2020-10-22 16:05:58 +02:00
Molkobain
dc789f51e9 Warnings suppression 2020-10-22 16:05:58 +02:00
Molkobain
116c0c0138 Portal: Fix double HTML encoding of fields metadata (label, raw value) 2020-10-22 16:05:58 +02:00
Pierre Goiffon
7fa7ebb59c 📝 Fix 2.9.0 version present in deprecated mentions 2020-10-21 19:24:24 +02:00
Pierre Goiffon
0119f6c395 UiBlock : fix typo + inspections 2020-10-21 19:08:17 +02:00
Pierre Goiffon
c8a30b0ccb 📝 N°2286 jquery-layout not used in the whole console anymore 2020-10-21 16:06:06 +02:00
Pierre Goiffon
8e31d77afb N°2591 fix deprecated info 2020-10-21 12:09:06 +02:00
Pierre Goiffon
c7dce73116 Remove dataprovider in iTopModulesPhpVersionIntegrationTest::testITopModulesPhpVersion (#175)
Remove dataprovider in \Combodo\iTop\Test\UnitTest\Integration\iTopModulesPhpVersionIntegrationTest::testITopModulesPhpVersion
The dataprovider was slow, and was very fragile because it contained much PHP code : as seen in 2221e05 when an error appears here it is very difficult to trace :/
So now we have a traditional PHPUnit test method, and still we are testing everytime all modules (not stopping on the first error : we have only one assert at the end instead of an assert per module), and also we're getting the list of modules having errors at the end
2020-10-20 17:10:36 +02:00
Molkobain
71bb071f38 N°2907 - Keep (standard) read-only tabs visible in edit mode
Specifics:
    - User / Grant matrix
    - Subnet / IP Usage
    - Incident / Known errors
    - UserRequest (Simple/ITIL) / Known errors
Generics:
    - History (moved to activity panel)
    - Notifications
2020-10-19 22:21:33 +02:00
Molkobain
deda05351b N°3182 - Show menu group initials when no CSS decoration classes specified 2020-10-19 21:23:07 +02:00
Pierre Goiffon
5d1de543cc Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	core/metamodel.class.php
2020-10-19 15:46:16 +02:00
Pierre Goiffon
be9a428529 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-10-19 15:38:11 +02:00
odain
e7360661be N°3381 - finish integrating healthcheck status.php page 2020-10-19 11:42:48 +02:00
Molkobain
6edb308480 N°3379 - Introduce more modern tooltip lib. in the backoffice
- Allow usage of quotes, especially in attributes description
- Allow line breaks, especially in attributes description
- Allow better positioning (automatic) when close to the screen limits
- Allow HTML (careful about XSS), content is sanitized by default
2020-10-16 11:10:51 +02:00
Pierre Goiffon
43a4d9fcd2 📝 PHPDoc for \cmdbAbstractObject::GetDisplaySet 2020-10-15 16:07:02 +02:00
Molkobain
9a47e3cf74 N°2844 - Continue work on object details: Quick fixes for alpha, will need to be refactored
- Improve display of details in creation/modification/transition modes
2020-10-15 15:55:05 +02:00
Molkobain
e7f01e7597 N°2847 - Alert: Improve spacing when followed by another alter or something else 2020-10-15 15:55:04 +02:00
Vincent Dumas
910358e04d #2639 Enrich tooltips for Action class 2020-10-15 13:12:59 +02:00
odain
4eebb538df 💚 add beforeSetup phpunit group to run test before setup 2020-10-14 18:09:23 +02:00
odain
d304284e36 💚 cleanup 2020-10-14 13:13:25 +02:00
odain
6e6557e909 💚 add beforeSetup phpunit group to run test before setup 2020-10-14 13:03:44 +02:00
Pierre Goiffon
548342159b Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-10-14 11:41:58 +02:00
Pierre Goiffon
2221e0541c 🐛 Fix version methods throwing exceptions with version 3.0.0 2020-10-14 11:31:02 +02:00
Molkobain
b15ed80732 Internal: Replace references to iTop 2.8.0 with iTop 3.0.0 2020-10-14 10:16:49 +02:00
Molkobain
986c08aa81 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-10-14 10:09:14 +02:00
Molkobain
8e6351d346 Internal: Replace references to iTop 2.8.0 with iTop 3.0.0 2020-10-14 10:04:10 +02:00
Molkobain
9b19ae5944 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-10-14 09:54:22 +02:00
Molkobain
e35965c065 N°2982 - Move precompiled files to proper module and update XML migration functions 2020-10-14 09:49:21 +02:00
Pierre Goiffon
5b2c279eac Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	js/components/breadcrumbs.js
2020-10-12 12:51:05 +02:00
Pierre Goiffon
72fc22c49a Merge remote-tracking branch 'origin/support/2.7' into develop 2020-10-12 12:49:17 +02:00
Molkobain
b7b1ef1a1f Fix double-encoded breadcrumbs entry introduced by 1e634a8bba 2020-10-09 20:54:51 +02:00
Molkobain
64a96ac001 N°2847 - Dashboard: Improve grid alignments and design of the badge dashlet 2020-10-09 18:42:53 +02:00
Thomas Casteleyn
eb3ad0999a Cleanup utils::GetSessionLog() (#172) 2020-10-09 15:59:24 +02:00
Molkobain
ecf678f39f N°2847 - Quick create: Refactor history truncation 2020-10-09 09:49:26 +02:00
Molkobain
5bc01d3950 N°2847 - Global search: Add config. parameter for max. history results ('global_search.max_history_results') 2020-10-09 09:49:26 +02:00
Stephen Abello
2ae50dfb06 N°2847 Fix Activity new entry form caselog picker disappearing after submitting once 2020-10-09 09:26:39 +02:00
Stephen Abello
b25a6a2f04 N°2847 Correctly open/close Activity new entry form 2020-10-09 09:26:39 +02:00
Molkobain
1bd9d35979 N°2847 - Quick create: Add config. parameter for max. history results ('quick_create.max_history_results') 2020-10-09 09:21:37 +02:00
Molkobain
1dd4d1479c N°2847 - Tab container: Fix min width (alignment with medallion) with short tab labels 2020-10-09 00:06:21 +02:00
Molkobain
00212e8127 N°2847 - Dashboard: Start rework for demo 2020-10-09 00:04:13 +02:00
Molkobain
061f3e9f51 N°2847 - Quick create: Add config. parameter for max. autocomplete results ('quick_create.max_autocomplete_results') 2020-10-08 18:17:35 +02:00
Stephen Abello
e6111609b0 N°3208 Finish Quick object creation component 2020-10-08 17:15:27 +02:00
Molkobain
3556a95f3b N°2847 - Tab container: Fix tab content horizontal alignment 2020-10-08 17:01:59 +02:00
Pierre Goiffon
82735254e7 Compiled CSS
As generated by the setup
2020-10-08 16:27:05 +02:00
Molkobain
44351784e6 N°2847 - Work on the tab container
- Fix CSS class names on vertical mode
- Add class constants
2020-10-08 16:25:45 +02:00
Molkobain
f87002dce3 Introduce CombodoBackofficeToolbox like in the portal to:
- Avoid name collision with other libs for our functions
- Document a set a function developers can use in their extensions, will be completed as things progress
2020-10-08 15:06:22 +02:00
Molkobain
c616e9b65f Remove swfobject.js from iTopWebPage as it was lost during previous merge 2020-10-08 14:38:34 +02:00
Stephen Abello
03418663f2 N°2847 Small scss fix to style JQuery UI modals 2020-10-08 13:30:31 +02:00
Stephen Abello
5a072c75eb N°2847 Fix object details width when using vertical tabs 2020-10-08 10:38:56 +02:00
Pierre Goiffon
10a945eb92 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-10-07 18:48:56 +02:00
Pierre Goiffon
6239c29422 Fix tests
3 files having wrong datamodel XML version
2020-10-07 18:48:46 +02:00
Pierre Goiffon
bb53aef09f Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	application/datamodel.application.xml
2020-10-07 18:37:06 +02:00
Stephen Abello
6cbc7fdb20 N°2847 Fix hardcoded values for Activity New Entry object 2020-10-07 17:01:44 +02:00
Stephen Abello
6a87ce48e1 N°2847 Renamed and moved files for Activity New Entry (thanks to @Molkobain comment on 157e0a6) 2020-10-07 17:01:44 +02:00
Stephen Abello
7770cb31fb N°2847 Add vertical tab options to object details 2020-10-07 17:01:44 +02:00
Molkobain
3adcee6da8 Fix regression introduced in 3b6a03d703 2020-10-07 16:01:23 +02:00
Molkobain
ff3ade7d62 Missing parts of the previous merge 2020-10-07 15:32:18 +02:00
Molkobain
ad59e81144 N°2847 - Activity panel: Improve width when expanded 2020-10-07 13:33:38 +02:00
Molkobain
67ab33e727 Merge branch 'feature/backoffice-full-moon-design' of https://github.com/Combodo/iTop into feature/backoffice-full-moon-design 2020-10-07 13:25:12 +02:00
Molkobain
e81e3a0f1c Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-10-07 13:18:14 +02:00
Stephen Abello
e89afa163e N°2847 Remove RichText from preferences 2020-10-07 11:36:44 +02:00
Molkobain
e48d257b00 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	application/ajaxwebpage.class.inc.php
#	application/csvpage.class.inc.php
#	application/displayblock.class.inc.php
#	application/itopwebpage.class.inc.php
#	application/utils.inc.php
#	application/webpage.class.inc.php
#	application/xmlpage.class.inc.php
#	datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml
#	datamodels/2.x/itop-knownerror-light/datamodel.itop-knownerror-light.xml
#	datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml
#	datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml
#	js/components/breadcrumbs.js
#	pages/navigator.php
#	test/core/DBSearchTest.php
2020-10-07 11:36:15 +02:00
BenGrenoble
51669d0479 Fix datamodel Navigation rules and module parameters 2020-10-07 11:08:11 +02:00
BenGrenoble
f983655c68 Fix datamodel Branding 2020-10-07 11:07:34 +02:00
Stephen Abello
157e0a6915 N°2847 Add backend for new entry form for activity panel 2020-10-07 09:46:23 +02:00
Pierre Goiffon
7d26b6092e Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	.idea/codeStyles/codeStyleConfig.xml
#	.idea/inspectionProfiles/Combodo.xml
2020-10-06 10:42:55 +02:00
Molkobain
f5e92359d9 N°2847 - Form: Fix return type in Form::GetAction() 2020-10-05 17:13:32 +02:00
Molkobain
8994f82e0f N°2847 - Fieldset: Fix alignement/positionning with siblings, parent (also some renaming and variables extraction) 2020-10-05 17:03:17 +02:00
Molkobain
8551322cd7 N°2847 - Multi columns layout: Fix alignment with parent (also some renaming to match conventions) 2020-10-05 16:26:41 +02:00
Molkobain
be075dd695 N°2847 - Object details: Fix alert messages overlapping header and multicolumn layout 2020-10-05 15:53:45 +02:00
Molkobain
0691fca412 Add @since to newly created constants 2020-10-05 13:22:29 +02:00
Molkobain
3298966174 N°3279 - Change "user_id" from integer to external 2020-10-05 13:21:27 +02:00
Molkobain
79514358db N°2847 - Tab container: Fix some issues
- Fix extra tabs list being behind the tab content
- Sanitize tab ID in the HTML
2020-10-05 11:03:23 +02:00
Molkobain
aa38be4578 Improve JSDoc of IsElementVisibleToTheUser() 2020-10-05 11:03:22 +02:00
Molkobain
e911290b3d Improve utils::Sanitize()
- Add class constants for $sSanitizationFilter values (You should use them instead of hard-coding the value, for example: utils::ENUM_SANIZATION_FILTER_XXX)
- Fix "element_identifier" filter of utils::Sanitize() to keep "-" character
- Add 'sanitize_identifier' filter to TWIG
2020-10-05 11:03:22 +02:00
Molkobain
dc24bbcd97 N°2847 - Comment old history tab (waiting for decision on the DisplayBareHistory before removing it) 2020-10-05 08:45:12 +02:00
Molkobain
ea8a8e3afa N°2847 - Add lifecycle states default colors 2020-10-05 08:43:32 +02:00
Molkobain
6fb5d20971 N°2847 - Tab container: Improve global look & feel; responsive tabs. 2020-10-04 23:22:41 +02:00
Molkobain
b3522017e7 Add new JS helper to know if an element is visible to user 2020-10-04 18:37:12 +02:00
Molkobain
e5b22d270e N°2847 - Add SCSS helper for hyperlinks to inherit colors from their parent instead of being of the default color (brand primary) 2020-10-03 23:41:50 +02:00
Molkobain
b8af22baad N°2847 - Fix object details being too wide 2020-10-02 22:09:18 +02:00
Molkobain
5626da7a5e N°2847 - Object details: Preliminary work
- Add ObjectFactory for ObjectDetails, ObjectCard, ...
- Update SCSS files to integrate its components

Note: A big part of the CSS is hardcoded in cmdbAbstract for now as we still need to discuss / work on the migration of the cmdbAbstract APIs (DisplayBareHeader, DisplayBareProperties, ...)
2020-10-02 11:40:46 +02:00
Molkobain
79ad75a2c2 N°2847 - Panel: Fix padding / font size 2020-10-02 11:40:26 +02:00
Molkobain
41c833d0c6 N°2847 - Work on TabContainer: Move SCSS and HTML files to the layout folder 2020-10-02 11:40:26 +02:00
Molkobain
29f58103b0 N°2847 - Comment (temp) migrated parts of the iTopWebPage 2020-10-02 11:40:26 +02:00
Molkobain
15a9856f89 N°2847 - Rework of Panel block
- Add 2 separate content areas (main and toolbar)
- Improve HTML template with header
- Prepare ObjectDetails block based on a panel
2020-10-02 11:40:25 +02:00
Stephen Abello
b3bb77c8ee N°2847 Finish UX/UI for new caselog entry 2020-10-02 10:54:40 +02:00
Stephen Abello
c2fe1bc5cb N°2847 Add new entry form for activity panel (static atm) 2020-10-02 10:54:39 +02:00
Molkobain
7f56911f41 Code cleanup 2020-10-01 22:07:41 +02:00
Molkobain
83a547bbf4 N°2844 - Fix mixed up activity entries
Note: Re-display the history tab for now so we can check consistency between history tab and activity panel
2020-10-01 22:02:03 +02:00
Molkobain
8e16d537ff N°2847 - Fix crash on object edition due to TitleFactory::MakeForObjectDetails() refactor 2020-10-01 17:50:59 +02:00
Molkobain
d9fe4599ff N°2847 - Fix tabs being centered instead of being align on the left 2020-10-01 17:49:10 +02:00
Molkobain
50bf0c9a27 N°2847 - Rework of TabContainer / Tab
- Add JS widget to handle front-end logic and for better encapsulation
- Move SCSS files to match convention
- Update SCSS files
- Remove unused SCSS file
- Move HTML templates to match convention
- Remove unused HTML template
- Renamed codes and folders to match convention
- Update PHPDoc
- Reformat code
- Remove usage of return type hinting when using "self"
2020-10-01 17:33:27 +02:00
Molkobain
261131d6d7 PHPDoc and code cleanup 2020-10-01 17:33:26 +02:00
Molkobain
5276b48ed4 N°2847 - Rework of MultiColumn / Column
- Remove unused HTML template
- Move SCSS files to match convention
- Move Column under MultiColumn to match convention (eg. ActivityPanel, PopoverMenu)
- Update PHPDoc
2020-10-01 17:33:26 +02:00
Molkobain
6fd56f750e N°2847 - UIContentBlock: Change code from "contentblock" to "content-block" to match convention 2020-10-01 17:33:25 +02:00
Molkobain
57fda98981 Reformat code 2020-10-01 17:33:24 +02:00
Molkobain
ce001a8097 N°2847 - Rework on iUIContentBlock and PageContent
* Add new Trait tUIContentAreas to handle multiple content areas across UIBlock classes (extracted from PageContent)
* Remove return type hinting in some method when type was "self" or the class itself when it's meant to be overloaded (works only with PHP 7.4+)
* Update some PHPDoc
* Reformat some brackets
2020-10-01 17:33:24 +02:00
Molkobain
d49dd8b993 N°2847 - Start classes icon update 2020-10-01 17:33:23 +02:00
Molkobain
eeec5989d9 Visual tests: Fix panels title 2020-10-01 17:22:28 +02:00
Molkobain
f25ef7f5ac N°2847 - Prepare object details header for demo (will be reworked) 2020-10-01 17:22:27 +02:00
odain
5862ecde69 N°3117: force itop-structure loading during setup 2020-09-30 17:32:07 +02:00
Molkobain
f8129fab74 N°2899 - Setup: Fix "For CryptoEngine ..." mandatory PHP extension introduced in the previous commit 2020-09-30 16:22:35 +02:00
odain
ee9343cf40 N°3117: fix refactor regarding itop-structure setup 2020-09-30 16:00:36 +02:00
Molkobain
1b2dd04409 N°2899 - Setup: Remove mbstring from optional PHP extensions now that it is mandatory (7d1a61a2f) (Thanks @Hispka !) 2020-09-30 14:38:55 +02:00
Eric
40f3d6efbe N°2847 - fix newsroom target for links 2020-09-30 11:59:22 +02:00
Eric
5c07591519 N°2847 - fix newsroom target for links 2020-09-30 11:56:55 +02:00
Eric
f0f7653884 N°2847 - revert fix inline scripts 2020-09-30 10:54:34 +02:00
Stephen Abello
de54d28c6b N°2847 Fix newsroom not being displayed with block render changes 2020-09-30 10:40:41 +02:00
Eric
292457595c N°2847 - Fix inline scripts 2020-09-30 10:36:01 +02:00
Eric
a99f03e510 N°2847 - Edit/Create objects 2020-09-30 09:56:03 +02:00
Stephen Abello
fd3f972948 N°2847 Add RichText input 2020-09-30 09:42:36 +02:00
Stephen Abello
12bf77d9ca N°2847 Refactor Inputs components 2020-09-30 09:42:36 +02:00
Stephen Abello
c0337eaa23 N°2847 Fix twig missing templates (missing file or wrong case) 2020-09-30 09:42:36 +02:00
Eric
b8d71b2bfb N°2847 - Edit/Create objects 2020-09-29 17:35:02 +02:00
Thomas Casteleyn
5ef6b6c5d0 Cleanup references to Flash (#163)
All remaining Flash files are removed. Dependent Combodo extensions will me modified for iTop 2.8.0 (Combodo ref N°3344, N°3345)

Old swf_navigator operation in UI.php was kept for compatibility but is deprecated

Co-authored-by: Molkobain <guillaume.lajarige@combodo.com>
Co-authored-by: Pierre Goiffon <pierre.goiffon@combodo.com>
Co-authored-by: rquetiez <romain.quetiez@combodo.com>
2020-09-29 15:56:08 +02:00
Eric
078f81e853 N°2847 - Shortcut buttons look 2020-09-29 15:02:05 +02:00
Eric
11a79501da N°988 - Backoffice: Hide empty fieldsets in object details 2020-09-29 14:21:01 +02:00
Molkobain
3dc7b66f6f N°2847 - Code clean up
* Fix TWIG exceptions due non existent JS templates for ajax tab
* Fix DisplayBlock::GetRenderContent and HistoryBlock::GetRenderContent signature mismatch warning
* Add return type hinting on ButtonFactory methods
* Rename ButtonFactory::MakeAlternativeNeutralActionButton() to ButtonFactory::MakeLinkNeutral()
* Add ButtonFactory::MakeLinkNeutral() to visual test page
* Fix button spacing/padding when only icon or label
2020-09-29 10:35:16 +02:00
Eric
2b0bdda1e0 Fix type hinting 2020-09-29 09:23:13 +02:00
Molkobain
794ee89d12 N°2847 - Fix "quick create" history display 2020-09-28 17:14:46 +02:00
Molkobain
c37aabfc8c N°2847 - Fix TWIG exceptions due non existent JS templates 2020-09-28 17:14:13 +02:00
Molkobain
987ab4cbfb PHPDoc and code cleanup 2020-09-28 16:54:58 +02:00
Molkobain
0aaa0a33bf N°2847 - Remove history tab from object details 2020-09-28 16:37:39 +02:00
odain
3fd9aa01ed Merge branch 'feature/b3217-2dev' into develop 2020-09-28 16:35:22 +02:00
odain
97380fd5f4 N°3117 Refactor of datamodel and dictionnaries for the sake of modularity
- New module itop-bridge-cmdb-ticket to cope with the need to install only the CMDB or/and ticketing
	- New module itop-structure to hold declarations required by iTop independently from ITIL. Note that these declarations should ideally be moved to application/datamodel.application.xml or core/datamodel.core.xml. Anyhow, the compiler suffers a few limitations and it has been decided to create this module as a workaround.
	- Split of known-error module into itop-faq-light and itop-knownerror-light
2020-09-28 16:34:06 +02:00
odain
bcd41dfe96 Merge branch 'feature/b3217-squash' into develop 2020-09-28 15:08:57 +02:00
odain
54eb86a16b N°3117 Refactor of datamodel and dictionnaries for the sake of modularity
- New module itop-bridge-cmdb-ticket to cope with the need to install only the CMDB or/and ticketing
	- New module itop-structure to hold declarations required by iTop independently from ITIL. Note that these declarations should ideally be moved to application/datamodel.application.xml or core/datamodel.core.xml. Anyhow, the compiler suffers a few limitations and it has been decided to create this module as a workaround.
	- Split of known-error module into itop-faq-light and itop-knownerror-light
2020-09-28 15:08:21 +02:00
Eric
795b28263a N°2847 - Fixed unit tests 2020-09-28 11:23:31 +02:00
Eric
8306ce36a0 N°2847 - Fixed multi-column bottom margin 2020-09-25 18:21:23 +02:00
Eric
6c46c7ff3a N°2847 - Fixed configuration editor 2020-09-25 18:16:05 +02:00
Eric
bfd4ba16d9 N°2847 - Action buttons 2020-09-25 16:02:18 +02:00
Eric
6969e4db1b N°2847 - Title CSS 2020-09-24 15:12:25 +02:00
Pierre Goiffon
661d84fc77 ThemeHandlerTest : fix tmp dir clean up 2020-09-24 14:17:08 +02:00
Pierre Goiffon
9eac12bd9c Fix CSS precompilation signature check on non Linux OS
* fix images paths comparison
* fix MD5

Were depending on platform : \ThemeHandlerTest::testGetIncludedImages test passed on Linux, not on Windows due to different DIRECTORY_SEPARATOR
2020-09-24 12:38:16 +02:00
Eric
ecd5a7aadf N°2847 - FieldSet, MultiColumn, Tab for object details CSS 2020-09-24 10:45:14 +02:00
Eric
7bf473d2a3 N°2847 - FieldSet, MultiColumn, Tab CSS 2020-09-23 14:51:18 +02:00
Eric
98d2e42bad N°2847 - FieldSet and Fields 2020-09-23 08:55:58 +02:00
Pierre Goiffon
9e9b192b3c N°2214 Blocks execution for PHP < 7.0.0
As older PHP version can cause multiple problems, and we still use lots of requires that can generate PARSE_ERROR on such version, we chose to add this very low-level control. Therefore user will get a clear error message, and oldest PHP version will be blocked in the whole application.
2020-09-22 15:35:13 +02:00
Pierre Goiffon
98789f28bb N°3198 Relations Table Mode : control duplicates server side 2020-09-22 13:32:32 +02:00
Eric
33f11d2a43 N°2847 - Form and Input WIP 2020-09-22 13:26:14 +02:00
Eric
40efaf0360 N°2847 - Tab Management 2020-09-22 08:24:18 +02:00
Eric
74160d2447 N°2847 - Add Title component
* Add id to button factory
 * Rework of Configuration File Editor page
2020-09-18 18:16:09 +02:00
Eric
08eada82f4 N°2847 - Fix External key selection 2020-09-18 18:16:09 +02:00
Eric
9cd719ab56 N°2847 - Separate inline scripts and css from html in the rendering of pages 2020-09-18 18:16:09 +02:00
Eric
e83dfe5982 N°2847 - refactor twigs 2020-09-18 18:16:08 +02:00
Pierre Goiffon
077ac81208 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-09-18 14:24:19 +02:00
Pierre Goiffon
36f8344ec9 Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	js/table-selectable-lines.js
2020-09-18 14:23:36 +02:00
Pierre Goiffon
745e4aa424 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	webservices/import.php
2020-09-18 10:48:34 +02:00
Pierre Goiffon
5405ae1d91 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-09-18 10:47:30 +02:00
Pierre Goiffon
ed1bb3e93a N°2214 Add PHP check in CLI scripts : 2nd code review with Romain
- fix wrong test in \SetupUtils::CheckPhpAndExtensions
- fix \SetupUtils::CheckGraphviz consumers in WizardStep : do not assume that only one CheckResult is returned... doing a for each loop instead !
- json_encode prb on Windows : pull up default messages from consumers to \SetupUtils::CheckGraphviz (was introduced in 6e754d4fa5)
2020-09-18 10:43:39 +02:00
Pierre Goiffon
0773660ef2 N°3198 Relations Table Mode : update table select line script
Was selecting line when clicking in the ext key widget (drop-down using Selectize, search/add widgets)
2020-09-17 14:48:00 +02:00
Eric
68b8490288 N°2847 - Refactor 2020-09-17 11:01:41 +02:00
Eric
f100e9bd09 N°2957 - allow empty class parameter 2020-09-16 17:57:38 +02:00
Eric
0ffe76e359 N°2847 - Fix Global search 2020-09-16 17:38:46 +02:00
Eric
e3739641b4 N°2847 - Ajax page javascripts 2020-09-16 17:16:01 +02:00
Eric
410a637598 N°2847 - Tab Management - iTop Pages refactoring - introduction of UIContentBlock as base block 2020-09-16 12:00:48 +02:00
Pierre Goiffon
eded4814a1 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-09-14 14:27:18 +02:00
Pierre Goiffon
b9f9322418 N°2214 Remove type hinting in SetuUtils for compatibility issues
This file needs to be able to run on very old PHP versions !
2020-09-14 11:38:55 +02:00
Pierre Goiffon
4badb90344 N°2214 Cron : remove :void to keep compatibility with PHP < 7.1
see https://www.php.net/manual/fr/migration71.new-features.php#migration71.new-features.void-functions
2020-09-14 11:02:18 +02:00
Thomas Casteleyn
0542a8e4f8 Synchro cleanup (#157)
* Cleanup duplicate code
* Document undocumented parameter
* Removed duplicate code for consistency
2020-09-14 09:53:40 +02:00
Pierre Goiffon
f0434f9125 🎨 N°2214 In SetupUtils::Check* methods : replace debug SetupLog calls by CheckResult::TRACE objects
This is the caller responsibility to log using the correct classes !
Also we need to remove this dependency O:)

Modifications:
* reverts e8e259dde9 as SetupPage is no longer called since N°2522 / c4b7be5b6f
* \SetupUtils::Log : remove useless final, add PHPDoc and type hinting to modified methods
* add PHPDoc and type hinting to modified methods
* add \CheckResult::TRACE
* replace SetupLog::Log calls by CheckResult::TRACE
* modify SetupUtils::Check* callers to handle the new TRACE severity
2020-09-11 18:37:30 +02:00
Lars Kaltefleiter
43cadb0ede N°3287 - Set from display name / label in action email
Co-authored-by: Thomas Casteleyn <thomas.casteleyn@me.com>
2020-09-11 09:50:14 +02:00
Pierre Goiffon
11f00cc229 N°2214 PHP version check in CLI scripts : remove some checks in CLI, and various improvements
Initial commit : b7136c0b7a

Removed checks : see \SetupUtils::CheckPhpAndExtensions PHPDoc

Other improvements :
* add PhpDoc on new public method \SetupUtils::CheckPhpAndExtensions
* add type hinting and new format on SetupUtils new methods
* refactor CheckResult[] filter method to be more generic
2020-09-10 16:52:47 +02:00
Stephen Abello
163c1ebc91 N°2847 Add disabled state for Button component 2020-09-10 15:33:35 +02:00
Eric
580e8ffa08 N°2847 - Refactor Web Pages classes - add deprecated files for extensions compatibility 2020-09-10 11:35:21 +02:00
rquetiez
f5a3bb2baa N°3251 - Null in data synchro (fixes the regression introduced in a1f5d80)
- restore the initial behavior, thanks to hipska through PR#166
- refactor so as to make it clear that no SQL injection is possible (and will never be)
- add PHPUnit tests on the data synchronization => up to 20s to execute
- fix utils::ExeciTopScript to alow its usage within the automated test
2020-09-09 22:41:13 +02:00
Eric
63f8ec594c N°2847 - Refactor Web Pages classes - Add UIBlockManager to the WebPage 2020-09-09 18:10:06 +02:00
Eric
2df2392cc6 Fix images in form edition 2020-09-09 14:27:58 +02:00
odain
d2e5e96a63 💚 fix remaining failed tests after merge 2020-09-09 06:52:09 +02:00
Molkobain
8193ada159 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	composer.json
#	css/light-grey.scss
#	lib/composer/autoload_classmap.php
#	lib/composer/autoload_static.php
2020-09-08 17:30:32 +02:00
Eric
46fe1661db Display modal 2020-09-08 17:20:13 +02:00
odain
2ff6658a56 Merge branch 'develop' into feature/b3217-2dev 2020-09-08 17:00:41 +02:00
Benjamin Planque
f25cd70e73 datamodel and dictionnaries refactoring/cleanup 2020-09-08 16:36:21 +02:00
odain
b5cfd1c61e 💚 uncomment test 2020-09-08 11:36:42 +02:00
Molkobain
3c9c3204ac N°2847 - Update folders order 2020-09-08 10:11:21 +02:00
odain
7ab274cb7b 💚 fix test after merge 2020-09-08 09:21:06 +02:00
Molkobain
7d1a61a2f9 N°2899 - Setup: Add mbstring as mandatory PHP extension 2020-09-08 09:12:43 +02:00
odain
3c299654e3 N°3265 - Log stacktrace when cron exception raised with debug enabled 2020-09-07 16:57:27 +02:00
odain
1eba8ba4ac Merge branch 'support/2.7' into develop 2020-09-07 16:48:15 +02:00
Thomas Casteleyn
9daf23576d Update the movie icon with a flat design 2020-09-07 15:20:08 +02:00
Thomas Casteleyn
b9f23177e7 Add attachment icon for movie files 2020-09-07 15:20:08 +02:00
Thomas Casteleyn
6542cf9d30 Add missing "
Co-authored-by: Eric Espié <eric.espie@combodo.com>
2020-09-06 20:30:36 +02:00
Thomas Casteleyn
610e74e642 Fix filename headers when downloading 2020-09-06 20:30:36 +02:00
OИUЯd da silva
c3ecd51153 Merge pull request #134 from itomig-de/feature/IsWriteAllowed
Enable external classes to use bypass function as well
2020-09-04 10:26:30 +02:00
Eric
ff722b579b Fix Dependent attributes update on DBObject::Set() 2020-09-03 12:57:01 +02:00
Eric
7e0fbd8c25 revert file 2020-09-02 15:42:44 +02:00
Eric
9f592223d9 Add Cron info to the report 2020-09-02 14:41:06 +02:00
Molkobain
99fc41a0f8 PHPDoc & code cleanup 2020-09-02 12:01:35 +02:00
Eric
76fbd3790f N°3123 - Update the list of required PHP extensions 2020-09-02 10:47:42 +02:00
Eric
d3b57c3bda N°3111 - Fix Portal export 2020-09-01 17:17:01 +02:00
acognet
a61ffaf3c0 N°3184 - Upgrade JQuery UI (iTop 2.8) 2020-09-01 16:36:49 +02:00
acognet
cc6ab8a7b7 N°3184 - Upgrade JQuery UI (iTop 2.8) 2020-09-01 09:06:14 +02:00
acognet
432a565221 N°2508 - Include Obsolescence icon within list and autocomplete 2020-09-01 09:06:14 +02:00
bruno-ds
7d8aaf91db :greenheart: test readability 2020-08-31 14:48:49 +02:00
Molkobain
eb99dc9f3f Add Lucas Mindêllo de Andrade (a.k.a @rokam) to the contributors list, huge thank you!
Note: We start to put GitHub aliases next to the full name of contributors. If you want yours to be added/updated, just comment this commit 🖖
2020-08-31 13:17:47 +02:00
Lucas Mindêllo de Andrade
f59f2b2109 Update PT BR translations (#159)
* Correct threshold translation
* Better translation for closed requests
* itop-portal better translation
* Fixed typo
* Missing PT-BR translations
* Removed ~~ from translated messages

Co-authored-by: Lucas Mindêllo de Andrade <lucas.rokam@gmail.com>
2020-08-31 13:12:20 +02:00
Molkobain
2840f6930b N°2847 - Navigation menu: Fix newsroom icon color on hover 2020-08-28 22:15:47 +02:00
Molkobain
c50b6c806a N°2847 - Navigation menu: Fix user picture size / position when image is taller than wide 2020-08-28 22:11:36 +02:00
odain
84cd18bcb8 💚 fix test to make conversion functions exist for any one found in datamodels files (itop pro version) 2020-08-28 21:05:04 +02:00
Molkobain
c2fcadd54d N°2847 - Add optional user_id to CMDBChange
Important: This can make the setup / toolkit to take a very long time on large databases as the "priv_change" table is one of the largest. SQL queries to run and/or a migration tool will be provided when 2.8.0 will be released.
2020-08-28 19:46:40 +02:00
odain
26ba45a8da 💚 fix test to make conversion functions exist for any one found in datamodels files 2020-08-28 18:35:05 +02:00
odain
4cc31bf941 💚 fix test to make conversion functions exist for any one found in datamodels files 2020-08-28 17:23:02 +02:00
odain
0d878a52a2 💚 fix test to make conversion functions exist for any one found in datamodels files 2020-08-28 16:48:04 +02:00
Eric
d1af851fcd N°2591 - API : deprecate \CMDBObject::CheckUserRights 2020-08-28 14:48:00 +02:00
Eric
ec72d1c767 N°3078 - Fix error on login while in maintenance mode
Fix infinite loop on login failure
2020-08-28 14:26:42 +02:00
Molkobain
3add77308a N°2847 - ActivityPanel: Fix entry's author info for CMDBChangeOp 2020-08-27 18:57:41 +02:00
Molkobain
d0ea3665be PHPDoc & code cleanup 2020-08-27 16:37:18 +02:00
Molkobain
ae19737472 PHPDoc & code cleanup 2020-08-27 16:23:29 +02:00
Molkobain
4c236b4422 N°2847 - Breadcrumbs: Fix type hint in SetNewEntry ($aNewEntry can be null) 2020-08-27 16:15:07 +02:00
acognet
c4b7be5b6f N°2522 - API : Deprecate SetupPage:log* 2020-08-27 11:17:42 +02:00
Eric
1afc6cd4c5 N°2363 - Fix typo 2020-08-27 10:50:34 +02:00
Eric
3b6a03d703 Fix warning if not countable 2020-08-27 09:57:00 +02:00
odain
a4842f9e5c N°2996 - Remove iTop version from css-variable.scss
Fix PCRE non backward compatibility between PHP 7.2 and 7.3
2020-08-27 09:37:10 +02:00
Molkobain
0bef32cf8d N°2847 - Alert & Panel: Add top spacing, fix border-radius and reorder some parts of the SCSS 2020-08-26 22:15:50 +02:00
Molkobain
825c70c001 Introduce type hinting in methods prototype (PHP >= 7.1) 2020-08-26 21:22:39 +02:00
Molkobain
77cd764b1c PHPDoc & code cleanup 2020-08-26 21:22:25 +02:00
Molkobain
2864d48fa7 N°2847 - Flag TODOs as 2.8.0 so we don't forget any 2020-08-26 20:37:22 +02:00
Molkobain
b5c97e35bf Add visibility to class constants (PHP >= 7.1) 2020-08-26 19:02:53 +02:00
odain
9a0d9d2c38 Merge branch 'feature/fast-theme-compilation2' into develop
N°2982 - speedup themes/scss compilation during setup
N°2996 - Remove iTop version from css-variable.scss
2020-08-26 17:59:31 +02:00
Molkobain
8bdaec0129 N°2847 - Alert & Panel: Improve factories, add color schemes, add visual tests 2020-08-26 17:45:22 +02:00
Molkobain
3b0ca9d771 N°2847 - Add "information" semantic to color palette, reorder colors like a rainbow 🌈 2020-08-26 17:45:22 +02:00
Molkobain
fa406c1f17 PHPDoc & code cleanup 2020-08-26 17:45:22 +02:00
odain
dd18430e70 remove GetCompiledModuleVersion deprecation 2020-08-26 16:33:49 +02:00
Molkobain
7f3c10b69d Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-08-26 16:09:23 +02:00
Molkobain
0a5e4effc0 N°2847 - Fix Html and Panel namespaces 2020-08-26 16:05:33 +02:00
Molkobain
737e17066f N°2847 - Add page for visual testing of blocks 2020-08-26 16:03:34 +02:00
Molkobain
8108dc8803 N°2847 - Button: Reformat SCSS file 2020-08-26 16:03:34 +02:00
Molkobain
61ec7f8053 N°2847 - Button: Add factory, color constants and refactor existing code to use the factory 2020-08-26 16:03:33 +02:00
Molkobain
d5f44ffd7b N°2847 - Alert: Add factory and color constants 2020-08-26 16:03:33 +02:00
Molkobain
d2bf4de84c N°2847 - Refactor some early choices
- TWIG: Change calls to object methods from simple notation (eg. oObject.Id) to complete notation (eg. oObject.GetId()) to avoid confusion with use of arrays and variables (eg. aObject.sId)
- UIBlock: $sId should not be first parameter as most of the time it can be ignored and generated by the system
- NewsroomMenu: Rename method for something more less ambiguous
- Html: Embed content in <div /> so we can easily find all such HTML fragments in the UI
2020-08-26 16:03:33 +02:00
Molkobain
5320f69c44 PHPDoc & code cleanup 2020-08-26 16:03:32 +02:00
Molkobain
eb1322dc3e N°2847 - iTopWebPage: Continue cleanup of the legacy code 2020-08-26 16:03:32 +02:00
odain
e8e259dde9 N°2214 - Add a PHP version check in CLI PHP scripts- fix import.php script 2020-08-26 15:56:11 +02:00
Eric
5df7c8bb07 Add Async tasks info 2020-08-26 16:12:22 +02:00
Eric
bf142e5e07 Add Async tasks info 2020-08-26 13:11:04 +02:00
odain
8319d655b9 💚 renaming postbuild test folder + fix tests 2020-08-26 12:42:48 +02:00
odain
f5c8411a07 💚 ThemeHandlerTest: test fix + enhance failure messages 2020-08-26 12:09:55 +02:00
Eric
2ad4fd1707 N°580 - Autocomplete with namesakes. Complement for select. Fix error when no complement specified 2020-08-25 13:12:53 +02:00
Eric
761c2a46a3 N°3234 - Cron rework
* some timezone refactors occurred
2020-08-25 11:20:17 +02:00
odain
c986927de9 CI switching 2020-08-24 14:11:25 +02:00
Eric
e8eb6d0e31 N°3249 - N°2957 - Revert one file per class => generate all the classes in the same file: model.<module>.php to address forward declaration of classes 2020-08-24 12:41:49 +02:00
Eric
077772ea9c N°2969 - moved dictionaries 2020-08-24 09:31:34 +02:00
rquetiez
3119af6c29 N°3268 Add test to check dictionary files: make sure that the Dict::Add declarations match the file name 2020-08-21 17:43:40 +02:00
Molkobain
83ef7cff8a N°3267 - Webservices: Fix optional headers not being taken into account 2020-08-21 16:55:03 +02:00
Stephen Abello
93eace9e5b N°2847 Fix preference page width 2020-08-21 14:02:41 +02:00
acognet
2142ffedc3 Add PHP Warning for misused of the function OptimizeColumnLoad 2020-08-20 16:44:44 +02:00
acognet
23c0a67914 N°580 - Autocomplete with namesakes. Complement for select 2020-08-20 16:42:43 +02:00
Eric
3b00911f62 Merge branch 'GurneyHallack-feature/api-core_get' into develop 2020-08-20 15:46:19 +02:00
Eric
b9b1e436ec Merge branch 'feature/api-core_get' of https://github.com/GurneyHallack/iTop into GurneyHallack-feature/api-core_get 2020-08-20 15:44:52 +02:00
Eric
87312d4457 N°2969 - Fix relative path of dictionaries 2020-08-20 14:45:43 +02:00
Stephen Abello
9dcb4bbfe5 N°2847 First step to convert user preferences page to our new design 2020-08-20 11:14:15 +02:00
Stephen Abello
11f27019e4 N°2847 - Add Html component
- Fix icons on buttons
- Comment some rules from legacy css
2020-08-20 11:14:15 +02:00
Stephen Abello
dd81c76aaa N°2847 Add margin to panels and buttons siblings 2020-08-20 11:14:14 +02:00
Pierre Goiffon
4e5bbcde35 N°3198 n:n relations in edit mode : refresh remote class fields on extkey change 2020-08-19 16:06:37 +02:00
Pierre Goiffon
7d7270296e 🎨 📝 format & add comment 2020-08-19 16:06:37 +02:00
Pierre Goiffon
44e188fa2c N°3198 n:n relations in edit mode : extkey to remote class is now editable 2020-08-19 16:06:11 +02:00
Molkobain
e6bab46854 Add unit tests for XML migration from 1.8 > 1.7 and 1.7 > 1.6 2020-08-19 11:36:40 +02:00
Molkobain
0a4ce0865e Increase XML version to 1.8 2020-08-19 11:12:39 +02:00
Molkobain
da1d3db204 N°2847 - Activity panel: Fix filtering on CMDBChangeOp entries 2020-08-19 11:01:13 +02:00
Molkobain
880be200ae Revert "N°2847 Fix first and last popover menu item border radius"
This reverts commit 417008f5
2020-08-19 10:23:40 +02:00
Molkobain
c5b8d3fa72 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design
# Conflicts:
#	application/datamodel.application.xml
#	application/itopwebpage.class.inc.php
#	css/light-grey.scss
2020-08-19 10:20:37 +02:00
Stephen Abello
417008f549 N°2847 Fix first and last popover menu item border radius 2020-08-19 10:12:04 +02:00
Molkobain
8077e5aa62 PHPDoc & code cleanup 2020-08-19 10:09:02 +02:00
Molkobain
2ce1c2efec N°2847 - Work on the ActivityPanel and PopoverMenu features
- Deprecate cmdbAbstractObject::DisplayBareHistory() as history will be replace by ActivityPanel
- Rename illustrations to original filenames to find source more easily
- Remove unused "max_history_case_log_entry_length" config. parameter
- Activity panel: Introduce iCMDBChangeOp and iCMDBChangeOpSetAttribute interface for better dependency injection
- Activity panel: Add placeholder when no entry
- Activity panel: Fix tab toolbar icons color
- Activity panel: Add history entries (entries after the first 50 are not loaded yet)
- Popover menu: Fix no border-radius on first/last entries hover
2020-08-19 10:09:01 +02:00
Pierre Goiffon
c69e83f779 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-08-18 17:50:39 +02:00
odain
91006351d3 Fix tests 💚 2020-08-18 17:20:47 +02:00
acognet
e43f55df03 N°2508 - Include Obsolescence icon within list and autocomplete 2020-08-18 15:55:03 +02:00
acognet
0f741f28f3 N°2508 - Include Obsolescence icon within list and autocomplete 2020-08-18 15:14:50 +02:00
acognet
4d2633a9ea N°2508 - Include Obsolescence icon within list and autocomplete 2020-08-18 14:19:36 +02:00
Eric
c5d265f66b N°3249 - Fix move To Production. Fix compiler code generation broken by N°2957 2020-08-18 14:17:01 +02:00
Eric
0311fe330b Fix setup CSS 2020-08-18 14:14:52 +02:00
acognet
4648e436ac N°2508 - Include Obsolescence icon within list and autocomplete 2020-08-18 12:42:48 +02:00
Stephen Abello
e39947f72c N°2847 Add semantic colors to alert and panel component 2020-08-18 12:06:18 +02:00
Stephen Abello
74db53d51d N°2847 Add alert component 2020-08-18 12:03:21 +02:00
odain
0b9ba4d053 Merge branch 'feature/release-checklist-2.7' into develop 2020-08-18 10:31:02 +02:00
acognet
700d11fa8f N°2508 - Include Obsolescence icon within list and autocomplete 2020-08-18 10:19:01 +02:00
acognet
f64e081c1f Remove file unused 2020-08-18 10:19:01 +02:00
acognet
54b6a29ba0 N°2393 - API : Font Awesome remove v4 compatibility 2020-08-18 10:19:00 +02:00
acognet
e0bf262343 N°2534 - dashboard: bug with autorefresh that deactivates filtering on organizations 2020-08-18 10:19:00 +02:00
Pierre Goiffon
3a276715d6 📝 SECURITY : fix typo 2020-08-18 09:18:37 +02:00
Stephen Abello
9516e7f023 N°2847 Add panel component 2020-08-17 17:02:57 +02:00
Stephen Abello
223b063c60 N°2847 Fix button component comment and trailing space 2020-08-17 17:02:57 +02:00
Molkobain
fabc92eb89 N°2847 - Activity panel: Fix glitch on closed case log entry 2020-08-17 10:00:44 +02:00
Pierre Goiffon
71848cb56b Merge branch 'support/2.7' into develop
# Conflicts:
#	.jenkins/bin/tests/phpunit.sh
#	Jenkinsfile
#	core/config.class.inc.php
#	core/dbobjectsearch.class.php
#	core/ormlinkset.class.inc.php
#	datamodels/2.x/combodo-db-tools/dbtools.php
#	dictionaries/nl.dictionary.itop.ui.php
2020-08-17 09:36:43 +02:00
Molkobain
d011c4e8f5 N°2847 - Global search: Fix past queries not displaying 2020-08-17 09:31:10 +02:00
Molkobain
46909c210c N°2847 - Activity panel: Display only necessary toolbar actions in the "Activity" tab 2020-08-17 08:38:44 +02:00
odain-cbd
71b725420a Merge branch 'develop' into feature/fast-theme-compilation2 2020-08-17 00:18:15 +02:00
rquetiez
fcd4ad6872 N°3052 add the core/application XML files and rewrite the test for two benefits:
a - the latest XML version is currently not correlated with the the version of iTop (though it seems to be)
b - see the converted XML... that could be saved to fix the report
2020-08-14 19:57:38 +02:00
Molkobain
e3f1deda00 N°2847 - Activity panel: Fix case log entries highlight strip when closed 2020-08-14 18:12:35 +02:00
odain
a538e3c1a2 N°2996 Remove iTop version from css variables
- $version does not exit anymore in css-variables.css. now its value is computed during setup and equals setup timestamp instead.
    - use precompiled files (declared in datamodels XML files) to check if theme compilation is required or not.
    - referenced images in scss files are included in precompiled file signatures just like scss files md5sum.
    - images declared in scss files with v=$version are reloaded automatically on browser side after each theme compilation (see xxx.png?v=timestamp)
    - precompiled files are replaced if theme compilation occurred. this will avoid same time consuming operation at next setup.
    - code cleanup: arrays / variables renamed
2020-08-14 17:03:16 +02:00
Stephen Abello
ac91c4d970 N°2847 Fix default action type value for Button constructor 2020-08-14 17:02:35 +02:00
Stephen Abello
3ffe3dd4bd N°2847 Add Button component 2020-08-14 16:26:16 +02:00
Stephen Abello
f70065b2fc N°2847 Add a border radius variable to scss 2020-08-14 16:26:16 +02:00
Stephen Abello
4b98f2ac25 N°2847 Add secondary color variables 2020-08-14 16:26:16 +02:00
Molkobain
398621693e N°2847 - Activity panel: Several improvements
- Improve display of code snippets
- Improve code structure for loops on case log colors
2020-08-14 12:18:47 +02:00
Molkobain
b9d7967c0e N°2847 - Activity panel: Add highlight color on case log entries 2020-08-14 10:41:51 +02:00
Molkobain
0c5c2eccad N°2847 - Navigation menu: Minor adjustments
- Adjust some sizes and colors on the navigation menu to match mockups
- Fix menu groups displaying over the user picture when menu is expanded
- Partially fix notification menu positioning
- Extract some sizes into variables for the popover menu
2020-08-13 23:34:18 +02:00
rquetiez
a1f5d8041e N°3251 (see also PR#152) - CSV data synchro allows "<NULL>" to be interpreted as "Let the attribute unchanged for the current row" 2020-08-13 21:49:30 +02:00
rquetiez
d679deba02 refactor CSV Parser unit tests from the old home-made framework into PHP Unit, and add new test to check that <NULL> is parsed as a real null value 2020-08-13 21:12:50 +02:00
Molkobain
df20d10afa N°2847 - Add activity panel to object details (and some variables renaming) 2020-08-13 18:57:07 +02:00
Molkobain
f2725c5a5c Add $bAllowDefaultPicture parameter to UserRights::GetContactPictureAbsUrl() to return null instead of the default picture 2020-08-13 17:46:48 +02:00
Pierre Goiffon
91c6916d86 🐛 Fix setup homepage error with PHP < 7.1.0
Was due to const visibility added in ormLinkSet class in commit b58a084d

This isn't supported before PHP 7.1.0, see https://www.php.net/manual/fr/language.oop5.constants.php :
> As of PHP 7.1.0 visibility modifiers are allowed for class constants.
2020-08-13 15:29:27 +02:00
Molkobain
f90a5b77ad Fix UserRights::GetContactPictureAbsUrl() when using optional $sLogin parameter 2020-08-13 14:47:07 +02:00
Molkobain
703161157b Fix UserRights::GetUserId() when using optional $sLogin parameter 2020-08-13 14:08:54 +02:00
Molkobain
55c896bcd3 Internal - Add parameter $aDesiredAttTypes to the MetaModel::GetAttributesList() to filter the result on some attribute types only 2020-08-13 14:08:54 +02:00
Molkobain
ef844c396e N°2847 - Add UIBlock::GetBlockCode() method to get the code within a TWIG no matter the object class (easier than calling the right class constant) 2020-08-13 14:08:54 +02:00
Molkobain
dfdeb21023 PHPDoc & code cleanup 2020-08-13 14:08:53 +02:00
Stephen Abello
47c0f946b3 N°2847 Better handle of weird shaped images for square logo and full logo 2020-08-12 15:07:33 +02:00
acognet
f90105c107 N°2534 - dashboard: bug with autorefresh that deactivates filtering on organizations 2020-08-12 14:18:05 +02:00
Stephen Abello
b0973263e3 N°2847 Add background color to user menu odd sections 2020-08-12 11:36:57 +02:00
Stephen Abello
4be0d8895d N°2847 Refactor newrooms with variables 2020-08-12 11:04:24 +02:00
Stephen Abello
6436e8cef4 N°2847 Add an indication on newsroom toggler when there is new messages 2020-08-12 09:30:19 +02:00
Stephen Abello
f8b6b37288 N°2847 Recolor svg illustrations with itop's primary color 2020-08-10 16:56:00 +02:00
Stephen Abello
8996117929 Allow additional paths for Twig Helper and add images/ to iTop webpage and block renderer paths 2020-08-10 16:56:00 +02:00
Molkobain
9be4743cf6 Internal - UserRights: Rename 1 var. and 1 method to something more meaningful 2020-08-10 14:49:19 +02:00
Stephen Abello
4195cd011c N°2847 * Fix wrong image name for default placeholder
* Close popupmenu when clicking on toggler a second time
* Fix an error in newsroom menu
2020-08-10 14:16:43 +02:00
Stephen Abello
440cda87b1 N°2847 Add full logo to expanded navigation menu 2020-08-10 12:14:09 +02:00
Molkobain
efb3f7f9cd Internal - Rework on User and UserRights classes:
- PHPDoc
- Add User::GetInitials() method
- Fix several calls to UserRights::FindUser()
- Add UserRights::GetUserInitials()
- Change usage of DBObject->Get('friendlyname') to DBObject->GetRawName()
2020-08-10 11:52:06 +02:00
odain
e5487edbfb Merge remote-tracking branch 'remotes/origin/feature/perf-serialization' into develop 2020-08-10 11:10:44 +02:00
odain-cbd
3cae585f78 Merge pull request #154 from Combodo/revert-136-feature/fast-theme-compilation
Revert "Faster compilation of themes"
2020-08-07 16:20:38 +02:00
odain-cbd
7b8c390a80 Revert "Faster compilation of themes" 2020-08-07 16:20:12 +02:00
odain-cbd
c3b9c6963e Merge pull request #136 from Combodo/feature/fast-theme-compilation
Faster compilation of themes
2020-08-07 16:13:09 +02:00
odain-cbd
e907c7825a Merge branch 'develop' into feature/fast-theme-compilation 2020-08-07 16:12:47 +02:00
Stephen Abello
ea7924663a N°2847 Add a user placeholder image selector in user preferences (poc, need to cleanup) 2020-08-07 15:42:10 +02:00
Stephen Abello
93871308d1 N°2847 Update newsroom templates with new variable in twig files 2020-08-07 09:25:33 +02:00
Molkobain
f20310b437 N°2847 - Change calls to methods in TWIG to something more meaningful to avoid consufion between methods, properties and array keys 2020-08-06 17:47:15 +02:00
Molkobain
af87a04d13 N°2847 - Add page content layout feature 2020-08-06 17:47:15 +02:00
Molkobain
521536526e N°2847 - GlobalSearch: Disable browser auto completion and fix past queries icon URL 2020-08-06 17:47:14 +02:00
Molkobain
1e3771dffa N°2847 - Fix content parameters (eg. aPage) not passed in subblocks 2020-08-06 17:47:14 +02:00
Molkobain
6666f3d033 N°2847 - Switch "AdminTools" and "ConfigTools" menu groups icon 2020-08-06 17:47:14 +02:00
Molkobain
470b566c1a N°2847 - Rework iTopWebPage layout (WIP Part X)
- iTopWebPage: Remove AddToMenu() method
- SCSS: Fix content areas height to occupy max. available space
2020-08-06 17:47:14 +02:00
Molkobain
c81f521d2e N°2847 - Uncomment some parts of the old CSS file to improve rendering while developing 2020-08-06 17:47:14 +02:00
Molkobain
3ed73ac020 N°2847 - Fix UI block var name being different across the different templates (painful when working with derivated block classes) 2020-08-06 17:47:14 +02:00
Molkobain
1604e233f9 PHPDoc & code cleanup 2020-08-06 17:47:14 +02:00
Pierre Goiffon
46f8fadac0 Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-08-06 17:37:56 +02:00
Pierre Goiffon
001e937568 N°2330 / N°3009 update PHP version in composer.json 2020-08-06 17:36:23 +02:00
odain
94ffcf4207 💚 use proper namespace 2020-08-06 15:39:19 +02:00
Stephen Abello
7b139ea893 N°2847 Typo in dict + a bit of css for newroom menu toggler in user menu 2020-08-06 15:21:09 +02:00
odain
76274557a3 N°3061 - :green_hear fix test 2020-08-06 14:52:48 +02:00
Stephen Abello
1231528ab5 N°2847 Newsroom menu : Delete debug messages and update a bit of scss 2020-08-06 14:41:35 +02:00
Stephen Abello
a5216de232 N°2847 First iteration for newsroom menu:
* Kept old js widget but refactored/tweak'd it with some of our new conventions
* Added a newsroom menu component extending popupmenu
* Working as in 2.7
Still todo :
* Add variables for scss file
* Use wiget variables for most of js widget
* Correctly place the popup (absolute with <> containers ? poppers.js ? )
* Intialize toggler red bubble when new messages are in
* Replace hardcoded text in usermenu with correct dict entry updated with the nb of new message
* Only allow to open the popup when ajax calls are done
2020-08-06 14:36:07 +02:00
odain
87b027e3bf N°3054/3052 - rename exclusion group for CI to skipPostBuild 2020-08-06 11:02:57 +02:00
odain
b0609e86d1 N°3060 - filter modules autoinstalled in checklist test 2020-08-06 11:00:53 +02:00
odain
b9145ca996 N°3060 - filter modules autoinstalled in checklist test 2020-08-06 10:51:39 +02:00
bruno-ds
4e0c6e5614 move tests from
- within checklist test suite
 - to standard test suite + excluded from the checklist
2020-08-06 09:55:06 +02:00
bruno-ds
da34383363 fix checklist unit tests 2020-08-06 08:57:41 +02:00
odain
b628bb5e80 add a comment 2020-08-05 15:38:43 +02:00
odain
4c294cb09c N°3060 - :green_heart fix text name collision 2020-08-05 15:38:28 +02:00
odain
dc5ee13ed1 N°3060 - Check consistency between the list of modules and installation.xml 2020-08-05 15:25:15 +02:00
odain
8707ae6024 💚 fix StatusTest.php 2020-08-05 14:53:57 +02:00
odain
ec0f726c26 💚 fix tests 2020-08-05 14:39:00 +02:00
odain
66d6db5174 add detailed info inside StatusTest.php 2020-08-05 14:25:59 +02:00
odain
2493fd57b2 add detailed info inside StatusTest.php 2020-08-05 14:19:39 +02:00
odain
e3a653010a adapt StatusTests.php to work on both iTop repository and packaged zip 2020-08-05 12:03:35 +02:00
odain
c98c364714 adapt UtilsTests.php to work on both iTop repository and packaged zip 2020-08-05 11:38:27 +02:00
odain
a545bb8729 detailed unit test failure messages 2020-08-05 11:12:28 +02:00
Pierre Goiffon
c9dab08324 💚 Fix DBSearchTest::testSelectInWithVariableExpressions 2020-08-04 15:36:53 +02:00
Stephen Abello
18b653f641 N°2847 Handle no contact linked to user on usr menu icon hover 2020-08-04 14:44:42 +02:00
odain
56e2f63385 enhance phpunit test messages in case of failure 2020-08-04 14:13:41 +02:00
bruno-ds
4f04031183 .make builder robustness 2020-08-04 13:56:53 +02:00
bruno-ds
cdc3bbcb0c unitTest: better error message 2020-08-04 13:56:27 +02:00
Stephen Abello
2db7611368 N°2847 Fix wrong variable use in popover menu scss 2020-08-04 13:54:18 +02:00
Stephen Abello
f4f846496b N°2847 Add Separator item to popover menu items 2020-08-04 13:49:51 +02:00
Molkobain
d6ab310d24 N°3233 - Fix unit test and autoloader due to folders renaming in /sources 2020-08-04 13:26:58 +02:00
Molkobain
bc4b36fda6 N°3233 - Remove "display_template" from object details page 2020-08-04 13:26:57 +02:00
Molkobain
c61a66ca69 N°3233 - Remove "display_template" from datamodel classes 2020-08-04 13:26:57 +02:00
Molkobain
bd0480b5c5 N°3233 - Remove "display_template" from the metamodel and compiler 2020-08-04 13:26:57 +02:00
Molkobain
c93c53488d N°3185 - Rename tag to "main_logo_compact" and complete XML migration methods 2020-08-04 13:26:57 +02:00
Molkobain
19b5290e80 N°3231 - Allow browser access to static resources files in the /lib folder 2020-08-04 13:26:57 +02:00
Molkobain
a15235d522 N°2847 - Global Search: Fix class images URL, rename internal variable 2020-08-04 13:26:57 +02:00
Stephen Abello
c7e95676d2 N°2847 Fix first and last section border radius for popover menu 2020-08-04 12:13:39 +02:00
Stephen Abello
4aeb3ed448 N°2847 Cleanup scss with helpers 2020-08-04 12:00:03 +02:00
Stephen Abello
4e7b68aa9a N°2847 Open user menu when clicking user picture or user welcome message 2020-08-04 11:53:44 +02:00
Stephen Abello
e22c36c503 N°2847 Add tooltip to user menu picture and rework these dictionary entries 2020-08-04 10:02:56 +02:00
Stephen Abello
7264defc35 N°2847 Add padding to user menu message caret 2020-08-04 10:02:13 +02:00
Stephen Abello
baa2fb2906 Rename a NavigationMenu method (see ed1f3aa950 (r41148365)) 2020-08-04 09:27:55 +02:00
Stephen Abello
1d954b80a8 Rename a dictionnary entry (see ed1f3aa950 (r41148310)) 2020-08-04 09:27:12 +02:00
Stephen Abello
1d13181135 Split a scss variable in two (see ed1f3aa950 (r41148286)) 2020-08-04 09:26:12 +02:00
Stephen Abello
ed1f3aa950 N°2847 Add bottom menu part :
* Notifications icon
* User icon
* Extended user data
Still todo:
* Initialize Newsroom and feed infos to template
* Initialize User popover menu
2020-08-03 17:07:21 +02:00
Molkobain
e2e62eca5e N°2847 - Rework iTopWebPage layout (WIP Part IX)
- Refactor some CSS classes to something more semantic and cross app (.ibo-<COMPONENT>--is-<STATE> => .ibo-<STATE>)
- AjaxWebPage: Deprecated AddToMenu method
2020-08-03 11:27:06 +02:00
Stephen Abello
a85c7e9f8f Add Popover menu and subitems such as URL and JS items. 2020-08-03 11:16:54 +02:00
Molkobain
ac2280df50 N°2847 - Breadcrumbs: Rollback try to hide first elements when too many elements 2020-08-03 09:26:02 +02:00
Molkobain
61d611c136 N°2847 - Rework iTopWebPage layout (WIP Part VIII)
- Navigation menu: Change scrollbar color to something more visible
- Navigation menu: Close drawer when filter focused and "Escape" key hit
- Top bar: Fix element's ID in its standard delivery
- Top bar: Fix drawers opening under the top bar but above the main content
- iTopWebPage: Add AddUiBlock() method to easily add a layout/component in the page
- iTopWebPage: Fix Header/Footer parts (formerly North/South Panels) and Banner part
- WebPage: Handle duplicate stylesheets (like it was already doing for JS scripts)
2020-08-02 21:35:33 +02:00
Molkobain
f59de920c1 PHPDoc & code cleanup 2020-08-02 21:18:23 +02:00
Molkobain
7b13078bf9 N°2847 - Rework iTopWebPage layout (WIP Part VII)
- Breadcrumbs: Hide first elements when not enough space to show them all
- iTopWebPage: Temporary fix for modals in order to enable // dev. on the linkedsets refactoring
2020-07-30 17:57:36 +02:00
Molkobain
3406ebf1fd Merge remote-tracking branch 'origin/develop' into feature/backoffice-full-moon-design 2020-07-30 15:23:06 +02:00
Molkobain
1f0211b45a N°2847 - Rework iTopWebPage layout (WIP Part VI)
- iTopWebPage: Restore "open search" feature
- iTopWebPage: Change all resources URL to absolute in order to benefit from the "duplicate removal" benefits
- iTopWebPage: Remove obsolete method IsMenuPaneVisible()
- Config: Add new parameters quick_create.enabled / global_search.enabled / breadcrumb.enabled
- utils: Add new GetAppRevisionNumber() method
- Introduce iUIBlock interface for UI layouts, components, ...
- Introduce BlockRenderer to properly render blocks
- Add "render_block" function to TwigHelper to render blocks directly from TWIG
- Refactor layouts and components into proper block classes to fit the new architecture
2020-07-30 15:22:17 +02:00
Molkobain
b207ae1bb3 PHPDoc & code cleanup 2020-07-30 15:22:16 +02:00
Molkobain
e788a44382 TwigHelper: Add file type constants for an easier use by the service consumers 2020-07-30 15:22:16 +02:00
Molkobain
71dbccd034 N°3216 - Refactor form files to be part of the autoloader 2020-07-30 15:22:16 +02:00
Molkobain
3df3619b7c N°3215 - Refactor renderer files to be part of the autoloader 2020-07-30 15:22:16 +02:00
Molkobain
a969d352ec N°2847 - Rework iTopWebPage layout (WIP Part V)
- iTopWebPage: Restore "open search" feature
2020-07-30 15:22:16 +02:00
Molkobain
59b6dd735e Add helpers to retrieve some of the current contact data in UserRights 2020-07-30 15:22:16 +02:00
Molkobain
1766d42350 Internal: Refactor some harcoded attribute codes into constants in UserRights 2020-07-30 15:22:16 +02:00
Molkobain
3b6654588a N°2847 - Change images paths and names to something more functional 2020-07-30 15:22:16 +02:00
Pierre Goiffon
b58a084de5 N°2334 n:n relations : same fields displayed in EDIT and VIEW modes
Before we were only showing lnk fields in VIEW, and lnk+remote in EDIT (excluding some fields, see below).
Now by default (as this is customizable in VIEW mode) we have the same !

Rules to choose fields are moved from \UILinksWidget::__construct to :
  * \MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass
  * \MetaModel::GetZListAttDefsFilteredForIndirectLinkClass
2020-07-30 11:40:54 +02:00
Pierre Goiffon
5d686d733f 🔧 Disable useless inspections 2020-07-30 11:33:57 +02:00
Pierre Goiffon
ea92973e9b 📝 PHPDoc for DisplayBlock 2020-07-29 12:23:15 +02:00
Pierre Goiffon
14505fc5e6 🔧 Change SlowArrayOperationsInLoopInspection from error to warning 2020-07-29 12:22:52 +02:00
Pierre Goiffon
90b60f90ae Update files version to 2.8.0 2020-07-28 11:40:16 +02:00
Pierre Goiffon
a26c2cbcdb 📝 Add phpdoc on DBObjectSearch method 2020-07-28 11:35:30 +02:00
Pierre Goiffon
cc708f6ccc 🙈 Add temp ignore rules 2020-07-28 11:03:59 +02:00
Molkobain
d4694b271f N°2847 - Rework iTopWebPage layout (WIP Part IV)
- iTopWebPage: Clean up some commented sections
- iTopWebPage: Marked some new methods as @internal
- iTopWebPage: Restore page content to allow // developments
- Quick create: Add quick object creation box to the top bar
- Global search: Improve cinematic with other widgets
- Components / Layouts: Move JS parts to iTopWebPage, will be put in dedicated PHP helpers later
2020-07-27 15:09:35 +02:00
Molkobain
d56f6e684a N°2847 - Add Bulma SCSS lib. 2020-07-27 15:09:35 +02:00
Molkobain
8999108fe3 Clean unwanted committed changes 2020-07-27 15:09:35 +02:00
Molkobain
929eccc88d Protect directory listing of /mode_modules 2020-07-27 15:09:34 +02:00
Molkobain
75812ca151 N°2847 - Rework iTopWebPage layout (WIP Part III)
- iTopWebPage: Fix top bar actions drawers opening under the advanced search widget
2020-07-27 15:09:34 +02:00
Molkobain
ec238569c4 Add method to get allowed classes for specific action (read/modify/...) and categories (bizmodel, ...) 2020-07-27 15:09:34 +02:00
Molkobain
b6117b157c PHPDoc & code cleanup 2020-07-27 15:09:34 +02:00
Molkobain
b3dcfea8dc N°3207 - Global search: Introduce new widget 2020-07-27 15:09:34 +02:00
Molkobain
00dc1d3f3b PHPDoc & code cleanup 2020-07-27 15:09:34 +02:00
Molkobain
da5d55a542 Fix appUserPreferences type hinting as it accepts more than just strings 2020-07-27 15:09:34 +02:00
Molkobain
6a3bbd8d49 N°2847 - SCSS: Refactor some variables names and some patterns into helpers 2020-07-27 15:09:34 +02:00
Molkobain
46deb9590d N°3199 - Add tippyjs / popper.js JS libs (introducing NPM dependencies management system for JS/CSS libs) 2020-07-27 15:09:34 +02:00
Molkobain
20ed4fe2d1 N°2847 - Theme: Add elevations as SCSS and CSS variables 2020-07-27 15:09:33 +02:00
Molkobain
87d7693be5 N°2847 - Rework iTopWebPage layout (WIP Part III)
- iTopWebPage: Extract processing in dedicated functions
- iTopWebPage: Add tooltips base feature
- iTopWebPage: Fix JS dictionaries
- iTopWebPage: Extract inline JS to dedicated file
- Top bar: Visual improvements
- Nav. menu: Visual improvements
- Nav. menu: Tooltip improvement
- Nav. menu: Toggle state preference saved
- Nav. menu: Add menus filter feature
- Breadcrumbs: Handle elements overflow
2020-07-27 15:09:33 +02:00
Molkobain
fa153d8504 N°3185 - Datamodel: Add square logo in branding 2020-07-27 15:09:33 +02:00
Molkobain
5d3009646a N°3182 - Datamodel: Add icon to menu groups 2020-07-27 15:09:33 +02:00
Molkobain
ec70d3546d PHPDoc & code cleanup 2020-07-27 15:09:33 +02:00
Molkobain
10a14a3844 N°3182 - Add support for MenuGroup CSS icon in the XML datamodel
- Introduce itop_design v1.8
- Default CSS classes are 'fas fa-ellipsis-v' from FontAwesome
- New tags <style><decoration_classes>xxx yyy</decoration_classes></style> under the <menu xsi:type="MenuGroup"> tag
2020-07-27 15:09:33 +02:00
Molkobain
0516862632 N°2847 - Rework of iTopWebPage: Preliminary work on the page content 2020-07-27 15:09:33 +02:00
Molkobain
ad379d3ef4 N°2847 - Rollback fix on pages <base> URL tag (to much consequences)
Fixing the base URL tag on WebPage and derivated classes would require to migrate all extensions which use the add_linked_script / add_linked_stylesheet functions with a relative URL, which is almost all extensions. So the benefit isn't worth the workload.

Note: We could have migrate extensions by replacing "../images|js|css/xxx.yyy" with utils::GetAbsoluteUrlAppRoot()."images|js|css/xxx.yyy" to make them compatible with iTop 2.8 and previous versions. But as said before, much work.
2020-07-27 15:09:33 +02:00
Molkobain
8efd8008fc N°2847 - Rework of the global iTopWebPage layout (Part II)
- Optimize TWIG templates includes (don't pass context to autonomous components)
- Preliminary work of the top bar and breadcrumbs features
- Removal of images dedicated to the breadcrumbs feature
2020-07-27 15:09:33 +02:00
Molkobain
6f9565d979 Remove duplicated entries from .gitignore 2020-07-27 15:09:32 +02:00
Molkobain
6f3ec99501 N°2847 - Update UI components dictionaries 2020-07-27 15:09:32 +02:00
Molkobain
ab681b0954 N°2847 - Rework of the global iTopWebPage layout (Part I)
- Rework of the iTopWebPage class
- Use of TWIG templates
- Preliminary work of the navigation menu
2020-07-27 15:09:32 +02:00
Molkobain
c9b80074f4 Add helper to know if we currently are in "debug" mode
Can be used to enable more features / debugging tools throughout the application.
2020-07-27 15:09:32 +02:00
Molkobain
f5df442e8b N°2847 - Improve SCSS files structure
- Refactor some mixins into helpers
- CSS3 variables set via SCSS variables are now using "interpolation" (#{$my-var} instead of just $my-var) to be compatible with more SCSS compiler
- Overloading some Bulma variables such as font-family
2020-07-27 15:09:32 +02:00
Molkobain
fab58d503b TwigBase: Change "add_itop_version" TWIG function to use cache buster timestamp 2020-07-27 15:09:32 +02:00
Molkobain
1a3411e99d TwigBase: Add 2 TWIG functions (get_absolute_url_app_root / get_absolute_url_modules_root) 2020-07-27 15:09:32 +02:00
Molkobain
ddcd6d4e91 N°2847 - Remove POC page 2020-07-27 15:09:32 +02:00
Molkobain
9eb826bb0b Add support for dictionaries in sub-folders of /dictionaries 2020-07-27 15:09:32 +02:00
Molkobain
a88df33167 Add SASS converter cache to .gitignore 2020-07-27 15:09:32 +02:00
Molkobain
ec48b397ad Code cleanup & PHPDoc 2020-07-27 15:09:31 +02:00
Molkobain
f8af900c73 N°2847 - WIP: Sync. commit 2020-07-27 15:09:31 +02:00
Molkobain
7d4455baf4 N°2847 - Start rework of the CSS/JS/TWIG files structure 2020-07-27 15:09:31 +02:00
Molkobain
e37c620d2d PHPDoc & code cleanup 2020-07-24 14:11:30 +02:00
Molkobain
dd4ef8f91a N°2870 - Portal: Fix "Notice: Undefined index: UI:PropertiesTab" on object form 2020-07-24 14:08:33 +02:00
bruno-ds
5edcc91182 CI: release checklist test suite config file 2020-07-24 09:10:04 +02:00
bruno-ds
e2b5992e80 n°3062 - css generator on build 2020-07-24 09:08:45 +02:00
bruno-ds
a2af061a34 n°3057 - bugfix & output improvement 2020-07-24 09:07:43 +02:00
bruno DA SILVA
a53ce32e54 N°3062 - setup.css file integrity test
this is for the task : "Generate setup.css at build time"
2020-07-22 17:09:51 +02:00
odain
08414296b6 tmp work: save timestamp + few fixes 2020-07-21 23:22:50 +02:00
Pierre Goiffon
06ee1f3a15 N°2284 add @since in old autocomplete widget file 2020-07-20 10:38:55 +02:00
Eric
27ed1f3307 N°2957 - Fix required files and generate files into src/Model 2020-07-20 09:58:51 +02:00
Thomas Casteleyn
4f72336301 Suggest correct filename on download 2020-07-17 13:17:18 +02:00
Thomas Casteleyn
868c157ae4 Change lifecycle generation to SVG instead of PNG 2020-07-17 13:17:18 +02:00
Thomas Casteleyn
c2b72c7688 More memory efficient image pass through 2020-07-17 13:17:18 +02:00
odain
307713d326 N°2996 - Remove iTop version from css-variable.scss
Include images in precompilation check
2020-07-16 18:41:55 +02:00
odain
2d8b888a18 N°2982 - speedup themes/scss compilation during setup 2020-07-16 18:41:01 +02:00
Denis Flaven
6b2b56cf72 Faster compilation of themes
- Ability to provided precompiled themes in the datamodel
- Check that a precompiled theme is still up-to-date based on a signature
2020-07-16 18:33:13 +02:00
bruno DA SILVA
52baed7960 3054 - whoop fix duplicated class name 2020-07-16 17:20:25 +02:00
bruno DA SILVA
7ae895d701 3054 - Check community modules version against major version 2020-07-16 16:56:49 +02:00
bruno DA SILVA
120452d2ee 3052 - follow 919a7a8ef5 folder renaming 2020-07-16 16:47:40 +02:00
bruno DA SILVA
25791a1d18 3052 - Check community modules XML version against latest version 2020-07-16 16:45:20 +02:00
odain
919a7a8ef5 PS-4 folder renaming 2020-07-16 16:43:43 +02:00
odain
da0e0a99c7 revert to no behat tests 2020-07-16 16:16:17 +02:00
bruno DA SILVA
82ba53d40b 3059 - Automatically set the documentation URLs 2020-07-16 16:11:46 +02:00
odain
7ed1feff3e test phpunit + behat 2020-07-16 15:47:20 +02:00
odain
6bdfdad09f test phpunit + behat 2020-07-16 15:44:47 +02:00
odain
8c3417adeb work locally 2020-07-16 15:38:23 +02:00
Eric
e4ab543aff Disable twig cache for developers (instead of having config flag) 2020-07-16 15:07:03 +02:00
Eric
11ef705816 N°2969 - change dictionaries folder from dictionary to dictionaries 2020-07-16 14:52:03 +02:00
odain
b3b440362a N°3057 - New build recipe: start only 2020-07-16 14:19:38 +02:00
odain
ee9e747b1c stop working/debugging locally 2020-07-16 09:46:31 +02:00
odain
1baf8a6424 use all phpunit as usual 2020-07-16 09:35:10 +02:00
odain
7a89bfc108 fix ci: force authent-local conf to be able to create admin in a test 2020-07-16 09:08:31 +02:00
odain
68c7b9a0e8 fix test that breaks config-itop.php 2020-07-15 19:56:25 +02:00
odain
9e3b1bfc78 use less tests + try sth 2020-07-15 19:47:35 +02:00
odain
01f9a6f758 fix test dependency: require admin user 2020-07-15 17:54:24 +02:00
odain
8e028753b7 without use config-itop.php inside backup 2020-07-15 17:38:18 +02:00
odain
343d626025 without backup again 2020-07-15 16:55:47 +02:00
odain
6078f39730 fix config_itop.php in backup 2020-07-15 16:51:47 +02:00
odain
78dc57bdb5 remove dump from backup 2020-07-15 16:40:14 +02:00
odain
94a468efc8 again 2020-07-15 16:34:42 +02:00
odain
864572c02a fix setup in freshinstall mode 2020-07-15 16:06:07 +02:00
odain
d353d56e7b add exception in test failed 2020-07-15 11:13:48 +02:00
odain
a3e6600195 try with a backup file 2020-07-15 11:04:16 +02:00
odain
51ab8706e8 fix setup file 2020-07-15 10:51:28 +02:00
odain
24f832de78 fix xml test file 2020-07-15 09:40:13 +02:00
Eric
7e970ee93c N°3015 - fix notice 2020-07-15 09:38:33 +02:00
Eric
e7fa9eda65 Use configuration for enabling twig cache 2020-07-15 09:33:54 +02:00
odain
e295226bd6 work locally 2020-07-15 08:08:11 +02:00
odain
78d23261ca use itop_infra ci 2020-07-13 17:02:32 +02:00
Eric
edefa4da4f N°2330 - set the php minimum version to 7.2 2020-07-13 15:35:18 +02:00
odain
4ba76f95b2 N°3053 - Check XML conversion methods 2020-07-13 10:56:45 +02:00
Eric
6320879fe1 Add a cleanup file to the database integrity report.
Use it for fast database cleanup (removal of all broken entries) when you don't want to try recovering inconsistent records.
Note that you may need multiple report to eliminate all the inconsistencies.
2020-07-10 17:57:13 +02:00
Romain Quetiez
acf0548c4c N°3171 - Friendly name and obsolescence flag not refreshed (#151)
- Compute any type of expression on server side
- Recompute friendly name and obsolescence flag on server side (DBOBject)
- Bonus : compute dependency for external keys
2020-07-10 17:26:37 +02:00
Eric
b1fa429234 N°2957 - Better check of the class parameter in requests 2020-07-10 14:41:38 +02:00
Eric
78516f437a N°2957 - Compile classes in separated files 2020-07-10 14:41:08 +02:00
Denis Flaven
ea75092bb3 N°3166 - fix for a crash in expressioncache
iTop was crashing if the expression/format for the name of a class contained a quote (')
2020-07-10 11:27:43 +02:00
acognet
df4dfe4803 N°2284 - Replace JQuery Autocompleter plugin by JQuery UI Autocomplete widget
N°2390 - Auto-complete - Relevant results in first
2020-07-09 09:14:44 +02:00
acognet
fddf30e6c3 N°2956 - Upgrade jQuery to v3.5.1 2020-07-09 09:07:11 +02:00
Eric
b718e26c7f N°2969 - Add support for dictionaries folder in modules 2020-07-08 15:21:07 +02:00
Eric
e564f71c14 N°2986 - Reintegrate application menus from "welcome itil" into application 2020-07-08 11:49:52 +02:00
Eric
4f3931e828 N°2370 - Remove MySQL views in iTop, moved to "combodo-views" extension 2020-07-08 10:33:15 +02:00
Eric
d73fdce3c5 Fix legacy regression 2020-07-07 13:58:36 +02:00
acognet
0787c622d4 N°2738 - Remove unused dict keys 2020-07-06 13:43:46 +02:00
annProg
4c11eb099b fix: Typology translation for zh_cn 2020-07-04 22:52:38 +02:00
acognet
9628a1d028 N°2284 - Replace JQuery Autocompleter plugin by JQuery UI Autocomplete widget and start of bug 2390 - Auto-complete - Relevant results in first 2020-07-02 11:46:51 +02:00
acognet
5585385d08 N°2393 - API : Font Awesome remove v4 compatibility 2020-07-02 11:03:05 +02:00
acognet
98870b06e3 2548 - API : remove \DBObject::GetRelationQueries overrides in default datamodel 2020-07-01 16:28:20 +02:00
bruno DA SILVA
46d91322c1 n°2556 - fix errors in the merge of support/2.7 into develop
The cherry picks resulted in an out of order apply of the commits, the result was that the wrong code was keeped
2020-07-01 15:41:46 +02:00
acognet
ca28f8f3c4 2393 - API : Font Awesome remove v4 compatibility 2020-06-30 16:28:15 +02:00
acognet
0aaa55b35b 3009 - PHP Minimum version raised to 7.1 2020-06-30 10:10:00 +02:00
acognet
e9a1167da6 N°2363 - API : deprecate old linkedset update pattern 2020-06-30 09:02:42 +02:00
acognet
a67fce66fc N°2999 - Optimize OQL 2020-06-26 16:42:03 +02:00
acognet
491d1d7d53 N°API : remove CMDBSource::GetNextInsertId 2020-06-26 16:20:15 +02:00
acognet
54b48dc908 N°2372 - API : remove \MetaModel::EnumLinksClasses and \MetaModel::EnumLinkingClasses 2020-06-26 16:17:04 +02:00
acognet
dadeab58eb N°852 - Cleanup: remove deprecated impact analysis algorithm and clean up old broken test 2020-06-26 15:03:10 +02:00
acognet
0ecdc6620b N°852 - Cleanup: remove deprecated impact analysis algorithm 2020-06-26 15:02:30 +02:00
acognet
80161b909e N°2362 - API : remove DBInsertTracked / DBUpdateTracked 2020-06-26 14:16:14 +02:00
Pierre Goiffon
888232e8c3 Merge remote-tracking branch 'origin/support/2.7' into develop
# Conflicts:
#	test/core/HTMLDOMSanitizerTest.php
2020-06-26 10:53:49 +02:00
Pierre Goiffon
d904883bdd 📝 CONTRIBUTING : fix release branch naming 2020-06-23 17:48:42 +02:00
bruno DA SILVA
35d2c3afac uses the conventionnal host 2020-06-23 15:10:09 +02:00
Molkobain
7cee8c3cd0 Update README.md 2020-06-23 15:05:41 +02:00
Molkobain
27622bbcec Update readme.txt 2020-06-23 15:05:38 +02:00
bruno DA SILVA
219270ce81 Removes latest versions to ease maintenance 2020-06-23 14:59:11 +02:00
OИUЯd da silva
16dd47a3b9 simplify the readme (#143)
Removes latest versions to ease maintenance
2020-06-23 14:51:52 +02:00
Molkobain
70835984de Add "software requirements" section to README
At first I added a complete section with a table and all currently supported versions of iTop, then @bruno-ds pointed out that it would only be redundant with the wiki page and just another source of information to maintain (which is totally true, thanks!). So instead it's just a simple link in the "resources" section.
2020-06-23 14:23:15 +02:00
Pierre Goiffon
d9224f43f2 AttributeImage : remove useless override
The previous code was removed in d5b0bb02, and until then the method was just calling the parent doing nothing more
2020-06-23 11:39:51 +02:00
Molkobain
5b9643d6fb Update PHPDoc 2020-06-22 16:06:21 +02:00
bruno DA SILVA
d3525190d5 N°2556 - Html sanitization preserve content of removed tags (except for a forbidden list)
forbidden list: see $aTagsContentRemovableList

(cherry picked from commit 746b47bb0e)
(cherry picked from commit 79909fadc0)
2020-06-22 11:40:38 +02:00
Pierre Goiffon
f20808d929 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-06-22 11:39:44 +02:00
Pierre Goiffon
aee80e41ca N°2214 Fix typo in method name
Introduced in b7136c0b7a
Many Thanks @jbostoen !
2020-06-19 13:47:15 +02:00
Pierre Goiffon
56ea6c8848 N°2214 fix @since after cherry-pick 2020-06-15 16:22:04 +02:00
Pierre Goiffon
b7136c0b7a N°2214 Add PHP check in CLI scripts
It is quite common that the PHP interpreter that is launched in CLI is different that the one used by the webserver. So iTop code launched by CLI could run in a context that doesn't meet iTop requirements !

This adds in the following scripts the same control that is done on the setup wizard first step :
* cron.php
* backup, check-backup
* export, exportv2
* bulk import
* synchro-exec, synchro-import

If the check throws at least one error then the script is stopped with an appropriate message, and a log is made (IssueLog, Error level, CLI channel)

(cherry picked from commit c768e18e2b : no risk taken for 2.7.1, so cherry picked for 2.8.0)
2020-06-15 15:20:17 +02:00
Pierre Goiffon
ea94986247 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-06-15 15:19:12 +02:00
odain
d8363067e6 ci 2 new options: coverture + run only one test 💚
try to have coverture option
2020-06-15 10:32:47 +02:00
odain
6e4f9a9b93 small fix 2020-06-12 09:14:09 +02:00
odain
29c4ca7e65 revert lib/composer/autoload unwanted commit 2020-06-11 21:33:54 +02:00
odain
4d3aefe2a4 fix all the tests 2020-06-11 21:29:03 +02:00
odain
c798e0a9cd fix absolute path test failure 2020-06-08 16:56:29 +02:00
odain
1b9c7b7766 fix test failed due to different absolute paths 2020-06-08 15:32:57 +02:00
odain
19ee8e4071 add recurseMkdir 2020-06-08 14:49:16 +02:00
Thomas Casteleyn
b302569feb Update Dutch SynchroAttribute::update_policy translation 2020-06-08 13:02:57 +02:00
odain
4741012fb9 debug testcase failed 2020-06-08 11:26:12 +02:00
odain
f5886f603b themehandler better test failure feedback 2020-06-08 10:57:57 +02:00
odain
d82086240b Merge branch 'feature/bug2996-checklist-compilation' into feature/fast-theme-compilation 2020-06-08 09:39:33 +02:00
odain
f47b16c571 adapt precompiled styles 2020-06-08 09:38:39 +02:00
odain
728b082a7d remove version from css-variables.scss 2020-06-08 09:35:12 +02:00
odain
31482ccef7 make sure GetCompiledModuleVersion returns timestamp 2020-06-08 09:34:45 +02:00
odain
ca99e612cf clean .make from css-variable version update 2020-06-08 09:33:51 +02:00
odain
c9c833a868 IssueLog instead of echo in ThemeHandler 2020-06-04 22:50:45 +02:00
odain
7cad4ac444 adapt testValidatePrecompiledStyles 2020-06-04 22:47:50 +02:00
odain
001680906d molkobain remarks in PR 2020-06-04 21:58:03 +02:00
odain-cbd
ed355cb8cf Update application/themehandler.class.inc.php
Co-authored-by: Molkobain <guillaume.lajarige@combodo.com>
2020-06-04 21:52:02 +02:00
odain-cbd
fa036ee724 Update application/themehandler.class.inc.php
Co-authored-by: Molkobain <guillaume.lajarige@combodo.com>
2020-06-04 21:47:02 +02:00
odain-cbd
e04548a61c Update application/themehandler.class.inc.php
Co-authored-by: Molkobain <guillaume.lajarige@combodo.com>
2020-06-04 21:45:29 +02:00
odain-cbd
25c55bdaf4 Update application/themehandler.class.inc.php
Co-authored-by: Molkobain <guillaume.lajarige@combodo.com>
2020-06-04 21:43:17 +02:00
odain-cbd
c1c43c7d5d Update application/themehandler.class.inc.php
Co-authored-by: Molkobain <guillaume.lajarige@combodo.com>
2020-06-04 21:43:00 +02:00
odain
b970d81272 Merge branch 'develop' into feature/fast-theme-compilation 2020-06-04 14:45:11 +02:00
odain
b7ffa9e3c0 bug2996: include images in precompiled signature 2020-06-04 08:55:28 +02:00
odain
e94d5418f4 Merge branch 'develop' into feature/bug2996-checklist-compilation 2020-06-03 11:32:26 +02:00
Pierre Goiffon
44008fc179 Merge remote-tracking branch 'origin/support/2.7' into develop 2020-05-26 12:26:44 +02:00
Thomas Casteleyn
c2f62a13e6 Fix duplicate version loading (#141) 2020-05-13 14:53:47 +02:00
odain
d13591ca85 create parameters.json folder when necessary 2020-05-04 15:24:45 +02:00
odain
f88f321d79 avoid to read SCSS signature when no file touched and itop theme loading only + create parameters.json folder when necessary 2020-05-04 15:16:50 +02:00
odain
c1b3cb1e7b avoid to read SCSS signature when no file touched and itop theme loading only + create parameters.json folder when necessary 2020-05-04 15:16:26 +02:00
odain
d43c72b3ac avoid to read SCSS signature when no file touched and itop theme loading only 2020-05-04 14:42:55 +02:00
odain
1457faa53a add precompiled themes validation test 2020-05-04 14:15:33 +02:00
odain
3ee4b564f0 add a test to always validate datamodel declared precompiled CSS 2020-05-04 11:16:51 +02:00
odain
0988c8ccbf Mock compiler caller for test + Cover with tests + small css compilation fixes 2020-05-02 00:45:15 +02:00
Pierre Goiffon
accda04a37 Hierarchical selection popup : now collapsed by default, and collapse all / expand all buttons (#132)
The collapse all / expand all is not printed if no child exists
Combodo implementation of PR #87
2020-04-27 11:21:37 +02:00
Pierre Goiffon
f364e7b043 N°2923 Datatable : fix var name typo
Thanks @bruno-ds !
2020-04-24 18:28:50 +02:00
Pierre Goiffon
42afe033ef N°2923 Datatable : use container id instead of externally generated id 2020-04-24 17:16:47 +02:00
Denis Flaven
5cfa06e36f Faster compilation of themes
- Ability to provided precompiled themes in the datamodel
- Check that a precompiled theme is still up-to-date based on a signature
2020-04-22 16:11:02 +02:00
Pierre Goiffon
8de4c0360d Merge remote-tracking branch 'origin/support/2.7' into develop 2020-04-20 16:08:15 +02:00
Molkobain
24130dd94f Change version number to 2.8.0-dev 2020-04-14 18:01:30 +02:00
Lars Hippler
427326d074 Enable external classes (i.e. iApplicationObjectExtension) to use bypass function as well 2020-04-08 17:24:12 +02:00
Pierre Goiffon
59cc6d3f76 📝 CONTRIBUTING : fix unecessary escape 2020-04-07 15:33:26 +02:00
Pierre Goiffon
ee37373cfa 📝 CONTRIBUTING : branch model paragraph small changes 2020-04-07 15:24:56 +02:00
Pierre Goiffon
56d9653f15 📝 CONTRIBUTING : modify branch model
We are renaming the master branch, so using a custom GitFlow branch model :)
2020-04-06 09:32:23 +02:00
Pierre Goiffon
d5670abdcc 📝 Fix PHPDoc for \MFElement::_FindNode
Introduced in 4688c92e
2020-04-06 08:57:23 +02:00
Pierre Goiffon
0360a3160d Merge remote-tracking branch 'origin/releases/germanium' into develop
# Conflicts:
#	setup/modelfactory.class.inc.php
2020-04-06 08:47:22 +02:00
Thomas Casteleyn
bcd21aefb4 📝 DBObject fix wrong PHPDoc (#133)
Thanks to @Hipska !
2020-04-06 08:32:31 +02:00
Pierre Goiffon
3cbcdd4f13 🎨 MFElement : fix access modifiers & PHPDoc 2020-03-31 08:53:22 +02:00
odain
c46d0f5662 N°2888 Impossibility to import iTop User with password policy 2020-03-30 17:48:01 +02:00
Pierre Goiffon
b2454d44ae 👥 Added @ousret to the contributor list
See #99
Thanks to him !
2020-03-30 08:39:35 +02:00
TAHRI Ahmed R
79cfb95f6e Support array for json_data posted in rest/json service (#99)
Previous syntax :
```
CURLOPT_POSTFIELDS => array(
	'auth_user' => 'admin',
	'auth_pwd' => 'admin',
	'json_data' => '{
	   "operation": "core/get",
	   "class": "Person",
	   "key": "SELECT Person", "limit": "10", "page": "1"
	}'
);
```

Now we can also use :
```
CURLOPT_POSTFIELDS => array(
	'auth_user' => 'admin',
	'auth_pwd' => 'admin',
	"json_data[operation]" => "core/get",
	"json_data[class]" => "Person",
	"json_data[key]" => "SELECT Person",
	"json_data[limit]" => 10,
	"json_data[page]" => 1
);
```
2020-03-27 18:11:09 +01:00
Pierre Goiffon
ff2e1a3507 Fix syntax error in core/email.class.inc.php
Missing ";" at the end of line :/
Introduced by 503afb98
2020-03-27 16:43:03 +01:00
Lars Hippler
503afb9831 Make it possible to add return path for mails (#95) 2020-03-27 16:20:59 +01:00
Pierre Goiffon
b6772917ae Merge branch 'release/2.7.0' into develop
# Conflicts:
#	.make/license/gen-community-license.sh
#	setup/licenses/community-licenses.xml
2020-03-27 15:20:08 +01:00
bruno DA SILVA
00971f9ec7 rollback on two composer options: adding them made no sense since this file is not meant to handle dependencies but just use the autoloader (dependencies are handled by the one a the root of the project) 2020-03-21 16:17:45 +01:00
bruno DA SILVA
a3a97fa228 added missing composer config for the portal's composer.json
- php 5.6+
 - dump the autoloader as optimized as possible
2020-03-21 16:13:28 +01:00
bruno DA SILVA
18c4ca9131 🐛 fix Cannot connect to the MySQL server for the CI's unattended_install 2020-03-20 15:15:29 +01:00
odain
466ddf768e Fix license generation tool 2020-03-17 18:59:29 +01:00
odain
76d26e8ef9 Fix license generation tool 2020-03-17 18:59:18 +01:00
odain
b526d6422b Adding a test to cover selectin/cmdb code
cleanup
2020-03-12 10:55:18 +01:00
Pierre Goiffon
63c02ff33d 📝 Fix PHPDoc typo 2020-03-09 16:00:15 +01:00
Molkobain
4688c92e7c Internal: PHPDoc and warnings suppression 2020-02-25 15:44:26 +01:00
odain
75ba9101a6 add tests to cover this change 2019-12-20 16:13:52 +01:00
odain
df64c184b6 Merge branch 'develop' into feature/perf-serialization 2019-12-20 10:49:29 +01:00
bruno DA SILVA
1ab09702d9 [POC] reduce metamodel serialized size by storing class name instead of class instance. This could be extended to more properties 2019-03-26 14:49:19 +01:00
Guy Couronné
618326d01f Correction unknown attribute id
operation : "core/get"
class : "NetworkDevice"
output_fields : "id,friendlyname" 
key : "SELECT NetworkDevice"

Code 100 “Error: Unknown attribute id from class NetworkDevice”
2019-03-18 08:53:50 +01:00
Guy Couronné
9b8dace833 Optimize Column load when using api core/get
Getting in SQL only desired columns to show
Used when output_fields != '*' || '*+'

Signed-off-by: Guy Couronné <gcouronne@sapiens.biz>
2019-03-15 11:17:58 +01:00
1833 changed files with 128857 additions and 41038 deletions

View File

@@ -141,7 +141,7 @@ ij_javascript_chained_call_dot_on_new_line = true
ij_javascript_class_brace_style = end_of_line
ij_javascript_comma_on_new_line = false
ij_javascript_do_while_brace_force = always
ij_javascript_else_on_new_line = true
ij_javascript_else_on_new_line = false
ij_javascript_enforce_trailing_comma = keep
ij_javascript_extends_keyword_wrap = off
ij_javascript_extends_list_wrap = off

20
.gitignore vendored
View File

@@ -1,4 +1,15 @@
################################### Temporary ignore rules during 2.8 UI/UX dev - START
/css/backoffice/main.css
# Sass converter
/**/.sass-cache/
################################### Temporary ignore rules during 2.8 UI/UX dev - END
# no slash at the end to handle also symlinks
/toolkit
/env-*
@@ -6,12 +17,6 @@
# maintenance mode (N°2240)
/.maintenance
# listing prevention in conf directory
/conf/**
!/conf/.htaccess
!/conf/index.php
!/conf/web.config
# composer reserver directory, from sources, populate/update using "composer install"
vendor/*
test/vendor/*
@@ -19,6 +24,7 @@ test/vendor/*
# all conf but listing prevention
/conf/**
!/conf/.htaccess
!/conf/index.php
!/conf/web.config
# all datas but listing prevention
@@ -134,3 +140,5 @@ local.properties
.cache-main
.scala_dependencies
.worksheet
/css/setup.css

View File

@@ -52,23 +52,23 @@ Here are the branches we use and their meaning :
For example, if no version is currently prepared for shipping we could have:
- `develop` containing future 2.8.0 version
- `develop` containing future 3.0.0 version
- `support/2.7`: 2.7.x maintenance version
- `support/2.6`: 2.6.x maintenance version
- `support/2.5`: 2.5.x maintenance version
In this example, when 2.8.0-beta is shipped that will become:
In this example, when 3.0.0-beta is shipped that will become:
- `develop`: future 2.9.0 version
- `release/2.8`: 2.8.0-beta
- `develop`: future 3.1.0 version
- `release/3.0.0`: 3.0.0-beta
- `support/2.7`: 2.7.x maintenance version
- `support/2.6`: 2.6.x maintenance version
- `support/2.5`: 2.5.x maintenance version
And when 2.8.0 final will be out:
And when 3.0.0 final will be out:
- `develop`: future 2.9.0 version
- `support/2.8`: 2.8.x maintenance version (will host developments for 2.8.1)
- `develop`: future 3.1.0 version
- `support/2.8`: 2.8.x maintenance version (will host developments for 3.0.1)
- `support/2.7`: 2.7.x maintenance version
- `support/2.6`: 2.6.x maintenance version
- `support/2.5`: 2.5.x maintenance version

View File

@@ -21,21 +21,35 @@ iTop also offers mass import tools and web services to integrate with your IT
- [Data synchronization][18] (for data federation)
## Latest release
- [Changes since the previous version][62]
- [New features][63]
- [Installation notes][64]
- [Download][65]
[62]: https://www.itophub.io/wiki/page?id=latest:release:change_log
[63]: https://www.itophub.io/wiki/page?id=latest:release:start
[64]: https://www.itophub.io/wiki/page?id=latest:install:start
[65]: https://sourceforge.net/projects/itop/files/latest/download
## Resources
- [iTop Forums][1]: community support
- [iTop Tickets][2]: for feature requests and bug reports
- [Releases download][3]
- [Documentation][4] covering both iTop and its official extensions
- [iTop Hub][5] : discover and install extensions !
- [Software requirements][4]
- [Documentation][5] covering both iTop and its official extensions
- [iTop Hub][6] : discover and install extensions !
[1]: https://sourceforge.net/p/itop/discussion/
[2]: https://sourceforge.net/p/itop/tickets/
[3]: https://sourceforge.net/projects/itop/files/itop/
[4]: https://www.itophub.io/wiki
[5]: https://store.itophub.io/en_US/
[4]: https://www.itophub.io/wiki/page?id=latest:install:upgrading_itop
[5]: https://www.itophub.io/wiki
[6]: https://store.itophub.io/en_US/
[10]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#configuration_management_cmdb
[11]: https://www.itophub.io/wiki/page?id=latest%3Adatamodel%3Astart#ticketing
@@ -49,47 +63,6 @@ iTop also offers mass import tools and web services to integrate with your IT
## Last releases
### Versions 2.7.*
- 2.7.1 published on April 8, 2020
- [Changes since the previous version][62]
- [New features][63]
- [Migration notes][64]
- [Download iTop 2.7.0-2][65]
[62]: https://www.itophub.io/wiki/page?id=2_7_0:release:change_log
[63]: https://www.itophub.io/wiki/page?id=2_7_0:release:2_7_whats_new
[64]: https://www.itophub.io/wiki/page?id=2_7_0:install:260_to_270_migration_notes
[65]: https://sourceforge.net/projects/itop/files/itop/2.7.0-2
### Versions 2.6.*
- 2.6.0 published on January 9, 2019
- [Changes since the previous version][58]
- [New features][59]
- [Migration notes][60]
- [Download iTop 2.6.3][61]
[58]: https://www.itophub.io/wiki/page?id=2_6_0:release:change_log
[59]: https://www.itophub.io/wiki/page?id=2_6_0:release:2_6_whats_new
[60]: https://www.itophub.io/wiki/page?id=2_6_0:install:250_to_260_migration_notes
[61]: https://sourceforge.net/projects/itop/files/itop/2.6.3
### Versions 2.5.*
- 2.5.0 published on July 11, 2018
- [Changes since the previous version][54]
- [New features][55]
- [Migration notes][56]
- [Download iTop 2.5.1][57]
[54]: https://www.itophub.io/wiki/page?id=2_5_0:release:change_log
[55]: https://www.itophub.io/wiki/page?id=2_5_0:release:2_5_whats_new
[56]: https://www.itophub.io/wiki/page?id=2_5_0:install:240_to_250_migration_notes
[57]: https://sourceforge.net/projects/itop/files/itop/2.5.1
## About Us
iTop development is sponsored, led and supported by [Combodo][0].
@@ -105,10 +78,10 @@ We would like to give a special thank you to the people from the community who c
- Alves, David
- Beck, Pedro
- Bilger, Jean-François
- Bostoen, Jeffrey
- Bostoen, Jeffrey (a.k.a @jbostoen)
- Cardoso, Anderson
- Cassaro, Bruno
- Casteleyn, Thomas
- Casteleyn, Thomas (a.k.a @Hipska)
- Castro, Randall Badilla
- Colantoni, Maria Laura
- Couronné, Guy
@@ -124,6 +97,7 @@ We would like to give a special thank you to the people from the community who c
- Lazcano, Federico
- Lucas, Jonathan
- Malik, Remie
- Mindêllo de Andrade, Lucas (a.k.a @rokam)
- Rosenke, Stephan
- Seki, Shoji
- Shilov, Vladimir

View File

@@ -9,7 +9,7 @@ responsible disclosure and will make every effort to acknowledge your contributi
### iTop vulnerabilities
Please send a procedure to reproduce iTop vulnerabilities to [itop-security@combodo.com](mailto:itop-security@combodo.com).
You can send us a standard "given / then / when" report, including iTop version, impacts, and maybe installed modules or data if they are
You can send us a standard "given / when / then" report, including iTop version, impacts, and maybe installed modules or data if they are
needed to reproduce.
### Dependencies vulnerabilities

View File

@@ -55,7 +55,6 @@ class URP_Profiles extends UserRightsBaseClassGUI
"db_table" => "priv_urp_profiles",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -170,11 +169,9 @@ class URP_Profiles extends UserRightsBaseClassGUI
function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
public static function GetReadOnlyAttributes()
@@ -243,7 +240,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -349,7 +345,6 @@ class URP_UserOrg extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userorg",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();

View File

@@ -77,7 +77,6 @@ class URP_Profiles extends UserRightsBaseClassGUI
"db_table" => "priv_urp_profiles",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -312,11 +311,9 @@ class URP_Profiles extends UserRightsBaseClassGUI
function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
}
@@ -336,7 +333,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -376,7 +372,6 @@ class URP_UserOrg extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userorg",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -417,7 +412,6 @@ class URP_ActionGrant extends UserRightsBaseClass
"db_table" => "priv_urp_grant_actions",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -454,7 +448,6 @@ class URP_StimulusGrant extends UserRightsBaseClass
"db_table" => "priv_urp_grant_stimulus",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -491,7 +484,6 @@ class URP_AttributeGrant extends UserRightsBaseClass
"db_table" => "priv_urp_grant_attributes",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();

View File

@@ -58,7 +58,6 @@ class URP_Profiles extends UserRightsBaseClass
"db_table" => "priv_urp_profiles",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -144,11 +143,9 @@ class URP_Profiles extends UserRightsBaseClass
function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage);
}
}
@@ -167,7 +164,6 @@ class URP_Dimensions extends UserRightsBaseClass
"db_table" => "priv_urp_dimensions",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -280,7 +276,6 @@ class URP_UserProfile extends UserRightsBaseClass
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -321,7 +316,6 @@ class URP_ProfileProjection extends UserRightsBaseClass
"db_table" => "priv_urp_profileprojection",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -402,7 +396,6 @@ class URP_ClassProjection extends UserRightsBaseClass
"db_table" => "priv_urp_classprojection",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -474,7 +467,6 @@ class URP_ActionGrant extends UserRightsBaseClass
"db_table" => "priv_urp_grant_actions",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -511,7 +503,6 @@ class URP_StimulusGrant extends UserRightsBaseClass
"db_table" => "priv_urp_grant_stimulus",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -548,7 +539,6 @@ class URP_AttributeGrant extends UserRightsBaseClass
"db_table" => "priv_urp_grant_attributes",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();

View File

@@ -30,8 +30,11 @@ function mb_str_replace($search, $replace, $subject, &$count = 0) {
$replacements = array_pad($replacements, count($searches), '');
foreach ($searches as $key => $search) {
$parts = mb_split(preg_quote($search), $subject);
$count += count($parts) - 1;
$subject = implode($replacements[$key], $parts);
if (is_array($parts))
{
$count += count($parts) - 1;
$subject = implode($replacements[$key], $parts);
}
}
} else {
// Call mb_str_replace for each subject in array, recursively

View File

@@ -1,417 +1,16 @@
<?php
/**
* Copyright (C) 2013-2020 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
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/AjaxPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT."/application/webpage.class.inc.php");
class ajax_page extends WebPage implements iTabbedPage
/**
* Class ajax_page
* @deprecated will be removed in 3.1.0 - moved to AjaxPage
*/
class ajax_page extends AjaxPage
{
/**
* Jquery style ready script
* @var array
*/
protected $m_sReadyScript;
protected $m_oTabs;
private $m_sMenu; // If set, then the menu will be updated
/**
* constructor for the web page
*
* @param string $s_title Not used
*/
function __construct($s_title) {
$sPrintable = utils::ReadParam('printable', '0');
$bPrintable = ($sPrintable == '1');
parent::__construct($s_title, $bPrintable);
$this->m_sReadyScript = "";
//$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->m_oTabs = new TabManager();
$this->sContentType = 'text/html';
$this->sContentDisposition = 'inline';
$this->m_sMenu = "";
utils::InitArchiveMode();
}
/**
* @inheritDoc
* @throws \Exception
*/
public function AddTabContainer($sTabContainer, $sPrefix = '')
{
$this->add($this->m_oTabs->AddTabContainer($sTabContainer, $sPrefix));
}
/**
* @inheritDoc
* @throws \Exception
*/
public function AddToTab($sTabContainer, $sTabCode, $sHtml)
{
$this->add($this->m_oTabs->AddToTab($sTabContainer, $sTabCode, $sHtml));
}
/**
* @inheritDoc
*/
public function SetCurrentTabContainer($sTabContainer = '')
{
return $this->m_oTabs->SetCurrentTabContainer($sTabContainer);
}
/**
* @inheritDoc
*/
public function SetCurrentTab($sTabCode = '', $sTabTitle = null)
{
return $this->m_oTabs->SetCurrentTab($sTabCode, $sTabTitle);
}
/**
* @inheritDoc
* @throws \Exception
*/
public function AddAjaxTab($sTabCode, $sUrl, $bCache = true, $sTabTitle = null)
{
$this->add($this->m_oTabs->AddAjaxTab($sTabCode, $sUrl, $bCache, $sTabTitle));
}
/**
* @inheritDoc
*/
public function GetCurrentTab()
{
return $this->m_oTabs->GetCurrentTab();
}
/**
* @inheritDoc
*/
public function RemoveTab($sTabCode, $sTabContainer = null)
{
$this->m_oTabs->RemoveTab($sTabCode, $sTabContainer);
}
/**
* @inheritDoc
*/
public function FindTab($sPattern, $sTabContainer = null)
{
return $this->m_oTabs->FindTab($sPattern, $sTabContainer);
}
/**
* Make the given tab the active one, as if it were clicked
* DOES NOT WORK: apparently in the *old* version of jquery
* that we are using this is not supported... TO DO upgrade
* the whole jquery bundle...
*/
public function SelectTab($sTabContainer, $sTabCode)
{
$this->add_ready_script($this->m_oTabs->SelectTab($sTabContainer, $sTabCode));
}
/**
* @param string $sHtml
*/
public function AddToMenu($sHtml)
{
$this->m_sMenu .= $sHtml;
}
/**
* @inheritDoc
*/
public function output()
{
if (!empty($this->sContentType))
{
$this->add_header('Content-type: '.$this->sContentType);
}
if (!empty($this->sContentDisposition))
{
$this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"');
}
foreach($this->a_headers as $s_header)
{
header($s_header);
}
if ($this->m_oTabs->TabsContainerCount() > 0)
{
$this->add_ready_script(
<<<EOF
// The "tab widgets" to handle.
var tabs = $('div[id^=tabbedContent]');
// Ugly patch for a change in the behavior of jQuery UI:
// Before jQuery UI 1.9, tabs were always considered as "local" (opposed to Ajax)
// when their href was beginning by #. Starting with 1.9, a <base> tag in the page
// is taken into account and causes "local" tabs to be considered as Ajax
// unless their URL is equal to the URL of the page...
if ($('base').length > 0)
{
$('div[id^=tabbedContent] > ul > li > a').each(function() {
var sHash = location.hash;
var sCleanLocation = location.href.toString().replace(sHash, '').replace(/#$/, '');
$(this).attr("href", sCleanLocation+$(this).attr("href"));
});
}
if ($.bbq)
{
// This selector will be reused when selecting actual tab widget A elements.
var tab_a_selector = 'ul.ui-tabs-nav a';
// Enable tabs on all tab widgets. The `event` property must be overridden so
// that the tabs aren't changed on click, and any custom event name can be
// specified. Note that if you define a callback for the 'select' event, it
// will be executed for the selected tab whenever the hash changes.
tabs.tabs({ event: 'change' });
// Define our own click handler for the tabs, overriding the default.
tabs.find( tab_a_selector ).click(function()
{
var state = {};
// Get the id of this tab widget.
var id = $(this).closest( 'div[id^=tabbedContent]' ).attr( 'id' );
// Get the index of this tab.
var idx = $(this).parent().prevAll().length;
// Set the state!
state[ id ] = idx;
$.bbq.pushState( state );
});
}
else
{
tabs.tabs();
}
EOF
);
}
// Render the tabs in the page (if any)
$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
// Additional UI widgets to be activated inside the ajax fragment
// Important: Testing the content type is not enough because some ajax handlers have not correctly positionned the flag (e.g json response corrupted by the script)
if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )
{
$this->add_ready_script(
<<<EOF
PrepareWidgets();
EOF
);
}
$this->outputCollapsibleSectionInit();
$oKPI = new ExecutionKPI();
$s_captured_output = $this->ob_get_clean_safe();
if (($this->sContentType == 'text/html') && ($this->sContentDisposition == 'inline'))
{
// inline content != attachment && html => filter all scripts for malicious XSS scripts
echo self::FilterXSS($this->s_content);
}
else
{
echo $this->s_content;
}
if (!empty($this->m_sMenu))
{
$uid = time();
echo "<div id=\"accordion_temp_$uid\">\n";
echo "<div id=\"accordion\">\n";
echo "<!-- Beginning of the accordion menu -->\n";
echo self::FilterXSS($this->m_sMenu);
echo "<!-- End of the accordion menu-->\n";
echo "</div>\n";
echo "</div>\n";
echo "<script type=\"text/javascript\">\n";
echo "$('#inner_menu').html($('#accordion_temp_$uid').html());\n";
echo "$('#accordion_temp_$uid').remove();\n";
echo "\n</script>\n";
}
//echo $this->s_deferred_content;
if (count($this->a_scripts) > 0)
{
echo "<script type=\"text/javascript\">\n";
echo implode("\n", $this->a_scripts);
echo "\n</script>\n";
}
if (count($this->a_linked_scripts) > 0)
{
echo "<script type=\"text/javascript\">\n";
foreach($this->a_linked_scripts as $sScriptUrl)
{
echo '$.getScript('.json_encode($sScriptUrl).");\n";
}
echo "\n</script>\n";
}
if (!empty($this->s_deferred_content))
{
echo "<script type=\"text/javascript\">\n";
echo "\$('body').append('".addslashes(str_replace("\n", '', $this->s_deferred_content))."');\n";
echo "\n</script>\n";
}
if (!empty($this->m_sReadyScript))
{
echo "<script type=\"text/javascript\">\n";
echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts
echo "\n</script>\n";
}
if(count($this->a_linked_stylesheets) > 0)
{
echo "<script type=\"text/javascript\">";
foreach($this->a_linked_stylesheets as $aStylesheet)
{
$sStylesheetUrl = $aStylesheet['link'];
echo "if (!$('link[href=\"{$sStylesheetUrl}\"]').length) $('<link href=\"{$sStylesheetUrl}\" rel=\"stylesheet\">').appendTo('head');\n";
}
echo "\n</script>\n";
}
if (trim($s_captured_output) != "")
{
echo self::FilterXSS($s_captured_output);
}
$oKPI->ComputeAndReport('Echoing');
if (class_exists('DBSearch'))
{
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
ExecutionKPI::ReportStats();
}
}
/**
* Adds a paragraph with a smaller font into the page
* NOT implemented (i.e does nothing)
* @param string $sText Content of the (small) paragraph
* @return void
*/
public function small_p($sText)
{
}
/**
* @inheritDoc
* @throws \Exception
*/
public function add($sHtml)
{
if (($this->m_oTabs->GetCurrentTabContainer() != '') && ($this->m_oTabs->GetCurrentTab() != ''))
{
$this->m_oTabs->AddToTab($this->m_oTabs->GetCurrentTabContainer(), $this->m_oTabs->GetCurrentTab(), $sHtml);
}
else
{
parent::add($sHtml);
}
}
/**
* @inheritDoc
*/
public function start_capture()
{
$sCurrentTabContainer = $this->m_oTabs->GetCurrentTabContainer();
$sCurrentTab = $this->m_oTabs->GetCurrentTab();
if (!empty($sCurrentTabContainer) && !empty($sCurrentTab))
{
$iOffset = $this->m_oTabs->GetCurrentTabLength();
return array('tc' => $sCurrentTabContainer, 'tab' => $sCurrentTab, 'offset' => $iOffset);
}
else
{
return parent::start_capture();
}
}
/**
* @inheritDoc
*/
public function end_capture($offset)
{
if (is_array($offset))
{
if ($this->m_oTabs->TabExists($offset['tc'], $offset['tab']))
{
$sCaptured = $this->m_oTabs->TruncateTab($offset['tc'], $offset['tab'], $offset['offset']);
}
else
{
$sCaptured = '';
}
}
else
{
$sCaptured = parent::end_capture($offset);
}
return $sCaptured;
}
/**
* @inheritDoc
*/
public function add_at_the_end($s_html, $sId = '')
{
if ($sId != '')
{
$this->add_script("$('#{$sId}').remove();"); // Remove any previous instance of the same Id
}
$this->s_deferred_content .= $s_html;
}
/**
* @inheritDoc
*/
public function add_ready_script($sScript)
{
$this->m_sReadyScript .= $sScript."\n";
}
/**
* @inheritDoc
*/
public function GetUniqueId()
{
assert(false);
return 0;
}
/**
* @inheritDoc
*/
public static function FilterXSS($sHTML)
{
return str_ireplace(array('<script', '</script>'), array('<!-- <removed-script', '</removed-script> -->'), $sHTML);
}
}

View File

@@ -37,4 +37,3 @@ require_once(APPROOT.'/application/utils.inc.php');
class ApplicationException extends CoreException
{
}
?>

View File

@@ -24,19 +24,23 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Component\Input\InputFactory;
use Combodo\iTop\Application\UI\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\UIBlock;
require_once(APPROOT."/application/utils.inc.php");
/**
* Interface for directing end-users to the relevant application
*/
*/
interface iDBObjectURLMaker
{
/**
* @param string $sClass
* @param string $iId
*
* @return string
*/
/**
* @param string $sClass
* @param string $iId
*
* @return string
*/
public static function MakeObjectURL($sClass, $iId);
}
@@ -200,23 +204,46 @@ class ApplicationContext
}
return implode("&", $aParams);
}
/**
* @since 3.0.0 N°2534 - dashboard: bug with autorefresh that deactivates filtering on organisation
* Returns the params as c[menu]:..., c[org_id]:....
* @return string The params
*/
public function GetForPostParams()
{
return json_encode($this->aValues);
}
/**
* Returns the context as sequence of input tags to be inserted inside a <form> tag
*
* @return string The context as a sequence of <input type="hidden" /> tags
*/
public function GetForForm()
{
$sContext = "";
foreach($this->aValues as $sName => $sValue)
{
foreach ($this->aValues as $sName => $sValue) {
$sContext .= "<input type=\"hidden\" name=\"c[$sName]\" value=\"".htmlentities($sValue, ENT_QUOTES, 'UTF-8')."\" />\n";
}
return $sContext;
}
/**
* Returns the context as sequence of input tags to be inserted inside a <form> tag
*
*/
public function GetForFormBlock(): UIBlock
{
$oContext = new UIContentBlock();
foreach ($this->aValues as $sName => $sValue) {
$oContext->AddSubBlock(InputFactory::MakeForHidden('c[$sName]', utils::HtmlEntities($sValue)));
}
return $oContext;
}
/**
* Returns the context as a hash array 'parameter_name' => value
*
* @return array The context information
*/
public function GetAsHash()

View File

@@ -869,7 +869,7 @@ abstract class ApplicationPopupMenuItem
class URLPopupMenuItem extends ApplicationPopupMenuItem
{
/** @ignore */
protected $sURL;
protected $sUrl;
/** @ignore */
protected $sTarget;
@@ -878,20 +878,32 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
*
* @param string $sUID The unique identifier of this menu in iTop... make sure you pass something unique enough
* @param string $sLabel The display label of the menu (must be localized)
* @param string $sURL If the menu is an hyperlink, provide the absolute hyperlink here
* @param string $sUrl If the menu is an hyperlink, provide the absolute hyperlink here
* @param string $sTarget In case the menu is an hyperlink and a specific target is needed (_blank for example), pass it here
*/
public function __construct($sUID, $sLabel, $sURL, $sTarget = '_top')
public function __construct($sUID, $sLabel, $sUrl, $sTarget = '_top')
{
parent::__construct($sUID, $sLabel);
$this->sURL = $sURL;
$this->sUrl = $sUrl;
$this->sTarget = $sTarget;
}
/** @ignore */
public function GetMenuItem()
{
return array('label' => $this->GetLabel(), 'url' => $this->sURL, 'target' => $this->sTarget, 'css_classes' => $this->aCssClasses);
return array('label' => $this->GetLabel(), 'url' => $this->GetUrl(), 'target' => $this-> GetTarget(), 'css_classes' => $this->aCssClasses);
}
/** @ignore */
public function GetUrl()
{
return $this->sUrl;
}
/** @ignore */
public function GetTarget()
{
return $this->sTarget;
}
}
@@ -905,7 +917,9 @@ class URLPopupMenuItem extends ApplicationPopupMenuItem
class JSPopupMenuItem extends ApplicationPopupMenuItem
{
/** @ignore */
protected $sJSCode;
protected $sJsCode;
/** @ignore */
protected $sUrl;
/** @ignore */
protected $aIncludeJSFiles;
@@ -923,7 +937,8 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
public function __construct($sUID, $sLabel, $sJSCode, $aIncludeJSFiles = array())
{
parent::__construct($sUID, $sLabel);
$this->sJSCode = $sJSCode;
$this->sJsCode = $sJSCode;
$this->sUrl = '#';
$this->aIncludeJSFiles = $aIncludeJSFiles;
}
@@ -933,9 +948,9 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
// Note: the semicolumn is a must here!
return array(
'label' => $this->GetLabel(),
'onclick' => $this->sJSCode.'; return false;',
'url' => '#',
'css_classes' => $this->aCssClasses,
'onclick' => $this->GetJsCode().'; return false;',
'url' => $this->GetUrl(),
'css_classes' => $this->GetCssClasses(),
);
}
@@ -944,6 +959,18 @@ class JSPopupMenuItem extends ApplicationPopupMenuItem
{
return $this->aIncludeJSFiles;
}
/** @ignore */
public function GetJsCode()
{
return $this->sJsCode;
}
/** @ignore */
public function GetUrl()
{
return $this->sUrl;
}
}
/**
@@ -1019,7 +1046,7 @@ class JSButtonItem extends JSPopupMenuItem
interface iPageUIExtension
{
/**
* Add content to the North pane
* Add content to the header of the page
*
* @param iTopWebPage $oPage The page to insert stuff into.
*
@@ -1028,7 +1055,7 @@ interface iPageUIExtension
public function GetNorthPaneHtml(iTopWebPage $oPage);
/**
* Add content to the South pane
* Add content to the footer of the page
*
* @param iTopWebPage $oPage The page to insert stuff into.
*

View File

@@ -43,7 +43,6 @@ class AuditRule extends cmdbAbstractObject
"db_table" => "priv_auditrule",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));

View File

@@ -1,84 +1,7 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Adapter class: when an API requires WebPage and you want to produce something else
*
* @copyright Copyright (C) 2016 Combodo SARL
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CaptureWebPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT."/application/webpage.class.inc.php");
class CaptureWebPage extends WebPage
{
protected $aReadyScripts;
function __construct()
{
parent::__construct('capture web page');
$this->aReadyScripts = array();
}
public function GetHtml()
{
$trash = $this->ob_get_clean_safe();
return $this->s_content;
}
public function GetJS()
{
$sRet = implode("\n", $this->a_scripts);
if (!empty($this->s_deferred_content))
{
$sRet .= "\n\$('body').append('".addslashes(str_replace("\n", '', $this->s_deferred_content))."');";
}
return $sRet;
}
public function GetReadyJS()
{
return "\$(document).ready(function() {\n".implode("\n", $this->aReadyScripts)."\n});";
}
public function GetCSS()
{
return $this->a_styles;
}
public function GetJSFiles()
{
return $this->a_linked_scripts;
}
public function GetCSSFiles()
{
return $this->a_linked_stylesheets;
}
public function output()
{
throw new Exception(__method__.' should not be called');
}
public function add_ready_script($sScript)
{
$this->aReadyScripts[] = $sScript;
}
}

View File

@@ -1,97 +1,7 @@
<?php
// Copyright (C) 2010-2015 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/>
/**
* CLI page
* The page adds the content-type text/XML and the encoding into the headers
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CLIPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT."/application/webpage.class.inc.php");
class CLIPage implements Page
{
function __construct($s_title)
{
}
public function output()
{
if (class_exists('DBSearch'))
{
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
ExecutionKPI::ReportStats();
}
}
public function add($sText)
{
echo $sText;
}
public function p($sText)
{
echo $sText."\n";
}
public function pre($sText)
{
echo $sText."\n";
}
public function add_comment($sText)
{
echo "#".$sText."\n";
}
public function table($aConfig, $aData, $aParams = array())
{
$aCells = array();
foreach($aConfig as $sName=>$aDef)
{
if (strlen($aDef['description']) > 0)
{
$aCells[] = $aDef['label'].' ('.$aDef['description'].')';
}
else
{
$aCells[] = $aDef['label'];
}
}
echo implode(';', $aCells)."\n";
foreach($aData as $aRow)
{
$aCells = array();
foreach($aConfig as $sName=>$aAttribs)
{
$sValue = $aRow["$sName"];
$aCells[] = $sValue;
}
echo implode(';', $aCells)."\n";
}
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@@ -1,113 +1,7 @@
<?php
// Copyright (C) 2010-2015 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/>
/**
* Simple web page with no includes or fancy formatting, useful to generateXML documents
* The page adds the content-type text/XML and the encoding into the headers
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/CSVPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT."/application/webpage.class.inc.php");
class CSVPage extends WebPage
{
function __construct($s_title) {
parent::__construct($s_title);
$this->add_header("Content-type: text/plain; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
//$this->add_header("Content-Transfer-Encoding: binary");
}
public function output()
{
$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
// Get the unexpected output but do nothing with it
$sTrash = $this->ob_get_clean_safe();
foreach($this->a_headers as $s_header)
{
header($s_header);
}
echo trim($this->s_content);
echo "\n";
if (class_exists('DBSearch'))
{
DBSearch::RecordQueryTrace();
}
if (class_exists('ExecutionKPI'))
{
ExecutionKPI::ReportStats();
}
}
public function small_p($sText)
{
}
public function add($sText)
{
$this->s_content .= $sText;
}
public function p($sText)
{
$this->s_content .= $sText."\n";
}
public function add_comment($sText)
{
$this->s_content .= "#".$sText."\n";
}
public function table($aConfig, $aData, $aParams = array())
{
$aCells = array();
foreach($aConfig as $sName=>$aDef)
{
if (strlen($aDef['description']) > 0)
{
$aCells[] = $aDef['label'].' ('.$aDef['description'].')';
}
else
{
$aCells[] = $aDef['label'];
}
}
$this->s_content .= implode(';', $aCells)."\n";
foreach($aData as $aRow)
{
$aCells = array();
foreach($aConfig as $sName=>$aAttribs)
{
$sValue = $aRow["$sName"];
$aCells[] = $sValue;
}
$this->s_content .= implode(';', $aCells)."\n";
}
}
}

View File

@@ -17,6 +17,11 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\UI\Component\Button\ButtonFactory;
use Combodo\iTop\Application\UI\Component\Toolbar\Toolbar;
use Combodo\iTop\Application\UI\Component\DataTable\DataTableSettings;
require_once(APPROOT.'application/dashboardlayout.class.inc.php');
require_once(APPROOT.'application/dashlet.class.inc.php');
require_once(APPROOT.'core/modelreflection.class.inc.php');
@@ -491,7 +496,7 @@ abstract class Dashboard
$("#attr_auto_reload_sec").prop('disabled', !$(this).is(':checked'));
} );
$('#select_layout').buttonset();
$('#select_layout').controlgroup();
$('#select_dashlet').droppable({
accept: '.dashlet',
drop: function(event, ui) {
@@ -529,28 +534,37 @@ EOF
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array(), $bCanEdit = true)
{
if (!array_key_exists('dashboard_div_id', $aExtraParams))
{
if (!array_key_exists('dashboard_div_id', $aExtraParams)) {
$aExtraParams['dashboard_div_id'] = utils::Sanitize($this->GetId(), '', 'element_identifier');
}
$oPage->add('<div class="dashboard-title-line"><div class="dashboard-title">'.htmlentities(Dict::S($this->sTitle), ENT_QUOTES, 'UTF-8', false).'</div></div>');
$sTitleForHTML = utils::HtmlEntities(Dict::S($this->sTitle));
$sHtml = "<div class=\"ibo-top-bar--toolbar-dashboard-title\">{$sTitleForHTML}</div>";
if ($oPage instanceof iTopWebPage) {
$oTopBar = $oPage->GetTopBarLayout();
$oToolbar = new Toolbar();
$oTopBar->SetToolbar($oToolbar);
$oToolbar->AddHtml($sHtml);
} else {
$oPage->add_script(<<<JS
$(".ibo-top-bar--toolbar-dashboard-title").html("$sTitleForHTML");
JS
);
}
/** @var \DashboardLayoutMultiCol $oLayout */
$oLayout = new $this->sLayoutClass();
foreach($this->aCells as $iCellIdx => $aDashlets)
{
foreach($aDashlets as $oDashlet)
{
foreach ($this->aCells as $iCellIdx => $aDashlets) {
foreach ($aDashlets as $oDashlet) {
$aDashletCoordinates = $oLayout->GetDashletCoordinates($iCellIdx);
$this->PrepareDashletForRendering($oDashlet, $aDashletCoordinates, $aExtraParams);
}
}
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
if (!$bEditMode)
{
if (!$bEditMode) {
$oPage->add_linked_script('../js/dashlet.js');
$oPage->add_linked_script('../js/dashboard.js');
}
@@ -928,6 +942,9 @@ class RuntimeDashboard extends Dashboard
$sExtraParams = json_encode($aAjaxParams);
$iReloadInterval = 1000 * $this->GetAutoReloadInterval();
$sReloadURL = $this->GetReloadURL();
$oAppContext = new ApplicationContext();
$sContext=$oAppContext->GetForPostParams();
//$sContext is named "c" because it use the existing code for context parameters c[org_id] and c[menu]
$oPage->add_script(
<<<EOF
if (typeof(AutoReloadDashboardId$sDivId) !== 'undefined')
@@ -941,14 +958,14 @@ class RuntimeDashboard extends Dashboard
function ReloadDashboard$sDivId()
{
// Do not reload when a dialog box is active
if (!($('.ui-dialog:visible').length > 0) && $('.dashboard_contents#$sDivId').is(':visible'))
if (!($('.ui-dialog:visible').length > 0) && $('.ibo-dashboard#$sDivId').is(':visible'))
{
$('.dashboard_contents#$sDivId').block();
$('.ibo-dashboard#$sDivId').block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
{ operation: 'reload_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, reload_url: '$sReloadURL'},
{ operation: 'reload_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, c: $sContext, reload_url: '$sReloadURL'},
function(data){
$('.dashboard_contents#$sDivId').html(data);
$('.dashboard_contents#$sDivId').unblock();
$('.ibo-dashboard#$sDivId').html(data);
$('.ibo-dashboard#$sDivId').unblock();
}
);
}
@@ -978,52 +995,58 @@ EOF
}
/**
* @param \iTopWebPage $oPage
* @param WebPage $oPage
* @param array $aAjaxParams
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
protected function RenderSelector($oPage, $aAjaxParams = array())
protected function RenderSelector(WebPage $oPage, $aAjaxParams = array())
{
$sId = $this->GetId();
$sDivId = utils::Sanitize($sId, '', 'element_identifier');
$sExtraParams = json_encode($aAjaxParams);
$sSelectorHtml = '<div class="dashboard-selector">';
if ($this->HasCustomDashboard())
{
$sSelectorHtml = '<div class="ibo-top-bar--toolbar-dashboard-selector">';
if ($this->HasCustomDashboard()) {
$bStandardSelected = appUserPreferences::GetPref('display_original_dashboard_'.$sId, false);
$sStandard = Dict::S('UI:Toggle:StandardDashboard');
$sSelectorHtml .= '<div class="selector-label">'.$sStandard.'</div>';
$sSelectorHtml .= '<label class="switch"><input type="checkbox" onchange="ToggleDashboardSelector'.$sDivId.'();" '.($bStandardSelected ? '' : 'checked').'><span class="slider round"></span></label></input></label>';
$sCustom = Dict::S('UI:Toggle:CustomDashboard');
$sSelectorHtml .= '<div class="selector-label">'.$sCustom.'</div>';
}
$sSelectorHtml .= '</div>';
$sSelectorHtml = addslashes($sSelectorHtml);
$sFile = addslashes($this->GetDefinitionFile());
$sReloadURL = $this->GetReloadURL();
$oPage->add_ready_script(
<<<EOF
$('.dashboard-title').after('$sSelectorHtml');
EOF
);
if ($oPage instanceof iTopWebPage) {
$oToolbar = $oPage->GetTopBarLayout()->GetToolbar();
$oToolbar->AddHtml($sSelectorHtml);
$oPage->add_script(
<<<EOF
$oPage->add_script(
<<<EOF
function ToggleDashboardSelector$sDivId()
{
$('.dashboard_contents#$sDivId').block();
$('.ibo-dashboard#$sDivId').block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
{ operation: 'toggle_dashboard', dashboard_id: '$sId', file: '$sFile', extra_params: $sExtraParams, reload_url: '$sReloadURL' },
function(data) {
$('.dashboard_contents#$sDivId').html(data);
$('.dashboard_contents#$sDivId').unblock();
$('.ibo-dashboard#$sDivId').html(data);
$('.ibo-dashboard#$sDivId').unblock();
}
);
}
EOF
);
);
} else {
$sSelectorHtml = addslashes($sSelectorHtml);
$oPage->add_script(<<<JS
$(".ibo-top-bar--toolbar-dashboard-selector").replaceWith("$sSelectorHtml");
JS
);
}
}
/**
@@ -1055,43 +1078,67 @@ EOF
*/
protected function RenderEditionTools(WebPage $oPage, $aExtraParams)
{
if (!($oPage instanceof iTopWebPage)) {
return;
}
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.iframe-transport.js');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.fileupload.js');
$sEditMenu = "<div id=\"DashboardMenu\"><ul><li><i class=\"top-right-icon icon-additional-arrow fas fa-pencil-alt\"></i><ul>";
$sId = utils::Sanitize($this->GetId(), '', 'element_identifier');
$sMenuTogglerId = "ibo-dashboard-menu-toggler-{$sId}";
$sPopoverMenuId = "ibo-dashboard-menu-popover-{$sId}";
$sName = 'UI:Dashboard:Actions';
$oToolbar = $oPage->GetTopBarLayout()->GetToolbar();
$oActionButton = ButtonFactory::MakeLinkNeutral('', '', 'fas fa-ellipsis-v', $sName, '', $sMenuTogglerId);
$oActionButton->AddCSSClasses("ibo-top-bar--toolbar-dashboard-menu-toggler");
$oToolbar->AddSubBlock($oActionButton);
$aActions = array();
$sFile = addslashes($this->sDefinitionFile);
$sJSExtraParams = json_encode($aExtraParams);
$bCanEdit = true;
if ($this->HasCustomDashboard())
{
if ($this->HasCustomDashboard()) {
$bCanEdit = !appUserPreferences::GetPref('display_original_dashboard_'.$this->GetId(), false);
}
if ($bCanEdit)
{
if ($bCanEdit) {
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:Edit'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");
$aActions[$oEdit->GetUID()] = $oEdit->GetMenuItem();
}
if ($this->bCustomized)
{
if ($this->bCustomized) {
$oRevert = new JSPopupMenuItem('UI:Dashboard:RevertConfirm', Dict::S('UI:Dashboard:Revert'),
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false");
"if (confirm('".addslashes(Dict::S('UI:Dashboard:RevertConfirm'))."')) return RevertDashboard('{$this->sId}', $sJSExtraParams); else return false");
$aActions[$oRevert->GetUID()] = $oRevert->GetMenuItem();
}
utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_DASHBOARD_ACTIONS, $this, $aActions);
$sEditMenu .= $oPage->RenderPopupMenuItems($aActions);
$sEditMenu = addslashes($sEditMenu);
$sReloadURL = $this->GetReloadURL();
$oPage->add_ready_script(
<<<EOF
$('.dashboard-title').after('$sEditMenu');
$('#DashboardMenu>ul').popupmenu();
$oToolbar->AddSubBlock($oPage->GetPopoverMenu($sPopoverMenuId, $aActions));
$oActionButton->AddCSSClasses('ibo-action-button')
->SetJsCode(<<<JS
$("#{$sPopoverMenuId}").popover_menu({toggler: "#{$sMenuTogglerId}"});
$('#{$sMenuTogglerId}').on('click', function(oEvent) {
var oEventTarget = $('#{$sMenuTogglerId}');
var aEventTargetPos = oEventTarget.position();
var popover = $("#{$sPopoverMenuId}");
EOF
);
popover.css({
// 'top': (aEventTargetPos.top + parseInt(oEventTarget.css('marginTop'), 10) + oEventTarget.height()) + 'px',
// 'left': (aEventTargetPos.left + parseInt(oEventTarget.css('marginLeft'), 10) + oEventTarget.width() - popover.width()) + 'px',
'top': (aEventTargetPos.top + oEventTarget.outerHeight(true)) + 'px',
'left': (aEventTargetPos.left + oEventTarget.outerWidth(true) - popover.width()) + 'px',
'z-index': 10060
});
popover.popover_menu("togglePopup");
});
JS
);
$sReloadURL = $this->GetReloadURL();
$oPage->add_script(
<<<EOF
<<<EOF
function EditDashboard(sId, sDashboardFile, aExtraParams)
{
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'dashboard_editor', id: sId, file: sDashboardFile, extra_params: aExtraParams, reload_url: '$sReloadURL'},
@@ -1575,12 +1622,10 @@ JS
{
$sDataTableId = Dashlet::APPUSERPREFERENCES_PREFIX.$sDashletId;
$aClassAliases = array();
try{
try {
$oFilter = $oDashlet->GetDBSearch($aExtraParams);
$aClassAliases = $oFilter->GetSelectedClasses();
}
catch (Exception $e)
{
} catch (Exception $e) {
//on error, return default value
return null;
}

View File

@@ -15,15 +15,17 @@
//
// 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\Component\Html\Html;
use Combodo\iTop\Application\UI\Layout\Dashboard\DashboardColumn;
use Combodo\iTop\Application\UI\Layout\Dashboard\DashboardLayout as UIDashboardLayout;
use Combodo\iTop\Application\UI\Layout\Dashboard\DashboardRow;
/**
* Dashboard presentation
*
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
*/
abstract class DashboardLayout
{
abstract public function Render($oPage, $aDashlets, $bEditMode = false);
@@ -114,61 +116,52 @@ abstract class DashboardLayoutMultiCol extends DashboardLayout
// Trim the list of cells to remove the invisible/empty ones at the end of the array
$aCells = $this->TrimCellsArray($aCells);
$oPage->add('<table style="width:100%;table-layout:fixed;"><tbody>');
$oDashboardLayout = new UIDashboardLayout();
$oPage->AddUiBlock($oDashboardLayout);
$iCellIdx = 0;
$fColSize = 100 / $this->iNbCols;
$sStyle = $bEditMode ? 'border: 1px #ccc dashed; width:'.$fColSize.'%;' : 'width: '.$fColSize.'%;';
$sClass = $bEditMode ? 'layout_cell edit_mode' : 'dashboard';
$iNbRows = ceil(count($aCells) / $this->iNbCols);
for($iRows = 0; $iRows < $iNbRows; $iRows++)
{
$oPage->add("<tr data-dashboard-row-index=\"$iRows\">");
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$sCellClass = ($iRows == $iNbRows-1) ? $sClass.' layout_last_used_rank' : $sClass;
$oPage->add("<td style=\"$sStyle\" class=\"$sCellClass\" data-dashboard-column-index=\"$iCols\" data-dashboard-cell-index=\"$iCellIdx\">");
if (array_key_exists($iCellIdx, $aCells))
{
for ($iRows = 0; $iRows < $iNbRows; $iRows++) {
$oDashboardRow = new DashboardRow();
$oDashboardLayout->AddDashboardRow($oDashboardRow);
for ($iCols = 0; $iCols < $this->iNbCols; $iCols++) {
$oDashboardColumn = new DashboardColumn($bEditMode);
$oDashboardColumn->SetCellIndex($iCellIdx);
$oDashboardRow->AddDashboardColumn($oDashboardColumn);
if (array_key_exists($iCellIdx, $aCells)) {
$aDashlets = $aCells[$iCellIdx];
if (count($aDashlets) > 0)
{
if (count($aDashlets) > 0) {
/** @var \Dashlet $oDashlet */
foreach($aDashlets as $oDashlet)
{
if ($oDashlet::IsVisible())
{
$oDashlet->DoRender($oPage, $bEditMode, true /* bEnclosingDiv */, $aExtraParams);
foreach ($aDashlets as $oDashlet) {
if ($oDashlet::IsVisible()) {
$oDashboardColumn->AddUIBlock($oDashlet->DoRender($oPage, $bEditMode, true /* bEnclosingDiv */, $aExtraParams));
}
}
} else {
$oDashboardColumn->AddUIBlock(new Html('&nbsp;'));
}
else
{
$oPage->add('&nbsp;');
}
} else {
$oDashboardColumn->AddUIBlock(new Html('&nbsp;'));
}
else
{
$oPage->add('&nbsp;');
}
$oPage->add('</td>');
$iCellIdx++;
}
$oPage->add('</tr>');
}
if ($bEditMode) // Add one row for extensibility
{
$sStyle = 'style="border: 1px #ccc dashed; width:'.$fColSize.'%;" class="layout_cell edit_mode layout_extension" data-dashboard-cell-index="'.$iCellIdx.'"';
$oPage->add("<tr data-dashboard-row-index=\"$iRows\">");
for($iCols = 0; $iCols < $this->iNbCols; $iCols++)
{
$oPage->add("<td $sStyle data-dashboard-column-index=\"$iCols\">");
$oPage->add('&nbsp;');
$oPage->add('</td>');
$oDashboardRow = new DashboardRow();
$oDashboardLayout->AddDashboardRow($oDashboardRow);
for ($iCols = 0; $iCols < $this->iNbCols; $iCols++) {
$oDashboardColumn = new DashboardColumn($bEditMode, true);
$oDashboardRow->AddDashboardColumn($oDashboardColumn);
$oDashboardColumn->AddUIBlock(new Html('&nbsp;'));
}
$oPage->add('</tr>');
}
$oPage->add('</tbody></table>');
return;
}
/**

View File

@@ -16,6 +16,13 @@
// 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\Component\Dashlet\DashletContainer;
use Combodo\iTop\Application\UI\Component\Dashlet\DashletFactory;
use Combodo\iTop\Application\UI\Component\Html\Html;
use Combodo\iTop\Application\UI\Component\Panel\PanelFactory;
use Combodo\iTop\Application\UI\iUIBlock;
use Combodo\iTop\Application\UI\UIBlock;
require_once(APPROOT.'application/forms.class.inc.php');
/**
@@ -50,7 +57,7 @@ abstract class Dashlet
$this->bRedrawNeeded = true; // By default: redraw each time a property changes
$this->bFormRedrawNeeded = false; // By default: no need to redraw the form (independent fields)
$this->aProperties = array(); // By default: there is no property
$this->aCSSClasses = array('dashlet');
$this->aCSSClasses = array('ibo-dashlet');
$this->sDashletType = get_class($this);
}
@@ -66,21 +73,17 @@ abstract class Dashlet
{
$refValue = $this->aProperties[$sProperty];
$sRefType = gettype($refValue);
if (gettype($sValue) == $sRefType)
{
if (gettype($sValue) == $sRefType) {
// Do not change anything in that case!
$ret = $sValue;
}
elseif ($sRefType == 'boolean')
{
} elseif ($sRefType == 'boolean') {
$ret = ($sValue == 'true');
}
elseif ($sRefType == 'array')
{
} elseif ($sRefType == 'array') {
$ret = explode(',', $sValue);
}
else
{
} elseif (is_array($sValue)) {
$ret = $sValue;
} else {
$ret = $sValue;
settype($ret, $sRefType);
}
@@ -191,10 +194,8 @@ abstract class Dashlet
*/
public function FromParams($aParams)
{
foreach ($this->aProperties as $sProperty => $value)
{
if (array_key_exists($sProperty, $aParams))
{
foreach ($this->aProperties as $sProperty => $value) {
if (array_key_exists($sProperty, $aParams)) {
$this->aProperties[$sProperty] = $aParams[$sProperty];
}
}
@@ -207,79 +208,59 @@ abstract class Dashlet
* @param bool $bEnclosingDiv
* @param array $aExtraParams
*/
public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = array())
public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = array()): UIBlock
{
$sCSSClasses = implode(' ', $this->aCSSClasses);
$sId = $this->GetID();
if ($bEnclosingDiv)
{
if ($bEditMode)
{
$oPage->add('<div class="'.$sCSSClasses.'" id="dashlet_'.$sId.'">');
$sCSSClasses = implode(' ', $this->aCSSClasses);
if ($bEnclosingDiv) {
if ($bEditMode) {
$oDashletContainer = new DashletContainer("dashlet_{$sId}");
} else {
$oDashletContainer = new DashletContainer();
}
else
{
$oPage->add('<div class="'.$sCSSClasses.'">');
}
}
else
{
foreach ($this->aCSSClasses as $sCSSClass)
{
$oDashletContainer->AddCSSClasses($sCSSClasses);
} else {
$oDashletContainer = new DashletContainer();
foreach ($this->aCSSClasses as $sCSSClass) {
$oPage->add_ready_script("$('#dashlet_".$sId."').addClass('$sCSSClass');");
}
}
try
{
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime')
{
$this->Render($oPage, $bEditMode, $aExtraParams);
try {
if (get_class($this->oModelReflection) == 'ModelReflectionRuntime') {
$oBlock = $this->Render($oPage, $bEditMode, $aExtraParams);
} else {
$oBlock = $this->RenderNoData($oPage, $bEditMode, $aExtraParams);
}
else
{
$this->RenderNoData($oPage, $bEditMode, $aExtraParams);
}
}
catch(UnknownClassOqlException $e)
{
$oDashletContainer->AddSubBlock($oBlock);
} catch (UnknownClassOqlException $e) {
// Maybe the class is part of a non-installed module, fail silently
// Except in Edit mode
if ($bEditMode)
{
$oPage->add('<div class="dashlet-content">');
$oPage->add('<h2>'.$e->GetUserFriendlyDescription().'</h2>');
$oPage->add('</div>');
if ($bEditMode) {
$oDashletContainer->AddCSSClasses("dashlet-content");
$oDashletContainer->AddHtml('<h2>'.$e->GetUserFriendlyDescription().'</h2>');
}
}
catch(OqlException $e)
{
$oPage->add('<div class="dashlet-content">');
$oPage->p($e->GetUserFriendlyDescription());
$oPage->add('</div>');
}
catch(Exception $e)
{
$oPage->add('<div class="dashlet-content">');
$oPage->p($e->getMessage());
$oPage->add('</div>');
} catch (OqlException $e) {
$oDashletContainer->AddCSSClasses("dashlet-content");
$oDashletContainer->AddHtml('<p>'.$e->GetUserFriendlyDescription().'</p>');
} catch (Exception $e) {
$oDashletContainer->AddCSSClasses("dashlet-content");
$oDashletContainer->AddHtml('<p>'.$e->getMessage().'</p>');
}
if ($bEnclosingDiv)
{
$oPage->add('</div>');
}
if ($bEditMode)
{
if ($bEditMode) {
$sClass = get_class($this);
$sType = $this->sDashletType;
$oPage->add_ready_script(
<<<EOF
<<<EOF
$('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass', 'dashlet_type': '$sType'});
EOF
);
}
return $oDashletContainer;
}
/**
@@ -303,7 +284,7 @@ EOF
* @param bool $bEditMode
* @param array $aExtraParams
*
* @return mixed
* @return iUIBlock
*/
abstract public function Render($oPage, $bEditMode = false, $aExtraParams = array());
@@ -313,10 +294,12 @@ EOF
* @param \WebPage $oPage
* @param bool $bEditMode
* @param array $aExtraParams
*
* @return iUIBlock
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$this->Render($oPage, $bEditMode, $aExtraParams);
return $this->Render($oPage, $bEditMode, $aExtraParams);
}
/**
@@ -619,12 +602,12 @@ class DashletUnknown extends Dashlet
$sIconUrl = utils::HtmlEntities(utils::GetAbsoluteUrlAppRoot().$aInfos['icon']);
$sExplainText = ($bEditMode) ? Dict::Format('UI:DashletUnknown:RenderText:Edit', $this->GetDashletType()) : Dict::S('UI:DashletUnknown:RenderText:View');
$oPage->add('<div class="dashlet-content">');
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$oPage->add('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oPage->add('</div>');
return $oDashletContainer;
}
/**
@@ -639,12 +622,12 @@ class DashletUnknown extends Dashlet
$sIconUrl = utils::HtmlEntities(utils::GetAbsoluteUrlAppRoot().$aInfos['icon']);
$sExplainText = Dict::Format('UI:DashletUnknown:RenderNoDataText:Edit', $this->GetDashletType());
$oPage->add('<div class="dashlet-content">');
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$oPage->add('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-image"><img src="'.$sIconUrl.'" /></div>');
$oDashletContainer->AddHtml('<div class="dashlet-ukn-text">'.$sExplainText.'</div>');
$oPage->add('</div>');
return $oDashletContainer;
}
/**
@@ -763,9 +746,9 @@ class DashletProxy extends DashletUnknown
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
// This should never be called.
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div>This dashlet is not supposed to be rendered as it is just a proxy for third-party widgets.</div>');
$oPage->add('</div>');
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$oDashletContainer->AddHtml('<div>This dashlet is not supposed to be rendered as it is just a proxy for third-party widgets.</div>');
return $oDashletContainer;
}
/**
@@ -780,12 +763,14 @@ class DashletProxy extends DashletUnknown
$sIconUrl = utils::HtmlEntities(utils::GetAbsoluteUrlAppRoot().$aInfos['icon']);
$sExplainText = Dict::Format('UI:DashletProxy:RenderNoDataText:Edit', $this->GetDashletType());
$oPage->add('<div class="dashlet-content">');
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$oPage->add('<div class="dashlet-pxy-image"><img src="'.$sIconUrl.'" /></div>');
$oPage->add('<div class="dashlet-pxy-text">'.$sExplainText.'</div>');
$sHtml = '';
$sHtml .= '<div class="dashlet-pxy-image"><img src="'.$sIconUrl.'" /></div>';
$sHtml .= '<div class="dashlet-pxy-text">'.$sExplainText.'</div>';
$oPage->add('</div>');
$oDashletContainer->AddHtml($sHtml);
return $oDashletContainer;
}
/**
@@ -816,7 +801,7 @@ class DashletEmptyCell extends Dashlet
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$oPage->add('&nbsp;');
return new Html('&nbsp;');
}
/**
@@ -866,8 +851,9 @@ class DashletPlainText extends Dashlet
$sText = utils::HtmlEntities($this->aProperties['text']);
$sText = str_replace(array("\r\n", "\n", "\r"), "<br/>", $sText);
$sId = 'plaintext_'.($bEditMode? 'edit_' : '').$this->sId;
$oPage->add('<div id="'.$sId.'" class="dashlet-content">'.$sText.'</div>');
$sId = 'plaintext_'.($bEditMode ? 'edit_' : '').$this->sId;
return DashletFactory::MakeForDashletText($sId, $sText);
}
/**
@@ -918,12 +904,8 @@ class DashletObjectList extends Dashlet
$sTitle = $this->aProperties['title'];
$sShowMenu = $this->aProperties['menu'] ? '1' : '0';
$oPage->add('<div class="dashlet-content">');
$sHtmlTitle = utils::HtmlEntities(Dict::S($sTitle)); // done in the itop block
if ($sHtmlTitle != '')
{
$oPage->add('<div class="main_header"><h1>&nbsp;'.$sHtmlTitle.'</h1></div>');
}
$oPanel = PanelFactory::MakeNeutral(Dict::S($sTitle));
$oFilter = $this->GetDBSearch($aExtraParams);
$oBlock = new DisplayBlock($oFilter, 'list');
$aParams = array(
@@ -931,28 +913,9 @@ class DashletObjectList extends Dashlet
'table_id' => self::APPUSERPREFERENCES_PREFIX.$this->sId,
);
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
$oBlock->Display($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
$oPage->add('</div>');
}
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
public function GetDBSearch($aExtraParams = array())
{
$sQuery = $this->aProperties['query'];
if (isset($aExtraParams['query_params']))
{
$aQueryParams = $aExtraParams['query_params'];
}
elseif (isset($aExtraParams['this->class']) && isset($aExtraParams['this->id']))
{
$oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']);
$aQueryParams = $oObj->ToArgsForQuery();
}
else
{
$aQueryParams = array();
}
return DBObjectSearch::FromOQL($sQuery, $aQueryParams);
return $oPanel;
}
/**
@@ -966,22 +929,35 @@ class DashletObjectList extends Dashlet
$oPage->add('<div class="dashlet-content">');
$sHtmlTitle = utils::HtmlEntities($this->oModelReflection->DictString($sTitle)); // done in the itop block
if ($sHtmlTitle != '')
{
if ($sHtmlTitle != '') {
$oPage->add('<h1>'.$sHtmlTitle.'</h1>');
}
$oQuery = $this->oModelReflection->GetQuery($sQuery);
$sClass = $oQuery->GetClass();
$oPage->add('<div id="block_fake_'.$this->sId.'" class="display_block">');
$oPage->p(Dict::S('UI:NoObjectToDisplay'));
if ($bShowMenu)
{
if ($bShowMenu) {
$oPage->p('<a>'.Dict::Format('UI:ClickToCreateNew', $this->oModelReflection->GetName($sClass)).'</a>');
}
$oPage->add('</div>');
$oPage->add('</div>');
}
public function GetDBSearch($aExtraParams = array())
{
$sQuery = $this->aProperties['query'];
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
} elseif (isset($aExtraParams['this->class']) && isset($aExtraParams['this->id'])) {
$oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']);
$aQueryParams = $oObj->ToArgsForQuery();
} else {
$aQueryParams = array();
}
return DBObjectSearch::FromOQL($sQuery, $aQueryParams);
}
/**
* @inheritdoc
*/
@@ -1199,93 +1175,81 @@ abstract class DashletGroupBy extends Dashlet
$sStyle = $this->aProperties['style'];
// First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
if (isset($aExtraParams['query_params']))
{
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
elseif (isset($aExtraParams['this->class']) && isset($aExtraParams['this->id']))
{
} elseif (isset($aExtraParams['this->class']) && isset($aExtraParams['this->id'])) {
$oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']);
$aQueryParams = $oObj->ToArgsForQuery();
}
else
{
} else {
$aQueryParams = array();
}
$oFilter = DBObjectSearch::FromOQL($sQuery, $aQueryParams);
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
$sClass = $oFilter->GetClass();
if (!$this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode))
{
$oPage->add('<p>'.Dict::S('UI:DashletGroupBy:MissingGroupBy').'</p>');
if (!$this->oModelReflection->IsValidAttCode($sClass, $this->sGroupByAttCode)) {
return new Html('<p>'.Dict::S('UI:DashletGroupBy:MissingGroupBy').'</p>');
}
else
{
switch($sStyle)
{
case 'bars':
$sType = 'chart';
$aParams = array(
'chart_type' => 'bars',
'chart_title' => $sTitle,
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
$sHtmlTitle = ''; // done in the itop block
break;
case 'pie':
$sType = 'chart';
$aParams = array(
'chart_type' => 'pie',
'chart_title' => $sTitle,
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
$sHtmlTitle = ''; // done in the itop block
break;
switch ($sStyle) {
case 'bars':
$sType = 'chart';
$aParams = array(
'chart_type' => 'bars',
'chart_title' => $sTitle,
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
$sHtmlTitle = ''; // done in the itop block
break;
case 'table':
default:
$sHtmlTitle = utils::HtmlEntities(Dict::S($sTitle)); // done in the itop block
$sType = 'count';
$aParams = array(
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
break;
}
case 'pie':
$sType = 'chart';
$aParams = array(
'chart_type' => 'pie',
'chart_title' => $sTitle,
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
$sHtmlTitle = ''; // done in the itop block
break;
$oPage->add('<div class="dashlet-content">');
if ($sHtmlTitle != '')
{
$oPage->add('<div class="main_header"><h1>&nbsp;'.$sHtmlTitle.'</h1></div>');
}
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$oBlock = new DisplayBlock($oFilter, $sType);
$oBlock->Display($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
if($bEditMode)
{
$oPage->add('<div class="dashlet-blocker"></div>');
}
$oPage->add('</div>');
case 'table':
default:
$sHtmlTitle = utils::HtmlEntities(Dict::S($sTitle)); // done in the itop block
$sType = 'count';
$aParams = array(
'group_by' => $this->sGroupByExpr,
'group_by_label' => $this->sGroupByLabel,
'aggregation_function' => $this->sAggregationFunction,
'aggregation_attribute' => $this->sAggregationAttribute,
'limit' => $this->sLimit,
'order_direction' => $this->sOrderDirection,
'order_by' => $this->sOrderBy,
);
break;
}
$oPanel = PanelFactory::MakeNeutral(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));
if ($bEditMode) {
$oPanel->AddHtml('<div class="dashlet-blocker"></div>');
}
return $oPanel;
}
/**
@@ -1368,9 +1332,9 @@ abstract class DashletGroupBy extends Dashlet
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$oPage->add('<div class="dashlet-content">');
$oPage->add('error!');
$oPage->add('</div>');
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$oDashletContainer->AddHtml('error!');
return $oDashletContainer;
}
/**
@@ -1691,26 +1655,27 @@ class DashletGroupByPie extends DashletGroupBy
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$sTitle = $this->aProperties['title'];
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.utils::HtmlEntities($sTitle).'</h1>' : '';
$oPage->add("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
$oDashletContainer->AddHtml("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
$aDisplayValues = $this->MakeSimulatedData();
$aColumns = array();
$aNames = array();
foreach($aDisplayValues as $idx => $aValue)
{
foreach ($aDisplayValues as $idx => $aValue) {
$aColumns[] = array('series_'.$idx, (int)$aValue['value']);
$aNames['series_'.$idx] = $aValue['label'];
}
$sJSColumns = json_encode($aColumns);
$sJSNames = json_encode($aNames);
$oPage->add_ready_script(
<<<EOF
<<<EOF
window.setTimeout(function() {
var chart = c3.generate({
bindto: '#{$sBlockId}',
@@ -1731,6 +1696,8 @@ var chart = c3.generate({
});}, 100);
EOF
);
return $oDashletContainer;
}
}
@@ -1763,25 +1730,26 @@ class DashletGroupByBars extends DashletGroupBy
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$sTitle = $this->aProperties['title'];
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$HTMLsTitle = ($sTitle != '') ? '<h1 style="text-align:center">'.utils::HtmlEntities($sTitle).'</h1>' : '';
$oPage->add("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
$oDashletContainer->AddHtml("<div style=\"background-color:#fff;padding:0.25em;\">$HTMLsTitle<div id=\"$sBlockId\" style=\"background-color:#fff;\"></div></div>");
$aDisplayValues = $this->MakeSimulatedData();
$aNames = array();
foreach($aDisplayValues as $idx => $aValue)
{
foreach ($aDisplayValues as $idx => $aValue) {
$aNames[$idx] = $aValue['label'];
}
$sJSNames = json_encode($aNames);
$sJson = json_encode($aDisplayValues);
$oPage->add_ready_script(
<<<EOF
<<<EOF
window.setTimeout(function() {
var chart = c3.generate({
bindto: '#{$sBlockId}',
@@ -1829,6 +1797,8 @@ window.setTimeout(function() {
}, 100);
EOF
);
return $oDashletContainer;
}
}
@@ -1860,40 +1830,44 @@ class DashletGroupByTable extends DashletGroupBy
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$oDashletContainer = new DashletContainer();
$aDisplayValues = $this->MakeSimulatedData();
$iTotal = 0;
foreach($aDisplayValues as $iRow => $aDisplayData)
{
foreach ($aDisplayValues as $iRow => $aDisplayData) {
$iTotal += $aDisplayData['value'];
}
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$oPage->add('<div id="'.$sBlockId.'" class="display_block">');
$oPage->add('<div class="dashlet-content">');
$oPage->add('<p>'.Dict::Format('UI:Pagination:HeaderNoSelection', $iTotal).'</p>');
$oPage->add('<table class="listResults">');
$oPage->add('<thead>');
$oPage->add('<tr>');
$oPage->add('<th class="header" title="">'.$this->sGroupByLabel.'</th>');
$oPage->add('<th class="header" title="'.Dict::S('UI:GroupBy:Count+').'">'.Dict::S('UI:GroupBy:Count').'</th>');
$oPage->add('</tr>');
$oPage->add('</thead>');
$oPage->add('<tbody>');
foreach($aDisplayValues as $aDisplayData)
{
$oPage->add('<tr class="even">');
$oPage->add('<td class=""><span title="Active">'.$aDisplayData['label'].'</span></td>');
$oPage->add('<td class=""><a>'.$aDisplayData['value'].'</a></td>');
$oPage->add('</tr>');
$sHtml = '';
$sHtml .= '<div id="'.$sBlockId.'" class="display_block">';
$sHtml .= '<div class="dashlet-content">';
$sHtml .= '<p>'.Dict::Format('UI:Pagination:HeaderNoSelection', $iTotal).'</p>';
$sHtml .= '<table class="listResults">';
$sHtml .= '<thead>';
$sHtml .= '<tr>';
$sHtml .= '<th class="header" title="">'.$this->sGroupByLabel.'</th>';
$sHtml .= '<th class="header" title="'.Dict::S('UI:GroupBy:Count+').'">'.Dict::S('UI:GroupBy:Count').'</th>';
$sHtml .= '</tr>';
$sHtml .= '</thead>';
$sHtml .= '<tbody>';
foreach ($aDisplayValues as $aDisplayData) {
$sHtml .= '<tr class="even">';
$sHtml .= '<td class=""><span title="Active">'.$aDisplayData['label'].'</span></td>';
$sHtml .= '<td class=""><a>'.$aDisplayData['value'].'</a></td>';
$sHtml .= '</tr>';
}
$oPage->add('</tbody>');
$oPage->add('</table>');
$oPage->add('</div>');
$sHtml .= '</tbody>';
$sHtml .= '</table>';
$sHtml .= '</div>';
$oPage->add('</div>');
$sHtml .= '</div>';
$oDashletContainer->AddHtml($sHtml);
return $oDashletContainer;
}
}
@@ -1922,14 +1896,7 @@ class DashletHeaderStatic extends Dashlet
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPage->add('<img src="'.$sIconPath.'">');
$oPage->add('<div class="main_header"><h1>&nbsp;'.$this->oModelReflection->DictString($sTitle).'</h1></div>');
$oPage->add('</div>');
$oPage->add('</div>');
return DashletFactory::MakeForDashletHeaderStatic($this->oModelReflection->DictString($sTitle), $sIconPath);
}
/**
@@ -2056,8 +2023,7 @@ class DashletHeaderDynamic extends Dashlet
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
$aValues = $this->GetValues();
if (count($aValues) > 0)
{
if (count($aValues) > 0) {
// Stats grouped by <group_by>
$sCSV = implode(',', $aValues);
$aParams = array(
@@ -2067,9 +2033,7 @@ class DashletHeaderDynamic extends Dashlet
'status_codes[block]' => $sCSV,
'context_filter' => 1,
);
}
else
{
} else {
// Simple stats
$aParams = array(
'title[block]' => $sTitle,
@@ -2078,31 +2042,29 @@ class DashletHeaderDynamic extends Dashlet
);
}
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oPanel = PanelFactory::MakeEnhancedNeutral(Dict::S(str_replace('_', ':', $sTitle)), $sIconPath);
$oPage->add('<img src="'.$sIconPath.'">');
if (isset($aExtraParams['query_params']))
{
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
elseif (isset($aExtraParams['this->class']))
{
} elseif (isset($aExtraParams['this->class'])) {
$oObj = MetaModel::GetObject($aExtraParams['this->class'], $aExtraParams['this->id']);
$aQueryParams = $oObj->ToArgsForQuery();
}
else
{
} else {
$aQueryParams = array();
}
$oFilter = DBObjectSearch::FromOQL($sQuery, $aQueryParams);
$oBlock = new DisplayBlock($oFilter, 'summary');
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$oBlock->Display($oPage, $sBlockId, array_merge($aExtraParams, $aParams));
$oBlock->DisplayIntoContentBlock($oPanel, $oPage, $sBlockId, array_merge($aExtraParams, $aParams));
$oPage->add('</div>');
$oPage->add('</div>');
$oSubTitle = $oPanel->GetSubTitle();
$oSet = new DBObjectSet($oFilter);
$iCount = $oSet->Count();
$oAppContext = new ApplicationContext();
$sHyperlink = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search&'.$oAppContext->GetForLink().'&filter='.rawurlencode($oFilter->serialize());
$oSubTitle->AddHtml('<a class="summary" href="'.$sHyperlink.'">'.Dict::Format(str_replace('_', ':', $sSubtitle), $iCount).'</a>');
return $oPanel;
}
/**
@@ -2122,46 +2084,48 @@ class DashletHeaderDynamic extends Dashlet
$oIconSelect = $this->oModelReflection->GetIconSelectionField('icon');
$sIconPath = utils::HtmlEntities($oIconSelect->MakeFileUrl($sIcon));
$oPage->add('<div class="dashlet-content">');
$oPage->add('<div class="main_header">');
$oDashletContainer = new DashletContainer(null, 'dashlet-content');
$oPage->add('<img src="'.$sIconPath.'">');
$sHtml = '';
$sHtml .= '<img src="'.$sIconPath.'">';
$sBlockId = 'block_fake_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
$iTotal = 0;
$aValues = $this->GetValues();
$oPage->add('<div class="display_block" id="'.$sBlockId.'">');
$oPage->add('<div class="summary-details">');
$oPage->add('<table><tbody>');
$oPage->add('<tr>');
foreach ($aValues as $sValue)
{
$sHtml .= '<div class="display_block" id="'.$sBlockId.'">';
$sHtml .= '<div class="summary-details">';
$sHtml .= '<table><tbody>';
$sHtml .= '<tr>';
foreach ($aValues as $sValue) {
$sValueLabel = $this->oModelReflection->GetValueLabel($sClass, $sGroupBy, $sValue);
$oPage->add(' <th>'.$sValueLabel.'</th>');
$sHtml .= ' <th>'.$sValueLabel.'</th>';
}
$oPage->add('</tr>');
$oPage->add('<tr>');
foreach ($aValues as $sValue)
{
$iCount = (int) rand(2, 100);
$sHtml .= '</tr>';
$sHtml .= '<tr>';
foreach ($aValues as $sValue) {
$iCount = (int)rand(2, 100);
$iTotal += $iCount;
$oPage->add(' <td>'.$iCount.'</td>');
$sHtml .= ' <td>'.$iCount.'</td>';
}
$oPage->add('</tr>');
$oPage->add('</tbody></table>');
$oPage->add('</div>');
$sHtml .= '</tr>';
$sHtml .= '</tbody></table>';
$sHtml .= '</div>';
$sTitle = $this->oModelReflection->DictString($sTitle);
$sSubtitle = $this->oModelReflection->DictFormat($sSubtitle, $iTotal);
$oPage->add('<h1>'.utils::HtmlEntities($sTitle).'</h1>');
$oPage->add('<a class="summary">'.utils::HtmlEntities($sSubtitle).'</a>');
$oPage->add('</div>');
$sHtml .= '<h1>'.utils::HtmlEntities($sTitle).'</h1>';
$sHtml .= '<a class="summary">'.utils::HtmlEntities($sSubtitle).'</a>';
$sHtml .= '</div>';
$sHtml .= '</div>';
$oDashletContainer->AddHtml($sHtml);
return $oDashletContainer;
$oPage->add('</div>');
$oPage->add('</div>');
}
/**
@@ -2306,8 +2270,8 @@ class DashletBadge extends Dashlet
{
parent::__construct($oModelReflection, $sId);
$this->aProperties['class'] = 'Contact';
$this->aCSSClasses[] = 'dashlet-inline';
$this->aCSSClasses[] = 'dashlet-badge';
$this->aCSSClasses[] = 'ibo-dashlet--is-inline';
$this->aCSSClasses[] = 'ibo-dashlet-badge';
}
/**
@@ -2317,17 +2281,16 @@ class DashletBadge extends Dashlet
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$oDashletContainer = new DashletContainer($this->sId, 'dashlet-content');
$sClass = $this->aProperties['class'];
$oPage->add('<div class="dashlet-content">');
$oFilter = new DBObjectSearch($sClass);
$oBlock = new DisplayBlock($oFilter, 'actions');
$aExtraParams['context_filter'] = 1;
$sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occurring in the same DOM)
$oBlock->Display($oPage, $sBlockId, $aExtraParams);
$oBlock->DisplayIntoContentBlock($oDashletContainer, $oPage, $sBlockId, $aExtraParams);
$oPage->add('</div>');
return $oDashletContainer;
}
/**
@@ -2335,25 +2298,25 @@ class DashletBadge extends Dashlet
*/
public function RenderNoData($oPage, $bEditMode = false, $aExtraParams = array())
{
$sClass = $this->aProperties['class'];
$oDashletContainer = new DashletContainer($this->sId, 'dashlet-content');
$sClass = $this->aProperties['class'];
$sIconUrl = $this->oModelReflection->GetClassIcon($sClass, false);
$sClassLabel = $this->oModelReflection->GetName($sClass);
$oPage->add('<div class="dashlet-content">');
$sHtml = '';
$sHtml .= '<div id="block_fake_'.$this->sId.'" class="display_block">';
$sHtml .= '<p>';
$sHtml .= ' <a class="actions"><img src="'.utils::HtmlEntities($sIconUrl).'" style="vertical-align:middle;float;left;margin-right:10px;border:0;">'.$sClassLabel.': 947</a>';
$sHtml .= '</p>';
$sHtml .= '<p>';
$sHtml .= ' <a>'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>';
$sHtml .= '</p>';
$sHtml .= '</div>';
$oPage->add('<div id="block_fake_'.$this->sId.'" class="display_block">');
$oPage->add('<p>');
$oPage->add(' <a class="actions"><img src="'.utils::HtmlEntities($sIconUrl).'" style="vertical-align:middle;float;left;margin-right:10px;border:0;">'.$sClassLabel.': 947</a>');
$oPage->add('</p>');
$oPage->add('<p>');
$oPage->add(' <a>'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>');
$oPage->add(' <br/>');
$oPage->add(' <a>'.Dict::Format('UI:SearchFor_Class', $sClassLabel).'</a>');
$oPage->add('</p>');
$oPage->add('</div>');
$oDashletContainer->AddHtml($sHtml);
$oPage->add('</div>');
return $oDashletContainer;
}
static protected $aClassList = null;

View File

@@ -1,5 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<classes>
<class id="AbstractResource" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<comment>/* Resource access control abstraction. Can be herited by abstract resource access control classes. Generaly controlled using UR_ACTION_MODIFY access right. */</comment>
<abstract>true</abstract>
</properties>
<presentation/>
<methods/>
</class>
<class id="ResourceAdminMenu" _delta="define">
<parent>AbstractResource</parent>
<properties>
<comment>/* AdminTools menu access control. */</comment>
<abstract>true</abstract>
<category>grant_by_profile</category>
</properties>
<presentation/>
<methods/>
</class>
<class id="ResourceRunQueriesMenu" _delta="define">
<parent>AbstractResource</parent>
<properties>
<comment>/* RunQueriesMenu menu access control. */</comment>
<abstract>true</abstract>
<category>grant_by_profile</category>
</properties>
<presentation/>
<methods/>
</class>
<class id="ResourceSystemMenu" _delta="define">
<parent>AbstractResource</parent>
<properties>
<comment>/* System menu access control. */</comment>
<abstract>true</abstract>
<category>grant_by_profile</category>
</properties>
<presentation/>
<methods/>
</class>
</classes>
<portals>
<portal id="backoffice" _delta="define">
<url>pages/UI.php</url>
@@ -12,13 +53,351 @@
</portal>
</portals>
<menus>
<menu id="WelcomeMenu" xsi:type="MenuGroup" _delta="define">
<rank>10</rank>
<style>
<decoration_classes>fas fa-home</decoration_classes>
</style>
</menu>
<menu id="WelcomeMenuPage" xsi:type="DashboardMenuNode" _delta="define">
<rank>10</rank>
<parent>WelcomeMenu</parent>
<definition>
<layout>DashboardLayoutOneCol</layout>
<title/>
<cells>
<cell id="0">
<rank>0</rank>
<dashlets>
</dashlets>
</cell>
</cells>
</definition>
</menu>
<menu id="MyShortcuts" xsi:type="ShortcutContainerMenuNode" _delta="define">
<rank>20</rank>
<parent>WelcomeMenu</parent>
</menu>
<menu id="UserManagement" xsi:type="TemplateMenuNode" _delta="define">
<rank>10</rank>
<parent>AdminTools</parent>
<template_file/>
</menu>
<menu id="UserAccountsMenu" xsi:type="OQLMenuNode" _delta="define">
<rank>11</rank>
<parent>UserManagement</parent>
<oql><![CDATA[SELECT User]]></oql>
<do_search>1</do_search>
<search_form_open>1</search_form_open>
<enable_class>User</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="ProfilesMenu" xsi:type="OQLMenuNode" _delta="define">
<rank>12</rank>
<parent>UserManagement</parent>
<oql><![CDATA[SELECT URP_Profiles]]></oql>
<do_search>1</do_search>
<enable_class>URP_Profiles</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="AuditCategories" xsi:type="OQLMenuNode" _delta="define">
<rank>20</rank>
<parent>AdminTools</parent>
<oql><![CDATA[SELECT AuditCategory]]></oql>
<do_search>1</do_search>
<enable_class>AuditCategory</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="Queries" xsi:type="TemplateMenuNode" _delta="define">
<rank>30</rank>
<parent>AdminTools</parent>
<template_file/>
</menu>
<menu id="RunQueriesMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>31</rank>
<parent>Queries</parent>
<url>$pages/run_query.php</url>
<enable_class>ResourceRunQueriesMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="QueryMenu" xsi:type="OQLMenuNode" _delta="define">
<rank>32</rank>
<parent>Queries</parent>
<oql><![CDATA[SELECT Query]]></oql>
<do_search>1</do_search>
<enable_class>Query</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="ExportMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>33</rank>
<parent>Queries</parent>
<url>$webservices/export-v2.php?interactive=1</url>
<enable_class>ResourceAdminMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="DataModelMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>40</rank>
<parent>AdminTools</parent>
<url>$pages/schema.php</url>
<enable_class>ResourceRunQueriesMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="UniversalSearchMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>35</rank>
<parent>Queries</parent>
<url>$pages/UniversalSearch.php</url>
<enable_class>ResourceAdminMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="ConfigurationTools" xsi:type="MenuGroup" _delta="define_if_not_exists">
<rank>90</rank>
<style>
<decoration_classes>fas fa-cog</decoration_classes>
</style>
</menu>
<menu id="DataSources" xsi:type="OQLMenuNode" _delta="define">
<rank>20</rank>
<parent>ConfigurationTools</parent>
<oql><![CDATA[SELECT SynchroDataSource]]></oql>
<do_search>1</do_search>
<enable_class>SynchroDataSource</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="NotificationsMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>40</rank>
<parent>ConfigurationTools</parent>
<url>$pages/notifications.php</url>
<enable_class>Trigger</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
<menu id="AdminTools" xsi:type="MenuGroup" _delta="define">
<rank>80</rank>
<style>
<decoration_classes>fas fa-tools</decoration_classes>
</style>
</menu>
<menu id="SystemTools" xsi:type="MenuGroup" _delta="define">
<rank>100</rank>
<enable_class>ResourceSystemMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
<style>
<decoration_classes>fas fa-terminal</decoration_classes>
</style>
</menu>
</menus>
<meta>
<classes>
<class id="cmdbAbstractObject" _delta="define">
<methods>
<method id="Set">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>deny</operation>
<types>
<type id="AttributeStopWatch"/>
<type id="AttributeSubItem"/>
<type id="AttributeExternalField"/>
<type id="AttributeLinkedSetIndirect"/>
<type id="AttributeLinkedSet"/>
<type id="AttributeImage"/>
<type id="AttributeBlob"/>
</types>
</type_restrictions>
</argument>
<argument id="2">
<type>string</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
<method id="SetIfNull">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>deny</operation>
<types>
<type id="AttributeStopWatch"/>
<type id="AttributeSubItem"/>
<type id="AttributeExternalField"/>
<type id="AttributeLinkedSetIndirect"/>
<type id="AttributeLinkedSet"/>
<type id="AttributeImage"/>
<type id="AttributeBlob"/>
</types>
</type_restrictions>
</argument>
<argument id="2">
<type>string</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
<method id="SetCurrentDate">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>allow</operation>
<types>
<type id="AttributeDate"/>
<type id="AttributeDateTime"/>
<type id="AttributeString"/>
</types>
</type_restrictions>
</argument>
</arguments>
</method>
<method id="SetCurrentUser">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>allow</operation>
<types>
<type id="AttributeExternalKey"/>
<type id="AttributeInteger"/>
<type id="AttributeString"/>
</types>
</type_restrictions>
</argument>
</arguments>
</method>
<method id="SetCurrentPerson">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>allow</operation>
<types>
<type id="AttributeExternalKey"/>
<type id="AttributeInteger"/>
<type id="AttributeString"/>
</types>
</type_restrictions>
</argument>
</arguments>
</method>
<method id="SetElapsedTime">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>allow</operation>
<types>
<type id="AttributeDuration"/>
</types>
</type_restrictions>
</argument>
<argument id="2">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>allow</operation>
<types>
<type id="AttributeDate"/>
<type id="AttributeDateTime"/>
</types>
</type_restrictions>
</argument>
<argument id="3">
<type>string</type>
<mandatory>false</mandatory>
</argument>
</arguments>
</method>
<method id="Reset">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>deny</operation>
<types>
<type id="AttributeStopWatch"/>
<type id="AttributeSubItem"/>
<type id="AttributeExternalField"/>
</types>
</type_restrictions>
</argument>
</arguments>
</method>
<method id="ResetStopWatch">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>allow</operation>
<types>
<type id="AttributeStopWatch"/>
</types>
</type_restrictions>
</argument>
</arguments>
</method>
<method id="Copy">
<arguments>
<argument id="1">
<type>attcode</type>
<mandatory>true</mandatory>
<type_restrictions>
<operation>deny</operation>
<types>
<type id="AttributeStopWatch"/>
<type id="AttributeSubItem"/>
<type id="AttributeExternalField"/>
</types>
</type_restrictions>
</argument>
<argument id="2">
<type>attcode</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
<method id="ApplyStimulus">
<arguments>
<argument id="1">
<type>string</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
<method id="PrefillCreationForm">
<arguments>
<argument id="1">
<type>reference</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
<method id="PrefillTransitionForm">
<arguments>
<argument id="1">
<type>reference</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
<method id="PrefillSearchForm">
<arguments>
<argument id="1">
<type>reference</type>
<mandatory>true</mandatory>
</argument>
</arguments>
</method>
</methods>
</class>
</classes>
</meta>
</itop_design>

View File

@@ -1,4 +1,7 @@
<?php
use Combodo\iTop\Renderer\BlockRenderer;
/**
* Copyright (C) 2013-2020 Combodo SARL
*
@@ -25,7 +28,7 @@ class DataTable
protected $sTableId; // identifier for saving the settings (combined with the class aliases)
protected $oSet; // The set of objects to display
protected $aClassAliases; // The aliases (alias => class) inside the set
protected $iNbObjects; // Total number of objects inthe set
protected $iNbObjects; // Total number of objects in the set
protected $bUseCustomSettings; // Whether or not the current display uses custom settings
protected $oDefaultSettings; // the default settings for displaying such a list
protected $bShowObsoleteData;
@@ -157,17 +160,17 @@ class DataTable
$sPager = $this->GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex);
$sActionsMenu = '';
$sToolkitMenu = '';
if ($bActionsMenu)
{
if ($bActionsMenu) {
$sActionsMenu = $this->GetActionsMenu($oPage, $aExtraParams);
}
if ($bToolkitMenu)
{
$sToolkitMenu = $this->GetToolkitMenu($oPage, $aExtraParams);
}
// if ($bToolkitMenu)
// {
// $sToolkitMenu = $this->GetToolkitMenu($oPage, $aExtraParams);
// }
$sDataTable = $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
$sConfigDlg = $this->GetTableConfigDlg($oPage, $aColumns, $bViewLink, $iDefaultPageSize);
$sHtml = "<table id=\"{$this->sDatatableContainerId}\" class=\"datatable\">";
$sHtml .= "<tr><td>";
$sHtml .= "<table style=\"width:100%;\">";
@@ -208,7 +211,7 @@ class DataTable
return $sHtml;
}
/**
* When refreshing the body of a paginated table, get the rows of the table (inside the TBODY)
* return string The HTML rows to insert inside the <tbody> node
@@ -355,9 +358,18 @@ EOF;
protected function GetActionsMenu(WebPage $oPage, $aExtraParams)
{
$oMenuBlock = new MenuBlock($this->oSet->GetFilter(), 'list');
$sHtml = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId);
return $sHtml;
$oBlock = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId);
foreach ($oBlock->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) {
$oPage->add_linked_stylesheet($sFileAbsUrl);
}
// JS files
foreach ($oBlock->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) {
$oPage->add_linked_script($sFileAbsUrl);
}
$oPage->RenderInlineScriptsAndCSSRecursively($oBlock);
return BlockRenderer::RenderBlockTemplates($oBlock);
}
/**
@@ -513,6 +525,7 @@ EOF;
return $aAttribs;
}
/**
* @param $aColumns
* @param $sSelectMode
@@ -860,396 +873,3 @@ class PrintableDataTable extends DataTable
return $sHtml;
}
}
class DataTableSettings implements Serializable
{
public $aClassAliases;
public $sTableId;
public $iDefaultPageSize;
public $aColumns;
/**
* DataTableSettings constructor.
*
* @param $aClassAliases
* @param null $sTableId
*/
public function __construct($aClassAliases, $sTableId = null)
{
$this->aClassAliases = $aClassAliases;
$this->sTableId = $sTableId;
$this->iDefaultPageSize = 10;
$this->aColumns = array();
}
/**
* @param $iDefaultPageSize
* @param $aSortOrder
* @param $aColumns
*/
protected function Init($iDefaultPageSize, $aSortOrder, $aColumns)
{
$this->iDefaultPageSize = $iDefaultPageSize;
$this->aColumns = $aColumns;
$this->FixVisibleColumns();
}
/**
* @return string
*/
public function serialize()
{
// Save only the 'visible' columns
$aColumns = array();
foreach($this->aClassAliases as $sAlias => $sClass)
{
$aColumns[$sAlias] = array();
foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
{
unset($aData['label']); // Don't save the display name
unset($aData['alias']); // Don't save the alias (redundant)
unset($aData['code']); // Don't save the code (redundant)
if ($aData['checked'])
{
$aColumns[$sAlias][$sAttCode] = $aData;
}
}
}
return serialize(
array(
'iDefaultPageSize' => $this->iDefaultPageSize,
'aColumns' => $aColumns,
)
);
}
/**
* @param string $sData
*
* @throws \Exception
*/
public function unserialize($sData)
{
$aData = unserialize($sData);
$this->iDefaultPageSize = $aData['iDefaultPageSize'];
$this->aColumns = $aData['aColumns'];
foreach($this->aClassAliases as $sAlias => $sClass)
{
foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
{
$aFieldData = false;
if ($sAttCode == '_key_')
{
$aFieldData = $this->GetFieldData($sAlias, $sAttCode, null, true /* bChecked */, $aData['sort']);
}
else if (MetaModel::isValidAttCode($sClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $aData['sort']);
}
if ($aFieldData)
{
$this->aColumns[$sAlias][$sAttCode] = $aFieldData;
}
else
{
unset($this->aColumns[$sAlias][$sAttCode]);
}
}
}
$this->FixVisibleColumns();
}
/**
* @param $aClassAliases
* @param $bViewLink
* @param $aDefaultLists
*
* @return \DataTableSettings
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
static public function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists)
{
$oSettings = new DataTableSettings($aClassAliases);
// Retrieve the class specific settings for each class/alias based on the 'list' ZList
//TODO let the caller pass some other default settings (another Zlist, extre fields...)
$aColumns = array();
foreach($aClassAliases as $sAlias => $sClass)
{
if ($aDefaultLists == null)
{
$aList = cmdbAbstract::FlattenZList(MetaModel::GetZListItems($sClass, 'list'));
}
else
{
$aList = $aDefaultLists[$sAlias];
}
$aSortOrder = MetaModel::GetOrderByDefault($sClass);
if ($bViewLink)
{
$sSort = 'none';
if(array_key_exists('friendlyname', $aSortOrder))
{
$sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc';
}
$sNormalizedFName = MetaModel::NormalizeFieldSpec($sClass, 'friendlyname');
if(array_key_exists($sNormalizedFName, $aSortOrder))
{
$sSort = $aSortOrder[$sNormalizedFName] ? 'asc' : 'desc';
}
$aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort);
}
foreach($aList as $sAttCode)
{
$sSort = 'none';
if(array_key_exists($sAttCode, $aSortOrder))
{
$sSort = $aSortOrder[$sAttCode] ? 'asc' : 'desc';
}
$oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode);
$aFieldData = $oSettings->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $sSort);
if ($aFieldData) $aColumns[$sAlias][$sAttCode] = $aFieldData;
}
}
$iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
$oSettings->Init($iDefaultPageSize, $aSortOrder, $aColumns);
return $oSettings;
}
/**
* @throws \CoreException
*/
protected function FixVisibleColumns()
{
foreach($this->aClassAliases as $sAlias => $sClass)
{
if (!isset($this->aColumns[$sAlias]))
{
continue;
}
foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
{
// Remove non-existent columns
// TODO: check if the existing ones are still valid (in case their type changed)
if (($sAttCode != '_key_') && (!MetaModel::IsValidAttCode($sClass, $sAttCode)))
{
unset($this->aColumns[$sAlias][$sAttCode]);
}
}
$aList = MetaModel::ListAttributeDefs($sClass);
// Add the other (non visible ones), sorted in alphabetical order
$aTempData = array();
foreach($aList as $sAttCode => $oAttDef)
{
if ( (!array_key_exists($sAttCode, $this->aColumns[$sAlias])) && (!($oAttDef instanceof AttributeLinkedSet || $oAttDef instanceof AttributeDashboard)))
{
$aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, false /* bChecked */, 'none');
if ($aFieldData) $aTempData[$aFieldData['label']] = $aFieldData;
}
}
ksort($aTempData);
foreach($aTempData as $sLabel => $aFieldData)
{
$this->aColumns[$sAlias][$aFieldData['code']] = $aFieldData;
}
}
}
/**
* @param $aClassAliases
* @param null $sTableId
* @param bool $bOnlyOnTable
*
* @return \DataTableSettings|null
* @throws \Exception
*/
static public function GetTableSettings($aClassAliases, $sTableId = null, $bOnlyOnTable = false)
{
$pref = null;
$oSettings = new DataTableSettings($aClassAliases, $sTableId);
if ($sTableId != null)
{
// An identified table, let's fetch its own settings (if any)
$pref = appUserPreferences::GetPref($oSettings->GetPrefsKey($sTableId), null);
}
if ($pref == null)
{
if (!$bOnlyOnTable)
{
// Try the global preferred values for this class / set of classes
$pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null);
}
if ($pref == null)
{
// no such settings, use the default values provided by the data model
return null;
}
}
$oSettings->unserialize($pref);
return $oSettings;
}
/**
* @return array
*/
public function GetSortOrder()
{
$aSortOrder = array();
foreach($this->aColumns as $sAlias => $aColumns)
{
foreach($aColumns as $aColumn)
{
if ($aColumn['sort'] != 'none')
{
$sCode = ($aColumn['code'] == '_key_') ? 'friendlyname' : $aColumn['code'];
$aSortOrder[$sCode] = ($aColumn['sort']=='asc'); // true for ascending, false for descending
}
}
break; // TODO: For now the Set object supports only sorting on the first class of the set
}
return $aSortOrder;
}
/**
* @param null $sTargetTableId
*
* @return bool
*/
public function Save($sTargetTableId = null)
{
$sSaveId = is_null($sTargetTableId) ? $this->sTableId : $sTargetTableId;
if ($sSaveId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead
$sSettings = $this->serialize();
appUserPreferences::SetPref($this->GetPrefsKey($sSaveId), $sSettings);
return true;
}
/**
* @return bool
*/
public function SaveAsDefault()
{
$sSettings = $this->serialize();
appUserPreferences::SetPref($this->GetPrefsKey(null), $sSettings);
return true;
}
/**
* Clear the preferences for this particular table
* @param $bResetAll boolean If true,the settings for all tables of the same class(es)/alias(es) are reset
*/
public function ResetToDefault($bResetAll)
{
if (($this->sTableId == null) && (!$bResetAll)) return false; // Cannot reset, the table is not identified, use force $bResetAll instead
if ($bResetAll)
{
// Turn the key into a suitable PCRE pattern
$sKey = $this->GetPrefsKey(null);
$sPattern = str_replace(array('|'), array('\\|'), $sKey); // escape the | character
$sPattern = '#^'.str_replace(array('*'), array('.*'), $sPattern).'$#'; // Don't use slash as the delimiter since it's used in our key to delimit aliases
appUserPreferences::UnsetPref($sPattern, true);
}
else
{
appUserPreferences::UnsetPref($this->GetPrefsKey($this->sTableId), false);
}
return true;
}
/**
* @param null $sTableId
*
* @return string
*/
protected function GetPrefsKey($sTableId = null)
{
return static::GetAppUserPreferenceKey($this->aClassAliases, $sTableId);
}
public static function GetAppUserPreferenceKey($aClassAliases, $sTableId)
{
if ($sTableId === null)
{
$sTableId = '*';
}
$aKeys = array();
foreach($aClassAliases as $sAlias => $sClass)
{
$aKeys[] = $sAlias.'-'.$sClass;
}
return implode('/', $aKeys).'|'.$sTableId;
}
/**
* @param $sAlias
* @param $sAttCode
* @param $oAttDef
* @param $bChecked
* @param $sSort
*
* @return array|bool
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
protected function GetFieldData($sAlias, $sAttCode, $oAttDef, $bChecked, $sSort)
{
$ret = false;
if ($sAttCode == '_key_')
{
$sLabel = Dict::Format('UI:ExtKey_AsLink', MetaModel::GetName($this->aClassAliases[$sAlias]));
$ret = array(
'label' => $sLabel,
'checked' => true,
'disabled' => true,
'alias' => $sAlias,
'code' => $sAttCode,
'sort' => $sSort,
);
}
else if (!$oAttDef->IsLinkSet())
{
$sLabel = $oAttDef->GetLabel();
if ($oAttDef->IsExternalKey())
{
$sLabel = Dict::Format('UI:ExtKey_AsLink', $oAttDef->GetLabel());
}
else if ($oAttDef->IsExternalField())
{
if ($oAttDef->IsFriendlyName())
{
$sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
}
else
{
$oExtAttDef = $oAttDef->GetExtAttDef();
$sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
}
}
elseif ($oAttDef instanceof AttributeFriendlyName)
{
$sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
}
$ret = array(
'label' => $sLabel,
'checked' => $bChecked,
'disabled' => false,
'alias' => $sAlias,
'code' => $sAttCode,
'sort' => $sSort,
);
}
return $ret;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +1,7 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/ErrorPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
class ErrorPage extends NiceWebPage
{
public function __construct($sTitle)
{
parent::__construct($sTitle);
$this->add_linked_script("../js/jquery.blockUI.js");
$this->add_linked_script("../setup/setup.js");
$this->add_saas("css/setup.scss");
}
public function info($sText)
{
$this->add("<p class=\"info\">$sText</p>\n");
$this->log_info($sText);
}
public function ok($sText)
{
$this->add("<div class=\"message message-valid\"><span class=\"message-title\">Success:</span>$sText</div>");
$this->log_ok($sText);
}
public function warning($sText)
{
$this->add("<div class=\"message message-warning\"><span class=\"message-title\">Warning:</span>$sText</div>");
$this->log_warning($sText);
}
public function error($sText)
{
$this->add("<div class=\"message message-error\">$sText</div>");
$this->log_error($sText);
}
public function output()
{
$sLogo = utils::GetAbsoluteUrlAppRoot().'/images/itop-logo.png';
$sTimeStamp = utils::GetCacheBusterTimestamp();
$sTitle = utils::HtmlEntities($this->s_title);
$this->s_content = <<<HTML
<div id="header" class="error_page">
<h1><a href="http://www.combodo.com/itop" target="_blank"><img title="iTop by Combodo" alt=" " src="{$sLogo}?t={$sTimeStamp}"></a>&nbsp;{$sTitle}</h1>
</div>
<div id="setup" class="error_page">
{$this->s_content}
</div>
HTML;
return parent::output();
}
public static function log_error($sText)
{
IssueLog::Error($sText);
}
public static function log_warning($sText)
{
IssueLog::Warning($sText);
}
public static function log_info($sText)
{
IssueLog::Info($sText);
}
public static function log_ok($sText)
{
IssueLog::Ok($sText);
}
public static function log($sText)
{
IssueLog::Ok($sText);
}
}

View File

@@ -44,7 +44,6 @@ class InputOutputTask extends cmdbAbstractObject
"db_table" => "priv_iotask",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +1,7 @@
<?php
// Copyright (C) 2010-2012 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/>
/**
* Class iTopWizardWebPage
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/iTopWizardWebPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once('itopwebpage.class.inc.php');
/**
* Web page to display a wizard in the iTop framework
*/
class iTopWizardWebPage extends iTopWebPage
{
var $m_iCurrentStep;
var $m_aSteps;
public function __construct($sTitle, $currentOrganization, $iCurrentStep, $aSteps)
{
parent::__construct($sTitle." - step $iCurrentStep of ".count($aSteps)." - ".$aSteps[$iCurrentStep - 1], $currentOrganization);
$this->m_iCurrentStep = $iCurrentStep;
$this->m_aSteps = $aSteps;
}
public function output()
{
$aSteps = array();
$iIndex = 0;
foreach($this->m_aSteps as $sStepTitle)
{
$iIndex++;
$sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep';
$aSteps[] = "<div class=\"$sStyle\"><span>$sStepTitle</span></div>";
}
$sWizardHeader = "<div class=\"wizHeader\"><h1>".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."</h1>\n".implode("<div class=\"wizSeparator\"><img align=\"bottom\" src=\"../images/wizArrow.gif\"></div>", $aSteps)."<br style=\"clear:both;\"/></div>\n";
$this->s_content = "$sWizardHeader<div class=\"wizContainer\">".$this->s_content."</div>";
parent::output();
}
}
?>

View File

@@ -224,7 +224,7 @@ class LoginTwigRenderer
}
$oCoreLoader = new Twig_Loader_Filesystem(array(), APPROOT.'templates');
$aCoreTemplatesPaths = array('login', 'login/password');
$aCoreTemplatesPaths = array('pages/login', 'pages/login/password');
// Having this path declared after the plugins let the plugins replace the core templates
$oCoreLoader->setPaths($aCoreTemplatesPaths);
// Having the core templates accessible within a different namespace offer the possibility to extend them while replacing them

View File

@@ -94,7 +94,6 @@ class LoginWebPage extends NiceWebPage
{
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css');
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css');
}
public static function SetLoginFailedMessage($sMessage)
@@ -102,6 +101,44 @@ class LoginWebPage extends NiceWebPage
self::$m_sLoginFailedMessage = $sMessage;
}
/**
* @param $oUser
* @param array $aProfiles
*
* @return array
* @throws \CoreException
* @throws \CoreUnexpectedValue
*/
public static function SynchronizeProfiles(&$oUser, array $aProfiles, $sOrigin)
{
$oProfilesSet = $oUser->Get(profile_list);
//delete old profiles
$aExistingProfiles = [];
while ($oProfile = $oProfilesSet->Fetch())
{
array_push($aExistingProfiles, $oProfile->Get('profileid'));
$iArrayKey = array_search($oProfile->Get('profileid'), $aProfiles);
if (!$iArrayKey)
{
$oProfilesSet->RemoveItem($oProfile->Get('profileid'));
}
else
{
unset($aProfiles[$iArrayKey]);
}
}
//add profiles not already linked with user
foreach ($aProfiles as $iProfileId)
{
$oLink = new URP_UserProfile();
$oLink->Set('profileid', $iProfileId);
$oLink->Set('reason', $sOrigin);
$oProfilesSet->AddItem(MetaModel::NewObject('URP_UserProfile', array('profileid' => $iProfileId, 'reason' => $sOrigin)));
}
$oUser->Set('profile_list', $oProfilesSet);
}
public function DisplayLoginHeader($bMainAppLogo = false)
{
$sLogo = 'itop-logo-external.png';
@@ -888,20 +925,12 @@ class LoginWebPage extends NiceWebPage
}
// Now synchronize the profiles
$oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile');
$sOrigin = 'External User provisioning';
if (isset($_SESSION['login_mode']))
{
$sOrigin .= " ({$_SESSION['login_mode']})";
}
foreach ($aProfiles as $iProfileId)
{
$oLink = new URP_UserProfile();
$oLink->Set('profileid', $iProfileId);
$oLink->Set('reason', $sOrigin);
$oProfilesSet->AddObject($oLink);
}
$oUser->Set('profile_list', $oProfilesSet);
$aExistingProfiles = self::SynchronizeProfiles($oUser, $aProfiles, $sOrigin);
if ($oUser->IsModified())
{
$oUser->DBWrite();

View File

@@ -17,6 +17,8 @@
* You should have received a copy of the GNU Affero General Public License
*/
use Combodo\iTop\Application\UI\Component\Title\TitleFactory;
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/application/template.class.inc.php');
require_once(APPROOT."/application/user.dashboard.class.inc.php");
@@ -51,7 +53,10 @@ require_once(APPROOT."/application/user.dashboard.class.inc.php");
* new OQLMenuNode('PersonsMenu', 'SELECT bizPerson', $oContactsMenu->GetIndex(), 0);
*
*/
/**
* Class ApplicationMenu
*/
class ApplicationMenu
{
/**
@@ -71,7 +76,10 @@ class ApplicationMenu
*/
static $sFavoriteSiloQuery = 'SELECT Organization';
static public function LoadAdditionalMenus()
/**
* @return void
*/
public static function LoadAdditionalMenus()
{
if (!self::$bAdditionalMenusLoaded)
{
@@ -97,10 +105,10 @@ class ApplicationMenu
/**
* Set the query used to limit the list of displayed organizations in the drop-down menu
* @param $sOQL string The OQL query returning a list of Organization objects
* @param string $sOQL The OQL query returning a list of Organization objects
* @return void
*/
static public function SetFavoriteSiloQuery($sOQL)
public static function SetFavoriteSiloQuery($sOQL)
{
self::$sFavoriteSiloQuery = $sOQL;
}
@@ -109,7 +117,7 @@ class ApplicationMenu
* Get the query used to limit the list of displayed organizations in the drop-down menu
* @return string The OQL query returning a list of Organization objects
*/
static public function GetFavoriteSiloQuery()
public static function GetFavoriteSiloQuery()
{
return self::$sFavoriteSiloQuery;
}
@@ -117,11 +125,11 @@ class ApplicationMenu
/**
* Check whether a menu Id is enabled or not
*
* @param $sMenuId
* @param string $sMenuId
*
* @throws \Exception
*/
static public function CheckMenuIdEnabled($sMenuId)
public static function CheckMenuIdEnabled($sMenuId)
{
self::LoadAdditionalMenus();
$oMenuNode = self::GetMenuNode(self::GetMenuIndexById($sMenuId));
@@ -140,11 +148,11 @@ class ApplicationMenu
* Main function to add a menu entry into the application, can be called during the definition
* of the data model objects
* @param MenuNode $oMenuNode
* @param $iParentIndex
* @param $fRank
* @param int $iParentIndex
* @param float $fRank
* @return int
*/
static public function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank)
public static function InsertMenu(MenuNode $oMenuNode, $iParentIndex, $fRank)
{
$index = self::GetMenuIndexById($oMenuNode->GetMenuId());
if ($index == -1)
@@ -185,20 +193,154 @@ class ApplicationMenu
/**
* Reflection API - Get menu entries
*
* @return array
*/
static public function ReflectionMenuNodes()
public static function ReflectionMenuNodes()
{
self::LoadAdditionalMenus();
return self::$aMenusIndex;
}
/**
* Get entries count for all the menus
*
* @param array $aExtraParams
*
* @return array
* @throws \DictExceptionMissingString
* @since 3.0.0
*/
public static function GetMenusCount($aExtraParams = array())
{
$aMenuGroups = static::GetMenuGroups($aExtraParams);
$aMenusCount = [];
foreach ($aMenuGroups as $aMenuGroup) {
$aSubMenuNodes = $aMenuGroup['aSubMenuNodes'];
$aMenusCount = array_merge($aMenusCount, static::GetSubMenusCount($aSubMenuNodes));
}
return $aMenusCount;
}
/**
* Recurse sub menus for counts
*
* @param array $aSubMenuNodes
*
* @return array
* @since 3.0.0
*/
private static function GetSubMenusCount(array $aSubMenuNodes)
{
$aSubMenusCount = [];
foreach ($aSubMenuNodes as $aSubMenuNode) {
if ($aSubMenuNode['bHasCount']) {
$oMenuNode = static::GetMenuNode(static::GetMenuIndexById($aSubMenuNode['sId']));
$aSubMenusCount[$aSubMenuNode['sId']] = $oMenuNode->GetEntriesCount();
}
$aSubMenusCount = array_merge($aSubMenusCount, static::GetSubMenusCount($aSubMenuNode['aSubMenuNodes']));
}
return $aSubMenusCount;
}
/**
* Return an array of menu groups
*
* @param array $aExtraParams
*
* @return array
* @throws \DictExceptionMissingString
* @since 3.0.0
*/
public static function GetMenuGroups($aExtraParams = array())
{
self::LoadAdditionalMenus();
// Sort the root menu based on the rank
usort(self::$aRootMenus, array('ApplicationMenu', 'CompareOnRank'));
$aMenuGroups = [];
foreach(static::$aRootMenus as $aMenuGroup)
{
if(!static::CanDisplayMenu($aMenuGroup))
{
continue;
}
$sMenuGroupIdx = $aMenuGroup['index'];
/** @var \MenuGroup $oMenuNode */
$oMenuNode = static::GetMenuNode($sMenuGroupIdx);
$aMenuGroups[] = [
'sId' => $oMenuNode->GetMenuID(),
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
'sInitials' => $oMenuNode->GetInitials(),
'sTitle' => $oMenuNode->GetTitle(),
'aSubMenuNodes' => static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams),
];
}
return $aMenuGroups;
}
/**
* Return an array of sub-menu nodes for $sMenuGroupIdx
*
* @param string $sMenuGroupIdx
* @param array $aExtraParams
*
* @return array
* @throws \DictExceptionMissingString
* @throws \Exception
* @since 3.0.0
*/
public static function GetSubMenuNodes($sMenuGroupIdx, $aExtraParams = array())
{
$aSubMenuItems = self::GetChildren($sMenuGroupIdx);
// Sort the children based on the rank
usort($aSubMenuItems, array('ApplicationMenu', 'CompareOnRank'));
$aSubMenuNodes = [];
foreach($aSubMenuItems as $aSubMenuItem)
{
if(!static::CanDisplayMenu($aSubMenuItem))
{
continue;
}
$sSubMenuItemIdx = $aSubMenuItem['index'];
$oSubMenuNode = static::GetMenuNode($sSubMenuItemIdx);
if(!$oSubMenuNode->IsEnabled())
{
continue;
}
$aSubMenuNodes[] = [
'sId' => $oSubMenuNode->GetMenuId(),
'sTitle' => $oSubMenuNode->GetTitle(),
'bHasCount' => $oSubMenuNode->HasCount(),
'sUrl' => $oSubMenuNode->GetHyperlink($aExtraParams),
'bOpenInNewWindow' => $oSubMenuNode->IsHyperLinkInNewWindow(),
'aSubMenuNodes' => static::GetSubMenuNodes($sSubMenuItemIdx, $aExtraParams),
];
}
return $aSubMenuNodes;
}
/**
* Entry point to display the whole menu into the web page, used by iTopWebPage
* @param \WebPage $oPage
* @param $aExtraParams
* @param array $aExtraParams
* @throws DictExceptionMissingString
*
* @deprecated Will be removed in 3.0.0, use static::GetMenuGroups() instead
*/
static public function DisplayMenu($oPage, $aExtraParams)
public static function DisplayMenu($oPage, $aExtraParams)
{
self::LoadAdditionalMenus();
// Sort the root menu based on the rank
@@ -237,7 +379,7 @@ EOF
* @param array $aMenu menu entry
* @return bool true if at least one menu is enabled
*/
static private function CanDisplayMenu($aMenu)
private static function CanDisplayMenu($aMenu)
{
$oMenuNode = self::GetMenuNode($aMenu['index']);
if ($oMenuNode->IsEnabled())
@@ -269,11 +411,12 @@ EOF
* @param array $aExtraParams
* @param int $iActiveMenu
*
* @return true if the currently selected menu is one of the submenus
* @return bool True if the currently selected menu is one of the submenus
* @throws DictExceptionMissingString
* @throws \Exception
* @deprecated Will be removed in 3.0.0, use static::GetSubMenuNodes() instead
*/
static protected function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
protected static function DisplaySubMenu($oPage, $aMenus, $aExtraParams, $iActiveMenu = -1)
{
// Sort the menu based on the rank
$bActive = false;
@@ -330,11 +473,11 @@ EOF
/**
* Helper function to sort the menus based on their rank
* @param $a
* @param $b
* @param array $a
* @param array $b
* @return int
*/
static public function CompareOnRank($a, $b)
public static function CompareOnRank($a, $b)
{
$result = 1;
if ($a['rank'] == $b['rank'])
@@ -353,7 +496,7 @@ EOF
* @param int $index
* @return MenuNode|null
*/
static public function GetMenuNode($index)
public static function GetMenuNode($index)
{
return isset(self::$aMenusIndex[$index]) ? self::$aMenusIndex[$index]['node'] : null;
}
@@ -363,7 +506,7 @@ EOF
* @param int $index
* @return array
*/
static public function GetChildren($index)
public static function GetChildren($index)
{
return self::$aMenusIndex[$index]['children'];
}
@@ -373,7 +516,7 @@ EOF
* @param string $sTitle Title of the menu (as passed when creating the menu)
* @return integer ID of the menu, or -1 if not found
*/
static public function GetMenuIndexById($sTitle)
public static function GetMenuIndexById($sTitle)
{
$index = -1;
/** @var MenuNode[] $aMenu */
@@ -392,7 +535,7 @@ EOF
* Retrieves the currently active menu (if any, otherwise the first menu is the default)
* @return string The Id of the currently active menu
*/
static public function GetActiveNodeId()
public static function GetActiveNodeId()
{
$oAppContext = new ApplicationContext();
$sMenuId = $oAppContext->GetCurrentValue('menu', null);
@@ -406,7 +549,7 @@ EOF
/**
* @return null|string
*/
static public function GetDefaultMenuId()
public static function GetDefaultMenuId()
{
static $sDefaultMenuId = null;
if (is_null($sDefaultMenuId))
@@ -426,7 +569,7 @@ EOF
* @param $sMenuId
* @return string
*/
static public function GetRootMenuId($sMenuId)
public static function GetRootMenuId($sMenuId)
{
$iMenuIndex = self::GetMenuIndexById($sMenuId);
if ($iMenuIndex == -1)
@@ -563,29 +706,45 @@ abstract class MenuNode
/**
* @return string
* @throws DictExceptionMissingString
*/
public function GetTitle()
{
return Dict::S("Menu:$this->sMenuId", str_replace('_', ' ', $this->sMenuId));
}
/**
* Indicates if the page corresponding to this menu node is countable
*
* @return bool true if corresponding page is countable
* @since 3.0.0
*/
public function HasCount()
{
return false;
}
/**
* Get the number of entries of the page corresponding to this menu item.
*
* @return int the number of entries
* @since 3.0.0
*/
public function GetEntriesCount()
{
return 0;
}
/**
* @return string
* @throws DictExceptionMissingString
*/
public function GetLabel()
{
$sRet = Dict::S("Menu:$this->sMenuId+", "");
if ($sRet === '')
{
if ($this->iParentIndex != -1)
{
if ($sRet === '') {
if ($this->iParentIndex != -1) {
$oParentMenu = ApplicationMenu::GetMenuNode($this->iParentIndex);
$sRet = $oParentMenu->GetTitle().' / '.$this->GetTitle();
}
else
{
} else {
$sRet = $this->GetTitle();
}
//$sRet = $this->GetTitle();
@@ -600,7 +759,10 @@ abstract class MenuNode
{
return $this->index;
}
/**
* @return void
*/
public function PopulateChildMenus()
{
foreach (ApplicationMenu::GetChildren($this->GetIndex()) as $aMenu)
@@ -725,18 +887,64 @@ abstract class MenuNode
*/
class MenuGroup extends MenuNode
{
/** @var string DEFAULT_DECORATION_CLASSES Set to null by default so it is replaced by initials when none is specified */
const DEFAULT_DECORATION_CLASSES = null;
/** @var string The CSS classes used to display the menu group's icon */
protected $sDecorationClasses = self::DEFAULT_DECORATION_CLASSES;
/**
* Create a top-level menu group and inserts it into the application's main menu
*
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
* @param float $fRank Number used to order the list, the groups are sorted based on this value
* @param string|null $sDecorationClasses CSS classes used to display the menu group's icon
* @param string $sEnableClass Name of class of object
* @param integer $iActionCode Either UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_DELETE, UR_ACTION_BULKREAD, UR_ACTION_BULKMODIFY or UR_ACTION_BULKDELETE
* @param integer $iAllowedResults Expected "rights" for the action: either UR_ALLOWED_YES, UR_ALLOWED_NO, UR_ALLOWED_DEPENDS or a mix of them...
* @param string $sEnableStimulus
*/
public function __construct($sMenuId, $fRank, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
public function __construct($sMenuId, $fRank, $sDecorationClasses = null, $sEnableClass = null, $iActionCode = null, $iAllowedResults = UR_ALLOWED_YES, $sEnableStimulus = null)
{
parent::__construct($sMenuId, -1 /* no parent, groups are at root level */, $fRank, $sEnableClass, $iActionCode, $iAllowedResults, $sEnableStimulus);
if(!empty($sDecorationClasses))
{
$this->sDecorationClasses = $sDecorationClasses;
}
}
/**
* Return true if the menu group has some decoration classes
*
* @return bool
* @since 3.0.0
*/
public function HasDecorationClasses()
{
return (empty($this->GetDecorationClasses()) === false);
}
/**
* Return the CSS classes used for decorating the menu group (typically the icon in the navigation menu)
*
* @return string
* @since 3.0.0
*/
public function GetDecorationClasses()
{
return $this->sDecorationClasses;
}
/**
* Returns the initials of the menu group, used by the rendering in case there is no decoration classes
*
* @return string
* @since 3.0.0
*/
public function GetInitials()
{
return mb_substr($this->GetTitle(), 0, 1);
}
/**
@@ -913,30 +1121,51 @@ class OQLMenuNode extends MenuNode
{
$sUsageId = utils::GetSafeId($sUsageId);
$oSearch = DBObjectSearch::FromOQL($sOql);
$sIcon = MetaModel::GetClassIcon($oSearch->GetClass());
//$sIcon = MetaModel::GetClassIcon($oSearch->GetClass(), false);
if ($bSearchPane)
{
if ($bSearchPane) {
$aParams = array_merge(array('open' => $bSearchOpen, 'table_id' => $sUsageId), $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("<p class=\"page-header\">$sIcon ".utils::HtmlEntities(Dict::S($sTitle))."</p>");
$oPage->add("<div class='sf_results_area'>");
$oTitle = TitleFactory::MakeForPage($sTitle);
$oPage->AddUiBlock($oTitle);
$aParams = array_merge(array('table_id' => $sUsageId), $aExtraParams);
$oBlock = new DisplayBlock($oSearch, 'list', false /* Asynchronous */, $aParams);
$oBlock->Display($oPage, $sUsageId);
if ($bEnableBreadcrumb && ($oPage instanceof iTopWebPage))
{
$oPage->add("</div>");
if ($bEnableBreadcrumb && ($oPage instanceof iTopWebPage)) {
// Breadcrumb
//$iCount = $oBlock->GetDisplayedCount();
$sPageId = "ui-search-".$oSearch->GetClass();
$sLabel = MetaModel::GetName($oSearch->GetClass());
$oPage->SetBreadCrumbEntry($sPageId, $sLabel, $sTitle, '', '../images/breadcrumb-search.png');
$oPage->SetBreadCrumbEntry($sPageId, $sLabel, $sTitle, '', 'fas fa-list', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
}
}
public function HasCount()
{
return true;
}
public function GetEntriesCount()
{
// Count the entries up to 99
$oSet = new DBObjectSet(DBSearch::FromOQL($this->sOQL));
$iCount = $oSet->CountWithLimit(99);
if ($iCount > 99) {
$iCount = "99+";
}
return $iCount;
}
}
/**
@@ -981,7 +1210,7 @@ class SearchMenuNode extends MenuNode
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
$oPage->SetBreadCrumbEntry("menu-".$this->sMenuId, $this->GetTitle(), '', '', utils::GetAbsoluteUrlAppRoot().'images/breadcrumb-search.png');
$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);
@@ -1191,7 +1420,7 @@ class DashboardMenuNode extends MenuNode
if ($oDashboard != null)
{
$sDivId = utils::Sanitize($this->sMenuId, '', 'element_identifier');
$oPage->add('<div class="dashboard_contents" id="'.$sDivId.'">');
$oPage->add('<div class="ibo-dashboard" id="'.$sDivId.'">');
$aExtraParams['dashboard_div_id'] = $sDivId;
$oDashboard->SetReloadURL($this->GetHyperlink($aExtraParams));
$oDashboard->Render($oPage, false, $aExtraParams);
@@ -1218,13 +1447,13 @@ class DashboardMenuNode extends MenuNode
}
if ($this->sMenuId == ApplicationMenu::GetDefaultMenuId())
{
$sIcon = '../images/breadcrumb_home.png';
$sIcon = 'fas fa-home';
}
else
{
$sIcon = '../images/breadcrumb-dashboard.png';
$sIcon = 'fas fa-chart-pie';
}
$oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon);
$oPage->SetBreadCrumbEntry("ui-dashboard-".$this->sMenuId, $this->GetTitle(), $sDescription, '', $sIcon, iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
}
}
else

View File

@@ -1,262 +1,7 @@
<?php
/**
* Copyright (C) 2013-2020 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
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/NiceWebPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT."/application/webpage.class.inc.php");
/**
* Web page with some associated CSS and scripts (jquery) for a fancier display
*/
class NiceWebPage extends WebPage
{
var $m_aReadyScripts;
var $m_sRootUrl;
public function __construct($s_title, $bPrintable = false)
{
parent::__construct($s_title, $bPrintable);
$this->m_aReadyScripts = array();
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.min.js');
if(utils::IsDevelopmentEnvironment()) // Needed since many other plugins still rely on oldies like $.browser
{
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.dev.js');
}
else
{
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.prod.min.js');
}
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-1.11.4.custom.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/hovertip.js');
// table sorting
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablesorter.pager.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.tablehover.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/table-selectable-lines.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_sorter.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/datatable.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/searchformforeignkeys.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/latinise/latinise.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_handler.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_handler_history.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_raw.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_string.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_field.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_numeric.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_enum.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_tag_set.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_key.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_hierarchical_key.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_abstract.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_time.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/clipboard.min.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/clipboardwidget.js');
$this->add_dict_entries('UI:Combo');
$this->add_ready_script(
<<< EOF
//add new widget called TruncatedList to properly display truncated lists when they are sorted
$.tablesorter.addWidget({
// give the widget a id
id: "truncatedList",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Check if there is a "truncated" line
this.truncatedList = false;
if ($("tr td.truncated",table).length > 0)
{
this.truncatedList = true;
}
if (this.truncatedList)
{
$("tr td",table).removeClass('truncated');
$("tr:last td",table).addClass('truncated');
}
}
});
$.tablesorter.addWidget({
// give the widget a id
id: "myZebra",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc..
$("tbody tr:even",table).addClass('even');
$("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even');
$("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even');
$("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even');
// In case we sort again the table, we need to remove the added 'even' classes on odd rows
$("tbody tr:odd",table).removeClass('even');
$("tbody tr.red_even:odd",table).removeClass('even').removeClass('red_even').addClass('red');
$("tbody tr.orange_even:odd",table).removeClass('even').removeClass('orange_even').addClass('orange');
$("tbody tr.green_even:odd",table).removeClass('even').removeClass('green_even').addClass('green');
}
});
$("table.listResults").tableHover(); // hover tables
EOF
);
$this->LoadTheme();
$this->m_sRootUrl = $this->GetAbsoluteUrlAppRoot();
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
$sAbsURLModulesRoot = addslashes($this->GetAbsoluteUrlModulesRoot());
$sEnvironment = addslashes(utils::GetCurrentEnvironment());
$sAppContext = addslashes($this->GetApplicationContext());
$this->add_script(
<<<EOF
function GetAbsoluteUrlAppRoot()
{
return '$sAbsURLAppRoot';
}
function GetAbsoluteUrlModulesRoot()
{
return '$sAbsURLModulesRoot';
}
function GetAbsoluteUrlModulePage(sModule, sPage, aArguments)
{
// aArguments is optional, it default to an empty hash
aArguments = typeof aArguments !== 'undefined' ? aArguments : {};
var sUrl = '$sAbsURLAppRoot'+'pages/exec.php?exec_module='+sModule+'&exec_page='+sPage+'&exec_env='+'$sEnvironment';
for (var sArgName in aArguments)
{
if (aArguments.hasOwnProperty(sArgName))
{
sUrl = sUrl + '&'+sArgName+'='+aArguments[sArgname];
}
}
return sUrl;
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
if (sContext.length > 0)
{
if (sURL.indexOf('?') == -1)
{
return sURL+'?'+sContext;
}
return sURL+'&'+sContext;
}
return sURL;
}
EOF
);
}
public function SetRootUrl($sRootUrl)
{
$this->m_sRootUrl = $sRootUrl;
}
public function small_p($sText)
{
$this->add("<p style=\"font-size:smaller\">$sText</p>\n");
}
public function GetAbsoluteUrlAppRoot()
{
return utils::GetAbsoluteUrlAppRoot();
}
public function GetAbsoluteUrlModulesRoot()
{
return utils::GetAbsoluteUrlModulesRoot();
}
function GetApplicationContext()
{
$oAppContext = new ApplicationContext();
return $oAppContext->GetForLink();
}
// By Rom, used by CSVImport and Advanced search
public function MakeClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null)
{
// $aTopLevelClasses = array('bizService', 'bizContact', 'logInfra', 'bizDocument');
// These are classes wich root class is cmdbAbstractObject !
$this->add("<select id=\"select_$sName\" name=\"$sName\">");
$aValidClasses = array();
foreach(MetaModel::GetClasses('bizmodel') as $sClassName)
{
if (is_null($iActionCode) || UserRights::IsActionAllowed($sClassName, $iActionCode))
{
$sSelected = ($sClassName == $sDefaultValue) ? " SELECTED" : "";
$sDescription = MetaModel::GetClassDescription($sClassName);
$sDisplayName = MetaModel::GetName($sClassName);
$aValidClasses[$sDisplayName] = "<option style=\"width: ".$iWidthPx." px;\" title=\"$sDescription\" value=\"$sClassName\"$sSelected>$sDisplayName</option>";
}
}
ksort($aValidClasses);
$this->add(implode("\n", $aValidClasses));
$this->add("</select>");
}
// By Rom, used by Advanced search
public function add_select($aChoices, $sName, $sDefaultValue, $iWidthPx)
{
$this->add("<select id=\"select_$sName\" name=\"$sName\">");
foreach($aChoices as $sKey => $sValue)
{
$sSelected = ($sKey == $sDefaultValue) ? " SELECTED" : "";
$this->add("<option style=\"width: ".$iWidthPx." px;\" value=\"".htmlspecialchars($sKey)."\"$sSelected>".htmlentities($sValue,
ENT_QUOTES, self::PAGES_CHARSET)."</option>");
}
$this->add("</select>");
}
public function add_ready_script($sScript)
{
$this->m_aReadyScripts[] = $sScript;
}
/**
* Outputs (via some echo) the complete HTML page by assembling all its elements
*/
public function output()
{
//$this->set_base($this->m_sRootUrl.'pages/');
if (count($this->m_aReadyScripts)>0)
{
$this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});");
}
parent::output();
}
/**
* @throws \Exception
* @since 2.7.0
*/
protected function LoadTheme()
{
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
$this->add_linked_stylesheet($sCssThemeUrl);
}
}

View File

@@ -1,248 +1,7 @@
<?php
/**
* Copyright (C) 2013-2019 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
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/PDFPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT.'application/utils.inc.php');
/**
* Custom class derived from TCPDF for providing custom headers and footers
*
* @author denis
*
*/
class iTopPDF extends TCPDF
{
protected $sDocumentTitle;
/**
* Shortcut for {@link TCPDF::SetFont}, to use the font configured
*
* @param string $style
* @param int $size
* @param string $fontfile
* @param string $subset
* @param bool $out
*
* @uses \TCPDF::SetFont()
* @uses \iTopPDF::GetPdfFont()
* @since 2.7.0
*/
public function SetFontParams($style, $size, $fontfile='', $subset='default', $out=true)
{
$siTopFont = self::GetPdfFont();
$this->SetFont($siTopFont, $style, $size, $fontfile, $subset, $out);
}
public function SetDocumentTitle($sDocumentTitle)
{
$this->sDocumentTitle = $sDocumentTitle;
}
/**
* Builds the custom header. Called for each new page.
*
* @see TCPDF::Header()
*/
public function Header()
{
// Title
// Set font
$this->SetFontParams('B', 10);
$iPageNumberWidth = 25;
$aMargins = $this->getMargins();
// Display the title (centered)
$this->SetXY($aMargins['left'] + $iPageNumberWidth, 0);
$this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - 2 * $iPageNumberWidth, 15, $this->sDocumentTitle,
0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
$this->SetFontParams('', 10);
// Display the page number (right aligned)
// Warning: the 'R'ight alignment does not work when using placeholders like $this->getAliasNumPage() or $this->getAliasNbPages()
$this->MultiCell($iPageNumberWidth, 15, Dict::Format('Core:BulkExport:PDF:PageNumber', $this->page), 0, 'R', false, 0 /* $ln */, '',
'', true, 0, false, true, 15, 'M' /* $valign */);
// Branding logo
$sBrandingIcon = APPROOT.'images/itop-logo.png';
if (file_exists(MODULESROOT.'branding/main-logo.png'))
{
$sBrandingIcon = MODULESROOT.'branding/main-logo.png';
}
$this->Image($sBrandingIcon, $aMargins['left'], 5, 0, 10);
}
// Page footer
public function Footer()
{
// No footer
}
/**
* dejavusans is a UTF-8 Unicode font. Standard PDF fonts like helvetica or times new roman are NOT UTF-8
* @return string font in the config file (export_pdf_font)
*/
public static function GetPdfFont()
{
$oConfig = utils::GetConfig();
$sPdfFont = $oConfig->Get('export_pdf_font');
return $sPdfFont;
}
}
/**
* Special class of WebPage for printing into a PDF document
*/
class PDFPage extends WebPage
{
/** @var \iTopPDF Instance of the TCPDF object for creating the PDF */
protected $oPdf;
public function __construct($s_title, $sPageFormat = 'A4', $sPageOrientation = 'L')
{
parent::__construct($s_title);
define(K_PATH_FONTS, APPROOT.'lib/combodo/tcpdf/fonts');
$this->oPdf = new iTopPDF($sPageOrientation, 'mm', $sPageFormat, true, self::PAGES_CHARSET, false);
// set document information
$this->oPdf->SetCreator(PDF_CREATOR);
$this->oPdf->SetAuthor('iTop');
$this->oPdf->SetTitle($s_title);
$this->oPdf->SetDocumentTitle($s_title);
$this->oPdf->setFontSubsetting(true);
// dejavusans is a UTF-8 Unicode font. Standard PDF fonts like helvetica or times new roman are NOT UTF-8
$this->oPdf->SetFontParams('', 10, '', true);
// set auto page breaks
$this->oPdf->SetAutoPageBreak(true, 15); // 15 mm break margin at the bottom
$this->oPdf->SetTopMargin(15);
// Add a page, we're ready to start
$this->oPdf->AddPage();
$this->SetContentDisposition('inline', $s_title.'.pdf');
$this->SetDefaultStyle();
}
/**
* Sets a default style (suitable for printing) to be included each time $this->oPdf->writeHTML() is called
*/
protected function SetDefaultStyle()
{
$this->add_style(
<<<EOF
table {
padding: 2pt;
}
table.listResults td {
border: 0.5pt solid #000 ;
}
table.listResults th {
background-color: #eee;
border: 0.5pt solid #000 ;
}
a {
text-decoration: none;
color: #000;
}
table.section td {
vertical-align: middle;
font-size: 10pt;
background-color:#eee;
}
td.icon {
width: 30px;
}
EOF
);
}
/**
* Get access to the underlying TCPDF object
*
* @return \iTopPDF
*/
public function get_tcpdf()
{
$this->flush();
return $this->oPdf;
}
/**
* Writes the currently buffered HTML content into the PDF. This can be useful:
* - to sync the flow in case you want to access the underlying TCPDF object for some specific/graphic output
* - to process the HTML by smaller chunks instead of processing the whole page at once for performance reasons
*/
public function flush()
{
if (strlen($this->s_content) > 0)
{
$sHtml = '';
if (count($this->a_styles) > 0)
{
$sHtml .= "<style>\n".implode("\n", $this->a_styles)."\n</style>\n";
}
$sHtml .= $this->s_content;
$this->oPdf->writeHTML($sHtml); // The style(s) must be supplied each time we call writeHtml
$this->s_content = '';
}
}
/**
* Whether or not the page is a PDF page
*
* @return boolean
*/
public function is_pdf()
{
return true;
}
/**
* Generates the PDF document and returns the PDF content as a string
*
* @return string
* @see WebPage::output()
*/
public function output()
{
$this->add_header('Content-type: application/x-pdf');
if (!empty($this->sContentDisposition))
{
$this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"');
}
foreach ($this->a_headers as $s_header)
{
header($s_header);
}
$this->flush();
echo $this->oPdf->Output($this->s_title.'.pdf', 'S');
}
public function get_pdf()
{
$this->flush();
return $this->oPdf->Output($this->s_title.'.pdf', 'S');
}
}

View File

@@ -40,7 +40,6 @@ abstract class Query extends cmdbAbstractObject
"db_table" => "priv_query",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -72,7 +71,6 @@ class QueryOQL extends Query
"db_table" => "priv_query_oql",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();

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\UI\Component\DataTable\DataTableSettings;
/**
@@ -39,7 +40,6 @@ abstract class Shortcut extends DBObject implements iDisplay
"db_table" => "priv_shortcut",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -161,7 +161,6 @@ class ShortcutOQL extends Shortcut
"db_table" => "priv_shortcut_oql",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();

View File

@@ -20,6 +20,8 @@
require_once(APPROOT.'/application/displayblock.class.inc.php');
/**
* This class manages the special template format used internally to build the iTop web pages
*
* @deprecated Since 3.0.0
*/
class DisplayTemplate
{

View File

@@ -25,6 +25,15 @@
*/
class ThemeHandler
{
const IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg'];
private static $oCompileCSSService;
public static function GetAppRootWithSlashes()
{
return str_replace('\\', '/', APPROOT);
}
/**
* Return default theme name and parameters
*
@@ -33,19 +42,19 @@ class ThemeHandler
*/
public static function GetDefaultThemeInformation()
{
return array(
return [
'name' => 'light-grey',
'parameters' => array(
'variables' => array(),
'imports' => array(
'parameters' => [
'variables' => [],
'imports' => [
'css-variables' => '../css/css-variables.scss',
),
'stylesheets' => array(
],
'stylesheets' => [
'jqueryui' => '../css/ui-lightness/jqueryui.scss',
'main' => '../css/light-grey.scss',
),
),
);
],
],
];
}
/**
@@ -112,37 +121,49 @@ class ThemeHandler
SetupUtils::builddir($sDefaultThemeDirPath);
}
static::CompileTheme($sThemeId, $aDefaultTheme['parameters']);
static::CompileTheme($sThemeId, false, "", $aDefaultTheme['parameters']);
}
// Return absolute url to theme compiled css
return utils::GetAbsoluteUrlModulesRoot().'/branding/themes/'.$sThemeId.'/main.css';
return utils::GetAbsoluteUrlModulesRoot().'branding/themes/'.$sThemeId.'/main.css';
}
/**
* Compile the $sThemeId theme
* Compile the $sThemeId theme, the actual compilation is skipped when either
* 1) The produced CSS file exists and is more recent than any of its components (imports, stylesheets)
* 2) The produced CSS file exists and its signature is equal to the expected signature (imports, stylesheets, variables)
*
* @param string $sThemeId
* @param boolean $bSetup
* @param string $sSetupCompilationTimestamp : setup compilation timestamp in micro secunds
* @param array|null $aThemeParameters Parameters (variables, imports, stylesheets) for the theme, if not passed, will be retrieved from compiled DM
* @param array|null $aImportsPaths Paths where imports can be found. Must end with '/'
* @param string|null $sWorkingPath Path of the folder used during compilation. Must end with a '/'
*
* @throws \CoreException
* @return boolean: indicate whether theme compilation occured
*/
public static function CompileTheme($sThemeId, $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null)
public static function CompileTheme($sThemeId, $bSetup=false, $sSetupCompilationTimestamp="", $aThemeParameters = null, $aImportsPaths = null, $sWorkingPath = null)
{
if ($sSetupCompilationTimestamp==="")
{
$sSetupCompilationTimestamp = microtime(true);
}
$sSetupCompilationTimestampInSecunds = (strpos($sSetupCompilationTimestamp, '.') !==false) ? explode('.', $sSetupCompilationTimestamp)[0] : $sSetupCompilationTimestamp;
$sEnv = APPROOT.'env-'.utils::GetCurrentEnvironment().'/';
// Default working path
if($sWorkingPath === null)
{
$sWorkingPath = APPROOT.'env-'.utils::GetCurrentEnvironment().'/';
$sWorkingPath = $sEnv;
}
// Default import paths (env-*)
if($aImportsPaths === null)
{
$aImportsPaths = array(
APPROOT.'env-'.utils::GetCurrentEnvironment().'/',
);
$aImportsPaths = [ $sEnv];
}
// Note: We do NOT check that the folder exists!
@@ -152,6 +173,11 @@ class ThemeHandler
// Save parameters if passed... (typically during DM compilation)
if(is_array($aThemeParameters))
{
if (!is_dir($sThemeFolderPath))
{
mkdir($sWorkingPath.'/branding/');
mkdir($sWorkingPath.'/branding/themes/');
}
file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters));
}
// ... Otherwise, retrieve them from compiled DM (typically when switching current theme in the config. file)
@@ -164,31 +190,588 @@ class ThemeHandler
}
}
$aThemeParametersWithVersion = self::CloneThemeParameterAndIncludeVersion($aThemeParameters, $sSetupCompilationTimestampInSecunds);
$sTmpThemeScssContent = '';
$iStyleLastModified = 0;
clearstatcache();
// Loading files to import and stylesheet to compile, also getting most recent modification time on overall files
$aStylesheetFiles = [];
foreach ($aThemeParameters['imports'] as $sImport)
{
$sTmpThemeScssContent .= '@import "'.$sImport.'";'."\n";
$iImportLastModified = @filemtime($sWorkingPath.$sImport);
$sFile = static::FindStylesheetFile($sImport, $aImportsPaths);
$iImportLastModified = @filemtime($sFile);
$aStylesheetFiles[] = $sFile;
$iStyleLastModified = $iStyleLastModified < $iImportLastModified ? $iImportLastModified : $iStyleLastModified;
}
foreach ($aThemeParameters['stylesheets'] as $sStylesheet)
{
$sTmpThemeScssContent .= '@import "'.$sStylesheet.'";'."\n";
$iStylesheetLastModified = @filemtime($sWorkingPath.$sStylesheet);
$sFile = static::FindStylesheetFile($sStylesheet, $aImportsPaths);
$iStylesheetLastModified = @filemtime($sFile);
$aStylesheetFiles[] = $sFile;
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
}
// Checking if our compiled css is outdated
if (!file_exists($sThemeCssPath) || (is_writable($sThemeFolderPath) && (@filemtime($sThemeCssPath) < $iStyleLastModified)))
$aIncludedImages=static::GetIncludedImages($aThemeParametersWithVersion, $aStylesheetFiles, $sThemeId);
foreach ($aIncludedImages as $sImage)
{
$sTmpThemeCssContent = utils::CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths, $aThemeParameters['variables']);
file_put_contents($sThemeCssPath, $sTmpThemeCssContent);
if (is_file($sImage))
{
$iStylesheetLastModified = @filemtime($sImage);
$iStyleLastModified = $iStyleLastModified < $iStylesheetLastModified ? $iStylesheetLastModified : $iStyleLastModified;
}
}
// Checking if our compiled css is outdated
$iFilemetime = @filemtime($sThemeCssPath);
$bFileExists = file_exists($sThemeCssPath);
$bVarSignatureChanged=false;
if ($bFileExists && $bSetup)
{
$sPrecompiledSignature = static::GetSignature($sThemeCssPath);
//check variable signature has changed which is independant from any file modification
if (!empty($sPrecompiledSignature)){
$sPreviousVariableSignature = static::GetVarSignature($sPrecompiledSignature);
$sCurrentVariableSignature = md5(json_encode($aThemeParameters['variables']));
$bVarSignatureChanged= ($sPreviousVariableSignature!==$sCurrentVariableSignature);
}
}
if (!$bFileExists || $bVarSignatureChanged || (is_writable($sThemeFolderPath) && ($iFilemetime < $iStyleLastModified)))
{
// Dates don't match. Second chance: check if the already compiled stylesheet exists and is consistent based on its signature
$sActualSignature = static::ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages);
if ($bFileExists && !$bSetup)
{
$sPrecompiledSignature = static::GetSignature($sThemeCssPath);
}
if (!empty($sPrecompiledSignature) && $sActualSignature == $sPrecompiledSignature)
{
touch($sThemeCssPath); // Stylesheet is up to date, mark it as more recent to speedup next time
}
else
{
// Alas, we really need to recompile
// Add the signature to the generated CSS file so that the file can be used as a precompiled stylesheet if needed
$sSignatureComment =
<<<CSS
/*
=== SIGNATURE BEGIN ===
$sActualSignature
=== SIGNATURE END ===
*/
CSS;
if (!static::$oCompileCSSService)
{
static::$oCompileCSSService = new CompileCSSService();
}
//store it again to change $version with latest compiled time
$sTmpThemeCssContent = static::$oCompileCSSService->CompileCSSFromSASS($sTmpThemeScssContent, $aImportsPaths,
$aThemeParametersWithVersion);
file_put_contents($sThemeFolderPath.'/theme-parameters.json', json_encode($aThemeParameters));
file_put_contents($sThemeCssPath, $sSignatureComment.$sTmpThemeCssContent);
SetupLog::Info("Theme $sThemeId file compiled.");
return true;
}
}
return false;
}
/**
* Compute the signature of a theme defined by its theme parameters. The signature is a JSON structure of
* 1) one MD5 of all the variables/values (JSON encoded)
* 2) the MD5 of each stylesheet file
* 3) the MD5 of each import file
* 3) the MD5 of each images referenced in style sheets
*
* @param string[] $aThemeParameters
* @param string[] $aImportsPaths
* @param string[] $aIncludedImages
*
* @return string
* @throws \Exception
*/
public static function ComputeSignature($aThemeParameters, $aImportsPaths, $aIncludedImages)
{
$aSignature = [
'variables' => md5(json_encode($aThemeParameters['variables'])),
'stylesheets' => [],
'imports' => [],
'images' => []
];
foreach ($aThemeParameters['imports'] as $key => $sImport)
{
$sFile = static::FindStylesheetFile($sImport, $aImportsPaths);
$aSignature['stylesheets'][$key] = md5_file($sFile);
}
foreach ($aThemeParameters['stylesheets'] as $key => $sStylesheet)
{
$sFile = static::FindStylesheetFile($sStylesheet, $aImportsPaths);
$aSignature['stylesheets'][$key] = md5_file($sFile);
}
foreach ($aIncludedImages as $sImage)
{
if (is_file($sImage)) {
$sUri = str_replace(self::GetAppRootWithSlashes(), '', $sImage);
$aSignature['images'][$sUri] = md5_file($sImage);
}
}
return json_encode($aSignature);
}
/**
* Search for images referenced in stylesheet files
*
* @param array $aThemeParametersVariables
* @param array $aStylesheetFiles
* @param string $sThemeId : used only for logging purpose
*
* @return array complete path of the images, but with slashes as dir separator instead of DIRECTORY_SEPARATOR
* @since 3.0.0
*/
public static function GetIncludedImages($aThemeParametersVariables, $aStylesheetFiles, $sThemeId)
{
$sTargetThemeFolderPath = static::GetCompiledThemeFolderAbsolutePath($sThemeId);
$aCompleteUrls = [];
$aToCompleteUrls = [];
$aMissingVariables = [];
$aFoundVariables = ['version'=>''];
$aMap = [
'aCompleteUrls' => $aCompleteUrls,
'aToCompleteUrls' => $aToCompleteUrls,
'aMissingVariables' => $aMissingVariables,
'aFoundVariables' => $aFoundVariables,
];
foreach ($aStylesheetFiles as $sStylesheetFile)
{
$aRes = static::GetAllUrlFromScss($aThemeParametersVariables, $sStylesheetFile);
/** @var array $aVal */
foreach($aMap as $key => $aVal)
{
if (array_key_exists($key, $aMap))
{
$aMap[$key] = array_merge($aVal, $aRes[$key]);
}
}
}
$aMap = static::ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFiles);
$aImages = [];
foreach ($aMap ['aCompleteUrls'] as $sUri => $sUrl)
{
$sImg = $sUrl;
if (preg_match("/(.*)\?/", $sUrl, $aMatches))
{
$sImg=$aMatches[1];
}
if (static::HasImageExtension($sImg)
&& ! array_key_exists($sImg, $aImages))
{
$sFilePath = realpath($sImg);
if ($sFilePath !== false) {
$sFilePathWithSlashes = str_replace('\\', '/', $sFilePath);
$aImages[$sImg] = $sFilePathWithSlashes;
continue;
}
$sCanonicalPath = static::CanonicalizePath($sTargetThemeFolderPath.'/'.$sImg);
$sFilePath = realpath($sCanonicalPath);
if ($sFilePath !== false) {
$sFilePathWithSlashes = str_replace('\\', '/', $sFilePath);
$aImages[$sImg] = $sFilePathWithSlashes;
continue;
}
SetupLog::Warning("Cannot find $sCanonicalPath ($sImg) during SCSS $sThemeId precompilation");
}
}
return array_values($aImages);
}
/**
* Reduce path without using realpath (works only when file exists)
* @param $path
*
* @return string
*/
public static function CanonicalizePath($path)
{
$path = explode('/', str_replace('//','/', $path));
$stack = array();
foreach ($path as $seg) {
if ($seg == '..') {
// Ignore this segment, remove last segment from stack
array_pop($stack);
continue;
}
if ($seg == '.') {
// Ignore this segment
continue;
}
$stack[] = $seg;
}
return implode('/', $stack);
}
/**
* Complete url using provided variables. Example with $var=1: XX + $var => XX1
* @param $aMap
* @param $aThemeParametersVariables
* @param $aStylesheetFile
*
* @return mixed
*/
public static function ResolveUncompleteUrlsFromScss($aMap, $aThemeParametersVariables, $aStylesheetFile)
{
$sContent="";
foreach ($aStylesheetFile as $sStylesheetFile)
{
if (is_file($sStylesheetFile))
{
$sContent .= '\n' . file_get_contents($sStylesheetFile);
}
}
$aMissingVariables=$aMap['aMissingVariables'];
$aFoundVariables=$aMap['aFoundVariables'];
$aToCompleteUrls=$aMap['aToCompleteUrls'];
$aCompleteUrls=$aMap['aCompleteUrls'];
list($aMissingVariables, $aFoundVariables) = static::FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent, true);
list($aToCompleteUrls, $aCompleteUrls) = static::ResolveUrls($aFoundVariables, $aToCompleteUrls, $aCompleteUrls);
$aMap['aMissingVariables']=$aMissingVariables;
$aMap['aFoundVariables']=$aFoundVariables;
$aMap['aToCompleteUrls']=$aToCompleteUrls;
$aMap['aCompleteUrls']=$aCompleteUrls;
return $aMap;
}
/**
* Find missing variable values from SCSS content based on their name.
*
* @param $aThemeParametersVariables
* @param $aMissingVariables
* @param $aFoundVariables
* @param $sContent : scss content
* @param bool $bForceEmptyValueWhenNotFound
*
* @return array
*/
public static function FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent, $bForceEmptyValueWhenNotFound=false)
{
$aNewMissingVars = [];
if (!empty($aMissingVariables))
{
foreach ($aMissingVariables as $var)
{
if (array_key_exists($var, $aThemeParametersVariables))
{
$aFoundVariables[$var] = $aThemeParametersVariables[$var];
}
else
{
if (preg_match_all("/\\\$$var\s*:\s*[\"']{0,1}(.*)[\"']{0,1};/", $sContent, $aValues))
{
$sValue = $aValues[1][0];
if (preg_match_all("/([^!]+)!/", $sValue, $aSubValues))
{
$sValue = trim($aSubValues[1][0], ' "\'');
}
if (strpos($sValue, '$') === false)
{
$aFoundVariables[$var] = $sValue;
}
else{
$aNewMissingVars[] = $var;
}
}
else
{
if ($bForceEmptyValueWhenNotFound)
{
$aFoundVariables[$var] = '';
}
else
{
$aNewMissingVars[] = $var;
}
}
}
}
}
return [ $aNewMissingVars, $aFoundVariables ];
}
/**
* @param $aFoundVariables
* @param array $aToCompleteUrls
* @param array $aCompleteUrls
*
* @return array
*/
public static function ResolveUrls($aFoundVariables, array $aToCompleteUrls, array $aCompleteUrls)
{
if (!empty($aFoundVariables))
{
$aFoundVariablesWithEmptyValue = [];
foreach ($aFoundVariables as $aFoundVariable => $sValue)
{
$aFoundVariablesWithEmptyValue[$aFoundVariable] = '';
}
foreach ($aToCompleteUrls as $sUrlTemplate)
{
unset($aToCompleteUrls[$sUrlTemplate]);
$sResolvedUrl = static::ResolveUrl($sUrlTemplate, $aFoundVariables);
if ($sResolvedUrl == false)
{
$aToCompleteUrls[$sUrlTemplate] = $sUrlTemplate;
}
else
{
$sUri = static::ResolveUrl($sUrlTemplate, $aFoundVariablesWithEmptyValue);
$aExplodedUri = explode('?', $sUri);
if (empty($aExplodedUri))
{
$aCompleteUrls[$sUri] = $sResolvedUrl;
}
else
{
$aCompleteUrls[$aExplodedUri[0]] = $sResolvedUrl;
}
}
}
}
return [ $aToCompleteUrls, $aCompleteUrls];
}
/**
* Find all referenced URLs from a SCSS file.
* @param $aThemeParametersVariables
* @param $sStylesheetFile
*
* @return array
*/
public static function GetAllUrlFromScss($aThemeParametersVariables, $sStylesheetFile)
{
$aCompleteUrls = [];
$aToCompleteUrls = [];
$aMissingVariables = [];
$aFoundVariables = [];
if (is_file($sStylesheetFile))
{
$sContent = file_get_contents($sStylesheetFile);
if (preg_match_all("/url\s*\((.*)\)/", $sContent, $aMatches))
{
foreach ($aMatches[1] as $path)
{
if (!array_key_exists($path, $aCompleteUrls)
&& !array_key_exists($path, $aToCompleteUrls))
{
if (preg_match_all("/\\$([\w\-_]+)/", $path, $aCurrentVars))
{
/** @var string $aCurrentVars */
foreach ($aCurrentVars[1] as $var)
{
if (!array_key_exists($var, $aMissingVariables))
{
$aMissingVariables[$var] = $var;
}
}
$aToCompleteUrls[$path] = $path;
}
else
{
$aCompleteUrls[$path] = trim($path, "\"'");
}
}
}
}
if (!empty($aMissingVariables))
{
list($aMissingVariables, $aFoundVariables) = static::FindMissingVariables($aThemeParametersVariables, $aMissingVariables, $aFoundVariables, $sContent);
list($aToCompleteUrls, $aCompleteUrls) = static::ResolveUrls($aFoundVariables, $aToCompleteUrls, $aCompleteUrls);
}
}
return [
'aCompleteUrls' => $aCompleteUrls,
'aToCompleteUrls' => $aToCompleteUrls,
'aMissingVariables' => $aMissingVariables,
'aFoundVariables' => $aFoundVariables
];
}
/**
* Calculate url based on its template + variables.
* @param $sUrlTemplate
* @param $aFoundVariables
*
* @return bool|string
*/
public static function ResolveUrl($sUrlTemplate, $aFoundVariables)
{
$aPattern= [];
$aReplacement= [];
foreach ($aFoundVariables as $aFoundVariable => $aFoundVariableValue)
{
//XX + $key + YY
$aPattern[]="/['\"]\s*\+\s*\\\$" . $aFoundVariable . "[\s\+]+\s*['\"]/";
$aReplacement[]=$aFoundVariableValue;
//$key + YY
$aPattern[]="/\\\$" . $aFoundVariable. "[\s\+]+\s*['\"]/";
$aReplacement[]=$aFoundVariableValue;
//XX + $key
$aPattern[]="/['\"]\s*[\+\s]+\\\$" . $aFoundVariable . "$/";
$aReplacement[]=$aFoundVariableValue;
}
$sResolvedUrl=preg_replace($aPattern, $aReplacement, $sUrlTemplate);
if (strpos($sResolvedUrl, "+")!==false)
{
return false;
}
return trim($sResolvedUrl, "\"'");
}
/**
* indicate whether a string ends with image suffix.
* @param $path
*
* @return bool
*/
private static function HasImageExtension($path)
{
foreach (static::IMAGE_EXTENSIONS as $sExt)
{
if (endsWith($path, $sExt))
{
return true;
}
}
return false;
}
/**
* Extract the signature for a generated CSS file. The signature MUST be alone one line immediately
* followed (on the next line) by the === SIGNATURE END === pattern
*
* Note the signature can be place anywhere in the CSS file (obviously inside a CSS comment !) but the
* function will be faster if the signature is at the beginning of the file (since the file is scanned from the start)
*
* @param $sFilepath
*
* @return string
*/
public static function GetSignature($sFilepath)
{
$sPreviousLine = '';
$hFile = @fopen($sFilepath, "r");
if ($hFile !== false)
{
$sLine = '';
do
{
$sPreviousLine = $sLine;
$sLine = rtrim(fgets($hFile)); // Remove the trailing \n
}
while (($sLine !== false) && ($sLine != '=== SIGNATURE END ==='));
fclose($hFile);
}
return $sPreviousLine;
}
public static function GetVarSignature($JsonSignature)
{
$aJsonArray = json_decode($JsonSignature, true);
if (array_key_exists('variables', $aJsonArray))
{
return $aJsonArray['variables'];
}
return false;
}
/**
* Find the given file in the list of ImportsPaths directory
* @param string $sFile
* @param string[] $aImportsPaths
* @throws Exception
* @return string
*/
public static function FindStylesheetFile($sFile, $aImportsPaths)
{
foreach($aImportsPaths as $sPath)
{
$sImportedFile = realpath($sPath.'/'.$sFile);
if (file_exists($sImportedFile))
{
return $sImportedFile;
}
}
return ''; // Not found, fail silently, maybe the SCSS compiler knowns better...
}
public static function MockCompileCSSService($oCompileCSSServiceMock)
{
static::$oCompileCSSService = $oCompileCSSServiceMock;
}
/**
* Clone variable array and include $version with bSetupCompilationTimestamp value
* @param $aThemeParameters
* @param $bSetupCompilationTimestamp
*
* @return array
*/
public static function CloneThemeParameterAndIncludeVersion($aThemeParameters, $bSetupCompilationTimestamp)
{
$aThemeParametersVariable = [];
if (array_key_exists('variables', $aThemeParameters))
{
if (is_array($aThemeParameters['variables']))
{
$aThemeParametersVariable = array_merge([], $aThemeParameters['variables']);
}
}
$aThemeParametersVariable['$version'] = $bSetupCompilationTimestamp;
return $aThemeParametersVariable;
}
}
class CompileCSSService
{
/**
* CompileCSSService constructor.
*/
public function __construct()
{
}
public function CompileCSSFromSASS($sSassContent, $aImportPaths = [], $aVariables = []){
return utils::CompileCSSFromSASS($sSassContent, $aImportPaths, $aVariables);
}
}

View File

@@ -17,7 +17,6 @@
* You should have received a copy of the GNU Affero General Public License
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
/**
@@ -68,6 +67,7 @@ class UIExtKeyWidget
protected $iId;
protected $sTargetClass;
protected $sAttCode;
//@deprecated
protected $bSearchMode;
//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
@@ -86,7 +86,29 @@ class UIExtKeyWidget
$sDisplayStyle = 'select'; // In search mode, always use a drop-down list
}
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle);
if(!$bSearchMode)
{
switch($sDisplayStyle)
{
case 'radio':
case 'radio_horizontal':
case 'radio_vertical':
return $oWidget->DisplayRadio($oPage, $iMaxComboLength, $bAllowTargetCreation, $oAllowedValues, $value, $sFieldName, $sDisplayStyle);
break;
case 'select':
case 'list':
default:
return $oWidget->DisplaySelect($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value,
$bMandatory, $sFieldName, $sFormPrefix, $aArgs);
}
}
else
{
return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle);
}
}
public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false)
@@ -98,6 +120,291 @@ class UIExtKeyWidget
}
/**
* @since 3.0.0 N°2508 - Include Obsolescence icon within list and autocomplete
* Get the HTML fragment corresponding to the ext key editing widget
* @param WebPage $oP The web page used for all the output
* @param array $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function DisplaySelect(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array())
{
$sTitle = addslashes($sTitle);
$oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey ibo-input-wrapper ibo-input-select-wrapper\" data-validation=\"untouched\">";
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
if($this->bSearchMode)
{
$sWizHelper = 'null';
$sWizHelperJSON = "''";
$sJSSearchMode = 'true';
}
else
{
if (isset($aArgs['wizHelper']))
{
$sWizHelper = $aArgs['wizHelper'];
}
else
{
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
}
$sWizHelperJSON = $sWizHelper.'.UpdateWizardToJSON()';
$sJSSearchMode = 'false';
}
if (is_null($oAllowedValues))
{
throw new Exception('Implementation: null value for allowed values definition');
}
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->sTargetClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
if (!$oAllowedValues->CountExceeds($iMaxComboLength))
{
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
$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);
$oAllowedValues->Rewind();
$bAddingValue=false;
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($oAllowedValues->GetClass());
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField)>0)
{
$bAddingValue=true;
}
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') )
{
// When there is only once choice, select it by default
$sDisplayValue=$oObj->GetName();
if($value != $oObj->GetKey())
{
$oPage->add_ready_script(
<<<EOF
$('#$this->iId').attr('data-validate','dependencies');
EOF
);
}
}
else
{
if ((is_array($value) && in_array($oObj->GetKey(), $value)) || ($value == $oObj->GetKey()))
{
$sDisplayValue=$oObj->GetKey();
// $sHTMLValue.="<div class='item' data-value='".$oObj->GetKey)."'>".$oObj->GetName()."</div>";
}
}
if ($oObj->IsObsolete()){
$aOption['obsolescence_flag'] ="1";
}
if ($bAddingValue)
{
$aArguments = [];
foreach ($aAdditionalField as $sAdditionalField)
{
array_push($aArguments,$oObj->Get($sAdditionalField));
}
$aOption['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
}
array_push($aOptions,$aOption);
}
$sHTMLValue .= "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\"></select>";
$sJsonOptions=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','$sDisplayValue');
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
EOF
);
}
else
{
// Too many choices, use an autocomplete
// Check that the given value is allowed
$oSearch = $oAllowedValues->GetFilter();
$oSearch->AddCondition('id', $value);
$oSet = new DBObjectSet($oSearch);
if ($oSet->Count() == 0)
{
$value = null;
}
if (is_null($value) || ($value == 0)) // Null values are displayed as ''
{
$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : '';
}
else
{
$sDisplayValue = $this->GetObjectName($value);
}
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 2; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
// the input for the auto-complete
$sHTMLValue .= "<input class=\"field_autocomplete ibo-input ibo-input-select ibo-input-select-autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$sHTMLValue .= "<div class=\"field_input_btn ibo-input-select--action-button ibo-input-select--action-button--search\" id=\"mini_search_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.Search();\"><i class=\"fas fa-search\"></i></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";
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script(
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$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}.AddAutocomplete($iMinChars, $sWizHelperJSON);
if ($('#ac_dlg_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
}
EOF
);
}
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<div class=\"field_input_btn ibo-input-select--action-button ibo-input-select--action-button--hierarchy\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div>";
$oPage->add_ready_script(
<<<JS
if ($('#ac_tree_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
}
JS
);
}
if ($bCreate && $bExtensions)
{
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<div class=\"field_input_btn ibo-input-select--action-button ibo-input-select--action-button--create\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div>";
$oPage->add_ready_script(
<<<JS
if ($('#ajax_{$this->iId}').length == 0)
{
$('body').append('<div id="ajax_{$this->iId}"></div>');
}
JS
);
}
$sHTMLValue .= "</div>";
$sHTMLValue .= "<span class=\"form_validation ibo-field-validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
return $sHTMLValue;
}
/**
* @since 3.0.0 N°2508 - Include Obsolescence icon within list and autocomplete
* Get the HTML fragment corresponding to the ext key editing widget
* @param WebPage $oP The web page used for all the output
* @param array $aArgs Extra context arguments
* @return string The HTML fragment to be inserted into the page
*/
public function DisplayRadio(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, DBObjectset $oAllowedValues, $value, $sFieldName, $sDisplayStyle)
{
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sHTMLValue = "<div class=\"field_input_zone field_input_extkey\">";
if (is_null($oAllowedValues))
{
throw new Exception('Implementation: null value for allowed values definition');
}
$oAllowedValues->SetShowObsoleteData(utils::ShowObsoleteData());
// We just need to compare the number of entries with MaxComboLength, so no need to get the real count.
if (!$oAllowedValues->CountExceeds($iMaxComboLength))
{
// Discrete list of values, use a SELECT or RADIO buttons depending on the config
$sValidationField = null;
$bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false;
$oAllowedValues->Rewind();
$aAllowedValues = array();
while($oObj = $oAllowedValues->Fetch())
{
$aAllowedValues[$oObj->GetKey()] = $oObj->GetName();
}
$sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $this->iId, "{$sAttrFieldPrefix}{$sFieldName}", false /* $bMandatory will be placed manually */, $bVertical, $sValidationField);
$aEventsList[] ='change';
}
else
{
$sHTMLValue .= "unable to display. Too much values";
}
$sHTMLValue .= '<div class="ibo-input-select--action-buttons">';
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
$oPage->add_ready_script(
<<<JS
if ($('#ac_tree_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
}
JS
);
}
if ($bCreate && $bExtensions)
{
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
$oPage->add_ready_script(
<<<JS
if ($('#ajax_{$this->iId}').length == 0)
{
$('body').append('<div id="ajax_{$this->iId}"></div>');
}
JS
);
}
$sHTMLValue .= "</div>";
$sHTMLValue .= "</div>";
// Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
//if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
//{
$sHTMLValue .= "<span class=\"form_validation ibo-field-validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
//}
return $sHTMLValue;
}
/**
* @deprecated Use DisplayBob
* Get the HTML fragment corresponding to the ext key editing widget
* @param WebPage $oP The web page used for all the output
* @param array $aArgs Extra context arguments
@@ -239,7 +546,7 @@ EOF
$oPage->add_ready_script("$('.multiselect').multiselect($sJSOptions);");
}
$oPage->add_ready_script(
<<<EOF
<<<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>";
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
@@ -269,10 +576,10 @@ EOF
{
$sDisplayValue = $this->GetObjectName($value);
}
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 3; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
$iMinChars = isset($aArgs['iMinChars']) ? $aArgs['iMinChars'] : 2; //@@@ $this->oAttDef->GetMinAutoCompleteChars();
// the input for the auto-complete
$sHTMLValue .= "<input class=\"field_autocomplete\" type=\"text\" id=\"label_$this->iId\" value=\"$sDisplayValue\"/>";
$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>";
// another hidden input to store & pass the object's Id
@@ -281,106 +588,100 @@ EOF
$JSSearchMode = $this->bSearchMode ? 'true' : 'false';
// Scripts to start the autocomplete and bind some events to it
$oPage->add_ready_script(
<<<EOF
<<<EOF
oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode, $sJSDoSearch);
oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
oACWidget_{$this->iId}.AddAutocomplete($iMinChars, $sWizHelperJSON);
if ($('#ac_dlg_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_dlg_{$this->iId}"></div>');
}
EOF
);
);
}
if ($bExtensions && MetaModel::IsHierarchicalClass($this->sTargetClass) !== false)
{
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"ibo-input-select--action-button\" id=\"mini_tree_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.HKDisplay();\"><i class=\"fas fa-sitemap\"></i></div></span>";
$oPage->add_ready_script(
<<<EOF
<<<JS
if ($('#ac_tree_{$this->iId}').length == 0)
{
$('body').append('<div id="ac_tree_{$this->iId}"></div>');
}
EOF
JS
);
}
if ($bCreate && $bExtensions)
{
$sCallbackName = (MetaModel::IsAbstract($this->sTargetClass)) ? 'SelectObjectClass' : 'CreateObject';
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"mini_button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
$sHTMLValue .= "<span class=\"field_input_btn\"><div class=\"ibo-input-select--action-button\" id=\"mini_add_{$this->iId}\" onClick=\"oACWidget_{$this->iId}.{$sCallbackName}();\"><i class=\"fas fa-plus\"></i></div></span>";
$oPage->add_ready_script(
<<<EOF
<<<JS
if ($('#ajax_{$this->iId}').length == 0)
{
$('body').append('<div id="ajax_{$this->iId}"></div>');
}
EOF
JS
);
}
$sHTMLValue .= "</div>";
// Note: This test is no longer necessary as we changed the markup to extract validation decoration in the standard .field_input_xxx container
//if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
//{
$sHTMLValue .= "<span class=\"form_validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
//}
$sHTMLValue .= "<span class=\"form_validation ibo-field-validation\" id=\"v_{$this->iId}\"></span><span class=\"field_status\" id=\"fstatus_{$this->iId}\"></span>";
return $sHTMLValue;
}
public function GetSearchDialog(WebPage $oPage, $sTitle, $oCurrObject = null)
{
$sHTML = '<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">';
$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div id="dc_'.$this->iId.'">');
if ( ($oCurrObject != null) && ($this->sAttCode != ''))
{
if (($oCurrObject != null) && ($this->sAttCode != '')) {
$oAttDef = MetaModel::GetAttributeDef(get_class($oCurrObject), $this->sAttCode);
/** @var \DBObject $oCurrObject */
$aArgs = $oCurrObject->ToArgsForQuery();
$aParams = array('query_params' => $aArgs);
$oSet = $oAttDef->GetAllowedValuesAsObjectSet($aArgs);
$oFilter = $oSet->GetFilter();
}
else
{
} else {
$aParams = array();
$oFilter = new DBObjectSearch($this->sTargetClass);
}
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$sHTML .= $oBlock->GetDisplay($oPage, $this->iId,
array(
'menu' => false,
'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}",
'table_inner_id' => "{$this->iId}_results",
'selection_mode' => true,
'selection_type' => 'single',
'cssCount' => '#count_'.$this->iId)
);
$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
$sHTML .= "<div id=\"dr_{$this->iId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHTML .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHTML .= "</div>\n";
$sHTML .= "<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\">&nbsp;&nbsp;";
$sHTML .= "<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">";
$sHTML .= "<input type=\"hidden\" id=\"count_{$this->iId}\" value=\"0\">";
$sHTML .= "</form>\n";
$sHTML .= '</div></div>';
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, $this->iId,
array(
'menu' => false,
'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}",
'table_inner_id' => "{$this->iId}_results",
'selection_mode' => true,
'selection_type' => 'single',
'cssCount' => '#count_'.$this->iId
)
));
$sCancel = Dict::S('UI:Button:Cancel');
$sOK = Dict::S('UI:Button:Ok');
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$oPage->add(<<<HTML
<form id="fr_{$this->iId}" OnSubmit="return oACWidget_{$this->iId}.DoOk();">
<div id="dr_{$this->iId}" style="vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;">
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div>
<input type="button" id="btn_cancel_{$this->iId}" value="{$sCancel}" onClick="$('#ac_dlg_{$this->iId}').dialog('close');">&nbsp;&nbsp;
<input type="button" id="btn_ok_{$this->iId}_results" value="{$sOK}" onClick="oACWidget_{$this->iId}.DoOk();">
<input type="hidden" id="count_{$this->iId}_results" value="0">
</form>
</div></div>
HTML
);
$sDialogTitle = addslashes($sTitle);
$oPage->add_ready_script(
<<<EOF
$oPage->add_ready_script(<<<JS
$('#ac_dlg_{$this->iId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, title: '$sDialogTitle', resizeStop: oACWidget_{$this->iId}.UpdateSizes, close: oACWidget_{$this->iId}.OnClose });
$('#fs_{$this->iId}').bind('submit.uiAutocomplete', oACWidget_{$this->iId}.DoSearchObjects);
$('#dc_{$this->iId}').resize(oACWidget_{$this->iId}.UpdateSizes);
EOF
);
$oPage->add($sHTML);
JS
);
}
/**
@@ -436,32 +737,53 @@ EOF
// Current extkey value, so we can display event if it is not available anymore (eg. archived).
$iCurrentExtKeyId = (is_null($oObj) || $this->sAttCode === '') ? 0 : $oObj->Get($this->sAttCode);
$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
$iMax = 150;
$oValuesSet->SetLimit($iMax);
$oValuesSet->SetSort(false);
$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oValuesSet->SetLimit($iMax);
$aValuesContains = $oValuesSet->GetValues(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'start_with');
asort($aValuesContains);
$aValues = array();
foreach($aValuesContains as $sKey => $sFriendlyName)
$aValues = $aValuesContains;
if (sizeof($aValues) < $iMax)
{
if (!isset($aValues[$sKey]))
$aValuesContains = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'contains');
asort($aValuesContains);
$iSize = sizeof($aValuesContains);
foreach ($aValuesContains as $sKey => $sFriendlyName)
{
$aValues[$sKey] = $sFriendlyName;
if (!isset($aValues[$sKey]))
{
$aValues[$sKey] = $sFriendlyName;
if (++$iSize >= $iMax)
{
break;
}
}
}
}
elseif (!in_array($sContains, $aValues))
{
$aValuesEquals = $oValuesSet->GetValuesForAutocomplete(array('this' => $oObj, 'current_extkey_id' => $iCurrentExtKeyId), $sContains, 'equals');
$aValues = array_merge($aValuesEquals, $aValues);
}
switch($sOutputFormat)
{
case static::ENUM_OUTPUT_FORMAT_JSON:
$aJsonMap = array();
foreach ($aValues as $sKey => $sLabel)
foreach ($aValues as $sKey => $aValue)
{
$aJsonMap[] = array('value' => $sKey, 'label' => $sLabel);
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']);
}
}
$oP->SetContentType('application/json');
@@ -469,9 +791,9 @@ EOF
break;
case static::ENUM_OUTPUT_FORMAT_CSV:
foreach($aValues as $sKey => $sFriendlyName)
foreach($aValues as $sKey => $aValue)
{
$oP->add(trim($sFriendlyName)."\t".$sKey."\n");
$oP->add(trim($aValue['label'])."\t".$sKey."\n");
}
break;
default:

View File

@@ -184,7 +184,7 @@ class UILinksWidgetDirect
// For security reasons: check that the "proposed" class is actually a subclass of the linked class
// and that the current user is allowed to create objects of this class
$sRealClass = '';
$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>');
//$oPage->add('<div class="wizContainer" style="vertical-align:top;"><div>');
$aSubClasses = MetaModel::EnumChildClasses($this->sLinkedClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$aPossibleClasses = array();
foreach($aSubClasses as $sCandidateClass)
@@ -294,20 +294,17 @@ class UILinksWidgetDirect
*/
public function GetObjectsSelectionDlg($oPage, $oCurrentObj, $aAlreadyLinked, $aPrefillFormParam = array())
{
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
//$oPage->add("<div class=\"wizContainer\" style=\"vertical-align:top;\">\n");
$oHiddenFilter = new DBObjectSearch($this->sLinkedClass);
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass))
{
if (($oCurrentObj != null) && MetaModel::IsSameFamilyBranch($this->sLinkedClass, $this->sClass)) {
// Prevent linking to self if the linked object is of the same family
// and already present in the database
if (!$oCurrentObj->IsNew())
{
if (!$oCurrentObj->IsNew()) {
$oHiddenFilter->AddCondition('id', $oCurrentObj->GetKey(), '!=');
}
}
if (count($aAlreadyLinked) > 0)
{
if (count($aAlreadyLinked) > 0) {
$oHiddenFilter->AddCondition('id', $aAlreadyLinked, 'NOTIN');
}
$oHiddenCriteria = $oHiddenFilter->GetCriteria();
@@ -319,18 +316,14 @@ class UILinksWidgetDirect
if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($this->sLinkedClass);
}
else
{
if (!$valuesDef instanceof ValueSetObjects)
{
} else {
if (!$valuesDef instanceof ValueSetObjects) {
throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').');
}
$oFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression());
}
if ($oCurrentObj != null)
{
if ($oCurrentObj != null) {
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$aArgs = array_merge($oCurrentObj->ToArgs('this'), $oFilter->GetInternalParams());
@@ -339,7 +332,7 @@ class UILinksWidgetDirect
$oCurrentObj->PrefillForm('search', $aPrefillFormParam);
}
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}",
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->sInputid}",
array(
'result_list_outer_selector' => "SearchResultsToAdd_{$this->sInputid}",
'table_id' => "add_{$this->sInputid}",
@@ -349,16 +342,21 @@ class UILinksWidgetDirect
'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sHiddenCriteria,
)
);
$sHtml .= "<form id=\"ObjectsAddForm_{$this->sInputid}\">\n";
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->sInputid}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHtml .= "</div>\n";
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->sInputid}\" value=\"0\"/>";
$sHtml .= "<button type=\"button\" class=\"cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;<button type=\"button\" class=\"ok\" disabled=\"disabled\">".Dict::S('UI:Button:Add')."</button>";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$oPage->add($sHtml);
));
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sCancel = Dict::S('UI:Button:Cancel');
$sAdd = Dict::S('UI:Button:Add');
$oPage->add(<<<HTML
<form id="ObjectsAddForm_{$this->sInputid}">
<div id="SearchResultsToAdd_{$this->sInputid}" style="vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;">
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div>
<input type="hidden" id="count_{$this->sInputid}" value="0"/>
<button type="button" class="cancel">{$sCancel}</button>&nbsp;&nbsp;<button type="button" class="ok" disabled="disabled">{$sAdd}</button>
</form>
HTML
);
}
/**

View File

@@ -24,7 +24,10 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'application/webpage.class.inc.php');
use Combodo\iTop\Application\UI\Component\DataTable\DataTableFactory;
use Combodo\iTop\Application\UI\Component\DataTable\StaticTable\FormTableRow\FormTableRow;
use Combodo\iTop\Renderer\BlockRenderer;
require_once(APPROOT.'application/displayblock.class.inc.php');
class UILinksWidget
@@ -39,6 +42,7 @@ class UILinksWidget
protected $m_sLinkedClass;
protected $m_sRemoteClass;
protected $m_bDuplicatesAllowed;
/** @var string[] list of editables attcodes */
protected $m_aEditableFields;
protected $m_aTableConfig;
@@ -46,7 +50,7 @@ class UILinksWidget
* UILinksWidget constructor.
*
* @param string $sClass
* @param string $sAttCode
* @param string $sAttCode AttributeLinkedSetIndirect attcode
* @param int $iInputId
* @param string $sNameSuffix
* @param bool $bDuplicatesAllowed
@@ -73,41 +77,36 @@ class UILinksWidget
/** @var AttributeExternalKey $oLinkingAttDef */
$oLinkingAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $this->m_sExtKeyToRemote);
$this->m_sRemoteClass = $oLinkingAttDef->GetTargetClass();
$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
$sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass);
$sDefaultState = MetaModel::GetDefaultState($this->m_sClass);
$this->m_aEditableFields = array();
$this->m_aTableConfig = array();
$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
$this->m_aTableConfig['form::checkbox'] = array(
'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">",
'description' => Dict::S('UI:SelectAllToggle+'),
);
foreach(MetaModel::FlattenZList(MetaModel::GetZListItems($this->m_sLinkedClass, 'list')) as $sAttCode)
$aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode);
foreach ($aLnkAttDefsToDisplay as $oLnkAttDef)
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sAttCode);
if ($sStateAttCode == $sAttCode)
{
// State attribute is always hidden from the UI
}
else if ($oAttDef->IsWritable() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $this->m_sExtKeyToRemote) && ($sAttCode != 'finalclass'))
{
$iFlags = MetaModel::GetAttributeFlags($this->m_sLinkedClass, $sDefaultState, $sAttCode);
if ( !($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY) )
{
$this->m_aEditableFields[] = $sAttCode;
$this->m_aTableConfig[$sAttCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
}
}
$sLnkAttCode = $oLnkAttDef->GetCode();
$this->m_aEditableFields[] = $sLnkAttCode;
$this->m_aTableConfig[$sLnkAttCode] = array('label' => $oLnkAttDef->GetLabel(), 'description' => $oLnkAttDef->GetDescription());
}
$this->m_aTableConfig['static::key'] = array( 'label' => MetaModel::GetName($this->m_sRemoteClass), 'description' => MetaModel::GetClassDescription($this->m_sRemoteClass));
foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
$this->m_aTableConfig['static::key'] = array(
'label' => MetaModel::GetName($this->m_sRemoteClass),
'description' => MetaModel::GetClassDescription($this->m_sRemoteClass),
);
$this->m_aEditableFields[] = $this->m_sExtKeyToRemote;
$aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass);
foreach ($aRemoteAttDefsToDisplay as $oRemoteAttDef)
{
// TO DO: check the state of the attribute: hidden or visible ?
if ($sFieldCode != 'finalclass')
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sRemoteClass, $sFieldCode);
$this->m_aTableConfig['static::'.$sFieldCode] = array( 'label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription());
}
$sRemoteAttCode = $oRemoteAttDef->GetCode();
$this->m_aTableConfig['static::'.$sRemoteAttCode] = array(
'label' => $oRemoteAttDef->GetLabel(),
'description' => $oRemoteAttDef->GetDescription(),
);
}
}
@@ -116,7 +115,7 @@ class UILinksWidget
*
* @param WebPage $oP Web page used for the ouput
* @param DBObject $oLinkedObj Remote object
* @param mixed $linkObjOrId Either the object linked or a unique number for new link records to add
* @param DBObject|int $linkObjOrId Either the lnk object or a unique number for new link records to add
* @param array $aArgs Extra context arguments
* @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to
* @param int $iUniqueId A unique identifier of new links
@@ -134,43 +133,39 @@ class UILinksWidget
$aRow = array();
$aFieldsMap = array();
$iKey = 0;
if(is_object($linkObjOrId) && (!$linkObjOrId->IsNew()))
if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew()))
{
$iKey = $linkObjOrId->GetKey();
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
$sPrefix .= "[$iKey][";
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$iKey}";
$aArgs['this'] = $linkObjOrId;
if($bReadOnly)
{
$aRow['form::checkbox'] = "";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
$aRow[$sFieldCode] = $sDisplayValue;
}
}
else
{
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$iKey\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$iKey\">";
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$linkObjOrId->GetKey().']';
$sSafeId = utils::GetSafeId($sFieldId);
$sValue = $linkObjOrId->Get($sFieldCode);
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId, $sNameSuffix, 0, $aArgs).
'</div></div></div>';
$aFieldsMap[$sFieldCode] = $sSafeId;
}
}
if ($bReadOnly)
{
$aRow['form::checkbox'] = "";
foreach ($this->m_aEditableFields as $sFieldCode)
{
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
$aRow[$sFieldCode] = $sDisplayValue;
}
}
else
{
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$iKey\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$iKey\">";
foreach ($this->m_aEditableFields as $sFieldCode)
{
$sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode);
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId);
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
}
}
$sState = $linkObjOrId->GetState();
$sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->m_sExtKeyToRemote);;
}
else
{
@@ -180,15 +175,18 @@ class UILinksWidget
// New link existing only in memory
$oNewLinkObj = $linkObjOrId;
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe,
$oCurrentObj); // Setting the extkey with the object also fills the related external fields
}
else
{
$iRemoteObjKey = $linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToRemote,
$oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe,
$oCurrentObj); // Setting the extkey with the object also fills the related external fields
}
$sPrefix .= "[-$iUniqueId][";
$sNameSuffix = "]"; // To make a tabular form
@@ -222,112 +220,150 @@ EOF
foreach($this->m_aEditableFields as $sFieldCode)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.-$iUniqueId.']';
$sSafeId = utils::GetSafeId($sFieldId);
$sSafeFieldId = $this->GetFieldId($iUniqueId, $sFieldCode);
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $oNewLinkObj, $oP, $sNameSuffix, $sSafeFieldId);
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
$sValue = $oNewLinkObj->Get($sFieldCode);
$sDisplayValue = $oNewLinkObj->GetEditValue($sFieldCode);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'.
cmdbAbstractObject::GetFormElementForField($oP, $this->m_sLinkedClass, $sFieldCode, $oAttDef, $sValue, $sDisplayValue, $sSafeId /* id */, $sNameSuffix, 0, $aArgs).
'</div></div></div>';
$aFieldsMap[$sFieldCode] = $sSafeId;
$oP->add_ready_script(<<<EOF
$oP->add_ready_script(
<<<JS
oWidget{$this->m_iInputId}.OnValueChange($iKey, $iUniqueId, '$sFieldCode', '$sValue');
EOF
);
JS
);
}
$sState = '';
$sRemoteKeySafeFieldId = $this->GetFieldId($iUniqueId, $this->m_sExtKeyToRemote);
}
if(!$bReadOnly)
{
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
}
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oP->add_script(
<<<EOF
var {$aArgs['wizHelper']} = new WizardHelper('{$this->m_sLinkedClass}', '', '$sState');
{$aArgs['wizHelper']}.SetFieldsMap($sJsonFieldsMap);
{$aArgs['wizHelper']}.SetFieldsCount($iFieldsCount);
EOF
);
$aRow['static::key'] = $oLinkedObj->GetHyperLink();
foreach(MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
if (!$bReadOnly)
{
$aRow['static::'.$sFieldCode] = $oLinkedObj->GetAsHTML($sFieldCode);
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
}
// Adding fields from remote class
// all fields are embedded in a span + added to $aFieldsMap array so that we can refresh them after extkey change
$aRemoteFieldsMap = [];
foreach (MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
{
$sSafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $sFieldCode);
$aRow['static::'.$sFieldCode] = "<span id='field_$sSafeFieldId'>".$oLinkedObj->GetAsHTML($sFieldCode).'</span>';
$aRemoteFieldsMap[$sFieldCode] = $sSafeFieldId;
}
// id field is needed so that remote object could be load server side
$aRemoteFieldsMap['id'] = $sRemoteKeySafeFieldId;
// Generate WizardHelper to update dependant fields
$this->AddWizardHelperInit($oP, $aArgs['wizHelper'], $this->m_sLinkedClass, $sState, $aFieldsMap);
//instantiate specific WizarHelper instance for remote class fields refresh
$bHasExtKeyUpdatingRemoteClassFields = (
array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs)
&& ($aArgs['replaceDependenciesByRemoteClassFields'])
);
if ($bHasExtKeyUpdatingRemoteClassFields)
{
$this->AddWizardHelperInit($oP, $aArgs['wizHelperRemote'], $this->m_sRemoteClass, $sState, $aRemoteFieldsMap);
}
return $aRow;
}
/**
* Display one row of the whole form
* @param WebPage $oP
* @param array $aConfig
* @param array $aRow
* @param int $iRowId
* @return string
*/
protected function DisplayFormRow(WebPage $oP, $aConfig, $aRow, $iRowId)
private function AddRowForFieldCode(&$aRow, $sFieldCode, &$aArgs, $oLnk, $oP, $sNameSuffix, $sSafeFieldId): void
{
$sHtml = '';
$sHtml .= "<tr id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_row_$iRowId\">\n";
foreach($aConfig as $sName=>$void)
if (($sFieldCode === $this->m_sExtKeyToRemote))
{
$sHtml .= "<td>".$aRow[$sName]."</td>\n";
// current field is the lnk extkey to the remote class
$aArgs['replaceDependenciesByRemoteClassFields'] = true;
$sRowFieldCode = 'static::key';
$aArgs['wizHelperRemote'] = $aArgs['wizHelper'].'_remote';
$aRemoteAttDefs = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass);
$aRemoteCodes = array_map(
function ($value) {
return $value->GetCode();
},
$aRemoteAttDefs
);
$aArgs['remoteCodes'] = $aRemoteCodes;
}
$sHtml .= "</tr>\n";
return $sHtml;
else
{
$aArgs['replaceDependenciesByRemoteClassFields'] = false;
$sRowFieldCode = $sFieldCode;
}
$sValue = $oLnk->Get($sFieldCode);
$sDisplayValue = $oLnk->GetEditValue($sFieldCode);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sRowFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'
.cmdbAbstractObject::GetFormElementForField(
$oP,
$this->m_sLinkedClass,
$sFieldCode,
$oAttDef,
$sValue,
$sDisplayValue,
$sSafeFieldId,
$sNameSuffix,
0,
$aArgs
)
.'</div></div></div>';
}
private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true)
{
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$iLnkId.']';
return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId;
}
private function AddWizardHelperInit($oP, $sWizardHelperVarName, $sWizardHelperClass, $sState, $aFieldsMap): void
{
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oP->add_script(
<<<JS
var $sWizardHelperVarName = new WizardHelper('$sWizardHelperClass', '', '$sState');
$sWizardHelperVarName.SetFieldsMap($sJsonFieldsMap);
$sWizardHelperVarName.SetFieldsCount($iFieldsCount);
$sWizardHelperVarName.SetReturnNotEditableFields(true);
$sWizardHelperVarName.SetWizHelperJsVarName('$sWizardHelperVarName');
JS
);
}
/**
* Display the table with the form for editing all the links at once
*
* @param WebPage $oP The web page used for the output
* @param array $aConfig The table's header configuration
* @param array $aData The tabular data to be displayed
*
* @return string Html fragment representing the form table
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
protected function DisplayFormTable(WebPage $oP, $aConfig, $aData)
{
$sHtml = "<input type=\"hidden\" name=\"attr_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"\">";
$sHtml .= "<table class=\"listResults\">\n";
// Header
$sHtml .= "<thead>\n";
$sHtml .= "<tr>\n";
foreach($aConfig as $sName=>$aDef)
$oTable = DataTableFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $aConfig);
foreach ($aData as $iRowId => $aRow)
{
$sHtml .= "<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n";
}
$sHtml .= "</tr>\n";
$sHtml .= "</thead>\n";
// Content
$sHtml .= "</tbody>\n";
$sEmptyRowStyle = '';
if (count($aData) != 0)
{
$sEmptyRowStyle = 'style="display:none;"';
$oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $aConfig, $aRow, $iRowId);
$oTable->AddRow($oRow);
}
foreach($aData as $iRowId => $aRow)
{
$sHtml .= $this->DisplayFormRow($oP, $aConfig, $aRow, $iRowId);
}
$sHtml .= "<tr $sEmptyRowStyle id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_empty_row\"><td colspan=\"".count($aConfig)."\" style=\"text-align:center;\">".Dict::S('UI:Message:EmptyList:UseAdd')."</td></tr>";
$sHtml .= "</tbody>\n";
// Footer
$sHtml .= "</table>\n";
$sHtml = BlockRenderer::RenderBlockTemplates($oTable);
return $sHtml;
}
@@ -360,20 +396,21 @@ EOF
$bDoSearch = !utils::IsHighCardinality($this->m_sRemoteClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
$oPage->add_ready_script(<<<EOF
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}', $sJSDoSearch);
oWidget{$this->m_iInputId}.Init();
EOF
$oPage->add_ready_script(<<<JS
oWidget{$this->m_iInputId} = new LinksWidget('{$this->m_sAttCode}{$this->m_sNameSuffix}', '{$this->m_sClass}', '{$this->m_sAttCode}', '{$this->m_iInputId}', '{$this->m_sNameSuffix}', $sDuplicates, $sWizHelper, '{$this->m_sExtKeyToRemote}', $sJSDoSearch);
oWidget{$this->m_iInputId}.Init();
JS
);
while($oCurrentLink = $oValue->Fetch())
while ($oCurrentLink = $oValue->Fetch())
{
// We try to retrieve the remote object as usual
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
// We try to retrieve the remote object as usual
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote),
false /* Must not be found */);
// If successful, it means that we can edit its link
if($oLinkedObj !== null)
{
$bReadOnly = false;
if ($oLinkedObj !== null)
{
$bReadOnly = false;
}
// Else we retrieve it without restrictions (silos) and will display its link as readonly
else
@@ -393,6 +430,7 @@ EOF
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
}
$sHtmlValue .= $this->DisplayFormTable($oPage, $this->m_aTableConfig, $aForm);
// To prevent adding forms inside the main form
$sHtmlValue .= "<span style=\"float:left;\">&nbsp;&nbsp;&nbsp;<img src=\"../images/tv-item-last.gif\">&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnRemove\" type=\"button\" value=\"".Dict::S('UI:RemoveLinkedObjectsOf_Class')."\" onClick=\"oWidget{$this->m_iInputId}.RemoveSelected();\" >";
$sHtmlValue .= "&nbsp;&nbsp;&nbsp;<input id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_btnAdd\" type=\"button\" value=\"".Dict::Format('UI:AddLinkedObjectsOf_Class', MetaModel::GetName($this->m_sRemoteClass))."\" onClick=\"oWidget{$this->m_iInputId}.AddObjects();\"><span id=\"{$this->m_sAttCode}{$this->m_sNameSuffix}_indicatorAdd\"></span></span>\n";
@@ -443,53 +481,55 @@ EOF
*/
public function GetObjectPickerDialog($oPage, $oCurrentObj, $sJson, $aAlreadyLinkedIds = array(), $aPrefillFormParam = array())
{
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
//$oPage->add("<div class=\"wizContainer\" style=\"vertical-align:top;\">\n");
$oAlreadyLinkedFilter = new DBObjectSearch($this->m_sRemoteClass);
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0)
{
if (!$this->m_bDuplicatesAllowed && count($aAlreadyLinkedIds) > 0) {
$oAlreadyLinkedFilter->AddCondition('id', $aAlreadyLinkedIds, 'NOTIN');
$oAlreadyLinkedExpression = $oAlreadyLinkedFilter->GetCriteria();
$sAlreadyLinkedExpression = $oAlreadyLinkedExpression->Render();
}
else
{
$sAlreadyLinkedExpression = $oAlreadyLinkedExpression->RenderExpression();
} else {
$sAlreadyLinkedExpression = '';
}
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
if(!empty($oCurrentObj))
{
if (!empty($oCurrentObj)) {
$this->SetSearchDefaultFromContext($oCurrentObj, $oFilter);
$aPrefillFormParam['filter'] = $oFilter;
$aPrefillFormParam['dest_class'] = $this->m_sRemoteClass;
$oCurrentObj->PrefillForm('search', $aPrefillFormParam);
}
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
array(
'menu' => false,
'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
'table_id' => 'add_'.$this->m_sAttCode,
'table_id' => "add_{$this->m_sAttCode}{$this->m_sNameSuffix}",
'table_inner_id' => "ResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}",
'selection_mode' => true,
'json' => $sJson,
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sAlreadyLinkedExpression,
));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}\">\n";
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHtml .= "</div>\n";
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"0\"/>";
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" disabled=\"disabled\" type=\"button\" onclick=\"return oWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$oPage->add($sHtml);
$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });");
$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('option', {title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."'});");
)));
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sCancel = Dict::S('UI:Button:Cancel');
$sAdd = Dict::S('UI:Button:Add');
$oPage->add(<<<HTML
<form id="ObjectsAddForm_{$this->m_sAttCode}{$this->m_sNameSuffix}">
<div id="SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}" style="vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;">
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div>
<input type="hidden" id="count_{$this->m_sAttCode}{$this->m_sNameSuffix}" value="0"/>
<input type="button" value="{$sCancel}" onClick="$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');">&nbsp;&nbsp;
<input id="btn_ok_add_{$this->m_sAttCode}{$this->m_sNameSuffix}" disabled="disabled" type="button" onclick="return oWidget{$this->m_iInputId}.DoAddObjects(this.id);" value="{$sAdd}">
</form>
HTML
);
$oPage->add_ready_script("$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, title:'".addslashes(Dict::Format('UI:AddObjectsOf_Class_LinkedWith_Class', MetaModel::GetName($this->m_sLinkedClass), MetaModel::GetName($this->m_sClass)))."' , autoOpen: false, modal: true, resizeStop: oWidget{$this->m_iInputId}.UpdateSizes });");
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix} form').bind('submit.uilinksWizard', oWidget{$this->m_iInputId}.SearchObjectsToAdd);");
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}').resize(oWidget{$this->m_iInputId}.UpdateSizes);");
}
@@ -547,7 +587,8 @@ EOF
if (is_object($oLinkedObj))
{
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$oP->add($this->DisplayFormRow($oP, $this->m_aTableConfig, $aRow, -$iAdditionId));
$oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aRow, -$iAdditionId);
$oP->AddUiBlock($oRow);
$iAdditionId++;
}
else

View File

@@ -24,7 +24,6 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
class UIPasswordWidget
@@ -66,7 +65,7 @@ class UIPasswordWidget
$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'[changed]" value="'.$sChangedValue.'"/>';
$sHtmlValue .= '</div>';
$sHtmlValue .= '<span class="form_validation" id="v_'.$this->iId.'"></span><span class="field_status" id="fstatus_'.$this->iId.'"></span>';
$sHtmlValue .= '<span class="form_validation ibo-field-validation" id="v_'.$this->iId.'"></span><span class="field_status" id="fstatus_'.$this->iId.'"></span>';
$oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate
$oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate

View File

@@ -21,7 +21,6 @@
*/
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
class UISearchFormForeignKeys
@@ -41,12 +40,11 @@ class UISearchFormForeignKeys
*/
public function ShowModalSearchForeignKeys($oPage, $sTitle)
{
$sHtml = "<div class=\"wizContainer\" style=\"vertical-align:top;\">\n";
$oFilter = new DBObjectSearch($this->m_sRemoteClass);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$sHtml .= $oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_iInputId}",
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, "SearchFormToAdd_{$this->m_iInputId}",
array(
'menu' => false,
'result_list_outer_selector' => "SearchResultsToAdd_{$this->m_iInputId}",
@@ -55,16 +53,23 @@ class UISearchFormForeignKeys
'selection_mode' => true,
'cssCount' => "#count_{$this->m_iInputId}",
'query_params' => $oFilter->GetInternalParams(),
));
$sHtml .= "<form id=\"ObjectsAddForm_{$this->m_iInputId}\">\n";
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_iInputId}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHtml .= "</div>\n";
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_iInputId}\" value=\"0\"/>";
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_iInputId}').dialog('close');\">&nbsp;&nbsp;<input id=\"btn_ok_{$this->m_iInputId}\" disabled=\"disabled\" type=\"button\" onclick=\"return oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$oPage->add($sHtml);
)));
$sEmptyList = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sCancel = Dict::S('UI:Button:Cancel');
$sAdd = Dict::S('UI:Button:Add');
$oPage->add(<<<HTML
<form id="ObjectsAddForm_{$this->m_iInputId}">
<div id="SearchResultsToAdd_{$this->m_iInputId}" style="vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;">
<div style="background: #fff; border:0; text-align:center; vertical-align:middle;"><p>{$sEmptyList}</p></div>
</div>
<input type="hidden" id="count_{$this->m_iInputId}" value="0"/>
<input type="button" value="{$sCancel}" onClick="$('#dlg_{$this->m_iInputId}').dialog('close');">&nbsp;&nbsp;
<input id="btn_ok_add_{$this->m_iInputId}" disabled="disabled" type="button" onclick="return oForeignKeysWidget{$this->m_iInputId}.DoAddObjects(this.id);" value="{$sAdd}">
</form>
HTML
);
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog({ width: $(window).width()*0.8, height: $(window).height()*0.8, autoOpen: false, modal: true, resizeStop: oForeignKeysWidget{$this->m_iInputId}.UpdateSizes });");
$oPage->add_ready_script("$('#dlg_{$this->m_iInputId}').dialog('option', {title:'$sTitle'});");
$oPage->add_ready_script("$('#SearchFormToAdd_{$this->m_iInputId} form').bind('submit.uilinksWizard', oForeignKeysWidget{$this->m_iInputId}.SearchObjectsToAdd);");
@@ -112,4 +117,4 @@ class UISearchFormForeignKeys
array('menu' => false, 'cssCount' => "#count_{$this->m_iInputId}", 'selection_mode' => true, 'table_id' => "add_{$this->m_iInputId}"));
}
}
}

View File

@@ -35,15 +35,20 @@ require_once(APPROOT.'/core/userrights.class.inc.php');
class appUserPreferences extends DBObject
{
private static $oUserPrefs = null; // Local cache
/**
* Get the value of the given property/preference
* If not set, the default value will be returned
*
* @param string $sCode Code/Name of the property to set
* @param string $sDefaultValue The default value
* @return string The value of the property for the current user
* @param mixed $defaultValue The default value
*
* @return mixed The value of the property for the current user
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function GetPref($sCode, $sDefaultValue)
public static function GetPref($sCode, $defaultValue)
{
if (self::$oUserPrefs == null)
{
@@ -56,29 +61,35 @@ class appUserPreferences extends DBObject
}
else
{
return $sDefaultValue;
return $defaultValue;
}
}
/**
* Set the value for a given preference, and stores it into the database
*
* @param string $sCode Code/Name of the property/preference to set
* @param string $sValue Value to set
* @param mixed $value Value to set
*
* @return void
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function SetPref($sCode, $sValue)
public static function SetPref($sCode, $value)
{
if (self::$oUserPrefs == null)
{
self::Load();
}
$aPrefs = self::$oUserPrefs->Get('preferences');
if (array_key_exists($sCode, $aPrefs) && ($aPrefs[$sCode] === $sValue))
if (array_key_exists($sCode, $aPrefs) && ($aPrefs[$sCode] === $value))
{
// Do not write it again
}
else
{
$aPrefs[$sCode] = $sValue;
$aPrefs[$sCode] = $value;
self::$oUserPrefs->Set('preferences', $aPrefs);
self::Save();
}
@@ -86,8 +97,14 @@ class appUserPreferences extends DBObject
/**
* Clears the value for a given preference (or list of preferences that matches a pattern), and updates the database
*
* @param string $sCodeOrPattern Code/Pattern of the properties/preferences to reset
* @param boolean $bPattern Whether or not the supplied code is a PCRE pattern
*
* @return void
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function UnsetPref($sCodeOrPattern, $bPattern = false)
{
@@ -119,10 +136,14 @@ class appUserPreferences extends DBObject
self::Save();
}
}
/**
* Call this function to get all the preferences for the user, packed as a JSON object
*
* @return string JSON representation of the preferences
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function GetAsJSON()
{
@@ -136,19 +157,29 @@ class appUserPreferences extends DBObject
/**
* Call this function if the user has changed (like when doing a logoff...)
*
* @return void
*/
public static function ResetPreferences()
{
self::$oUserPrefs = null;
}
/**
* Call this function to ERASE all the preferences from the current user
*
* @return void
*/
public static function ClearPreferences()
{
self::$oUserPrefs = null;
}
/**
* Save preferences in the DB
*
* @return void;
*/
protected static function Save()
{
if (self::$oUserPrefs != null)
@@ -161,10 +192,15 @@ class appUserPreferences extends DBObject
}
}
}
/**
* Loads the preferences for the current user, creating the record in the database
* if needed
*
* @return void;
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
protected static function Load()
{
@@ -193,6 +229,9 @@ class appUserPreferences extends DBObject
self::$oUserPrefs = $oObj;
}
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
@@ -213,9 +252,22 @@ class appUserPreferences extends DBObject
}
/**
* Overloading this function here to secure a fix done right before the release
* The real fix should be to implement this verb in DBObject
*/
* Overloading this function here to secure a fix done right before the release
* The real fix should be to implement this verb in DBObject
*
* @param \CMDBChange $oChange
* @param bool|null $bSkipStrongSecurity
* @param \DeletionPlan|null $oDeletionPlan
*
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
utils::PushArchiveMode(false);

View File

@@ -44,6 +44,57 @@ class FileUploadException extends Exception
*/
class utils
{
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_CLASS = 'class';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_STRING = 'string';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_PARAMETER = 'parameter';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_FIELD_NAME = 'field_name';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_TRANSACTION_ID = 'transaction_id';
/**
* @var string For XML / HTML node identifiers
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
/**
* @var string
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
/**
* @var string
* @since 3.0.0
*/
public const DEFAULT_SANITIZATION_FILTER = self::ENUM_SANITIZATION_FILTER_RAW_DATA;
/**
* Cache when getting config from disk or set externally (using {@link SetConfig})
* @internal
@@ -283,31 +334,32 @@ class utils
*
* @since 2.5.2 2.6.0 new 'transaction_id' filter
* @since 2.7.0 new 'element_identifier' filter
*
* @throws \CoreException
*/
protected static function Sanitize_Internal($value, $sSanitizationFilter)
{
switch ($sSanitizationFilter)
{
case 'integer':
case static::ENUM_SANITIZATION_FILTER_INTEGER:
$retValue = filter_var($value, FILTER_SANITIZE_NUMBER_INT);
break;
case 'class':
case static::ENUM_SANITIZATION_FILTER_CLASS:
$retValue = $value;
if (!MetaModel::IsValidClass($value))
{
$retValue = false;
if (($value != '') && !MetaModel::IsValidClass($value)) {
throw new CoreException(Dict::Format('UI:OQL:UnknownClassNoFix', utils::HtmlEntities($value)));
}
break;
case 'string':
case static::ENUM_SANITIZATION_FILTER_STRING:
$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
break;
case 'context_param':
case 'parameter':
case 'field_name':
case 'transaction_id':
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
if (is_array($value))
{
$retValue = array();
@@ -325,7 +377,7 @@ class utils
{
switch ($sSanitizationFilter)
{
case 'transaction_id':
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
// same as parameter type but keep the dot character
// see N°1835 : when using file transaction_id on Windows you get *.tmp tokens
// it must be included at the regexp beginning otherwise you'll get an invalid character error
@@ -333,18 +385,18 @@ class utils
array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
break;
case 'parameter':
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
// characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
break;
case 'field_name':
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
break;
case 'context_param':
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
break;
@@ -353,13 +405,12 @@ class utils
}
break;
// For XML / HTML node identifiers
case 'element_identifier':
$retValue = preg_replace('/[^a-zA-Z0-9_]/', '', $value);
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
$retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
break;
default:
case 'raw_data':
case static::ENUM_SANITIZATION_FILTER_RAW_DATA:
$retValue = $value;
// Do nothing
}
@@ -767,19 +818,22 @@ class utils
return new Config();
}
public static function InitTimeZone() {
$oConfig = self::GetConfig();
public static function InitTimeZone($oConfig = null)
{
if (is_null($oConfig))
{
$oConfig = self::GetConfig();
}
$sItopTimeZone = $oConfig->Get('timezone');
if (!empty($sItopTimeZone))
{
date_default_timezone_set($sItopTimeZone);
}
else
{
// Leave as is... up to the admin to set a value somewhere...
// see http://php.net/manual/en/datetime.configuration.php#ini.date.timezone
}
// else
// {
// // Leave as is... up to the admin to set a value somewhere...
// // see http://php.net/manual/en/datetime.configuration.php#ini.date.timezone
// }
}
/**
@@ -908,6 +962,28 @@ class utils
return $sAppRootUrl;
}
/**
* Return the complete revision number of the application
*
* @return string
* @since 3.0.0
*/
public static function GetAppRevisionNumber()
{
if (ITOP_REVISION == 'svn')
{
// This is NOT a version built using the build system, just display the main version
$sRevisionNumber = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION);
}
else
{
// This is a build made from SVN, let display the full information
$sRevisionNumber = Dict::Format('UI:iTopVersion:Long', ITOP_APPLICATION, ITOP_VERSION, ITOP_REVISION, ITOP_BUILD_DATE);
}
return $sRevisionNumber;
}
/**
* Helper to handle the variety of HTTP servers
* See N°286 (fixed in [896]), and N°634 (this fix)
@@ -941,16 +1017,11 @@ class utils
/**
* Get the _SESSION variable for logging purpose
* @return false|string
* @return string
*/
public static function GetSessionLog()
{
ob_start();
print_r($_SESSION);
$sSessionLog = ob_get_contents();
ob_end_clean();
return $sSessionLog;
return print_r($_SESSION, true);
}
static function DebugBacktrace($iLimit = 5)
@@ -994,19 +1065,19 @@ class utils
throw new Exception("The path to php must not be empty. Please set a value for 'php_path' in your configuration file.");
}
$sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
$sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
$sParamFile = self::GetParamSourceFile('auth_user');
if (is_null($sParamFile))
{
if (!isset($aArguments['auth_user'])) {
$sAuthUser = self::ReadParam('auth_user', '', 'raw_data');
$aArguments['auth_user'] = $sAuthUser;
}
if (!isset($aArguments['auth_pwd'])) {
$sAuthPwd = self::ReadParam('auth_pwd', '', 'raw_data');
$aArguments['auth_pwd'] = $sAuthPwd;
}
else
{
if (!isset($aArguments['param_file'])) {
$sParamFile = self::ReadParam('param_file', '', 'raw_data');
$aArguments['param_file'] = $sParamFile;
}
$aArgs = array();
foreach($aArguments as $sName => $value)
{
@@ -1158,6 +1229,7 @@ class utils
new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
new JSPopupMenuItem('UI:Menu:SwitchTabMode', Dict::S('UI:Menu:SwitchTabMode'), "SwitchTabMode()"),
);
break;
@@ -1388,13 +1460,12 @@ class utils
// For instance fopen does not allow to work around the bug: http://stackoverflow.com/questions/18191672/php-curl-ssl-routinesssl23-get-server-helloreason1112
// by setting the SSLVERSION to 3 as done below.
$aHeaders = explode("\n", $sOptionnalHeaders);
// N°3267 - Webservices: Fix optional headers not being taken into account
// See https://www.php.net/curl_setopt CURLOPT_HTTPHEADER
$aHTTPHeaders = array();
foreach($aHeaders as $sHeaderString)
{
if(preg_match('/^([^:]): (.+)$/', $sHeaderString, $aMatches))
{
$aHTTPHeaders[$aMatches[1]] = $aMatches[2];
}
$aHTTPHeaders[] = trim($sHeaderString);
}
// Default options, can be overloaded/extended with the 4th parameter of this method, see above $aCurlOptions
$aOptions = array(
@@ -1626,7 +1697,7 @@ class utils
set_time_limit(0);
// Compiling SASS
$sCss = $oSass->compile($sSassContent);
set_time_limit($iCurrentMaxExecTime);
set_time_limit(intval($iCurrentMaxExecTime));
return $sCss;
}
@@ -2082,33 +2153,38 @@ class utils
*/
public static function GetCacheBusterTimestamp()
{
if(!defined('COMPILATION_TIMESTAMP'))
{
if (!defined('COMPILATION_TIMESTAMP')) {
return ITOP_VERSION;
}
return COMPILATION_TIMESTAMP;
}
/**
* @return string eg : '2_7_0' ITOP_VERSION is '2.7.1-dev'
*/
public static function GetItopVersionWikiSyntax()
{
public static function GetItopVersionWikiSyntax() {
$sMinorVersion = self::GetItopMinorVersion();
return str_replace('.', '_', $sMinorVersion).'_0';
}
/**
* @param string $sPatchVersion if non provided, will call GetItopPatchVersion
*
* @return string eg 2.7 if ITOP_VERSION is '2.7.0-dev'
* @throws \Exception
*/
public static function GetItopMinorVersion()
{
$sPatchVersion = self::GetItopPatchVersion();
public static function GetItopMinorVersion($sPatchVersion = null) {
if (is_null($sPatchVersion)) {
$sPatchVersion = self::GetItopPatchVersion();
}
$aExplodedVersion = explode('.', $sPatchVersion);
if (empty($aExplodedVersion[0]) || empty($aExplodedVersion[1]))
{
if (count($aExplodedVersion) < 2) {
throw new Exception('iTop version is wrongfully configured!');
}
if (($aExplodedVersion[0] == '') || ($aExplodedVersion[1] == '')) {
throw new Exception('iTop version is wrongfully configured!');
}
@@ -2118,9 +2194,9 @@ class utils
/**
* @return string eg '2.7.0' if ITOP_VERSION is '2.7.0-dev'
*/
public static function GetItopPatchVersion()
{
public static function GetItopPatchVersion() {
$aExplodedVersion = explode('-', ITOP_VERSION);
return $aExplodedVersion[0];
}
@@ -2153,6 +2229,18 @@ class utils
return ITOP_REVISION === 'svn';
}
/**
* Check if debug is enabled in the current environment.
* Currently just checking if the "debug=true" parameter is in the URL, but could be more complex.
*
* @return bool
* @since 3.0.0
*/
public static function IsDebugEnabled()
{
return utils::ReadParam('debug') === 'true';
}
/**
* @see https://php.net/manual/en/function.finfo-file.php
*
@@ -2316,12 +2404,17 @@ class utils
return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' ')));
}
public static function FilterXSS($sHTML)
{
return str_ireplace('<script', '&lt;script', $sHTML);
}
/**
* @param \cmdbAbstractObject $oCmdbAbstract
* @param \Exception $oException
*
* @throws \Exception
* @since 2.7.2/ 2.8.0
* @since 2.7.2/ 3.0.0
*/
public static function EnrichRaisedException($oCmdbAbstract, $oException)
{
@@ -2337,4 +2430,11 @@ class utils
$e = new CoreException($sMessage, null, '', $oException);
throw $e;
}
/**
* @since 3.0.0
*/
public static function IsEasterEggAllowed(){
return (stripos(ITOP_VERSION, 'alpha') !== false) || utils::IsDevelopmentEnvironment();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -335,20 +335,41 @@ class WizardHelper
{
$sResult = $this->m_aData['m_oFieldsMap'][$sFieldName];
}
return $sResult;
}
public function GetReturnNotEditableFields()
{
return $this->m_aData['m_bReturnNotEditableFields'] ?? false;
}
/**
* @return string JS code to be executed for fields update
* @since 3.0.0 N°3198
*/
public function GetJsForUpdateFields()
{
$sWizardHelperJsVar = ($this->m_aData['m_sWizHelperJsVarName']) ?? 'oWizardHelper'.$this->GetFormPrefix();
$sWizardHelperJson = $this->ToJSON();
return <<<JS
{$sWizardHelperJsVar}.m_oData = {$sWizardHelperJson};
{$sWizardHelperJsVar}.UpdateFields();
JS;
}
static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet)
{
$aSet = json_decode($sJsonSet, true); // true means hash array instead of object
$oSet = CMDBObjectSet::FromScratch($sLinkClass);
foreach($aSet as $aLinkObj)
foreach ($aSet as $aLinkObj)
{
$oLink = MetaModel::NewObject($sLinkClass);
foreach($aLinkObj as $sAttCode => $value)
foreach ($aLinkObj as $sAttCode => $value)
{
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0))
if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0))
{
// For external keys: load the target object so that external fields
// get filled too

View File

@@ -1,111 +1,7 @@
<?php
// Copyright (C) 2010-2015 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/>
/**
* Class XMLPage
*
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @deprecated will be removed in 3.1.0 - moved to sources/application/WebPage/XMLPage.php
* @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2020 Combodo SARL
*/
require_once(APPROOT."/application/webpage.class.inc.php");
/**
* Simple web page with no includes or fancy formatting, useful to generateXML documents
* The page adds the content-type text/XML and the encoding into the headers
*/
class XMLPage extends WebPage
{
/**
* For big XML files, it's better NOT to store everything in memory and output the XML piece by piece
*/
var $m_bPassThrough;
var $m_bHeaderSent;
function __construct($s_title, $bPassThrough = false)
{
parent::__construct($s_title);
$this->m_bPassThrough = $bPassThrough;
$this->m_bHeaderSent = false;
$this->add_header("Content-type: text/xml; charset=".self::PAGES_CHARSET);
$this->add_header('Cache-control: no-cache, no-store, must-revalidate');
$this->add_header('Pragma: no-cache');
$this->add_header('Expires: 0');
$this->add_header('X-Frame-Options: deny');
$this->add_header("Content-location: export.xml");
}
public function output()
{
if (!$this->m_bPassThrough)
{
// Get the unexpected output but do nothing with it
$sTrash = $this->ob_get_clean_safe();
$sCharset = self::PAGES_CHARSET;
$this->s_content = "<?xml version=\"1.0\" encoding=\"$sCharset\"?".">\n".trim($this->s_content);
$this->add_header("Content-Length: ".strlen($this->s_content));
foreach($this->a_headers as $s_header)
{
header($s_header);
}
echo $this->s_content;
}
if (class_exists('DBSearch'))
{
DBSearch::RecordQueryTrace();
}
}
public function add($sText)
{
if (!$this->m_bPassThrough)
{
parent::add($sText);
}
else
{
if ($this->m_bHeaderSent)
{
echo $sText;
}
else
{
$s_captured_output = $this->ob_get_clean_safe();
foreach($this->a_headers as $s_header)
{
header($s_header);
}
$sCharset = self::PAGES_CHARSET;
echo "<?xml version=\"1.0\" encoding=\"$sCharset\"?".">\n";
echo trim($s_captured_output);
echo trim($this->s_content);
echo $sText;
$this->m_bHeaderSent = true;
}
}
}
public function small_p($sText)
{
}
public function table($aConfig, $aData, $aParams = array())
{
}
}

View File

@@ -1,7 +1,7 @@
<?php
/**
* Copyright (C) 2013-2019 Combodo SARL
* Copyright (C) 2013-2020 Combodo SARL
*
* This file is part of iTop.
*
@@ -18,16 +18,31 @@
* You should have received a copy of the GNU Affero General Public License
*/
/**
* Checks PHP version
*
* This is a hard-coded check that limits errors : we are stopping for anything < PHP 7.0.0
* The "real one" will be done in {@link \SetupUtils::CheckPhpVersion()}
*
* @since 3.0.0 N°2214
*/
$bIsValidPhpVersion = false;
if (PHP_MAJOR_VERSION >= 7) {
$bIsValidPhpVersion = true;
} else {
echo 'Your PHP version ('.PHP_VERSION.') isn\'t supported.';
exit(-1);
}
define('ITOP_DEFAULT_ENV', 'production');
define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
if (function_exists('microtime'))
{
if (function_exists('microtime')) {
$fItopStarted = microtime(true);
}
else
{
} else {
$fItopStarted = 1000 * time();
}

View File

@@ -2,7 +2,7 @@
"type": "project",
"license": "AGPLv3",
"require": {
"php": ">=5.6.0",
"php": ">=7.1.3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-gd": "*",
@@ -37,7 +37,7 @@
},
"config": {
"platform": {
"php": "5.6.0"
"php": "7.2.0"
},
"vendor-dir": "lib",
"preferred-install": {
@@ -52,7 +52,9 @@
"application",
"sources/application",
"sources/Composer",
"sources/Controller"
"sources/Controller",
"sources/Form",
"sources/Renderer"
],
"exclude-from-classmap": [
"core/dbobjectsearch.class.php",
@@ -61,6 +63,7 @@
"core/legacy/querybuildercontextlegacy.class.inc.php",
"core/querybuilderexpressions.class.inc.php",
"core/legacy/querybuilderexpressionslegacy.class.inc.php",
"application/startup.inc.php",
"application/loginform.class.inc.php",
"application/loginbasic.class.inc.php",
"application/logindefault.class.inc.php",

View File

@@ -35,6 +35,10 @@ require_once(APPROOT.'/core/email.class.inc.php');
*/
abstract class Action extends cmdbAbstractObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
@@ -47,7 +51,6 @@ abstract class Action extends cmdbAbstractObject
"db_table" => "priv_action",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -57,15 +60,32 @@ abstract class Action extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list", array("linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
// - Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list'));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status'));
// Search criteria
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
// - Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status'));
// - Criteria of the advanced search form
// MetaModel::Init_SetZListItems('advanced_search', array('name'));
}
/**
* Encapsulate the execution of the action and handle failure & logging
*
* @param \Trigger $oTrigger
* @param array $aContextArgs
*
* @return mixed
*/
abstract public function DoExecute($oTrigger, $aContextArgs);
/**
* @return bool
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function IsActive()
{
switch($this->Get('status'))
@@ -79,6 +99,13 @@ abstract class Action extends cmdbAbstractObject
}
}
/**
* Return true if the current action status is set on "test"
*
* @return bool
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function IsBeingTested()
{
switch($this->Get('status'))
@@ -99,6 +126,10 @@ abstract class Action extends cmdbAbstractObject
*/
abstract class ActionNotification extends Action
{
/**
* @inheritDoc
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
@@ -111,17 +142,20 @@ abstract class ActionNotification extends Action
"db_table" => "priv_action_notification",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
// - Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list'));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status'));
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
// - Criteria of the std search form
// MetaModel::Init_SetZListItems('standard_search', array('name'));
// - Criteria of the advanced search form
// MetaModel::Init_SetZListItems('advanced_search', array('name'));
}
}
@@ -132,6 +166,9 @@ abstract class ActionNotification extends Action
*/
class ActionEmail extends ActionNotification
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -144,7 +181,6 @@ class ActionEmail extends ActionNotification
"db_table" => "priv_action_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -152,7 +188,9 @@ class ActionEmail extends ActionNotification
MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("from_label", array("allowed_values"=>null, "sql"=>"from_label", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("reply_to_label", array("allowed_values"=>null, "sql"=>"reply_to_label", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
@@ -161,11 +199,15 @@ class ActionEmail extends ActionNotification
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance', 'trigger_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list
// - Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'from_label', 'reply_to', 'reply_to_label', 'to', 'cc', 'bcc', 'subject', 'body', 'importance', 'trigger_list'));
// - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject'));
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('name','description', 'status', 'subject')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
// - Criteria of the std search form
MetaModel::Init_SetZListItems('standard_search', array('name','description', 'status', 'subject'));
// - Criteria of the advanced search form
// MetaModel::Init_SetZListItems('advanced_search', array('name'));
}
// count the recipients found
@@ -175,7 +217,18 @@ class ActionEmail extends ActionNotification
// executed in the background, while making sure that any issue would be reported clearly
protected $m_aMailErrors; //array of strings explaining the issue
// returns a the list of emails as a string, or a detailed error description
/**
* Return a the list of emails as a string, or a detailed error description
*
* @param string $sRecipAttCode
* @param array $aArgs
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
protected function FindRecipients($sRecipAttCode, $aArgs)
{
$sOQL = $this->Get($sRecipAttCode);
@@ -224,9 +277,7 @@ class ActionEmail extends ActionNotification
}
/**
* @param \Trigger $oTrigger
* @param array $aContextArgs
*
* @inheritDoc
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
@@ -306,6 +357,7 @@ class ActionEmail extends ActionNotification
*
* @return string
* @throws \CoreException
* @throws \Exception
*/
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
{
@@ -316,15 +368,17 @@ class ActionEmail extends ActionNotification
$this->m_aMailErrors = array();
$bRes = false; // until we do succeed in sending the email
// Determine recicipients
// Determine recipients
//
$sTo = $this->FindRecipients('to', $aContextArgs);
$sCC = $this->FindRecipients('cc', $aContextArgs);
$sBCC = $this->FindRecipients('bcc', $aContextArgs);
$sFrom = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
$sFromLabel = MetaModel::ApplyParams($this->Get('from_label'), $aContextArgs);
$sReplyTo = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
$sReplyToLabel = MetaModel::ApplyParams($this->Get('reply_to_label'), $aContextArgs);
$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
@@ -347,7 +401,7 @@ class ActionEmail extends ActionNotification
if (isset($sTo)) $oLog->Set('to', $sTo);
if (isset($sCC)) $oLog->Set('cc', $sCC);
if (isset($sBCC)) $oLog->Set('bcc', $sBCC);
if (isset($sFrom)) $oLog->Set('from', $sFrom);
if (isset($sFrom)) $oLog->Set('from', empty($sFromLabel) ? $sFrom : "$sFromLabel <$sFrom>");
if (isset($sSubject)) $oLog->Set('subject', $sSubject);
if (isset($sBody)) $oLog->Set('body', $sBody);
}
@@ -367,15 +421,15 @@ class ActionEmail extends ActionNotification
$sTestBody .= "<li>TO: $sTo</li>\n";
$sTestBody .= "<li>CC: $sCC</li>\n";
$sTestBody .= "<li>BCC: $sBCC</li>\n";
$sTestBody .= "<li>From: $sFrom</li>\n";
$sTestBody .= "<li>Reply-To: $sReplyTo</li>\n";
$sTestBody .= empty($sFromLabel) ? "<li>From: $sFrom</li>\n": "<li>From: $sFromLabel &lt;$sFrom&gt;</li>\n";
$sTestBody .= empty($sReplyToLabel) ? "<li>Reply-To: $sReplyTo</li>\n": "<li>Reply-To: $sReplyToLabel &lt;$sReplyTo&gt;</li>\n";
$sTestBody .= "<li>References: $sReference</li>\n";
$sTestBody .= "</ul>\n";
$sTestBody .= "</p>\n";
$sTestBody .= "</div>\n";
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
$oEmail->SetRecipientTO($this->Get('test_recipient'));
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetRecipientFrom($sFrom, $sFromLabel);
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
}
@@ -386,8 +440,8 @@ class ActionEmail extends ActionNotification
$oEmail->SetRecipientTO($sTo);
$oEmail->SetRecipientCC($sCC);
$oEmail->SetRecipientBCC($sBCC);
$oEmail->SetRecipientFrom($sFrom);
$oEmail->SetRecipientReplyTo($sReplyTo);
$oEmail->SetRecipientFrom($sFrom, $sFromLabel);
$oEmail->SetRecipientReplyTo($sReplyTo, $sReplyToLabel);
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
}
@@ -439,4 +493,3 @@ class ActionEmail extends ActionNotification
}
}
}
?>

View File

@@ -65,6 +65,10 @@ class ExecAsyncTask implements iBackgroundProcess
*/
abstract class AsyncTask extends DBObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
@@ -77,7 +81,6 @@ abstract class AsyncTask extends DBObject
"db_table" => "priv_async_task",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
@@ -285,11 +288,13 @@ abstract class AsyncTask extends DBObject
/**
* Throws an exception (message and code)
*
* @return string
*/
abstract public function DoProcess();
/**
* Describes the error codes that DoProcess can return by the mean of exceptions
* Describes the error codes that DoProcess can return by the mean of exceptions
*/
static public function EnumErrorCodes()
{
@@ -316,7 +321,6 @@ class AsyncSendEmail extends AsyncTask
"db_table" => "priv_async_send_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -352,6 +356,11 @@ class AsyncSendEmail extends AsyncTask
$oNew->DBInsert();
}
/**
* @inheritDoc
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function DoProcess()
{
$sMessage = $this->Get('message');

View File

@@ -31,13 +31,9 @@ require_once('ormlinkset.class.inc.php');
require_once('ormset.class.inc.php');
require_once('ormtagset.class.inc.php');
require_once('htmlsanitizer.class.inc.php');
require_once(APPROOT.'sources/autoload.php');
require_once('customfieldshandler.class.inc.php');
require_once('ormcustomfieldsvalue.class.inc.php');
require_once('datetimeformat.class.inc.php');
// This should be changed to a use when we go full-namespace
require_once(APPROOT.'sources/form/validator/validator.class.inc.php');
require_once(APPROOT.'sources/form/validator/notemptyextkeyvalidator.class.inc.php');
/**
* MissingColumnException - sent if an attribute is being created but the column is missing in the row
@@ -295,7 +291,7 @@ abstract class AttributeDefinition
* @param \DBObject $oHostObject
* @param $value Object error if any, null otherwise
*
* @return bool
* @return bool|string true for no errors, false or error message otherwise
*/
public function CheckValue(DBObject $oHostObject, $value)
{
@@ -1030,10 +1026,17 @@ abstract class AttributeDefinition
$oFormField->AddValidator(new Validator($this->GetValidationPattern()));
}
// Description
$sAttDescription = $this->GetDescription();
if(!empty($sAttDescription))
{
$oFormField->SetDescription($this->GetDescription());
}
// Metadata
$oFormField->AddMetadata('attribute-code', $this->GetCode());
$oFormField->AddMetadata('attribute-type', get_class($this));
$oFormField->AddMetadata('attribute-label', utils::HtmlEntities($this->GetLabel()));
$oFormField->AddMetadata('attribute-label', $this->GetLabel());
// - Attribute flags
$aPossibleAttFlags = MetaModel::EnumPossibleAttributeFlags();
foreach($aPossibleAttFlags as $sFlagCode => $iFlagValue)
@@ -1050,7 +1053,7 @@ abstract class AttributeDefinition
// - Value raw
if ($this::IsScalar())
{
$oFormField->AddMetadata('value-raw', utils::HtmlEntities($oObject->Get($this->GetCode())));
$oFormField->AddMetadata('value-raw', (string) $oObject->Get($this->GetCode()));
}
return $oFormField;
@@ -1270,6 +1273,15 @@ abstract class AttributeDefinition
{
return (string)$value;
}
/*
* return string
*/
public function GetRenderForDataTable(string $sClassAlias) :string
{
$sRenderFunction = "return data;";
return $sRenderFunction;
}
}
class AttributeDashboard extends AttributeDefinition
@@ -2273,22 +2285,17 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
/** @var \AttributeExternalKey $oExtKeyToRemote */
$oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
$sRemoteClass = $oExtKeyToRemote->GetTargetClass();
foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef)
{
if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect)
{
foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) {
if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) {
continue;
}
if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass())
{
if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) {
continue;
}
if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote())
{
if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) {
continue;
}
if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe())
{
if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) {
continue;
}
$oRet = $oRemoteAttDef;
@@ -4994,6 +5001,7 @@ class AttributePhoneNumber extends AttributeString
return '<a class="tel" href="'.$sUrl.'"><span class="text_decoration '.$sUrlDecorationClass.'"></span>'.parent::GetAsHTML($sValue).'</a>';
}
}
/**
@@ -6654,6 +6662,23 @@ class AttributeExternalKey extends AttributeDBFieldVoid
return (int)$proposedValue;
}
public function GetPrerequisiteAttributes($sClass = null)
{
$aAttributes = parent::GetPrerequisiteAttributes($sClass);
$oExpression = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression())->GetCriteria();
foreach ($oExpression->GetParameters('this') as $sAttCode)
{
// Skip the id as it cannot change anyway
if ($sAttCode =='id') continue;
if (!in_array($sAttCode, $aAttributes))
{
$aAttributes[] = $sAttCode;
}
}
return $aAttributes;
}
public function GetMaximumComboLength()
{
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
@@ -6759,8 +6784,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
if (!is_null($oHostObject))
{
if (!is_null($oHostObject)) {
return $oHostObject->GetAsHTML($this->GetCode(), $oHostObject);
}
@@ -7962,18 +7986,6 @@ class AttributeImage extends AttributeBlob
return "Image";
}
/**
* {@inheritDoc}
* @see AttributeBlob::MakeRealValue()
*/
public function MakeRealValue($proposedValue, $oHostObj)
{
$oDoc = parent::MakeRealValue($proposedValue, $oHostObj);
// The validation of the MIME Type is done by CheckFormat below
return $oDoc;
}
/**
* Check that the supplied ormDocument actually contains an image
* {@inheritDoc}
@@ -10309,6 +10321,7 @@ class AttributeEnumSet extends AttributeSet
return $sRes;
}
/**
* @param ormSet $value
* @param string $sSeparator
@@ -11590,7 +11603,17 @@ class AttributeFriendlyName extends AttributeDefinition
public function GetPrerequisiteAttributes($sClass = null)
{
return $this->GetOptional("depends_on", array());
// Code duplicated with AttributeObsolescenceFlag
$aAttributes = $this->GetOptional("depends_on", array());
$oExpression = $this->GetOQLExpression();
foreach ($oExpression->ListRequiredFields() as $sClass => $sAttCode)
{
if (!in_array($sAttCode, $aAttributes))
{
$aAttributes[] = $sAttCode;
}
}
return $aAttributes;
}
public static function IsScalar()
@@ -12822,7 +12845,17 @@ class AttributeObsolescenceFlag extends AttributeBoolean
public function GetPrerequisiteAttributes($sClass = null)
{
return $this->GetOptional("depends_on", array());
// Code duplicated with AttributeFriendlyName
$aAttributes = $this->GetOptional("depends_on", array());
$oExpression = $this->GetOQLExpression();
foreach ($oExpression->ListRequiredFields() as $sClass => $sAttCode)
{
if (!in_array($sAttCode, $aAttributes))
{
$aAttributes[] = $sAttCode;
}
}
return $aAttributes;
}
public function IsDirectField()

View File

@@ -185,12 +185,9 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
static::DEFAULT_MODULE_SETTING_ENABLED
);
$sItopTimeZone = $this->getOConfig()->Get('timezone');
$timezone = new DateTimeZone($sItopTimeZone);
if (!$bEnabled)
{
return new DateTime('3000-01-01', $timezone);
return new DateTime('3000-01-01');
}
// 1st - Interpret the list of days as ordered numbers (monday = 1)
@@ -209,7 +206,7 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
throw new ProcessInvalidConfigException($this->GetModuleName().": wrong format for setting '".static::MODULE_SETTING_TIME."' (found '$sProcessTime')");
}
$oNow = new DateTime($sCurrentTime, $timezone);
$oNow = new DateTime($sCurrentTime);
$iNextPos = false;
$sDay = $oNow->format('N');
for ($iDay = (int) $sDay; $iDay <= 7; $iDay++)
@@ -244,7 +241,6 @@ abstract class AbstractWeeklyScheduledProcess implements iScheduledProcess
$oRet->modify('+'.$iMove.' days');
}
list($sHours, $sMinutes) = explode(':', $sProcessTime);
/** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection non used new parameter in PHP 7.1 */
$oRet->setTime((int)$sHours, (int)$sMinutes);
return $oRet;

View File

@@ -56,7 +56,6 @@ class BackgroundTask extends DBObject
"db_table" => "priv_backgroundtask",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
@@ -72,7 +71,7 @@ class BackgroundTask extends DBObject
MetaModel::Init_AddAttribute(new AttributeDecimal("average_run_duration", array("allowed_values"=>null, "sql"=>"average_run_duration", "digits"=> 8, "decimals"=> 3, "default_value"=>"0", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBoolean("running", array("allowed_values"=>null, "sql"=>"running", "default_value"=>false, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('active,paused'), "sql"=>"status", "default_value"=>'active', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('active,paused,removed'), "sql"=>"status", "default_value"=>'active', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("system_user", array("allowed_values"=>null, "sql"=>"system_user", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
}

View File

@@ -948,7 +948,7 @@ class BulkChange
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
foreach($this->m_aData as $iRow => $aRowData)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
if (isset($aResult[$iRow]["__STATUS__"]))
{
// An issue at the earlier steps - skip the rest
@@ -1067,13 +1067,13 @@ class BulkChange
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited))
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
}
}
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(itval($iPreviousTimeLimit));
// Fill in the blanks - the result matrix is expected to be 100% complete
//

View File

@@ -52,12 +52,18 @@ class CMDBChange extends DBObject
//MetaModel::Init_InheritAttributes();
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())));
}
// Helper to keep track of the author of a given change,
// taking into account a variety of cases (contact attached or not, impersonation)
static public function GetCurrentUserName()
/**
* Helper to keep track of the author of a given change,
* taking into account a variety of cases (contact attached or not, impersonation)
*
* @return string
* @throws \OQLException
*/
public static function GetCurrentUserName()
{
if (UserRights::IsImpersonated())
{
@@ -70,6 +76,19 @@ class CMDBChange extends DBObject
return $sUserString;
}
/**
* Return the current user
*
* @return string|null
* @throws \OQLException
* @since 3.0.0
*/
public static function GetCurrentUserId()
{
// Note: We might have use only UserRights::GetRealUserId() as it would have done the same thing in the end
return UserRights::IsImpersonated() ? UserRights::GetRealUserId() : UserRights::GetUserId();
}
public function GetUserName()
{
if (preg_match('/^(.*)\\(CSV\\)$/i', $this->Get('userinfo'), $aMatches))
@@ -83,5 +102,3 @@ class CMDBChange extends DBObject
return $sUser;
}
}
?>

View File

@@ -31,7 +31,22 @@
* @package iTopORM
*/
class CMDBChangeOp extends DBObject
/**
* Interface iCMDBChangeOp
*
* @since 3.0.0
*/
interface iCMDBChangeOp
{
/**
* Describe (as a text string) the modifications corresponding to this change
*
* @return string
*/
public function GetDescription();
}
class CMDBChangeOp extends DBObject implements iCMDBChangeOp
{
public static function Init()
{
@@ -54,6 +69,7 @@ class CMDBChangeOp extends DBObject
MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date")));
MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo")));
MetaModel::Init_AddAttribute(new AttributeExternalField("user_id", array("allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"user_id")));
MetaModel::Init_AddAttribute(new AttributeString("objclass", array("allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeObjectKey("objkey", array("allowed_values"=>null, "class_attcode"=>"objclass", "sql"=>"objkey", "is_null_allowed"=>false, "depends_on"=>array())));
@@ -62,7 +78,7 @@ class CMDBChangeOp extends DBObject
}
/**
* Describe (as a text string) the modifications corresponding to this change
* @inheritDoc
*/
public function GetDescription()
{
@@ -83,8 +99,6 @@ class CMDBChangeOp extends DBObject
}
}
/**
* Record the creation of an object
*
@@ -92,6 +106,9 @@ class CMDBChangeOp extends DBObject
*/
class CMDBChangeOpCreate extends CMDBChangeOp
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -110,7 +127,7 @@ class CMDBChangeOpCreate extends CMDBChangeOp
}
/**
* Describe (as a text string) the modifications corresponding to this change
* @inheritDoc
*/
public function GetDescription()
{
@@ -118,7 +135,6 @@ class CMDBChangeOpCreate extends CMDBChangeOp
}
}
/**
* Record the deletion of an object
*
@@ -126,6 +142,9 @@ class CMDBChangeOpCreate extends CMDBChangeOp
*/
class CMDBChangeOpDelete extends CMDBChangeOp
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -147,16 +166,16 @@ class CMDBChangeOpDelete extends CMDBChangeOp
// Last friendly name of the object
MetaModel::Init_AddAttribute(new AttributeString("fname", array("allowed_values"=>null, "sql"=>"fname", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
return Dict::S('Change:ObjectDeleted');
}
}
/**
* Record the modification of an attribute (abstract)
*
@@ -164,6 +183,9 @@ class CMDBChangeOpDelete extends CMDBChangeOp
*/
class CMDBChangeOpSetAttribute extends CMDBChangeOp
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -194,6 +216,9 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp
*/
class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -216,10 +241,10 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
$sResult = '';
@@ -250,7 +275,10 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute
{
public static function Init()
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
(
@@ -273,10 +301,10 @@ class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
/**
* @inheritDoc
*/
public function GetDescription()
{
$sResult = '';
$sTargetObjectClass = $this->Get('objclass');
@@ -307,6 +335,9 @@ class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -329,10 +360,10 @@ class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
$sResult = '';
@@ -363,6 +394,9 @@ class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -384,10 +418,10 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
@@ -430,11 +464,15 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
return $sResult;
}
}
/**
* Safely record the modification of one way encrypted password
*/
class CMDBChangeOpSetAttributeOneWayPassword extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -456,10 +494,10 @@ class CMDBChangeOpSetAttributeOneWayPassword extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
@@ -495,6 +533,9 @@ class CMDBChangeOpSetAttributeOneWayPassword extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -516,10 +557,10 @@ class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
@@ -558,6 +599,9 @@ class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -579,10 +623,10 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
@@ -623,6 +667,9 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -644,10 +691,10 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
$sResult = '';
@@ -685,6 +732,9 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -705,8 +755,9 @@ class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
* @inheritDoc
*/
public function GetDescription()
{
@@ -748,6 +799,9 @@ class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
*/
class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -769,10 +823,10 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
// Temporary, until we change the options of GetDescription() -needs a more global revision
@@ -799,14 +853,18 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
}
$oObj = $oMonoObjectSet->Fetch();
$oCaseLog = $oObj->Get($this->Get('attcode'));
$iMaxVisibleLength = MetaModel::getConfig()->Get('max_history_case_log_entry_length', 0);
$sTextEntry = '<div class="history_entry history_entry_truncated"><div class="history_html_content">'.$oCaseLog->GetEntryAt($this->Get('lastentry')).'</div></div>';
$sResult = Dict::Format('Change:AttName_EntryAdded', $sAttName, $sTextEntry);
}
return $sResult;
}
/**
* @param string $sRawText
*
* @return string
*/
protected function ToHtml($sRawText)
{
return str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sRawText, ENT_QUOTES, 'UTF-8'));
@@ -820,6 +878,9 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpPlugin extends CMDBChangeOp
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -841,10 +902,10 @@ class CMDBChangeOpPlugin extends CMDBChangeOp
*/
MetaModel::Init_InheritAttributes();
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
return $this->Get('description');
@@ -858,6 +919,9 @@ class CMDBChangeOpPlugin extends CMDBChangeOp
*/
abstract class CMDBChangeOpSetAttributeLinks extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -888,6 +952,9 @@ abstract class CMDBChangeOpSetAttributeLinks extends CMDBChangeOpSetAttribute
*/
class CMDBChangeOpSetAttributeLinksAddRemove extends CMDBChangeOpSetAttributeLinks
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -908,8 +975,8 @@ class CMDBChangeOpSetAttributeLinksAddRemove extends CMDBChangeOpSetAttributeLin
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
$sResult = '';
@@ -952,6 +1019,9 @@ class CMDBChangeOpSetAttributeLinksAddRemove extends CMDBChangeOpSetAttributeLin
*/
class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -972,8 +1042,8 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
* @inheritDoc
*/
public function GetDescription()
{
$sResult = '';
@@ -1039,6 +1109,9 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
*/
class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
{
/**
* @inheritDoc
*/
public static function Init()
{
$aParams = array
@@ -1062,7 +1135,7 @@ class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
}
/**
* Describe (as a text string) the modifications corresponding to this change
* @inheritDoc
*/
public function GetDescription()
{

View File

@@ -94,6 +94,7 @@ abstract class CMDBObject extends DBObject
// Note: this value is static, but that could be changed because it is sometimes a real issue (see update of interfaces / connected_to
protected static $m_oCurrChange = null;
protected static $m_sInfo = null; // null => the information is built in a standard way
protected static $m_sUserId = null; // null => the user doing the change is unknown
protected static $m_sOrigin = null; // null => the origin is 'interactive'
/**
@@ -154,6 +155,21 @@ abstract class CMDBObject extends DBObject
self::$m_sInfo = $sInfo;
}
/**
* Provide information about the user doing the change
*
* @see static::SetTrackInfo
* @see static::SetCurrentChange
*
* @param string $sId ID of the user doing the change, null if not done by a user (eg. background task)
*
* @since 3.0.0
*/
public static function SetTrackUserId($sId)
{
self::$m_sUserId = $sId;
}
/**
* Provides information about the origin of the change
*
@@ -182,6 +198,25 @@ abstract class CMDBObject extends DBObject
return self::$m_sInfo;
}
}
/**
* Get the ID of the user doing the change (defaulting to null)
*
* @return string|null
* @throws \OQLException
* @since 3.0.0
*/
protected static function GetTrackUserId()
{
if (is_null(self::$m_sUserId))
{
return CMDBChange::GetCurrentUserId();
}
else
{
return self::$m_sUserId;
}
}
/**
* Get the 'origin' information (defaulting to 'interactive')
@@ -202,12 +237,21 @@ abstract class CMDBObject extends DBObject
* Set to {@link $m_oCurrChange} a standard change record (done here 99% of the time, and nearly once per page)
*
* The CMDBChange is persisted so that it has a key > 0, and any new CMDBChangeOp can link to it
*
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/
protected static function CreateChange()
{
self::$m_oCurrChange = MetaModel::NewObject("CMDBChange");
self::$m_oCurrChange->Set("date", time());
self::$m_oCurrChange->Set("userinfo", self::GetTrackInfo());
self::$m_oCurrChange->Set("user_id", self::GetTrackUserId());
self::$m_oCurrChange->Set("origin", self::GetTrackOrigin());
self::$m_oCurrChange->DBInsert();
}
@@ -513,7 +557,14 @@ abstract class CMDBObject extends DBObject
/**
* Helper to ultimately check user rights before writing (Insert, Update or Delete)
* The check should never fail, because the UI should prevent from such a usage
* Anyhow, if the user has found a workaround... the security gets enforced here
* Anyhow, if the user has found a workaround... the security gets enforced here
*
* @deprecated 3.0.0 N°2591 will be removed in 3.1.0
*
* @param bool $bSkipStrongSecurity
* @param int $iActionCode
*
* @throws \SecurityException
*/
protected function CheckUserRights($bSkipStrongSecurity, $iActionCode)
{
@@ -538,83 +589,6 @@ abstract class CMDBObject extends DBObject
}
}
/**
* @deprecated 2.7.0 N°2361 simply use {@link DBInsert} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@link CMDBObject::SetCurrentChange} before.
*
* @param \CMDBChange $oChange
* @param null $bSkipStrongSecurity
*
* @return int|null
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
* @throws \SecurityException
*/
public function DBInsertTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$ret = $this->DBInsertTracked_Internal();
return $ret;
}
/**
* @deprecated 2.7.0 N°2361 simply use {@link DBInsertNoReload} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@link CMDBObject::SetCurrentChange} before.
*
* @param \CMDBChange $oChange
* @param null $bSkipStrongSecurity
*
* @return int
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
* @throws \SecurityException
*/
public function DBInsertTrackedNoReload(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$ret = $this->DBInsertTracked_Internal(true);
return $ret;
}
/**
* @deprecated 2.7.0 N°2361 simply use {@link DBInsert} or {@link DBInsertNoReload} instead
*
* @param bool $bDoNotReload
*
* @return integer Identifier of the created object
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \CoreWarning
* @throws \MySQLException
* @throws \OQLException
*/
protected function DBInsertTracked_Internal($bDoNotReload = false)
{
if ($bDoNotReload)
{
$ret = $this->DBInsertNoReload();
}
else
{
$ret = $this->DBInsert();
}
return $ret;
}
public function DBClone($newKey = null)
{
return $this->DBCloneTracked_Internal();
@@ -647,24 +621,6 @@ abstract class CMDBObject extends DBObject
return $ret;
}
/**
* @deprecated 2.7.0 N°2361 simply use {@link DBUpdate} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@link CMDBObject::SetCurrentChange} before.
*
* @param \CMDBChange $oChange
* @param null $bSkipStrongSecurity
*
* @return int|void
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \SecurityException
*/
public function DBUpdateTracked(CMDBChange $oChange, $bSkipStrongSecurity = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_MODIFY);
$this->DBUpdate();
}
/**
* @param null $oDeletionPlan
@@ -684,31 +640,6 @@ abstract class CMDBObject extends DBObject
return $this->DBDeleteTracked_Internal($oDeletionPlan);
}
/**
* @deprecated 2.7.0 N°2361 simply use {@link DBDelete} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@link CMDBObject::SetCurrentChange} before.
*
* @param \CMDBChange $oChange
* @param null $bSkipStrongSecurity
* @param null $oDeletionPlan
*
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \SecurityException
*/
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
self::SetCurrentChange($oChange);
$this->CheckUserRights($bSkipStrongSecurity, UR_ACTION_DELETE);
$this->DBDeleteTracked_Internal($oDeletionPlan);
}
/**
* @param null $oDeletionPlan
*

View File

@@ -859,25 +859,6 @@ class CMDBSource
self::$m_iTransactionLevel = 0;
}
/**
*
* @deprecated 2.7.0 N°1627 use ItopCounter instead
*
* @param string $sTable
*
* @return int
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function GetNextInsertId($sTable)
{
$sSQL = "SHOW TABLE STATUS LIKE '$sTable'";
$oResult = self::Query($sSQL);
$aRow = $oResult->fetch_assoc();
return $aRow['Auto_increment'];
}
public static function GetInsertId()
{
$iRes = self::$m_oMysqli->insert_id;

File diff suppressed because it is too large Load Diff

View File

@@ -93,7 +93,7 @@ class CoreException extends Exception
* getTraceAsString() cannot be overrided and it is limited as only current exception stack is returned.
* we need stack of all previous exceptions
* @uses __tostring() already does the work.
* @since 2.7.2/ 2.8.0
* @since 2.7.2/ 3.0.0
*/
public function getFullStackTraceAsString(){
return "" . $this;

View File

@@ -301,7 +301,7 @@ EOF
}
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$aData = array();
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
@@ -339,7 +339,7 @@ EOF
// Restore original date & time formats
AttributeDateTime::SetFormat($oPrevDateTimeFormat);
AttributeDate::SetFormat($oPrevDateFormat);
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{

View File

@@ -135,7 +135,7 @@ class CSVParser
// More time for the next row
if ($this->m_iTimeLimitPerRow !== null)
{
set_time_limit($this->m_iTimeLimitPerRow);
set_time_limit(intval($this->m_iTimeLimitPerRow));
}
}
protected function __AddCellTrimmed($c = null, $aFieldMap = null)
@@ -194,7 +194,7 @@ class CSVParser
{
// Give some time for the first row
$iTimeLimit = ini_get('max_execution_time');
set_time_limit($this->m_iTimeLimitPerRow);
set_time_limit(intval($this->m_iTimeLimitPerRow));
}
for($i = 0; $i <= $iDataLength ; $i++)
{
@@ -255,7 +255,7 @@ class CSVParser
if ($iTimeLimit !== null)
{
// Restore the previous time limit
set_time_limit($iTimeLimit);
set_time_limit(intval($iTimeLimit));
}
return $this->m_aDataSet;
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.7">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
<user_rights>
<profiles>
<profile id="1024" _delta="define">

View File

@@ -547,74 +547,64 @@ abstract class DBObject implements iDisplay
*/
public function Set($sAttCode, $value)
{
if ($sAttCode == 'finalclass')
{
if ($sAttCode == 'finalclass') {
// Ignore it - this attribute is set upon object creation and that's it
return false;
}
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if (!$oAttDef->IsWritable())
{
if (!$oAttDef->IsWritable()) {
$sClass = get_class($this);
throw new Exception("Attempting to set the value on the read-only attribute $sClass::$sAttCode");
}
if ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
{
if ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty) {
// First time Set is called... ensure that the object gets fully loaded
// Otherwise we would lose the values on a further Reload
// + consistency does not make sense !
$this->Reload();
}
if ($oAttDef->IsExternalKey())
{
if (is_object($value))
{
// Setting an external key with a whole object (instead of just an ID)
// let's initialize also the external fields that depend on it
// (useful when building objects in memory and not from a query)
/** @var \AttributeExternalKey $oAttDef */
if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
{
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
}
if ($oAttDef->IsExternalKey() && is_object($value)) {
// Setting an external key with a whole object (instead of just an ID)
// let's initialize also the external fields that depend on it
// (useful when building objects in memory and not from a query)
/** @var \AttributeExternalKey $oAttDef */
if ((get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass()))) {
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
}
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
/** @var \AttributeExternalField $oDef */
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
{
/** @var \DBObject $value */
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
$this->m_aLoadedAtt[$sCode] = true;
}
foreach (MetaModel::GetDependentAttributes(get_class($this), $sAttCode) as $sCode) {
$oDef = MetaModel::GetAttributeDef(get_class($this), $sCode);
/** @var \AttributeExternalField $oDef */
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode)) {
/** @var \DBObject $value */
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
$this->m_aLoadedAtt[$sCode] = true;
}
elseif ($oDef->IsBasedOnOQLExpression()) {
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
unset($this->m_aLoadedAtt[$sCode]);
}
}
else if ($this->m_aCurrValues[$sAttCode] != $value)
{
// Setting an external key, but no any other information is available...
// Invalidate the corresponding fields so that they get reloaded in case they are needed (See Get())
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
{
/** @var \AttributeExternalKey $oDef */
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
{
} else {
if ($this->m_aCurrValues[$sAttCode] !== $value) {
// Invalidate dependent fields so that they get reloaded in case they are needed (See Get())
//
foreach (MetaModel::GetDependentAttributes(get_class($this), $sAttCode) as $sCode) {
$oDef = MetaModel::GetAttributeDef(get_class($this), $sCode);
if (($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode)) || $oDef->IsBasedOnOQLExpression()) {
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
unset($this->m_aLoadedAtt[$sCode]);
}
}
}
}
if ($oAttDef->IsLinkSet() && ($value != null))
{
if ($oAttDef->IsLinkSet() && ($value != null)) {
$realvalue = clone $this->m_aCurrValues[$sAttCode];
$realvalue->UpdateFromCompleteList($value);
}
else
{
} else {
$realvalue = $oAttDef->MakeRealValue($value, $this);
}
$this->_Set($sAttCode, $realvalue);
@@ -787,20 +777,20 @@ abstract class DBObject implements iDisplay
{
// Standard case... we have the information directly
}
elseif ($this->m_bIsInDB && !$this->m_bDirty)
elseif ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
{
// Lazy load (polymorphism): complete by reloading the entire object
// #@# non-scalar attributes.... handle that differently?
$oKPI = new ExecutionKPI();
$this->Reload();
$oKPI->ComputeStats('Reload', get_class($this).'/'.$sAttCode);
}
elseif ($sAttCode == 'friendlyname')
elseif ($oAttDef->IsBasedOnOQLExpression())
{
// The friendly name is not computed and the object is dirty
// Todo: implement the computation of the friendly name based on sprintf()
//
$this->m_aCurrValues[$sAttCode] = '';
// Recompute -which is likely to call Get()
//
/** @var AttributeFriendlyName|\AttributeObsolescenceFlag $oAttDef */
$this->m_aCurrValues[$sAttCode] = $this->EvaluateExpression($oAttDef->GetOQLExpression());
$this->m_aLoadedAtt[$sAttCode] = true;
}
else
{
@@ -1683,6 +1673,7 @@ abstract class DBObject implements iDisplay
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
{
$iFlags = 0; // By default (if no life cycle) no flag at all
$sClass = get_class($this);
$aReadOnlyAtts = $this->GetReadOnlyAttributes();
if (($aReadOnlyAtts != null) && (in_array($sAttCode, $aReadOnlyAtts)))
@@ -1690,16 +1681,16 @@ abstract class DBObject implements iDisplay
return OPT_ATT_READONLY;
}
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
if (!empty($sStateAttCode))
if (MetaModel::HasLifecycle($sClass))
{
if ($sTargetState != '')
{
$iFlags = MetaModel::GetAttributeFlags(get_class($this), $sTargetState, $sAttCode);
$iFlags = MetaModel::GetAttributeFlags($sClass, $sTargetState, $sAttCode);
}
else
{
$iFlags = MetaModel::GetAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode);
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$iFlags = MetaModel::GetAttributeFlags($sClass, $this->Get($sStateAttCode), $sAttCode);
}
}
$aReasons = array();
@@ -1752,10 +1743,10 @@ abstract class DBObject implements iDisplay
public function GetTransitionFlags($sAttCode, $sStimulus, &$aReasons = array(), $sOriginState = '')
{
$iFlags = 0; // By default (if no lifecycle) no flag at all
$sClass = get_class($this);
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
// If no state attribute, there is no lifecycle
if (empty($sStateAttCode))
if (!MetaModel::HasLifecycle($sClass))
{
return $iFlags;
}
@@ -1763,6 +1754,7 @@ abstract class DBObject implements iDisplay
// Retrieving current state if necessary
if ($sOriginState === '')
{
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$sOriginState = $this->Get($sStateAttCode);
}
@@ -1770,7 +1762,7 @@ abstract class DBObject implements iDisplay
$iAttributeFlags = $this->GetAttributeFlags($sAttCode, $aReasons, $sOriginState);
// Retrieving transition flags
$iTransitionFlags = MetaModel::GetTransitionFlags(get_class($this), $sOriginState, $sStimulus, $sAttCode);
$iTransitionFlags = MetaModel::GetTransitionFlags($sClass, $sOriginState, $sStimulus, $sAttCode);
// Merging transition flags with attribute flags
$iFlags = $iTransitionFlags | $iAttributeFlags;
@@ -1821,10 +1813,12 @@ abstract class DBObject implements iDisplay
public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array())
{
$iFlags = 0;
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
if (!empty($sStateAttCode))
$sClass = get_class($this);
if (MetaModel::HasLifecycle($sClass))
{
$iFlags = MetaModel::GetInitialStateAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode);
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$iFlags = MetaModel::GetInitialStateAttributeFlags($sClass, $this->Get($sStateAttCode), $sAttCode);
}
return $iFlags; // No need to care about the synchro flags since we'll be creating a new object anyway
}
@@ -1832,13 +1826,13 @@ abstract class DBObject implements iDisplay
/**
* Check if the given (or current) value is suitable for the attribute
*
* @api
* @api-advanced
*
* @param string $sAttCode
* @param boolean|string $value true if successful, the error description otherwise
* @api
* @api-advanced
*
* @return bool|string
* @param string $sAttCode
* @param mixed $value if not passed, then will get value using Get method
*
* @return bool|string true if successful, the error description otherwise
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
@@ -2118,23 +2112,75 @@ abstract class DBObject implements iDisplay
}
$aChildClassesWithRuleDisabled = MetaModel::GetChildClassesWithDisabledUniquenessRule($sRuleRootClass, $sUniquenessRuleId);
if (!empty($aChildClassesWithRuleDisabled))
{
if (!empty($aChildClassesWithRuleDisabled)) {
$oUniquenessQuery->AddConditionForInOperatorUsingParam('finalclass', $aChildClassesWithRuleDisabled, false);
}
return $oUniquenessQuery;
}
/**
* @param string $sAttCode
* @param mixed $value
*
* @uses m_aCheckWarnings to log to user duplicates found
*
* @since 3.0.0 N°3198 check duplicates if necessary :<br>
* Before we could only add or remove lnk using the uilinks widget. This widget has a filter based on existing values, and so
* forbids to add duplicates.<br>
* Now we can modify existing entries using the extkey widget, and the widget doesn't have (yet !) such
* a filter. The widget will be updated later on, but as a first step we're checking for duplicates server side. This is a non
* blocking warning, as it was already possible to add duplicates by other means.
*/
protected function DoCheckLinkedSetDuplicates($sAttCode, $value)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if (!($oAttDef instanceof AttributeLinkedSetIndirect)) {
return;
}
if ($oAttDef->DuplicatesAllowed()) {
return true;
}
/** @var \ormLinkSet $value */
$aModifiedLnk = $value->ListModifiedLinks();
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
$aExistingRemotesId = $value->GetColumnAsArray($sExtKeyToRemote, true);
$aExistingRemotesFriendlyName = $value->GetColumnAsArray($sExtKeyToRemote.'_friendlyname', true);
$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;
}
$iLnkId = $oModifiedLnk->GetKey();
$aDuplicatesFields[] = $aExistingRemotesFriendlyName[$iLnkId];
}
if (!empty($aDuplicatesFields)) {
$this->m_aCheckWarnings[] = Dict::Format('Core:AttributeLinkedSetDuplicatesFound',
$oAttDef->GetLabel(),
implode(', ', $aDuplicatesFields));
}
}
/**
* Check integrity rules (before inserting or updating the object)
*
* **This method is not meant to be called directly, use DBObject::CheckToWrite()!**
* **This method is not meant to be called directly, use DBObject::CheckToWrite()!**
* Errors should be inserted in $m_aCheckIssues and $m_aCheckWarnings arrays
*
* @overwritable-hook You can extend this method in order to provide your own logic.
* @see CheckToWrite()
* @see $m_aCheckIssues
*
* @overwritable-hook You can extend this method in order to provide your own logic.
* @see CheckToWrite()
* @see $m_aCheckIssues
* @see $m_aCheckWarnings
*
* @throws \ArchivedObjectException
@@ -2150,14 +2196,14 @@ abstract class DBObject implements iDisplay
$aChanges = $this->ListChanges();
foreach($aChanges as $sAttCode => $value)
{
foreach($aChanges as $sAttCode => $value) {
$res = $this->CheckValue($sAttCode);
if ($res !== true)
{
if ($res !== true) {
// $res contains the error description
$this->m_aCheckIssues[] = "Unexpected value for attribute '$sAttCode': $res";
}
$this->DoCheckLinkedSetDuplicates($sAttCode, $value);
}
if (count($this->m_aCheckIssues) > 0)
{
@@ -2960,46 +3006,6 @@ abstract class DBObject implements iDisplay
return $this->m_iKey;
}
/**
* @internal
*
* @deprecated 2.7.0 N°2361 simply use {@see DBObject::DBInsert()} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@see CMDBObject::SetCurrentChange()} before.
*
* @param CMDBChange $oChange
*
* @return int|null
* @throws CoreException
*/
public function DBInsertTracked(CMDBChange $oChange)
{
CMDBObject::SetCurrentChange($oChange);
return $this->DBInsert();
}
/**
* @internal
*
* @deprecated 2.7.0 N°2361 simply use {@see DBObject::DBInsertNoReload()} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@see CMDBObject::SetCurrentChange()} before.
*
* @param CMDBChange $oChange
*
* @return int
* @throws ArchivedObjectException
* @throws CoreCannotSaveObjectException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws CoreWarning
* @throws MySQLException
* @throws OQLException
*/
public function DBInsertTrackedNoReload(CMDBChange $oChange)
{
CMDBObject::SetCurrentChange($oChange);
return $this->DBInsertNoReload();
}
/**
* Creates a copy of the current object into the database
*
@@ -3358,25 +3364,6 @@ abstract class DBObject implements iDisplay
$this->m_aPreviousValuesForUpdatedAttributes = $aPreviousValuesForUpdatedAttributes;
}
/**
*
* @internal
*
* @deprecated 2.7.0 N°2361 simply use {@see DBObject::DBUpdate()} instead, that will automatically create and persist a CMDBChange object.
* If you need to persist your own, call {@see CMDBObject::SetCurrentChange()} before.
*
* @param CMDBChange $oChange
*
* @return int
* @throws CoreCannotSaveObjectException
* @throws CoreException
*/
public function DBUpdateTracked(CMDBChange $oChange)
{
CMDBObject::SetCurrentChange($oChange);
return $this->DBUpdate();
}
/**
* Make the current changes persistent - clever wrapper for Insert or Update
*
@@ -3612,7 +3599,7 @@ abstract class DBObject implements iDisplay
// As a temporary fix: delete only the objects that are still to be deleted...
if ($oToDelete->m_bIsInDB)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$oToDelete->DBDeleteSingleObject();
}
}
@@ -3627,43 +3614,18 @@ abstract class DBObject implements iDisplay
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
{
$oToUpdate->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$oToUpdate->DBUpdate();
}
}
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
return $oDeletionPlan;
}
/**
* @internal
*
* @deprecated 2.7.0 N°2361 simply use {@see DBObject::DBDelete()} instead.
* If you need to persist your own, call {@see CMDBObject::SetCurrentChange()} before.
*
* @param CMDBChange $oChange
* @param boolean $bSkipStrongSecurity
* @param \DeletionPlan $oDeletionPlan
*
* @throws ArchivedObjectException
* @throws CoreCannotSaveObjectException
* @throws CoreException
* @throws CoreUnexpectedValue
* @throws DeleteException
* @throws MySQLException
* @throws MySQLHasGoneAwayException
* @throws OQLException
*/
public function DBDeleteTracked(CMDBChange $oChange, $bSkipStrongSecurity = null, &$oDeletionPlan = null)
{
CMDBObject::SetCurrentChange($oChange);
$this->DBDelete($oDeletionPlan);
}
/**
/**
* @internal
*
* @return array
@@ -3673,11 +3635,12 @@ abstract class DBObject implements iDisplay
*/
public function EnumTransitions()
{
$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
if (empty($sStateAttCode)) return array();
$sClass = get_class($this);
if (!MetaModel::HasLifecycle($sClass)) return array();
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$sState = $this->Get($sStateAttCode);
return MetaModel::EnumTransitions(get_class($this), $sState);
return MetaModel::EnumTransitions($sClass, $sState);
}
/**
@@ -3723,14 +3686,14 @@ abstract class DBObject implements iDisplay
public function ApplyStimulus($sStimulusCode, $bDoNotWrite = false)
{
$sClass = get_class($this);
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
if (empty($sStateAttCode))
if (!MetaModel::HasLifecycle($sClass))
{
throw new CoreException('No lifecycle for the class '.$sClass);
}
MyHelpers::CheckKeyInArray('object lifecycle stimulus', $sStimulusCode, MetaModel::EnumStimuli($sClass));
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$aStateTransitions = $this->EnumTransitions();
if (!array_key_exists($sStimulusCode, $aStateTransitions))
{
@@ -4458,22 +4421,7 @@ abstract class DBObject implements iDisplay
}
/**
* implement relations
*
* Return an empty set for the parent of all
*
* this way of implementing the relations suffers limitations (not handling the redundancy)
* and you should consider defining those things in XML
*
* @internal
* @deprecated
*/
public static function GetRelationQueries($sRelCode)
{
return array();
}
/**
* Reserved: do not overload
*
@@ -4484,72 +4432,6 @@ abstract class DBObject implements iDisplay
return array();
}
/**
* Use GetRelatedObjectsDown/Up instead to take redundancy into account
*
* @internal
* @deprecated
*/
public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array())
{
// Temporary patch: until the impact analysis GUI gets rewritten,
// let's consider that "depends on" is equivalent to "impacts/up"
// The current patch has been implemented in DBObject and MetaModel
$sHackedRelCode = $sRelCode;
$bDown = true;
if ($sRelCode == 'depends on')
{
$sHackedRelCode = 'impacts';
$bDown = false;
}
foreach (MetaModel::EnumRelationQueries(get_class($this), $sHackedRelCode, $bDown) as $sDummy => $aQueryInfo)
{
$sQuery = $bDown ? $aQueryInfo['sQueryDown'] : $aQueryInfo['sQueryUp'];
//$bPropagate = $aQueryInfo["bPropagate"];
//$iDepth = $bPropagate ? $iMaxDepth - 1 : 0;
$iDepth = $iMaxDepth - 1;
// Note: the loop over the result set has been written in an unusual way for error reporting purposes
// In the case of a wrong query parameter name, the error occurs on the first call to Fetch,
// thus we need to have this first call into the try/catch, but
// we do NOT want to nest the try/catch for the error message to be clear
try
{
$oFlt = DBObjectSearch::FromOQL($sQuery);
$oObjSet = new DBObjectSet($oFlt, array(), $this->ToArgsForQuery());
$oObj = $oObjSet->Fetch();
}
catch (Exception $e)
{
$sClassOfDefinition = $aQueryInfo['_legacy_'] ? get_class($this).'(or a parent)::GetRelationQueries()' : $aQueryInfo['sDefinedInClass'];
throw new Exception("Wrong query for the relation $sRelCode/$sClassOfDefinition/{$aQueryInfo['sNeighbour']}: ".$e->getMessage());
}
if ($oObj)
{
do
{
$sRootClass = MetaModel::GetRootClass(get_class($oObj));
$sObjKey = $oObj->GetKey();
if (array_key_exists($sRootClass, $aResults))
{
if (array_key_exists($sObjKey, $aResults[$sRootClass]))
{
continue; // already visited, skip
}
}
$aResults[$sRootClass][$sObjKey] = $oObj;
if ($iDepth > 0)
{
$oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults);
}
}
while ($oObj = $oObjSet->Fetch());
}
}
return $aResults;
}
/**
* Compute the "RelatedObjects" (forward or "down" direction) for the object
* for the specified relation
@@ -4683,7 +4565,7 @@ abstract class DBObject implements iDisplay
{
foreach ($aPotentialDeletes as $sRemoteExtKey => $aData)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
/** @var \AttributeExternalKey $oAttDef */
$oAttDef = $aData['attribute'];
@@ -4715,7 +4597,7 @@ abstract class DBObject implements iDisplay
}
}
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
}
/**
@@ -5461,10 +5343,10 @@ abstract class DBObject implements iDisplay
*/
public static function MakeDefaultInstance($sClass)
{
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$oObj = MetaModel::NewObject($sClass);
if (!empty($sStateAttCode))
if (MetaModel::HasLifecycle($sClass))
{
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$sTargetState = MetaModel::GetDefaultState($sClass);
$oObj->Set($sStateAttCode, $sTargetState);
}
@@ -5561,5 +5443,33 @@ abstract class DBObject implements iDisplay
break;
}
}
public function EvaluateExpression(Expression $oExpression)
{
$aFields = $oExpression->ListRequiredFields();
$aArgs = array();
foreach ($aFields as $sFieldDesc)
{
$aFieldParts = explode('.', $sFieldDesc);
if (count($aFieldParts) == 2)
{
$sClass = $aFieldParts[0];
$sAttCode = $aFieldParts[1];
}
else
{
$sClass = get_class($this);
$sAttCode = $aFieldParts[0];
}
if (get_class($this) != $sClass) continue;
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode)) continue;
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$aSQLValues = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
$value = reset($aSQLValues);
$aArgs[$sFieldDesc] = $value;
}
return $oExpression->Evaluate($aArgs);
}
}

View File

@@ -907,10 +907,14 @@ class DBObjectSearch extends DBSearch
}
/**
* @param DBObjectSearch $oFilter (can be modified)
* @param $sExtKeyAttCode
* Add a link to another filter, using an extkey already present in current filter
*
* @param DBObjectSearch $oFilter filter to join to (can be modified)
* @param string $sExtKeyAttCode extkey present in current filter, that allows to points to $oFilter
* @param int $iOperatorCode
* @param null $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed
* @param array $aRealiasingMap array of <old-alias> => <new-alias>, for each alias that has changed.
* Doesn't change existing alias, use {@link \DBObjectSearch::RenameAlias()} for that.
*
* @throws CoreException
* @throws CoreWarning
*/
@@ -1372,7 +1376,7 @@ class DBObjectSearch extends DBSearch
// Make the list of acceptable arguments... could be factorized with run_query, into oSearch->GetQueryParams($bExclude magic params)
$aNakedMagicArguments = array();
foreach (MetaModel::PrepareQueryArguments(array()) as $sArgName => $value)
foreach (MetaModel::PrepareQueryArguments(array(),array(), $this->GetExpectedArguments()) as $sArgName => $value)
{
$iPos = strpos($sArgName, '->object()');
if ($iPos === false)
@@ -1435,7 +1439,7 @@ class DBObjectSearch extends DBSearch
{
$aParams = array_merge($aContextParams, $this->m_aParams);
}
$aParams = MetaModel::PrepareQueryArguments($aParams);
$aParams = MetaModel::PrepareQueryArguments($aParams,array(), $this->GetExpectedArguments());
}
else
{
@@ -1628,7 +1632,7 @@ class DBObjectSearch extends DBSearch
$aRet = array('selects' => array(), 'joins' => array(), 'where' => array());
$aParams = array_merge($this->m_aParams);
$aParams = MetaModel::PrepareQueryArguments($aParams);
$aParams = MetaModel::PrepareQueryArguments($aParams, array(), $this->GetExpectedArguments());
foreach ($this->m_aSelectedClasses as $sAlias => $sClass)
{
@@ -1835,7 +1839,7 @@ class DBObjectSearch extends DBSearch
{
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($this);
$oSQLQuery = $oSQLObjectQueryBuilder->MakeSQLObjectDeleteQuery();
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams(), $this->GetExpectedArguments());
$sRet = $oSQLQuery->RenderDelete($aScalarArgs);
return $sRet;
}
@@ -1851,7 +1855,7 @@ class DBObjectSearch extends DBSearch
{
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($this);
$oSQLQuery = $oSQLObjectQueryBuilder->MakeSQLObjectUpdateQuery($aValues);
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams(), $this->GetExpectedArguments());
$sRet = $oSQLQuery->RenderUpdate($aScalarArgs);
return $sRet;
}
@@ -1870,7 +1874,7 @@ class DBObjectSearch extends DBSearch
{
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($this);
$oSQLQuery = $oSQLObjectQueryBuilder->MakeSQLObjectUpdateQuery($aValues);
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams());
$aScalarArgs = MetaModel::PrepareQueryArguments($aArgs, $this->GetInternalParams(), $this->GetExpectedArguments());
$sRet = $oSQLQuery->RenderInsert($aScalarArgs);
return $sRet;
}

View File

@@ -222,6 +222,7 @@ class DBObjectSet implements iDBObjectSetIterator
if (!is_array($aAttToLoad))
{
$this->m_aAttToLoad = null;
trigger_error ( "OptimizeColumnLoad : wrong format actual :(".print_r($aAttToLoad, true)."). should be [alias=>[attributes]]", E_USER_WARNING );
return;
}
foreach ($aAttToLoad as $sAlias => $aAttCodes)
@@ -229,6 +230,7 @@ class DBObjectSet implements iDBObjectSetIterator
if (!is_array($aAttCodes))
{
$this->m_aAttToLoad = null;
trigger_error ( "OptimizeColumnLoad : wrong format actual :(".print_r($aAttToLoad, true)."). should be [alias=>[attributes]]", E_USER_WARNING );
return;
}
}
@@ -267,6 +269,7 @@ class DBObjectSet implements iDBObjectSetIterator
}
}
}
// Add the friendly name anytime
$oFriendlyNameAttDef = MetaModel::GetAttributeDef($sClass, 'friendlyname');
$aAttToLoadWithAttDef[$sClassAlias]['friendlyname'] = $oFriendlyNameAttDef;
@@ -1312,34 +1315,6 @@ class DBObjectSet implements iDBObjectSetIterator
return $oNewSet;
}
/**
* Will be deprecated soon - use MetaModel::GetRelatedObjectsDown/Up instead to take redundancy into account
*
* @throws \Exception
*/
public function GetRelatedObjects($sRelCode, $iMaxDepth = 99)
{
$aRelatedObjs = array();
$aVisited = array(); // optimization for consecutive calls of MetaModel::GetRelatedObjects
$this->Seek(0);
while ($oObject = $this->Fetch())
{
$aMore = $oObject->GetRelatedObjects($sRelCode, $iMaxDepth, $aVisited);
foreach ($aMore as $sClass => $aRelated)
{
foreach ($aRelated as $iObj => $oObj)
{
if (!isset($aRelatedObjs[$sClass][$iObj]))
{
$aRelatedObjs[$sClass][$iObj] = $oObj;
}
}
}
}
return $aRelatedObjs;
}
/**
* Compute the "RelatedObjects" (forward or "down" direction) for the set
* for the specified relation
@@ -1496,7 +1471,7 @@ class DBObjectSet implements iDBObjectSetIterator
public function ListConstantFields()
{
// The complete list of arguments will include magic arguments (e.g. current_user->attcode)
$aScalarArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs);
$aScalarArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs, $this->m_oFilter->ListParameters());
$aConst = $this->m_oFilter->ListConstantFields();
foreach($aConst as $sClassAlias => $aVals)
@@ -1515,7 +1490,7 @@ class DBObjectSet implements iDBObjectSetIterator
public function ApplyParameters()
{
$aAllArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs);
$aAllArgs = MetaModel::PrepareQueryArguments($this->m_oFilter->GetInternalParams(), $this->m_aArgs, $this->m_oFilter->GetExpectedArguments());
$this->m_oFilter->ApplyParameters($aAllArgs);
}
}

View File

@@ -603,11 +603,11 @@ abstract class DBSearch
elseif (($iPos = strpos($sParam, '->')) !== false)
{
$sParamName = substr($sParam, 0, $iPos);
if (isset($aContextParams[$sParamName.'->object()']))
if (isset($aContextParams[$sParamName.'->object()']) || isset($aContextParams[$sParamName]))
{
$sAttCode = substr($sParam, $iPos + 2);
/** @var \DBObject $oObj */
$oObj = $aContextParams[$sParamName.'->object()'];
$oObj = isset($aContextParams[$sParamName.'->object()']) ? $aContextParams[$sParamName.'->object()'] : $aContextParams[$sParamName];
if ($oObj->IsModified())
{
if ($sAttCode == 'id')

View File

@@ -116,7 +116,7 @@ class DeletionPlan
{
foreach($aToUpdate as $iId => $aData)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$this->m_iToUpdate++;
$oObject = $aData['to_reset'];
@@ -142,7 +142,7 @@ class DeletionPlan
}
}
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
}
public function GetIssues()

View File

@@ -874,7 +874,7 @@ class DisplayableGraph extends SimpleGraph
$oNodesIter = new RelationTypeIterator($oGraph, 'Node');
foreach($oNodesIter as $oNode)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
switch(get_class($oNode))
{
case 'RelationObjectNode':
@@ -921,7 +921,7 @@ class DisplayableGraph extends SimpleGraph
$oEdgesIter = new RelationTypeIterator($oGraph, 'Edge');
foreach($oEdgesIter as $oEdge)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$oSourceNode = $oNewGraph->GetNode($oEdge->GetSourceNode()->GetId());
$oSinkNode = $oNewGraph->GetNode($oEdge->GetSinkNode()->GetId());
$oNewEdge = new DisplayableEdge($oNewGraph, $oEdge->GetId(), $oSourceNode, $oSinkNode);
@@ -932,7 +932,7 @@ class DisplayableGraph extends SimpleGraph
$aEdgeKeys = array();
foreach($oEdgesIter as $oEdge)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$sSourceId = $oEdge->GetSourceNode()->GetId();
$sSinkId = $oEdge->GetSinkNode()->GetId();
if ($sSourceId == $sSinkId)
@@ -958,7 +958,7 @@ class DisplayableGraph extends SimpleGraph
$oNodesIter = new RelationTypeIterator($oNewGraph, 'Node');
foreach($oNodesIter as $oNode)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
if ($bDirectionDown && $oNode->GetProperty('source'))
{
$oNode->GroupSimilarNeighbours($oNewGraph, $iGroupingThreshold, true, $bDirectionDown);
@@ -973,7 +973,7 @@ class DisplayableGraph extends SimpleGraph
$iGroupIdx = 0;
foreach($oIterator as $oNode)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
if ($oNode instanceof DisplayableGroupNode)
{
if ($oNode->GetObjectCount() == 0)
@@ -995,7 +995,7 @@ class DisplayableGraph extends SimpleGraph
$aEdgeKeys = array();
foreach($oEdgesIter as $oEdge)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$sSourceId = $oEdge->GetSourceNode()->GetId();
$sSinkId = $oEdge->GetSinkNode()->GetId();
if ($sSourceId == $sSinkId)
@@ -1017,7 +1017,7 @@ class DisplayableGraph extends SimpleGraph
}
}
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
return $oNewGraph;
}
@@ -1257,14 +1257,14 @@ class DisplayableGraph extends SimpleGraph
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
foreach($oIterator as $sId => $oEdge)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$oEdge->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs);
}
$oIterator = new RelationTypeIterator($this, 'Node');
foreach($oIterator as $sId => $oNode)
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$oNode->RenderAsPDF($oPdf, $this, $fScale, $aContextDefs);
}

View File

@@ -100,7 +100,7 @@ class EMail
}
if (array_key_exists('reply_to', $aData))
{
$oMessage->SetRecipientReplyTo($aData['reply_to']);
$oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']);
}
if (array_key_exists('to', $aData))
{
@@ -465,10 +465,14 @@ class EMail
}
}
public function SetRecipientReplyTo($sAddress)
public function SetRecipientReplyTo($sAddress, $sLabel = '')
{
$this->m_aData['reply_to'] = $sAddress;
if (!empty($sAddress))
$this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel);
if ($sLabel != '')
{
$this->m_oMessage->setReplyTo(array($sAddress => $sLabel));
}
else if (!empty($sAddress))
{
$this->m_oMessage->setReplyTo($sAddress);
}

View File

@@ -31,7 +31,6 @@ class Event extends DBObject implements iDisplay
"db_table" => "priv_event",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
@@ -127,7 +126,6 @@ class EventNotification extends Event
"db_table" => "priv_event_notification",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false),
'indexes' => array(
array('object_id'),
@@ -163,7 +161,6 @@ class EventNotificationEmail extends EventNotification
"db_table" => "priv_event_email",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
@@ -201,7 +198,6 @@ class EventIssue extends Event
"db_table" => "priv_event_issue",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
@@ -301,7 +297,6 @@ class EventWebService extends Event
"db_table" => "priv_event_webservice",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);
@@ -337,7 +332,6 @@ class EventRestService extends Event
"db_table" => "priv_event_restservice",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
"order_by_default" => array('date' => false)
);
MetaModel::Init_Params($aParams);

View File

@@ -296,7 +296,7 @@ EOF
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$aData = array();
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
{
@@ -314,7 +314,7 @@ EOF
fwrite($hFile, json_encode($aData)."\n");
$iCount++;
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{

View File

@@ -118,7 +118,7 @@ EOF;
{
$sKey = static::GetKey($sClass, $sAttCode);
$oExpr = DBObjectSearch::GetPolymorphicExpression($sClass, $sAttCode);
return "'".$sKey."' => '".serialize($oExpr)."',\n";
return var_export($sKey, true)." => ".var_export(serialize($oExpr), true).",\n"; // Beware, string values can contain quotes, backslashes, etc!
}
/**

View File

@@ -128,7 +128,7 @@ class HTMLBulkExport extends TabularBulkExport
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$oMainObj = $aRow[$sFirstAlias];
$sHilightClass = '';
if ($oMainObj)
@@ -160,7 +160,7 @@ class HTMLBulkExport extends TabularBulkExport
$sData .= "</tr>";
$iCount++;
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{

View File

@@ -24,14 +24,14 @@ abstract class HTMLSanitizer
{
// Do nothing..
}
/**
* Sanitizes the given HTML document
* @param string $sHTML
* @return string
*/
abstract public function DoSanitize($sHTML);
/**
* Sanitize an HTML string with the configured sanitizer, falling back to HTMLDOMSanitizer in case of Exception or invalid configuration
* @param string $sHTML
@@ -50,7 +50,7 @@ abstract class HTMLSanitizer
IssueLog::Warning('The configured "html_sanitizer" class "'.$sSanitizerClass.'" is not a subclass of HTMLSanitizer. Will use HTMLDOMSanitizer as the default sanitizer.');
$sSanitizerClass = 'HTMLDOMSanitizer';
}
try
{
$oSanitizer = new $sSanitizerClass();
@@ -70,7 +70,7 @@ abstract class HTMLSanitizer
{
IssueLog::Error('Failed to sanitize an HTML string with "HTMLDOMSanitizer". The following exception occured: '.$e->getMessage());
IssueLog::Error('The HTML will NOT be sanitized.');
$sCleanHTML = $sHTML;
$sCleanHTML = $sHTML;
}
}
return $sCleanHTML;
@@ -97,7 +97,7 @@ class HTMLNullSanitizer extends HTMLSanitizer
{
return $sHTML;
}
}
/**
@@ -109,7 +109,7 @@ class HTMLNullSanitizer extends HTMLSanitizer
class HTMLPurifierSanitizer extends HTMLSanitizer
{
protected static $oPurifier = null;
public function __construct()
{
if (self::$oPurifier == null)
@@ -120,7 +120,7 @@ class HTMLPurifierSanitizer extends HTMLSanitizer
throw new Exception("Missing library '$sLibPath', cannot use HTMLPurifierSanitizer.");
}
require_once($sLibPath);
$oPurifierConfig = HTMLPurifier_Config::createDefault();
$oPurifierConfig->set('Core.Encoding', 'UTF-8'); // defaults to 'UTF-8'
$oPurifierConfig->set('HTML.Doctype', 'XHTML 1.0 Strict'); // defaults to 'XHTML 1.0 Transitional'
@@ -142,11 +142,11 @@ class HTMLPurifierSanitizer extends HTMLSanitizer
self::$oPurifier = new HTMLPurifier($oPurifierConfig);
}
}
public function DoSanitize($sHTML)
{
$sCleanHtml = self::$oPurifier->purify($sHTML);
return $sCleanHtml;
return $sCleanHtml;
}
}
*/
@@ -278,13 +278,13 @@ class HTMLDOMSanitizer extends HTMLSanitizer
$sHTML = preg_replace('~\xc2\xa0~', ' ', $sHTML);
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
$this->CleanNode($this->oDoc);
$oXPath = new DOMXPath($this->oDoc);
$sXPath = "//body";
$oNodesList = $oXPath->query($sXPath);
if ($oNodesList->length == 0)
{
// No body, save the whole document
@@ -297,10 +297,10 @@ class HTMLDOMSanitizer extends HTMLSanitizer
// remove the body tag itself
$sCleanHtml = str_replace( array('<body>', '</body>'), '', $sCleanHtml);
}
return $sCleanHtml;
}
protected function CleanNode(DOMNode $oElement)
{
$aAttrToRemove = array();
@@ -341,7 +341,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
$oElement->removeAttribute($sName);
}
}
if ($oElement->hasChildNodes())
{
$aChildElementsToRemove = array();
@@ -390,7 +390,7 @@ class HTMLDOMSanitizer extends HTMLSanitizer
}
return implode(';', $aAllowedStyles);
}
protected function IsValidAttributeContent($sAttributeName, $sValue)
{
if (array_key_exists($sAttributeName, self::$aAttrsWhiteList))

View File

@@ -1,30 +1,27 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Copyright (C) 2013-2020 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
*/
define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_inlineimage&id=');
/**
* Persistent classes (internal): store images referenced inside HTML formatted text fields
*
* @copyright Copyright (C) 2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class InlineImage extends DBObject
{
/** @var string attribute to be added to IMG tags to contain ID */
@@ -32,6 +29,11 @@ class InlineImage extends DBObject
/** @var string attribute to be added to IMG tags to contain secret */
const DOM_ATTR_SECRET = 'data-img-secret';
/**
*
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
@@ -69,8 +71,9 @@ class InlineImage extends DBObject
/**
* Maps the given context parameter name to the appropriate filter/search code for this class
*
* @param string $sContextParam Name of the context parameter, e.g. 'org_id'
* @return string Filter code, e.g. 'customer_id'
* @return string|null Filter code, e.g. 'customer_id'
*/
public static function MapContextParam($sContextParam)
{
@@ -86,8 +89,15 @@ class InlineImage extends DBObject
/**
* Set/Update all of the '_item' fields
*
* @param DBObject $oItem Container item
* @param bool $bUpdateOnChange
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
*/
public function SetItem(DBObject $oItem, $bUpdateOnChange = false)
{
@@ -121,7 +131,12 @@ class InlineImage extends DBObject
/**
* Give a default value for item_org_id (if relevant...)
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function SetDefaultOrgId()
{
@@ -157,12 +172,19 @@ class InlineImage extends DBObject
}
}
}
/**
* When posting a form, finalize the creation of the inline images
* related to the specified object
*
*
* @param DBObject $oObject
*
* @return void
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
public static function FinalizeInlineImages(DBObject $oObject)
{
@@ -203,10 +225,21 @@ class InlineImage extends DBObject
));
}
}
/**
* Cleanup the pending images if the form is not submitted
*
* @param string $sTempId
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public static function OnFormCancel($sTempId)
{
@@ -227,13 +260,16 @@ class InlineImage extends DBObject
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
));
}
/**
* Parses the supplied HTML fragment to rebuild the attribute src="" for images
* that refer to an InlineImage (detected via the attribute data-img-id="") so that
* the URL is consistent with the current URL of the application.
*
* @param string $sHtml The HTML fragment to process
*
* @return string The modified HTML
* @throws \Exception
*/
public static function FixUrls($sHtml)
{
@@ -268,6 +304,9 @@ class InlineImage extends DBObject
* so that we can later reconstruct the full "src" URL when needed
*
* @param \DOMElement $oElement
*
* @return void
* @throws \Exception
*/
public static function ProcessImageTag(DOMElement $oElement)
{
@@ -301,6 +340,8 @@ class InlineImage extends DBObject
/**
* Get the javascript fragment - to be added to "on document ready" - to adjust (on the fly) the width on Inline Images
*
* @return string
*/
public static function FixImagesWidth()
{
@@ -309,15 +350,15 @@ class InlineImage extends DBObject
if ($iMaxWidth != 0)
{
$sJS =
<<<EOF
<<<JS
$('img[data-img-id]').each(function() {
if ($(this).width() > $iMaxWidth)
if ($(this).width() > {$iMaxWidth})
{
$(this).css({'max-width': '{$iMaxWidth}px', width: '', height: '', 'max-height': ''});
}
$(this).addClass('inline-image').attr('href', $(this).attr('src'));
}).magnificPopup({type: 'image', closeOnContentClick: true });
EOF
JS
;
}
@@ -563,6 +604,9 @@ JS
;
}
/**
* @inheritDoc
*/
protected function AfterInsert()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
@@ -581,6 +625,9 @@ JS
parent::AfterInsert();
}
/**
* @inheritDoc
*/
protected function AfterUpdate()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
@@ -599,7 +646,10 @@ JS
parent::AfterUpdate();
}
protected function AfterDelete()
/**
* @inheritDoc
*/
protected function AfterDelete()
{
IssueLog::Trace(__METHOD__, 'InlineImage', array(
'id' => $this->GetKey(),
@@ -625,20 +675,16 @@ JS
*/
class InlineImageGC implements iBackgroundProcess
{
public function GetPeriodicity()
/**
* @inheritDoc
*/
public function GetPeriodicity()
{
return 1;
}
/**
* @param int $iTimeLimit
*
* @return string
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \OQLException
* @inheritDoc
*/
public function Process($iTimeLimit)
{
@@ -692,5 +738,5 @@ class InlineImageGC implements iBackgroundProcess
}
return $iProcessed;
}
}
}

View File

@@ -2602,5 +2602,9 @@ class DBObjectSearch extends DBSearch
return $oExpression;
}
public function ListParameters()
{
return $this->GetCriteria()->ListParameters();
}
}

View File

@@ -129,8 +129,7 @@ abstract class RotatingLogFileNameBuilder implements iLogFileNameBuilder
public function CheckAndRotateLogFile()
{
$oConfig = utils::GetConfig();
$sItopTimeZone = $oConfig->Get('timezone');
$timezone = new DateTimeZone($sItopTimeZone);
utils::InitTimeZone($oConfig);
if ($this->GetLastModifiedDateForFile() === null)
{
@@ -145,11 +144,13 @@ abstract class RotatingLogFileNameBuilder implements iLogFileNameBuilder
return;
}
$oDateTime = DateTime::createFromFormat('U', $iLogDateLastModifiedTimeStamp);
$sItopTimeZone = $oConfig->Get('timezone');
$timezone = new DateTimeZone($sItopTimeZone);
$oDateTime->setTimezone($timezone);
$this->SetLastModifiedDateForFile($oDateTime);
}
$oNow = new DateTime('now', $timezone);
$oNow = new DateTime();
$bShouldRotate = $this->ShouldRotate($this->GetLastModifiedDateForFile(), $oNow);
if (!$bShouldRotate)
{
@@ -830,4 +831,4 @@ class LogFileRotationProcess implements iScheduledProcess
throw new ProcessException(self::class.' : The configured filename builder is invalid (log_filename_builder_impl="'.$sLogFileNameBuilder.'")');
}
}
}

View File

@@ -126,6 +126,13 @@ abstract class MetaModel
/** @var string */
protected static $m_sEnvironment = 'production';
/**
* MetaModel constructor.
*/
public function __construct()
{
}
/**
* @return array
*/
@@ -722,7 +729,51 @@ abstract class MetaModel
return array('%1$s', array($nameRawSpec));
}
}
/**
* @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"]))
{
return array($sClass, array());
}
$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
*
@@ -802,12 +853,33 @@ abstract class MetaModel
}
/**
* Return true if the $sClass has a state attribute defined.
*
* Note that having a state attribute does NOT mean having a lifecycle!
* - A Person with active/inactive state won't have transitions and therefore no lifecycle
* - A UserRequest will have transitions between its states and so a lifecycle
*
* @see self::HasLifecycle($sClass)
* @param string $sClass Datamodel class to check
*
* @return bool
* @throws \CoreException
* @since 3.0.0
*/
final public static function HasStateAttributeCode(string $sClass)
{
return !empty(self::GetStateAttributeCode($sClass));
}
/**
* Return the code of the attribute carrying the state of the instance of the class
*
* @param string $sClass
*
* @return string
* @throws \CoreException
*/
final public static function GetStateAttributeCode($sClass)
final public static function GetStateAttributeCode(string $sClass)
{
self::_check_subclass($sClass);
@@ -821,7 +893,7 @@ abstract class MetaModel
* @throws \CoreException
* @throws \Exception
*/
final public static function GetDefaultState($sClass)
final public static function GetDefaultState(string $sClass)
{
$sDefaultState = '';
$sStateAttrCode = self::GetStateAttributeCode($sClass);
@@ -833,6 +905,37 @@ abstract class MetaModel
return $sDefaultState;
}
/**
* Return true if the $sClass has an image attribute defined
*
* @param string $sClass
*
* @return bool
* @throws \CoreException
* @since 3.0.0
*/
final public static function HasImageAttributeCode(string $sClass)
{
return !empty(self::GetImageAttributeCode($sClass));
}
/**
* Return the code of the attribute carrying the image representing an instance of the class
*
* @param string $sClass Datamodel class to get the image attribute code for
*
* @return mixed
* @throws \CoreException
* @since 3.0.0
*/
final public static function GetImageAttributeCode(string $sClass)
{
self::_check_subclass($sClass);
// image_attcode isn't a mandatory class parameter, so it might not be in the $m_aClassParam array
return isset(self::$m_aClassParams[$sClass]["image_attcode"]) ? self::$m_aClassParams[$sClass]["image_attcode"] : '';
}
/**
* @param string $sClass
*
@@ -846,20 +949,6 @@ abstract class MetaModel
return self::$m_aClassParams[$sClass]["reconc_keys"];
}
/**
* @param string $sClass
*
* @return string
* @throws \CoreException
*/
final public static function GetDisplayTemplate($sClass)
{
self::_check_subclass($sClass);
return array_key_exists("display_template",
self::$m_aClassParams[$sClass]) ? self::$m_aClassParams[$sClass]["display_template"] : '';
}
/**
* @param string $sClass
* @param bool $bOnlyDeclared
@@ -926,11 +1015,6 @@ abstract class MetaModel
{
self::_check_subclass($sClass);
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
// Temporary implementation: later, we might be able to compute
// the dependencies, based on the attributes definition
// (allowed values and default values)
// Even non-writable attributes (like ExternalFields) can now have Prerequisites
return $oAtt->GetPrerequisiteAttributes();
}
@@ -1221,15 +1305,36 @@ abstract class MetaModel
}
/**
* Return an array of attributes codes for the $sClass. The list can be limited to some attribute types only.
*
* @param string $sClass
* @param string[] $aDesiredAttTypes Array of AttributeDefinition classes to filter the list on
*
* @return array
* @throws \CoreException
*/
final public static function GetAttributesList($sClass)
final public static function GetAttributesList($sClass, $aDesiredAttTypes = [])
{
self::_check_subclass($sClass);
return array_keys(self::$m_aAttribDefs[$sClass]);
if(empty($aDesiredAttTypes))
{
return array_keys(self::$m_aAttribDefs[$sClass]);
}
$aMatchingAttCodes = [];
foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef)
{
foreach($aDesiredAttTypes as $sDesiredAttType)
{
if(is_a($oAttDef, $sDesiredAttType))
{
$aMatchingAttCodes[] = $sAttCode;
}
}
}
return $aMatchingAttCodes;
}
/**
@@ -1448,6 +1553,7 @@ abstract class MetaModel
{
$aLinkedSets = array();
foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) {
// Note: Careful, this will only return SUB-classes, which does NOT include AttributeLinkedset itself! We might want to use "is_a()" instead.
if (is_subclass_of($oAtt, 'AttributeLinkedSet')) {
$aLinkedSets[$sAttCode] = $oAtt;
}
@@ -1495,6 +1601,27 @@ abstract class MetaModel
}
}
/** @var array Cache for caselog attributes of the classes */
protected static $m_aCaseLogsAttributesCache = [];
/**
* Return an array of attribute codes for the caselogs attributes of $sClass
*
* @param string $sClass
*
* @return array
* @throws \CoreException
* @since 3.0.0
*/
final public static function GetCaseLogs(string $sClass)
{
if (!isset(static::$m_aCaseLogsAttributesCache[$sClass])) {
static::$m_aCaseLogsAttributesCache[$sClass] = self::GetAttributesList($sClass, ['AttributeCaseLog']);
}
return static::$m_aCaseLogsAttributesCache[$sClass];
}
/** @var array */
protected static $m_aTrackForwardCache = array();
@@ -1800,7 +1927,7 @@ abstract class MetaModel
* @param string $sClass
* @param string $sListCode
*
* @return array
* @return array list of attribute codes
*/
public static function GetZListItems($sClass, $sListCode)
{
@@ -1820,6 +1947,82 @@ abstract class MetaModel
return self::GetZListItems($sParentClass, $sListCode);
}
/**
* @param string $sRemoteClass
*
* @return \AttributeDefinition[] list of attdefs to display by default for the remote class
*
* @since 3.0.0 N°2334
*/
public static function GetZListAttDefsFilteredForIndirectRemoteClass($sRemoteClass)
{
$aAttCodesToPrint = [];
foreach (MetaModel::GetZListItems($sRemoteClass, 'list') as $sFieldCode)
{
//TODO: check the state of the attribute: hidden or visible ?
if ($sFieldCode == 'finalclass')
{
continue;
}
$oRemoteAttDef = MetaModel::GetAttributeDef($sRemoteClass, $sFieldCode);
$aAttCodesToPrint[] = $oRemoteAttDef;
}
return $aAttCodesToPrint;
}
/**
* @param string $sClass left class
* @param string $sAttCode AttributeLinkedSetIndirect attcode
*
* @return \AttributeDefinition[] list of attdefs to display by default for lnk class
*
* @throws \CoreException
* @since 3.0.0 N°2334
*/
public static function GetZListAttDefsFilteredForIndirectLinkClass($sClass, $sAttCode)
{
$aAttCodesToPrint = [];
$oLinkedSetAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sLinkedClass = $oLinkedSetAttDef->GetLinkedClass();
$sExtKeyToRemote = $oLinkedSetAttDef->GetExtKeyToRemote();
$sExtKeyToMe = $oLinkedSetAttDef->GetExtKeyToMe();
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$sDefaultState = MetaModel::GetDefaultState($sClass);
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sLinkedClass, 'list')) as $sLnkAttCode)
{
$oLnkAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sLnkAttCode);
if ($sStateAttCode == $sLnkAttCode)
{
// State attribute is always hidden from the UI
continue;
}
if (($sLnkAttCode == $sExtKeyToMe)
|| ($sLnkAttCode == $sExtKeyToRemote)
|| ($sLnkAttCode == 'finalclass'))
{
continue;
}
if (!($oLnkAttDef->IsWritable()))
{
continue;
}
$iFlags = MetaModel::GetAttributeFlags($sLinkedClass, $sDefaultState, $sLnkAttCode);
if (!($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY))
{
$aAttCodesToPrint[] = $oLnkAttDef;
}
}
return $aAttCodesToPrint;
}
/**
* @param string $sClass
* @param string $sListCode
@@ -1992,7 +2195,6 @@ abstract class MetaModel
*/
protected static function ComputeRelationQueries($sRelCode)
{
$bHasLegacy = false;
$aQueries = array();
foreach(self::GetClasses() as $sClass)
{
@@ -2078,158 +2280,8 @@ abstract class MetaModel
}
}
}
// Read legacy definitions
// The up/down queries have to be reconcilied, which can only be done later when all the classes have been browsed
//
// The keys used to store a query (up or down) into the array are built differently between the modern and legacy made data:
// Modern way: aQueries[sClass]['up'|'down'][sArrowId], where sArrowId is made of the source class + neighbour id (XML def)
// Legacy way: aQueries[sClass]['up'|'down'][sRemoteClass]
// The modern way does allow for several arrows between two classes
// The legacy way aims at simplifying the transformation (reconciliation between up and down)
if ($sRelCode == 'impacts')
{
$sRevertCode = 'depends on';
$aLegacy = call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode));
foreach($aLegacy as $sId => $aLegacyEntry)
{
$bHasLegacy = true;
$oFilter = DBObjectSearch::FromOQL($aLegacyEntry['sQuery']);
$sRemoteClass = $oFilter->GetClass();
// Determine wether the query is inherited from a parent or not
$bInherited = false;
foreach(self::EnumParentClasses($sClass) as $sParent)
{
if (!isset($aQueries[$sParent]['down'][$sRemoteClass]))
{
continue;
}
if ($aLegacyEntry['sQuery'] == $aQueries[$sParent]['down'][$sRemoteClass]['sQueryDown'])
{
$bInherited = true;
$aQueries[$sClass]['down'][$sRemoteClass] = $aQueries[$sParent]['down'][$sRemoteClass];
break;
}
}
if (!$bInherited)
{
$aQueries[$sClass]['down'][$sRemoteClass] = array(
'_legacy_' => true,
'sDefinedInClass' => $sClass,
'sFromClass' => $sClass,
'sToClass' => $sRemoteClass,
'sDirection' => 'down',
'sQueryDown' => $aLegacyEntry['sQuery'],
'sQueryUp' => null,
'sNeighbour' => $sRemoteClass // Normalize the neighbour id
);
}
}
$aLegacy = call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRevertCode));
foreach($aLegacy as $sId => $aLegacyEntry)
{
$bHasLegacy = true;
$oFilter = DBObjectSearch::FromOQL($aLegacyEntry['sQuery']);
$sRemoteClass = $oFilter->GetClass();
// Determine wether the query is inherited from a parent or not
$bInherited = false;
foreach(self::EnumParentClasses($sClass) as $sParent)
{
if (!isset($aQueries[$sParent]['up'][$sRemoteClass]))
{
continue;
}
if ($aLegacyEntry['sQuery'] == $aQueries[$sParent]['up'][$sRemoteClass]['sQueryUp'])
{
$bInherited = true;
$aQueries[$sClass]['up'][$sRemoteClass] = $aQueries[$sParent]['up'][$sRemoteClass];
break;
}
}
if (!$bInherited)
{
$aQueries[$sClass]['up'][$sRemoteClass] = array(
'_legacy_' => true,
'sDefinedInClass' => $sRemoteClass,
'sFromClass' => $sRemoteClass,
'sToClass' => $sClass,
'sDirection' => 'both',
'sQueryDown' => null,
'sQueryUp' => $aLegacyEntry['sQuery'],
'sNeighbour' => $sClass// Normalize the neighbour id
);
}
}
}
//else
//{
// Cannot take the legacy system into account... simply ignore it
//}
} // foreach class
// Perform the up/down reconciliation for the legacy definitions
if ($bHasLegacy)
{
foreach(self::GetClasses() as $sClass)
{
// Foreach "up" legacy query, update its "down" counterpart
if (isset($aQueries[$sClass]['up']))
{
foreach($aQueries[$sClass]['up'] as $sNeighbourId => $aNeighbourData)
{
if (!array_key_exists('_legacy_', $aNeighbourData))
{
continue;
}
if (!$aNeighbourData['_legacy_'])
{
continue;
} // Skip modern definitions
$sLocalClass = $aNeighbourData['sToClass'];
foreach(self::EnumChildClasses($aNeighbourData['sFromClass'], ENUM_CHILD_CLASSES_ALL) as $sRemoteClass)
{
if (isset($aQueries[$sRemoteClass]['down'][$sLocalClass]))
{
$aQueries[$sRemoteClass]['down'][$sLocalClass]['sQueryUp'] = $aNeighbourData['sQueryUp'];
$aQueries[$sRemoteClass]['down'][$sLocalClass]['sDirection'] = 'both';
}
// Be silent in order to transparently support legacy data models where the counterpart query does not always exist
//else
//{
// throw new Exception("Legacy definition of the relation '$sRelCode/$sRevertCode', defined on $sLocalClass (relation: $sRevertCode, inherited to $sClass), missing the counterpart query on class $sRemoteClass ($sRelCode)");
//}
}
}
}
// Foreach "down" legacy query, update its "up" counterpart (if any)
foreach($aQueries[$sClass]['down'] as $sNeighbourId => $aNeighbourData)
{
if (!$aNeighbourData['_legacy_'])
{
continue;
} // Skip modern definitions
$sLocalClass = $aNeighbourData['sFromClass'];
foreach(self::EnumChildClasses($aNeighbourData['sToClass'], ENUM_CHILD_CLASSES_ALL) as $sRemoteClass)
{
if (isset($aQueries[$sRemoteClass]['up'][$sLocalClass]))
{
$aQueries[$sRemoteClass]['up'][$sLocalClass]['sQueryDown'] = $aNeighbourData['sQueryDown'];
}
}
}
}
}
return $aQueries;
}
@@ -2347,6 +2399,22 @@ abstract class MetaModel
{
return self::$m_aStates[$sClass];
}
elseif (self::HasStateAttributeCode($sClass))
{
$sStateAttCode = self::GetStateAttributeCode($sClass);
$oAttDef = self::GetAttributeDef($sClass, $sStateAttCode);
$aStates = [];
foreach($oAttDef->GetAllowedValues() as $sStateCode => $sStateLabel)
{
$aStates[$sStateCode] = [
'attribute_inherit' => '',
'attribute_list' => [],
];
}
return $aStates;
}
else
{
return array();
@@ -2404,6 +2472,21 @@ abstract class MetaModel
}
}
/**
* Return true if $sClass has a lifecycle, which means that it has a state attribute AND stimuli
*
* @param string $sClass
*
* @return bool
* @throws \CoreException
* @since 3.0.0
* @see self::HasStateAttributeCode($sClass)
*/
public static function HasLifecycle(string $sClass)
{
return self::HasStateAttributeCode($sClass) && !empty(self::EnumStimuli($sClass));
}
/**
* @param string $sClass
* @param string $sStateValue
@@ -2492,17 +2575,13 @@ abstract class MetaModel
public static function GetAttributeFlags($sClass, $sState, $sAttCode)
{
$iFlags = 0; // By default (if no life cycle) no flag at all
$sStateAttCode = self::GetStateAttributeCode($sClass);
if (!empty($sStateAttCode))
{
if (self::HasLifecycle($sClass)) {
$aStates = MetaModel::EnumStates($sClass);
if (!array_key_exists($sState, $aStates))
{
if (!array_key_exists($sState, $aStates)) {
throw new CoreException("Invalid state '$sState' for class '$sClass', expecting a value in {".implode(', ', array_keys($aStates))."}");
}
$aCurrentState = $aStates[$sState];
if ((array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])))
{
if ((array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list']))) {
$iFlags = $aCurrentState['attribute_list'][$sAttCode];
}
}
@@ -2522,18 +2601,14 @@ abstract class MetaModel
public static function GetTransitionFlags($sClass, $sState, $sStimulus, $sAttCode)
{
$iFlags = 0; // By default (if no lifecycle) no flag at all
$sStateAttCode = self::GetStateAttributeCode($sClass);
if (!empty($sStateAttCode))
{
if (self::HasLifecycle($sClass)) {
$aTransitions = MetaModel::EnumTransitions($sClass, $sState);
if (!array_key_exists($sStimulus, $aTransitions))
{
if (!array_key_exists($sStimulus, $aTransitions)) {
throw new CoreException("Invalid transition '$sStimulus' for class '$sClass', expecting a value in {".implode(', ', array_keys($aTransitions))."}");
}
$aCurrentTransition = $aTransitions[$sStimulus];
if ((array_key_exists('attribute_list', $aCurrentTransition)) && (array_key_exists($sAttCode, $aCurrentTransition['attribute_list'])))
{
if ((array_key_exists('attribute_list', $aCurrentTransition)) && (array_key_exists($sAttCode, $aCurrentTransition['attribute_list']))) {
$iFlags = $aCurrentTransition['attribute_list'][$sAttCode];
}
}
@@ -2612,37 +2687,30 @@ abstract class MetaModel
public static function GetInitialStateAttributeFlags($sClass, $sState, $sAttCode)
{
$iFlags = self::GetAttributeFlags($sClass, $sState, $sAttCode); // Be default set the same flags as the 'target' state
$sStateAttCode = self::GetStateAttributeCode($sClass);
if (!empty($sStateAttCode))
{
if (self::HasLifecycle($sClass)) {
$aStates = MetaModel::EnumInitialStates($sClass);
if (array_key_exists($sState, $aStates))
{
if (array_key_exists($sState, $aStates)) {
$bReadOnly = (($iFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY);
$bHidden = (($iFlags & OPT_ATT_HIDDEN) == OPT_ATT_HIDDEN);
foreach($aStates[$sState] as $sPrevState)
{
foreach($aStates[$sState] as $sPrevState) {
$iPrevFlags = self::GetAttributeFlags($sClass, $sPrevState, $sAttCode);
if (($iPrevFlags & OPT_ATT_HIDDEN) != OPT_ATT_HIDDEN)
{
if (($iPrevFlags & OPT_ATT_HIDDEN) != OPT_ATT_HIDDEN) {
$bReadOnly = $bReadOnly && (($iPrevFlags & OPT_ATT_READONLY) == OPT_ATT_READONLY); // if it is/was not readonly => then it's not
}
$bHidden = $bHidden && (($iPrevFlags & OPT_ATT_HIDDEN) == OPT_ATT_HIDDEN); // if it is/was not hidden => then it's not
}
if ($bReadOnly)
{
if ($bReadOnly) {
$iFlags = $iFlags | OPT_ATT_READONLY;
}
else
{
else {
$iFlags = $iFlags & ~OPT_ATT_READONLY;
}
if ($bHidden)
{
if ($bHidden) {
$iFlags = $iFlags | OPT_ATT_HIDDEN;
}
else
{
else {
$iFlags = $iFlags & ~OPT_ATT_HIDDEN;
}
}
@@ -2781,7 +2849,7 @@ abstract class MetaModel
$aInterfaces = array('iApplicationUIExtension', 'iPreferencesExtension', 'iApplicationObjectExtension', 'iLoginFSMExtension', 'iLoginUIExtension', 'iLogoutExtension', 'iQueryModifier', 'iOnClassInitialization', 'iPopupMenuExtension', 'iPageUIExtension', 'iPortalUIExtension', 'ModuleHandlerApiInterface', 'iNewsroomProvider', 'iModuleExtension');
foreach($aInterfaces as $sInterface)
{
self::$m_aExtensionClasses[$sInterface] = array();
self::$m_aExtensionClassNames[$sInterface] = array();
}
foreach(get_declared_classes() as $sPHPClass)
@@ -2792,11 +2860,7 @@ abstract class MetaModel
{
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable())
{
if (is_null($oExtensionInstance))
{
$oExtensionInstance = new $sPHPClass;
}
self::$m_aExtensionClasses[$sInterface][$sPHPClass] = $oExtensionInstance;
self::$m_aExtensionClassNames[$sInterface][$sPHPClass] = $sPHPClass;
}
}
}
@@ -2856,6 +2920,25 @@ abstract class MetaModel
}
self::$m_aClassParams[$sPHPClass]['obsolescence_expression'] = $sObsolescence;
// Inherit fields semantic
// - State attribute
$bParentHasStateAttribute = (isset(self::$m_aClassParams[$sParent]['state_attcode']) && !empty(self::$m_aClassParams[$sParent]['state_attcode']));
$bHasStateAttribute = (isset(self::$m_aClassParams[$sPHPClass]['state_attcode']) && !empty(self::$m_aClassParams[$sPHPClass]['state_attcode']));
if($bParentHasStateAttribute && !$bHasStateAttribute) {
// Set attribute code
self::$m_aClassParams[$sPHPClass]['state_attcode'] = self::$m_aClassParams[$sParent]['state_attcode'];
// Set states
self::$m_aStates[$sPHPClass] = self::$m_aStates[$sParent];
}
// - Image attribute
$bParentHasImageAttribute = (isset(self::$m_aClassParams[$sParent]['image_attcode']) && !empty(self::$m_aClassParams[$sParent]['image_attcode']));
$bHasImageAttribute = (isset(self::$m_aClassParams[$sPHPClass]['image_attcode']) && !empty(self::$m_aClassParams[$sPHPClass]['image_attcode']));
if($bParentHasImageAttribute && !$bHasImageAttribute) {
// Set attribute code
self::$m_aClassParams[$sPHPClass]['image_attcode'] = self::$m_aClassParams[$sParent]['image_attcode'];
}
foreach(MetaModel::EnumPlugins('iOnClassInitialization') as $sPluginClass => $oClassInit)
{
$oClassInit->OnAfterClassInitialization($sPHPClass);
@@ -3262,18 +3345,6 @@ abstract class MetaModel
// In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation)
}
/**
* To be overloaded by biz model declarations
*
* @param string $sRelCode
*
* @return array
*/
public static function GetRelationQueries($sRelCode)
{
// In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation)
return array();
}
/**
* @param array $aParams
@@ -3283,11 +3354,12 @@ abstract class MetaModel
public static function Init_Params($aParams)
{
// Check mandatory params
// Warning: Do not put image_attcode as a mandatory attribute or it will break all PHP datamodel classes
$aMandatParams = array(
"category" => "group classes by modules defining their visibility in the UI",
"key_type" => "autoincrement | string",
"name_attcode" => "define wich attribute is the class name, may be an array of attributes (format specified in the dictionary as 'Class:myclass/Name' => '%1\$s %2\$s...'",
"state_attcode" => "define wich attribute is representing the state (object lifecycle)",
"name_attcode" => "define which attribute is the class name, may be an array of attributes (format specified in the dictionary as 'Class:myclass/Name' => '%1\$s %2\$s...'",
"state_attcode" => "define which attribute is representing the state (object lifecycle)",
"reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes",
"db_table" => "database table",
"db_key_field" => "database field which is the key",
@@ -5754,32 +5826,9 @@ abstract class MetaModel
$sView = self::DBGetView($sClass);
if (CMDBSource::IsTable($sView))
{
// Check that the view is complete
//
// Note: checking the list of attributes is not enough because the columns can be stable while the SELECT is not stable
// Example: new way to compute the friendly name
// The correct comparison algorithm is to compare the queries,
// by using "SHOW CREATE VIEW" (MySQL 5.0.1 required) or to look into INFORMATION_SCHEMA/views
// both requiring some privileges
// Decision: to simplify, let's consider the views as being wrong anytime
// Rework the view
//
$oFilter = new DBObjectSearch($sClass, '');
$oFilter->AllowAllData();
$sSQL = $oFilter->MakeSelectQuery();
$aErrors[$sClass]['*'][] = "Redeclare view '$sView' (systematic - to support an eventual change in the friendly name computation)";
$aSugFix[$sClass]['*'][] = "ALTER VIEW `$sView` AS $sSQL";
}
else
{
// Create the view
//
$oFilter = new DBObjectSearch($sClass, '');
$oFilter->AllowAllData();
$sSQL = $oFilter->MakeSelectQuery();
$aErrors[$sClass]['*'][] = "Missing view for class: $sClass";
$aSugFix[$sClass]['*'][] = "DROP VIEW IF EXISTS `$sView`";
$aSugFix[$sClass]['*'][] = "CREATE VIEW `$sView` AS $sSQL";
// Remove deprecated views
$aErrors[$sClass]['*'][] = "Remove view '$sView' (deprecated, consider installing combodo-views if needed)";
$aSugFix[$sClass]['*'][] = "DROP VIEW `$sView`";
}
}
return array($aErrors, $aSugFix);
@@ -6321,6 +6370,7 @@ abstract class MetaModel
// N°2478 utils has his own private attribute
// @see utils::GetConfig : it always call MetaModel, but to be sure we're doing this extra copy anyway O:)
utils::InitTimeZone($oConfiguration);
utils::SetConfig($oConfiguration);
// Set log ASAP
@@ -6361,19 +6411,6 @@ abstract class MetaModel
DBSearch::EnableQueryIndentation(self::$m_oConfig->Get('query_indentation_enabled'));
DBSearch::EnableOptimizeQuery(self::$m_oConfig->Get('query_optimization_enabled'));
// PHP timezone first...
//
$sPHPTimezone = self::$m_oConfig->Get('timezone');
if ($sPHPTimezone == '')
{
// Leave as is... up to the admin to set a value somewhere...
//$sPHPTimezone = date_default_timezone_get();
}
else
{
date_default_timezone_set($sPHPTimezone);
}
// Note: load the dictionary as soon as possible, because it might be
// needed when some error occur
$sAppIdentity = 'itop-'.MetaModel::GetEnvironmentId();
@@ -6417,7 +6454,7 @@ abstract class MetaModel
if (is_array($result))
{
// todo - verifier que toutes les classes mentionnees ici sont chargees dans InitClasses()
self::$m_aExtensionClasses = $result['m_aExtensionClasses'];
self::$m_aExtensionClassNames = $result['m_aExtensionClassNames'];
self::$m_Category2Class = $result['m_Category2Class'];
self::$m_aRootClasses = $result['m_aRootClasses'];
self::$m_aParentClasses = $result['m_aParentClasses'];
@@ -6454,7 +6491,7 @@ abstract class MetaModel
$oKPI = new ExecutionKPI();
$aCache = array();
$aCache['m_aExtensionClasses'] = self::$m_aExtensionClasses;
$aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames;
$aCache['m_Category2Class'] = self::$m_Category2Class;
$aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass"
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
@@ -6538,8 +6575,8 @@ abstract class MetaModel
return md5(APPROOT).'-'.self::$m_sEnvironment;
}
/** @var array */
protected static $m_aExtensionClasses = array();
/** @var array */
protected static $m_aExtensionClassNames = [];
/**
* @param string $sToInclude
@@ -7185,99 +7222,11 @@ abstract class MetaModel
return $aResult;
}
/**
* @deprecated 2.5.0 It is not recommended to use this function: call {@link MetaModel::GetLinkClasses} instead !
* The only difference with EnumLinkingClasses is the output format
*
* @see MetaModel::GetLinkClasses
* @return string[] classes having at least two external keys (thus too many classes as compared to GetLinkClasses)
*
*/
public static function EnumLinksClasses()
{
// Returns a flat array of classes having at least two external keys
$aResult = array();
foreach(self::$m_aAttribDefs as $sSomeClass => $aClassAttributes)
{
$iExtKeyCount = 0;
foreach($aClassAttributes as $sAttCode => $oAttDef)
{
if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass)
{
continue;
}
if ($oAttDef->IsExternalKey())
{
$iExtKeyCount++;
}
}
if ($iExtKeyCount >= 2)
{
$aResult[] = $sSomeClass;
}
}
return $aResult;
}
/**
* @deprecated 2.5.0 It is not recommended to use this function: call {@link MetaModel::GetLinkClasses} instead !
* The only difference with EnumLinksClasses is the output format
*
* @see MetaModel::GetLinkClasses
*
*@param string $sClass
*
* @return string[] classes having at least two external keys (thus too many classes as compared to GetLinkClasses)
* @throws \CoreException
*
*/
public static function EnumLinkingClasses($sClass = "")
{
// N-N links, array of sLinkClass => (array of sAttCode=>sClass)
$aResult = array();
foreach(self::EnumLinksClasses() as $sSomeClass)
{
$aTargets = array();
$bFoundClass = false;
foreach(self::ListAttributeDefs($sSomeClass) as $sAttCode => $oAttDef)
{
if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass)
{
continue;
}
if ($oAttDef->IsExternalKey())
{
$sRemoteClass = $oAttDef->GetTargetClass();
if (empty($sClass))
{
$aTargets[$sAttCode] = $sRemoteClass;
}
elseif ($sClass == $sRemoteClass)
{
$bFoundClass = true;
}
else
{
$aTargets[$sAttCode] = $sRemoteClass;
}
}
}
if (empty($sClass) || $bFoundClass)
{
$aResult[$sSomeClass] = $aTargets;
}
}
return $aResult;
}
/**
* Using GetLinkClasses is the recommended way to determine if a class is
* actually an N-N relation because it is based on the decision made by the
* designer the data model
*
* This function has two siblings that will be soon deprecated:
* {@link MetaModel::EnumLinkingClasses} and {@link MetaModel::EnumLinkClasses}
*
* @return array (target class => (external key code => target class))
* @throws \CoreException
*/
@@ -7423,25 +7372,9 @@ abstract class MetaModel
*/
public static function EnumPlugins($sInterface, $sFilterInstanceOf = null)
{
if (!array_key_exists($sInterface, self::$m_aExtensionClasses))
{
return array();
}
$pluginManager = new PluginManager(self::$m_aExtensionClassNames);
if (is_null($sFilterInstanceOf))
{
return self::$m_aExtensionClasses[$sInterface];
}
$fFilterCallback = function ($instance) use ($sFilterInstanceOf)
{
return $instance instanceof $sFilterInstanceOf;
};
return array_filter(
self::$m_aExtensionClasses[$sInterface],
$fFilterCallback
);
return $pluginManager->EnumPlugins($sInterface, $sFilterInstanceOf);
}
/**
@@ -7452,16 +7385,9 @@ abstract class MetaModel
*/
public static function GetPlugins($sInterface, $sClassName)
{
$oInstance = null;
if (array_key_exists($sInterface, self::$m_aExtensionClasses))
{
if (array_key_exists($sClassName, self::$m_aExtensionClasses[$sInterface]))
{
return self::$m_aExtensionClasses[$sInterface][$sClassName];
}
}
$pluginManager = new PluginManager(self::$m_aExtensionClassNames);
return $oInstance;
return $pluginManager->GetPlugins($sInterface, $sClassName);
}
/**

View File

@@ -31,7 +31,7 @@ class ExpressionHelper {
*
* @uses \DBSearch::AllowAllData()
*
* @since 2.7.2 2.8.0 N°3324
* @since 2.7.2 3.0.0 N°3324
*/
public static function ExpressionAllowAllDataCallback($oExpression, $bAllowAllData) {
if (!($oExpression instanceof NestedQueryExpression)) {
@@ -42,6 +42,9 @@ class ExpressionHelper {
}
}
class NotYetEvaluatedExpression extends CoreException
{
}
/**
* @method Check($oModelReflection, array $aAliases, $sSourceQuery)
@@ -129,6 +132,47 @@ abstract class Expression {
*/
abstract public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false);
/**
* Collect parameters, i.e. :parameter
*
* @param null $sParentFilter
*
* @return array
*/
public function GetParameters($sParentFilter = null)
{
$aParameters = array();
$unused = $this->RenderExpression(false, $aParameters, true);
if (!is_null($sParentFilter)) $sParentFilter .= '->';
$aRet = array();
foreach($aParameters as $sParameter => $unused)
{
if (is_null($sParentFilter))
{
$aRet[] = $sParameter;
}
else
{
if (substr($sParameter, 0, strlen($sParentFilter)) == $sParentFilter)
{
$aRet[] = substr($sParameter, strlen($sParentFilter));
}
}
}
return $aRet;
}
/**
* Evaluate the value of the expression
*
* @param array $aArgs
*
* @throws \Exception if terms cannot be evaluated as scalars
*/
abstract public function Evaluate(array $aArgs);
/**
* Recursively renders the expression as a structure (array) suitable for a JSON export
*
@@ -345,6 +389,16 @@ class SQLExpression extends Expression
return $this->m_sSQL;
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
throw new Exception('a nested query cannot be evaluated');
}
// recursive rendering
public function toJSON(&$aArgs = null, $bRetrofitParams = false)
{
@@ -492,6 +546,149 @@ class BinaryExpression extends Expression
return "($sLeft $sOperator $sRight)";
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @return mixed
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
$mLeft = $this->GetLeftExpr()->Evaluate($aArgs);
$mRight = $this->GetRightExpr()->Evaluate($aArgs);
$sOperator = $this->GetOperator();
$sType = null;
switch($sOperator)
{
case '+':
case '-':
case '*':
case '/':
$sType = 'maths';
break;
case '=':
case '!=':
case '<>':
$sType = 'comp';
break;
case '>':
case '>=':
case '<':
case '<=':
$sType = 'numcomp';
break;
case 'OR':
case 'AND':
$sType = 'logical';
break;
case 'LIKE':
$sType = 'like';
break;
default:
throw new Exception("Operator '$sOperator' not yet supported");
}
switch ($sType){
case 'logical':
$bLeft = static::CastToBool($mLeft);
$bRight = static::CastToBool($mRight);
switch ($sOperator)
{
case 'OR':
$result = (int)($bLeft || $bRight);
break;
case 'AND':
$result = (int)($bLeft && $bRight);
break;
default:
throw new Exception("Logic: unknown operator '$sOperator'");
}
break;
case 'maths':
$iLeft = (int) $mLeft;
$iRight = (int) $mRight;
switch ($sOperator)
{
case '+' : $result = $iLeft + $iRight; break;
case '-' : $result = $iLeft - $iRight; break;
case '*' : $result = $iLeft * $iRight; break;
case '/' : $result = $iLeft / $iRight; break;
default:
throw new Exception("Logic: unknown operator '$sOperator'");
}
break;
case 'comp':
$left = $mLeft;
$right = $mRight;
switch ($sOperator)
{
case '=' : $result = ($left == $right); break;
case '!=' : $result = ($left != $right); break;
case '<>' : $result = ($left != $right); break;
default:
throw new Exception("Logic: unknown operator '$sOperator'");
}
break;
case 'numcomp':
$iLeft = static::ComparableValue($mLeft);
$iRight = static::ComparableValue($mRight);
switch ($sOperator)
{
case '=' : $result = ($iLeft == $iRight); break;
case '>' : $result = ($iLeft > $iRight); break;
case '<' : $result = ($iLeft < $iRight); break;
case '>=' : $result = ($iLeft >= $iRight); break;
case '<=' : $result = ($iLeft <= $iRight); break;
case '!=' : $result = ($iLeft != $iRight); break;
case '<>' : $result = ($iLeft != $iRight); break;
default:
throw new Exception("Logic: unknown operator '$sOperator'");
}
break;
case 'like':
$sEscaped = preg_quote($mRight, '/');
$sEscaped = str_replace(array('%', '_', '\\\\.*', '\\\\.'), array('.*', '.', '%', '_'), $sEscaped);
$result = (int) preg_match("/$sEscaped/i", $mLeft);
break;
}
return $result;
}
static protected function CastToBool($mValue)
{
if (is_string($mValue))
{
if (is_numeric($mValue))
{
return abs($mValue) > 0;
}
return false;
}
return (bool)$mValue;
}
static protected function ComparableValue($mixed)
{
if (is_string($mixed))
{
$oDate = new \DateTime($mixed);
if (($oDate->format('Y-m-d') == $mixed) || ($oDate->format('Y-m-d H:i:s') == $mixed))
{
$iRet = $oDate->format('U');
}
else
{
$iRet = (int) $mixed;
}
}
else
{
$iRet = $mixed;
}
return $iRet;
}
/**
* {@inheritDoc}
* @throws \MissingQueryArgument
@@ -894,6 +1091,16 @@ class MatchExpression extends BinaryExpression
return $sRet;
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
throw new Exception('evaluation of MATCHES not implemented yet');
}
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
/** @var \FieldExpression $oLeft */
@@ -932,6 +1139,16 @@ class UnaryExpression extends Expression
return CMDBSource::Quote($this->m_value);
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
return $this->m_value;
}
/**
* {@inheritDoc}
* @throws \MissingQueryArgument
@@ -1078,6 +1295,16 @@ class ScalarExpression extends UnaryExpression
return $sRet;
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
return $this->m_value;
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -1386,6 +1613,21 @@ class FieldExpression extends UnaryExpression
return "`{$this->m_sParent}`.`{$this->m_sName}`";
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
$sKey = empty($this->m_sParent) ? $this->m_sName : "{$this->m_sParent}.{$this->m_sName}";
if (!array_key_exists($sKey, $aArgs))
{
throw new Exception("Missing field '$sKey' from context");
}
return $aArgs[$sKey];
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -1441,7 +1683,8 @@ class FieldExpression extends UnaryExpression
public function ListRequiredFields()
{
return array($this->m_sParent.'.'.$this->m_sName);
$sField = empty($this->m_sParent) ? $this->m_sName : "{$this->m_sParent}.{$this->m_sName}";
return array($sField);
}
public function CollectUsedParents(&$aTable)
@@ -1822,6 +2065,16 @@ class VariableExpression extends UnaryExpression
}
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
throw new Exception('not implemented yet');
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -1962,6 +2215,16 @@ class ListExpression extends Expression
return '('.implode(', ', $aRes).')';
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
throw new Exception('list expression not yet supported');
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -2177,6 +2440,16 @@ class NestedQueryExpression extends Expression
}
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
throw new Exception('a nested query cannot be evaluated');
}
public function Browse(Closure $callback)
{
$callback($this);
@@ -2279,6 +2552,252 @@ class FunctionExpression extends Expression
return $this->m_sVerb.'('.implode(', ', $aRes).')';
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
switch($this->m_sVerb)
{
case 'CONCAT':
$sRet = '';
foreach ($this->m_aArgs as $iPos => $oExpr)
{
$item = $oExpr->Evaluate($aArgs);
if (is_null($item)) return null;
$sRet .= $item;
}
return $sRet;
case 'CONCAT_WS':
if (count($this->m_aArgs) < 3)
{
throw new \Exception("Function {$this->m_sVerb} requires at least 3 arguments");
}
$sSeparator = $this->m_aArgs[0]->Evaluate($aArgs);
foreach ($this->m_aArgs as $iPos => $oExpr)
{
if ($iPos == 0) continue;
$item = $oExpr->Evaluate($aArgs);
if (is_null($item)) return null;
$aStrings[] = $item;
}
$sRet = implode($sSeparator, $aStrings);
return $sRet;
case 'SUBSTR':
if (count($this->m_aArgs) < 2)
{
throw new \Exception("Function {$this->m_sVerb} requires at least 2 arguments");
}
$sString = $this->m_aArgs[0]->Evaluate($aArgs);
$iRawPos = $this->m_aArgs[1]->Evaluate($aArgs);
$iPos = $iRawPos > 0 ?
$iRawPos - 1// 0-based in PHP (1-based in SQL)
: $iRawPos; // Negative
if (count($this->m_aArgs) == 2)
{
// Up to the end of the string
$sRet = substr($sString, $iPos);
}
else
{
// Length specified
$iLen = $this->m_aArgs[2]->Evaluate($aArgs);
$sRet = substr($sString, $iPos, $iLen);
}
return $sRet;
case 'TRIM':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$sRet = trim($this->m_aArgs[0]->Evaluate($aArgs));
return $sRet;
case 'INET_ATON':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$sRet = ip2long($this->m_aArgs[0]->Evaluate($aArgs));
return $sRet;
case 'INET_NTOA':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$sRet = long2ip($this->m_aArgs[0]->Evaluate($aArgs));
return $sRet;
case 'ISNULL':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$sRet = is_null($this->m_aArgs[0]->Evaluate($aArgs));
return $sRet;
case 'COALESCE':
if (count($this->m_aArgs) < 1)
{
throw new \Exception("Function {$this->m_sVerb} requires at least 1 argument");
}
$ret = null;
foreach($this->m_aArgs as $iPos => $oExpr)
{
$ret = $oExpr->Evaluate($aArgs);
if (!is_null($ret)) break;
}
return $ret;
case 'IF':
if (count($this->m_aArgs) != 3)
{
throw new \Exception("Function {$this->m_sVerb} requires 3 arguments");
}
$bCond = $this->m_aArgs[0]->Evaluate($aArgs);
if ($bCond)
{
$ret = $this->m_aArgs[1]->Evaluate($aArgs);
}
else
{
$ret = $this->m_aArgs[2]->Evaluate($aArgs);
}
return $ret;
case 'ELT':
if (count($this->m_aArgs) < 2)
{
throw new \Exception("Function {$this->m_sVerb} requires at least 2 arguments");
}
// First argument is the 1-based position
$iPosition = (int) $this->m_aArgs[0]->Evaluate($aArgs);
if (($iPosition == 0) || ($iPosition >= count($this->m_aArgs)))
{
// Out of range
$ret = null;
}
else
{
$ret = $this->m_aArgs[$iPosition]->Evaluate($aArgs);
}
return $ret;
case 'DATE':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$sRet = date('Y-m-d', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
return $sRet;
case 'YEAR':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$iRet = (int) date('Y', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
return $iRet;
case 'MONTH':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$iRet = (int) date('m', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
return $iRet;
case 'DAY':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$iRet = (int) date('d', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
return $iRet;
case 'DATE_FORMAT':
if (count($this->m_aArgs) != 2)
{
throw new \Exception("Function {$this->m_sVerb} requires 2 arguments");
}
$oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs));
$sFormat = $this->m_aArgs[1]->Evaluate($aArgs);
$sFormat = str_replace(
array('%y', '%x', '%w', '%W', '%v', '%T', '%S', '%r', '%p', '%M', '%l', '%k', '%I', '%h', '%b', '%a', '%D', '%c', '%e', '%Y', '%d', '%m', '%H', '%i', '%s'),
array('y', 'o', 'w', 'l', 'W', 'H:i:s', 's', 'h:i:s A', 'A', 'F', 'g', 'H', 'h', 'h','M', 'D', 'jS', 'n', 'j', 'Y', 'd', 'm', 'H', 'i', 's'),
$sFormat);
if (preg_match('/%j/', $sFormat))
{
$sFormat = str_replace('%j', date_format($oDate, 'z') + 1, $sFormat);
}
if (preg_match('/%[fUuVX]/', $sFormat))
{
throw new NotYetEvaluatedExpression("Expression ".$this->RenderExpression().' cannot be evaluated (known limitation)');
}
$sRet = date_format($oDate, $sFormat);
return $sRet;
case 'TO_DAYS':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs));
$oZero = new DateTime('1582-01-01');
$iRet = (int) $oDate->diff($oZero)->format('%a') + 577815;
return $iRet;
case 'FROM_DAYS':
if (count($this->m_aArgs) != 1)
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
$iSince1582 = $this->m_aArgs[0]->Evaluate($aArgs) - 577814;
$oDate = new DateTime("1582-01-01 +$iSince1582 days");
$sRet = $oDate->format('Y-m-d');
return $sRet;
case 'NOW':
$sRet = date('Y-m-d H:i:s');
return $sRet;
case 'CURRENT_DATE':
$sRet = date('Y-m-d');
return $sRet;
case 'DATE_ADD':
if (count($this->m_aArgs) != 2)
{
throw new \Exception("Function {$this->m_sVerb} requires 2 arguments");
}
$sStartDate = $this->m_aArgs[0]->Evaluate($aArgs);
$sInterval = $this->m_aArgs[1]->Evaluate($aArgs);
$oDate = new DateTime("$sStartDate +$sInterval");
$sRet = $oDate->format('Y-m-d H:i:s');
return $sRet;
case 'DATE_SUB':
if (count($this->m_aArgs) != 2)
{
throw new \Exception("Function {$this->m_sVerb} requires 2 arguments");
}
$sStartDate = $this->m_aArgs[0]->Evaluate($aArgs);
$sInterval = $this->m_aArgs[1]->Evaluate($aArgs);
$oDate = new DateTime("$sStartDate -$sInterval");
$sRet = $oDate->format('Y-m-d H:i:s');
return $sRet;
default:
throw new Exception("Function {$this->m_sVerb} cannot be evaluated -unhandled yet");
}
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -2605,6 +3124,17 @@ class IntervalExpression extends Expression
return 'INTERVAL '.$this->m_oValue->RenderExpression($bForSQL, $aArgs, $bRetrofitParams).' '.$this->m_sUnit;
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
$iValue = $this->m_oValue->Evaluate($aArgs);
return "$iValue {$this->m_sUnit}";
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -2723,6 +3253,21 @@ class CharConcatExpression extends Expression
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
$sRet = '';
foreach ($this->m_aExpressions as $oExpr)
{
$sRet .= $oExpr->Evaluate($aArgs);
}
return $sRet;
}
/**
* {@inheritDoc}
* @see Expression::ToJSON()
@@ -2864,6 +3409,21 @@ class CharConcatWSExpression extends CharConcatExpression
return "CAST(CONCAT_WS($sSep, ".implode(', ', $aRes).") AS CHAR)";
}
/**
* Evaluate the value of the expression
* @param array $aArgs
* @throws \Exception if terms cannot be evaluated as scalars
*/
public function Evaluate(array $aArgs)
{
$aRes = array();
foreach ($this->m_aExpressions as $oExpr)
{
$aRes .= $oExpr->Evaluate($aArgs);
}
return implode($this->m_separator, $aRes);
}
public function Browse(Closure $callback)
{
$callback($this);

View File

@@ -30,6 +30,9 @@ require_once('dbobjectiterator.php');
class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
{
const LINK_ALIAS = 'Link';
const REMOTE_ALIAS = 'Remote';
protected $sHostClass; // subclass of DBObject
protected $sAttCode; // xxxxxx_list
protected $sClass; // class of the links
@@ -786,11 +789,13 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
/**
* @param bool $bShowObsolete
*
* @return \DBObjectSet
* @return \DBObjectSet indirect relations will get `SELECT L,R ...` (l = lnk class, R = remote)
* @throws \CoreException
* @throws \CoreWarning
* @throws \MySQLException
* @throws \Exception
*
* @since 3.0.0 N°2334 returns both lnk and remote classes for indirect relations
*/
public function ToDBObjectSet($bShowObsolete = true)
{
@@ -802,7 +807,12 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
/** @var \AttributeExternalKey $oLinkingAttDef */
$oLinkingAttDef = MetaModel::GetAttributeDef($this->sClass, $sExtKeyToRemote);
// 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);
if (!$bShowObsolete && MetaModel::IsObsoletable($sTargetClass))
{
$oNotObsolete = new BinaryExpression(
@@ -810,10 +820,9 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
'=',
new ScalarExpression(0)
);
$oNotObsoleteRemote = new DBObjectSearch($sTargetClass);
$oNotObsoleteRemote->AddConditionExpression($oNotObsolete);
$oLinkSearch->AddCondition_PointingTo($oNotObsoleteRemote, $sExtKeyToRemote);
$oRemoteClassSearch->AddConditionExpression($oNotObsolete);
}
if (!utils::IsArchiveMode() && MetaModel::IsArchivable($sTargetClass))
{
$oNotArchived = new BinaryExpression(
@@ -821,10 +830,14 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
'=',
new ScalarExpression(0)
);
$oNotArchivedRemote = new DBObjectSearch($sTargetClass);
$oNotArchivedRemote->AddConditionExpression($oNotArchived);
$oLinkSearch->AddCondition_PointingTo($oNotArchivedRemote, $sExtKeyToRemote);
$oRemoteClassSearch->AddConditionExpression($oNotArchived);
}
$oLinkSearch->AddCondition_PointingTo($oRemoteClassSearch, $sExtKeyToRemote);
$oLinkSearch->RenameAlias($oLinkSearch->GetClassAlias(), self::LINK_ALIAS);
$oLinkSearch->RenameAlias($sTargetClass, self::REMOTE_ALIAS);
$oLinkSearch->SetSelectedClasses([self::LINK_ALIAS, self::REMOTE_ALIAS]);
}
$oLinkSet = new DBObjectSet($oLinkSearch);
$oLinkSet->SetShowObsoleteData($bShowObsolete);

View File

@@ -0,0 +1,30 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class PluginInstanciationManager
{
public function InstantiatePlugins($m_aExtensionClassNames, $sInterface)
{
$newPerInstanceClasses = array();
if (array_key_exists($sInterface, $m_aExtensionClassNames))
{
foreach ($m_aExtensionClassNames[$sInterface] as $sClassName)
{
if (class_exists($sClassName))
{
$class = new ReflectionClass($sClassName);
if ($class->isInstantiable())
{
$newPerInstanceClasses[$sClassName] = new $sClassName();
}
}
}
}
return $newPerInstanceClasses;
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* @copyright Copyright (C) 2010-2020 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class PluginManager
{
private $m_aExtensionClassNames;
private static $m_aExtensionClasses;
private $m_pluginInstantiationManager;
public function __construct($m_aExtensionClassNames, $m_pluginInstanciationManager = null)
{
if (is_null(self::$m_aExtensionClasses)) {
self::$m_aExtensionClasses = [];
}
$this->m_aExtensionClassNames = $m_aExtensionClassNames;
if ($m_pluginInstanciationManager == null)
{
$this->m_pluginInstantiationManager = new PluginInstanciationManager();
}
else
{
$this->m_pluginInstantiationManager = $m_pluginInstanciationManager;
}
}
/**
* @param string $sInterface
* @param bool $bCanInstantiatePlugins internal use, let this value to true
* @param string|null $sFilterInstanceOf [optional] if given, only instance of this string will be returned
*
* @return array classes=>instance implementing the given interface
*/
public function EnumPlugins($sInterface, $sFilterInstanceOf = null, $bCanInstantiatePlugins = true)
{
$aPlugins = array();
if (array_key_exists($sInterface, self::$m_aExtensionClasses))
{
$aAllPlugins = array_values(self::$m_aExtensionClasses[$sInterface]);
if (is_null($sFilterInstanceOf))
{
return $aAllPlugins;
};
$aPlugins = array();
foreach ($aAllPlugins as $instance)
{
if ($instance instanceof $sFilterInstanceOf)
{
$aPlugins[] = $instance;
}
}
}
else
{
if ($bCanInstantiatePlugins && array_key_exists($sInterface, $this->m_aExtensionClassNames))
{
$this->InstantiatePlugins($sInterface);
return $this->EnumPlugins($sInterface, $sFilterInstanceOf, false);
}
}
return $aPlugins;
}
public function InstantiatePlugins($sInterface)
{
self::$m_aExtensionClasses[$sInterface] = $this->m_pluginInstantiationManager->InstantiatePlugins($this->m_aExtensionClassNames, $sInterface);
}
/**
* @param string $sInterface
* @param string $sClassName
* @param bool $bCanInstantiatePlugins internal use, let this value to true
*
* @return mixed the instance of the specified plug-ins for the given interface
*/
public function GetPlugins($sInterface, $sClassName, $bCanInstantiatePlugins = true)
{
$oInstance = null;
if (array_key_exists($sInterface, self::$m_aExtensionClasses))
{
if (array_key_exists($sClassName, self::$m_aExtensionClasses[$sInterface]))
{
return self::$m_aExtensionClasses[$sInterface][$sClassName];
}
}
else
{
if ($bCanInstantiatePlugins && array_key_exists($sInterface, $this->m_aExtensionClassNames))
{
$this->InstantiatePlugins($sInterface);
return $this->GetPlugins($sInterface, $sClassName, false);
}
}
return $oInstance;
}
}

View File

@@ -472,7 +472,7 @@ class RelationGraph extends SimpleGraph
{
do
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$sObjectRef = RelationObjectNode::MakeId($oRelatedObj);
$oRelatedNode = $this->GetNode($sObjectRef);
@@ -501,7 +501,7 @@ class RelationGraph extends SimpleGraph
while ($oRelatedObj = $oObjSet->Fetch());
}
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
}
}
}

View File

@@ -467,6 +467,18 @@ class CoreServices implements iRestServiceProvider
}
else
{
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*')
{
$aFields = $aShowFields[$sClass];
//Id is not a valid attribute to optimize
if (in_array('id', $aFields))
{
unset($aFields[array_search('id', $aFields)]);
}
$aAttToLoad = array($oObjectSet->GetClassAlias() => $aFields);
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
}
while ($oObject = $oObjectSet->Fetch())
{
$oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);

View File

@@ -266,7 +266,7 @@ EOF
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
while($aRow = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
$sData .= "<tr>";
foreach($this->aStatusInfo['fields'] as $iCol => $aFieldSpec)
@@ -362,7 +362,7 @@ EOF
$sData .= "</tr>";
$iCount++;
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{

View File

@@ -50,7 +50,6 @@ abstract class Trigger extends cmdbAbstractObject
"db_table" => "priv_trigger",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -168,7 +167,6 @@ abstract class TriggerOnObject extends Trigger
"db_table" => "priv_trigger_onobject",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -295,7 +293,6 @@ class TriggerOnPortalUpdate extends TriggerOnObject
"db_table" => "priv_trigger_onportalupdate",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -328,7 +325,6 @@ abstract class TriggerOnStateChange extends TriggerOnObject
"db_table" => "priv_trigger_onstatechange",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -363,7 +359,6 @@ class TriggerOnStateEnter extends TriggerOnStateChange
"db_table" => "priv_trigger_onstateenter",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -397,7 +392,6 @@ class TriggerOnStateLeave extends TriggerOnStateChange
"db_table" => "priv_trigger_onstateleave",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -431,7 +425,6 @@ class TriggerOnObjectCreate extends TriggerOnObject
"db_table" => "priv_trigger_onobjcreate",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -465,7 +458,6 @@ class TriggerOnObjectDelete extends TriggerOnObject
"db_table" => "priv_trigger_onobjdelete",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -500,7 +492,6 @@ class TriggerOnObjectUpdate extends TriggerOnObject
"db_table" => "priv_trigger_onobjupdate",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
@@ -593,7 +584,6 @@ class lnkTriggerAction extends cmdbAbstractObject
"db_table" => "priv_link_action_trigger",
"db_key_field" => "link_id",
"db_finalclass_field" => "",
"display_template" => "",
"is_link" => true,
);
MetaModel::Init_Params($aParams);
@@ -633,7 +623,6 @@ class TriggerOnThresholdReached extends TriggerOnObject
"db_table" => "priv_trigger_threshold",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();

View File

@@ -45,25 +45,85 @@ define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions
*/
abstract class UserRightsAddOnAPI
{
/**
* @param string $sAdminUser
* @param string $sAdminPwd
* @param string $sLanguage
*
* @return mixed
*/
abstract public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US'); // could be used during initial installation
/**
* @return void
*/
abstract public function Init(); // loads data (possible optimizations)
// Used to build select queries showing only objects visible for the given user
/**
* Used to build select queries showing only objects visible for the given user
*
* @param string $sLogin
* @param string $sClass
* @param array $aSettings
*
* @return mixed
*/
abstract public function GetSelectFilter($sLogin, $sClass, $aSettings = array()); // returns a filter object
abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null);
abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null);
abstract public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null);
abstract public function IsAdministrator($oUser);
abstract public function IsPortalUser($oUser);
abstract public function FlushPrivileges();
/**
* @param \User $oUser
* @param string $sClass
* @param int $iActionCode
* @param null $oInstanceSet
*
* @return bool
*/
abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null);
/**
* @param \User $oUser
* @param string $sClass
* @param string $sStimulusCode
* @param \DBObjectSet|null $oInstanceSet
*
* @return bool
*/
abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null);
/**
* @param \User $oUser
* @param string $sClass
* @param string $sAttCode
* @param int $iActionCode
* @param \DBObjectSet|null $oInstanceSet
*
* @return bool
*/
abstract public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null);
/**
* @param \User $oUser
*
* @return bool
*/
abstract public function IsAdministrator($oUser);
/**
* @param \User $oUser
*
* @return bool
*/
abstract public function IsPortalUser($oUser);
/**
* @return void
*/
abstract public function FlushPrivileges();
/**
* Default behavior for addons that do not support profiles
*
* @param $oUser User
* @param \User $oUser
* @return array
*/
public function ListProfiles($oUser)
@@ -72,7 +132,18 @@ abstract class UserRightsAddOnAPI
}
/**
* ...
* ...
*
* @param string$sClass
* @param array $aAllowedOrgs
* @param array $aSettings
* @param string|null $sAttCode
*
* @return \DBObjectSearch
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings = array(), $sAttCode = null)
{
@@ -156,6 +227,9 @@ abstract class UserRightsAddOnAPI
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
abstract class User extends cmdbAbstractObject
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
@@ -168,7 +242,6 @@ abstract class User extends cmdbAbstractObject
"db_table" => "priv_user",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -202,7 +275,11 @@ abstract class User extends cmdbAbstractObject
/*
* Compute a name in best effort mode
*/
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
*/
public function GetFriendlyName()
{
if (!MetaModel::IsValidAttCode(get_class($this), 'contactid'))
@@ -230,6 +307,32 @@ abstract class User extends cmdbAbstractObject
return $this->Get('login');
}
/*
* Compute the initials in best effort mode
*
* @return string
* @throws \ArchivedObjectException
* @throws \CoreException
* @since 3.0.0
*/
public function GetInitials()
{
$sInitials = '';
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
{
$sInitials .= mb_substr($this->Get('first_name'), 0, 1);
$sInitials .= mb_substr($this->Get('last_name'), 0, 1);
}
if (empty($sInitials))
{
$sInitials = mb_substr($this->Get('login'), 0, 1);
}
return $sInitials;
}
protected $oContactObject;
/**
@@ -409,25 +512,23 @@ abstract class User extends cmdbAbstractObject
function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage, 'bizmodel,grant_by_profile');
// debug
if (false)
{
$oPage->SetCurrentTab('More on user rigths (dev only)');
$oPage->add("<h3>User rights</h3>\n");
$this->DoShowGrantSumary($oPage, 'addon/userrights');
$oPage->add("<h3>Change log</h3>\n");
$this->DoShowGrantSumary($oPage, 'core/cmdb');
$oPage->add("<h3>Application</h3>\n");
$this->DoShowGrantSumary($oPage, 'application');
$oPage->add("<h3>GUI</h3>\n");
$this->DoShowGrantSumary($oPage, 'gui');
}
$oPage->SetCurrentTab('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage, 'bizmodel,grant_by_profile');
// debug
if (false)
{
$oPage->SetCurrentTab('More on user rigths (dev only)');
$oPage->add("<h3>User rights</h3>\n");
$this->DoShowGrantSumary($oPage, 'addon/userrights');
$oPage->add("<h3>Change log</h3>\n");
$this->DoShowGrantSumary($oPage, 'core/cmdb');
$oPage->add("<h3>Application</h3>\n");
$this->DoShowGrantSumary($oPage, 'application');
$oPage->add("<h3>GUI</h3>\n");
$this->DoShowGrantSumary($oPage, 'gui');
}
}
@@ -570,25 +671,38 @@ interface iSelfRegister
*/
class UserRights
{
const DEFAULT_USER_CONTACT_ID_ATTCODE = 'contactid';
const DEFAULT_CONTACT_ORG_ID_ATTCODE = 'org_id';
const DEFAULT_CONTACT_ORG_ID_FRIENDLYNAME_ATTCODE = self::DEFAULT_CONTACT_ORG_ID_ATTCODE.'_friendlyname';
const DEFAULT_CONTACT_FIRSTNAME_ATTCODE = 'first_name';
const DEFAULT_CONTACT_PICTURE_ATTCODE = 'picture';
public static $m_aCacheUsers;
/** @var UserRightsAddOnAPI $m_oAddOn */
protected static $m_oAddOn;
protected static $m_oUser;
protected static $m_oRealUser;
protected static $m_sSelfRegisterAddOn = null;
protected static $m_aAdmins = array();
protected static $m_aPortalUsers = array();
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
private static $m_sLastLoginStatus = null;
/**
* @param string $sModuleName
*
* @return void
* @throws \CoreException
*/
public static function SelectModule($sModuleName)
{
if (!class_exists($sModuleName))
{
throw new CoreException("Could not select this module, '$sModuleName' in not a valid class name");
return;
}
if (!is_subclass_of($sModuleName, 'UserRightsAddOnAPI'))
{
throw new CoreException("Could not select this module, the class '$sModuleName' is not derived from UserRightsAddOnAPI");
return;
}
self::$m_oAddOn = new $sModuleName;
self::$m_oAddOn->Init();
@@ -596,6 +710,12 @@ class UserRights
self::$m_oRealUser = null;
}
/**
* @param string $sModuleName
*
* @return void
* @throws \CoreException
*/
public static function SelectSelfRegister($sModuleName)
{
if (!class_exists($sModuleName))
@@ -605,19 +725,33 @@ class UserRights
self::$m_sSelfRegisterAddOn = $sModuleName;
}
/**
* @return \UserRightsAddOnAPI
*/
public static function GetModuleInstance()
{
return self::$m_oAddOn;
}
// Installation: create the very first user
/**
* Installation: create the very first user
*
* @param string $sAdminUser
* @param string $sAdminPwd
* @param string $sLanguage
*
* @return bool
*/
public static function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{
$bRes = self::$m_oAddOn->CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage);
self::FlushPrivileges(true /* reset admin cache */);
return $bRes;
}
/**
* @return bool
*/
public static function IsLoggedIn()
{
if (self::$m_oUser == null)
@@ -630,9 +764,17 @@ class UserRights
}
}
public static function Login($sName, $sAuthentication = 'any')
/**
* @param string $sLogin Login of the concerned user
* @param string $sAuthentication
*
* @return bool
* @throws \DictExceptionUnknownLanguage
* @throws \OQLException
*/
public static function Login($sLogin, $sAuthentication = 'any')
{
$oUser = self::FindUser($sName, $sAuthentication);
$oUser = self::FindUser($sLogin, $sAuthentication);
if (is_null($oUser))
{
return false;
@@ -649,38 +791,55 @@ class UserRights
return true;
}
public static function CheckCredentials($sName, $sPassword, $sLoginMode = 'form', $sAuthentication = 'any')
/**
* @param string $sLogin Login of the user to check the credentials for
* @param string $sPassword
* @param string $sLoginMode
* @param string $sAuthentication
*
* @return bool
* @throws \OQLException
*/
public static function CheckCredentials($sLogin, $sPassword, $sLoginMode = 'form', $sAuthentication = 'any')
{
$oUser = self::FindUser($sName, $sAuthentication);
$oUser = self::FindUser($sLogin, $sAuthentication);
if (is_null($oUser))
{
// Check if the user does not exist at all or if it is just disabled
if (self::FindUser($sName, $sAuthentication, true) == null)
if (self::FindUser($sLogin, $sAuthentication, true) == null)
{
// User does not exist at all
$bCheckCredentialsAndCreateUser = self::CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => $bCheckCredentialsAndCreateUser);
$bCheckCredentialsAndCreateUser = self::CheckCredentialsAndCreateUser($sLogin, $sPassword, $sLoginMode, $sAuthentication);
self::$m_sLastLoginStatus = array('sName' => $sLogin, 'bSuccess' => $bCheckCredentialsAndCreateUser);
return $bCheckCredentialsAndCreateUser;
}
else
{
// User is actually disabled
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => false);
self::$m_sLastLoginStatus = array('sName' => $sLogin, 'bSuccess' => false);
return false;
}
}
if (!$oUser->CheckCredentials($sPassword))
{
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => false);
self::$m_sLastLoginStatus = array('sName' => $sLogin, 'bSuccess' => false);
return false;
}
self::UpdateUser($oUser, $sLoginMode, $sAuthentication);
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => true);
self::$m_sLastLoginStatus = array('sName' => $sLogin, 'bSuccess' => true);
return true;
}
/**
* @param string $sName
* @param string $sPassword
* @param string $sLoginMode
* @param string $sAuthentication
*
* @return mixed
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
@@ -689,6 +848,11 @@ class UserRights
}
}
/**
* @param \User $oUser
* @param string $sLoginMode
* @param string $sAuthentication
*/
public static function UpdateUser($oUser, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
@@ -696,7 +860,10 @@ class UserRights
call_user_func(array(self::$m_sSelfRegisterAddOn, 'UpdateUser'), $oUser, $sLoginMode, $sAuthentication);
}
}
/**
* @return bool
*/
public static function TrustWebServerContext()
{
if (!is_null(self::$m_oUser))
@@ -711,9 +878,11 @@ class UserRights
/**
* Tells whether or not the archive mode is allowed to the current user
* @return boolean
*
* @return bool
* @throws \CoreException
*/
static function CanBrowseArchive()
public static function CanBrowseArchive()
{
if (is_null(self::$m_oUser))
{
@@ -732,6 +901,9 @@ class UserRights
return $bRet;
}
/**
* @return bool
*/
public static function CanChangePassword()
{
if (MetaModel::DBIsReadOnly())
@@ -749,16 +921,24 @@ class UserRights
}
}
public static function ChangePassword($sOldPassword, $sNewPassword, $sName = '')
/**
* @param string $sOldPassword
* @param string $sNewPassword
* @param string $sLogin Login of the concerned user
*
* @return bool
* @throws \OQLException
*/
public static function ChangePassword($sOldPassword, $sNewPassword, $sLogin = '')
{
if (empty($sName))
if (empty($sLogin))
{
$oUser = self::$m_oUser;
}
else
{
// find the id out of the login string
$oUser = self::FindUser($sName);
$oUser = self::FindUser($sLogin);
}
if (is_null($oUser))
{
@@ -772,15 +952,18 @@ class UserRights
}
/**
* @param string $sName Login identifier of the user to impersonate
* @param string $sLogin Login identifier of the user to impersonate
*
* @return bool True if an impersonation occurred
* @throws \DictExceptionUnknownLanguage
* @throws \OQLException
*/
public static function Impersonate($sName)
public static function Impersonate($sLogin)
{
if (!self::CheckLogin()) return false;
$bRet = false;
$oUser = self::FindUser($sName);
$oUser = self::FindUser($sLogin);
if ($oUser)
{
$bRet = true;
@@ -799,13 +982,16 @@ class UserRights
// Do impersonate!
self::$m_oUser = $oUser;
Dict::SetUserLanguage(self::GetUserLanguage());
$_SESSION['impersonate_user'] = $sName;
$_SESSION['impersonate_user'] = $sLogin;
self::_ResetSessionCache();
}
}
return $bRet;
}
/**
* @throws \DictExceptionUnknownLanguage
*/
public static function Deimpersonate()
{
if (!is_null(self::$m_oRealUser))
@@ -817,6 +1003,11 @@ class UserRights
}
}
/**
* Return the current user login or an empty string if nobody connected.
*
* @return string
*/
public static function GetUser()
{
if (is_null(self::$m_oUser))
@@ -829,7 +1020,9 @@ class UserRights
}
}
/** User */
/**
* @return \User|null
*/
public static function GetUserObject()
{
if (is_null(self::$m_oUser))
@@ -841,7 +1034,10 @@ class UserRights
return self::$m_oUser;
}
}
/**
* @return string
*/
public static function GetUserLanguage()
{
if (is_null(self::$m_oUser))
@@ -855,9 +1051,15 @@ class UserRights
}
}
public static function GetUserId($sName = '')
/**
* @param string $sLogin
*
* @return string|null
* @throws \OQLException
*/
public static function GetUserId($sLogin = '')
{
if (empty($sName))
if (empty($sLogin))
{
// return current user id
if (is_null(self::$m_oUser))
@@ -869,7 +1071,7 @@ class UserRights
else
{
// find the id out of the login string
$oUser = self::$m_oAddOn->FindUser($sName);
$oUser = self::FindUser($sLogin);
if (is_null($oUser))
{
return null;
@@ -878,27 +1080,163 @@ class UserRights
}
}
public static function GetContactId($sName = '')
/**
* @param string $sLogin Login of the user from which we return the contact ID
*
* @return string
* @throws \Exception
*/
public static function GetContactId($sLogin = '')
{
if (empty($sName))
if (empty($sLogin))
{
$oUser = self::$m_oUser;
}
else
{
$oUser = FindUser($sName);
$oUser = self::FindUser($sLogin);
}
if (is_null($oUser))
{
return '';
}
if (!MetaModel::IsValidAttCode(get_class($oUser), 'contactid'))
if (!MetaModel::IsValidAttCode(get_class($oUser), static::DEFAULT_USER_CONTACT_ID_ATTCODE))
{
return '';
}
return $oUser->Get('contactid');
return $oUser->Get(static::DEFAULT_USER_CONTACT_ID_ATTCODE);
}
/**
* Return the absolute URL of the contact picture
*
* @param string $sLogin Login of the user from which we return the picture URL
* @param bool $bAllowDefaultPicture Set to false if you want it to return null instead of the default picture URL when the contact has no picture defined. This can be useful when we want to display something else than the default picture (eg. initials)
*
* @return null|string
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \Exception
* @since 3.0.0
*/
public static function GetContactPictureAbsUrl($sLogin = '', $bAllowDefaultPicture = true)
{
// First, the default picture
if($bAllowDefaultPicture === true)
{
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'images/user-pictures/' . 'user-profile-default-256px.png';
}
else
{
$sPictureUrl = null;
}
// 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);
$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))
{
/** @var \ormDocument $oPicture */
$oPicture = $oContact->Get(static::DEFAULT_CONTACT_PICTURE_ATTCODE);
if($oPicture->IsEmpty())
{
if($bAllowDefaultPicture === true)
{
/** @var \AttributeImage $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$sPictureUrl = $oAttDef->Get('default_image');
}
else
{
$sPictureUrl = null;
}
}
else
{
if (ContextTag::Check(ContextTag::TAG_PORTAL)) {
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'pages/exec.php/object/document/display/'.$sContactClass.'/'.$oContact->GetKey().'/'.static::DEFAULT_CONTACT_PICTURE_ATTCODE.'?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);
}
}
}
}
return $sPictureUrl;
}
/**
* Return the organization name of the current user's contact.
* If the user has no contact linked, null is returned.
*
* @return string|null
* @throws \Exception
* @since 3.0.0
*/
public static function GetContactOrganizationFriendlyname()
{
$sOrgFriendlyname = null;
$oContact = static::GetContactObject();
if(!is_null($oContact) && MetaModel::IsValidAttCode(get_class($oContact), static::DEFAULT_CONTACT_ORG_ID_FRIENDLYNAME_ATTCODE))
{
$sOrgFriendlyname = $oContact->Get(static::DEFAULT_CONTACT_ORG_ID_FRIENDLYNAME_ATTCODE);
}
return $sOrgFriendlyname;
}
/**
* Return the first name of the current user's contact.
* If the user has no contact, null is returned.
*
* @return string|null
* @throws \Exception
* @since 3.0.0
*/
public static function GetContactFirstname()
{
$sFirstname = null;
$oContact = static::GetContactObject();
if(!is_null($oContact) && MetaModel::IsValidAttCode(get_class($oContact), static::DEFAULT_CONTACT_FIRSTNAME_ATTCODE))
{
$sFirstname = $oContact->Get(static::DEFAULT_CONTACT_FIRSTNAME_ATTCODE);
}
return $sFirstname;
}
/**
* Return the friendlyname of the current user's contact.
* If the user has no contact, null is returned.
*
* @return string|null
* @throws \Exception
* @since 3.0.0
*/
public static function GetContactFriendlyname()
{
$sFriendlyname = null;
$oContact = static::GetContactObject();
if(!is_null($oContact))
{
$sFriendlyname = $oContact->GetRawName();
}
return $sFriendlyname;
}
/**
* @return \DBObject|null
*/
public static function GetContactObject()
{
if (is_null(self::$m_oUser))
@@ -911,16 +1249,23 @@ class UserRights
}
}
// Render the user name in best effort mode
public static function GetUserFriendlyName($sName = '')
/**
* Render the user name in best effort mode
*
* @param string $sLogin Login of the user we want to retrieve the friendlyname
*
* @return string
* @throws \OQLException
*/
public static function GetUserFriendlyName($sLogin = '')
{
if (empty($sName))
if (empty($sLogin))
{
$oUser = self::$m_oUser;
}
else
{
$oUser = FindUser($sName);
$oUser = self::FindUser($sLogin);
}
if (is_null($oUser))
{
@@ -929,6 +1274,48 @@ class UserRights
return $oUser->GetFriendlyName();
}
/**
* Render the user initials in best effort mode
*
* @param string $sLogin Login of the user from which we want to retrieve the initials
*
* @return string
* @throws \OQLException
* @since 3.0.0
*/
public static function GetUserInitials($sLogin = '')
{
if (empty($sLogin))
{
$oUser = self::$m_oUser;
}
else
{
$oUser = self::FindUser($sLogin);
}
if (is_null($oUser))
{
$sInitials = '';
$aLoginParts = explode(' ', $sLogin);
foreach($aLoginParts as $sLoginPart)
{
// Keep only upper case first letters
// eg. "My first name My last name" => "MM"
// eg. "Carrie Anne Moss" => "CAM"
if(preg_match('/^\p{Lu}/u', $sLoginPart) > 0)
{
$sInitials .= mb_substr($sLoginPart, 0, 1);
}
}
return $sInitials;
}
return $oUser->GetInitials();
}
/**
* @return bool
*/
public static function IsImpersonated()
{
if (is_null(self::$m_oRealUser))
@@ -938,6 +1325,9 @@ class UserRights
return true;
}
/**
* @return string
*/
public static function GetRealUser()
{
if (is_null(self::$m_oRealUser))
@@ -947,11 +1337,17 @@ class UserRights
return self::$m_oRealUser->Get('login');
}
/**
* @return \User|null
*/
public static function GetRealUserObject()
{
return self::$m_oRealUser;
}
/**
* @return string
*/
public static function GetRealUserId()
{
if (is_null(self::$m_oRealUser))
@@ -961,6 +1357,9 @@ class UserRights
return self::$m_oRealUser->GetKey();
}
/**
* @return string
*/
public static function GetRealUserFriendlyName()
{
if (is_null(self::$m_oRealUser))
@@ -970,6 +1369,9 @@ class UserRights
return self::$m_oRealUser->GetFriendlyName();
}
/**
* @return bool
*/
protected static function CheckLogin()
{
if (!self::IsLoggedIn())
@@ -983,7 +1385,7 @@ class UserRights
/**
* Add additional filter for organization silos to all the requests.
*
* @param $sClass
* @param string $sClass
* @param array $aSettings
*
* @return bool|\Expression
@@ -1015,11 +1417,13 @@ class UserRights
/**
* @param string $sClass
* @param int $iActionCode
* @param DBObjectSet $oInstanceSet
* @param User $oUser
* @param \DBObjectSet $oInstanceSet
* @param \User $oUser
*
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO|UR_ALLOWED_DEPENDS)
* @throws \CoreException
*/
public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/$oInstanceSet = null, $oUser = null)
public static function IsActionAllowed($sClass, $iActionCode, $oInstanceSet = null, $oUser = null)
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) return UR_ALLOWED_YES;
@@ -1071,6 +1475,15 @@ class UserRights
}
}
/**
* @param string $sClass
* @param string $sStimulusCode
* @param \DBObjectSet|null $oInstanceSet
* @param \User|null $oUser
*
* @return bool
* @throws \CoreException
*/
public static function IsStimulusAllowed($sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
{
// When initializing, we need to let everything pass trough
@@ -1102,9 +1515,11 @@ class UserRights
* @param string $sClass
* @param string $sAttCode
* @param int $iActionCode
* @param DBObjectSet $oInstanceSet
* @param User $oUser
* @param \DBObjectSet $oInstanceSet
* @param \User $oUser
*
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO)
* @throws \CoreException
*/
public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/$oInstanceSet = null, $oUser = null)
{
@@ -1139,7 +1554,11 @@ class UserRights
}
protected static $m_aAdmins = array();
/**
* @param \User|null $oUser
*
* @return bool
*/
public static function IsAdministrator($oUser = null)
{
if (!self::CheckLogin()) return false;
@@ -1156,7 +1575,11 @@ class UserRights
return self::$m_aAdmins[$iUser];
}
protected static $m_aPortalUsers = array();
/**
* @param \User|null $oUser
*
* @return bool
*/
public static function IsPortalUser($oUser = null)
{
if (!self::CheckLogin()) return false;
@@ -1173,6 +1596,9 @@ class UserRights
return self::$m_aPortalUsers[$iUser];
}
/**
* @return array
*/
public static function GetAllowedPortals()
{
$aAllowedPortals = array();
@@ -1198,6 +1624,50 @@ class UserRights
return $aAllowedPortals;
}
/**
* @see UR_ACTION_READ, UR_ACTION_MODIFY, ...
*
* @param int $iActionCode
* @param array $aCategories
* @param bool $bWithLabels
* @param \User|null $oUser
*
* @return array
* @throws \DictExceptionMissingString
* @throws \CoreException
*/
public static function GetAllowedClasses($iActionCode, $aCategories = array('bizmodel'), $bWithLabels = false, $oUser = null)
{
$aAllowedClasses = [];
foreach(MetaModel::GetClasses(implode(',', $aCategories)) as $sClass)
{
if(static::IsActionAllowed($sClass, $iActionCode, null, $oUser) === UR_ALLOWED_YES)
{
if($bWithLabels)
{
$aAllowedClasses[$sClass] = MetaModel::GetName($sClass);
}
else
{
$aAllowedClasses[] = $sClass;
}
}
}
// Sort by label
if($bWithLabels)
{
asort($aAllowedClasses);
}
return $aAllowedClasses;
}
/**
* @param \User|null $oUser
*
* @return array|mixed
*/
public static function ListProfiles($oUser = null)
{
if (is_null($oUser))
@@ -1261,8 +1731,6 @@ class UserRights
}
}
static $m_aCacheUsers;
/**
* Find a user based on its login and its type of authentication
*
@@ -1320,6 +1788,18 @@ class UserRights
return $oUser;
}
/**
* @param string$sClass
* @param array $aAllowedOrgs
* @param array $aSettings
* @param string|null $sAttCode
*
* @return \DBObjectSearch
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings = array(), $sAttCode = null)
{
return self::$m_oAddOn->MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings, $sAttCode);

View File

@@ -141,7 +141,10 @@ class ValueSetObjects extends ValueSetDefinition
$this->m_oExtraCondition = $oFilter;
$this->m_bIsLoaded = false;
}
public function SetOrderBy(array $aOrderBy)
{
$this->m_aOrderBy = $aOrderBy;
}
public function ToObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null)
{
if ($this->m_bAllowAllData)
@@ -217,7 +220,7 @@ class ValueSetObjects extends ValueSetDefinition
$this->m_sOperation = $sOperation;
$this->m_aValues = array();
if ($this->m_bAllowAllData)
{
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
@@ -243,7 +246,7 @@ class ValueSetObjects extends ValueSetDefinition
$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
$aFields = $oExpression->ListRequiredFields();
$sClass = $oFilter->GetClass();
foreach($aFields as $sField)
/*foreach($aFields as $sField)
{
$aFieldItems = explode('.', $sField);
if ($aFieldItems[0] != $sClass)
@@ -251,7 +254,7 @@ class ValueSetObjects extends ValueSetDefinition
$sOperation = 'contains';
break;
}
}
}*/
switch ($sOperation)
{
@@ -345,6 +348,160 @@ class ValueSetObjects extends ValueSetDefinition
{
$this->m_bSort = $bSort;
}
public function GetValuesForAutocomplete($aArgs, $sContains = '', $sOperation = 'contains')
{
if (!$this->m_bIsLoaded || ($sContains != $this->m_sContains) || ($sOperation != $this->m_sOperation))
{
$this->LoadValuesForAutocomplete($aArgs, $sContains, $sOperation);
$this->m_bIsLoaded = true;
}
// The results are already filtered and sorted (on friendly name)
$aRet = $this->m_aValues;
return $aRet;
}
/**
* @param $aArgs
* @param string $sContains
* @param string $sOperation 'contains' or 'equals_start_with'
*
* @return bool
* @throws \CoreException
* @throws \OQLException
*/
protected function LoadValuesForAutocomplete($aArgs, $sContains = '', $sOperation = 'contains')
{
$this->m_sContains = $sContains;
$this->m_sOperation = $sOperation;
$this->m_aValues = array();
if ($this->m_bAllowAllData)
{
$oFilter = DBObjectSearch::FromOQL_AllData($this->m_sFilterExpr);
}
else
{
$oFilter = DBObjectSearch::FromOQL($this->m_sFilterExpr);
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
}
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)
{
$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
}
}
//$oExpression = DBObjectSearch::GetPolymorphicExpression($oFilter->GetClass(), 'friendlyname');
$sClass = $oFilter->GetClass();
switch ($sOperation)
{
case 'equals':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains);
foreach($aAttributes as $sAttribute)
{
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, '=', $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// Unions are much faster than OR conditions
$oFilter = new DBUnionSearch($aFilters);
break;
case 'start_with':
$aAttributes = MetaModel::GetFriendlyNameAttributeCodeList($sClass);
$sClassAlias = $oFilter->GetClassAlias();
$aFilters = array();
$oValueExpr = new ScalarExpression($sContains.'%');
foreach($aAttributes as $sAttribute)
{
$oNewFilter = $oFilter->DeepClone();
$oNameExpr = new FieldExpression($sAttribute, $sClassAlias);
$oCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oNewFilter->AddConditionExpression($oCondition);
$aFilters[] = $oNewFilter;
}
// Unions are much faster than OR conditions
$oFilter = new DBUnionSearch($aFilters);
break;
default:
$oValueExpr = new ScalarExpression('%'.$sContains.'%');
$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());
$oNewCondition = new BinaryExpression($oNameExpr, 'LIKE', $oValueExpr);
$oFilter->AddConditionExpression($oNewCondition);
break;
}
$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs, null, $this->m_iLimit, 0, $this->m_bSort);
if (empty($this->m_sValueAttCode))
{
$aAttToLoad = array($oFilter->GetClassAlias() => array('friendlyname'));
}
else
{
$aAttToLoad = array($oFilter->GetClassAlias() => array($this->m_sValueAttCode));
}
$aComplementAttributeSpec = MetaModel::GetComplementAttributeSpec($sClass);
$sFormatAdditionalField = $aComplementAttributeSpec[0];
$aAdditionalField = $aComplementAttributeSpec[1];
if (count($aAdditionalField)>0)
{
$aAttToLoad = array_merge ($aAttToLoad, [$oFilter->GetClassAlias() => $aAdditionalField]);
}
$oObjects->OptimizeColumnLoad($aAttToLoad);
while ($oObject = $oObjects->Fetch())
{
$aData=[];
if (empty($this->m_sValueAttCode))
{
$aData['label'] = $oObject->GetName();
}
else
{
$aData['label'] = $oObject->Get($this->m_sValueAttCode);
}
if($oObject->IsObsolete())
{
$aData['obsolescence_flag']='1';
}
else
{
$aData['obsolescence_flag']='0';
}
if (count($aAdditionalField)>0)
{
$aArguments = [];
foreach ($aAdditionalField as $sAdditionalField)
{
array_push ($aArguments,$oObject->Get($sAdditionalField));
}
$aData['additional_field'] = vsprintf($sFormatAdditionalField, $aArguments);
}
else
{
$aData['additional_field']='';
}
$this->m_aValues[$oObject->GetKey()] = $aData;
}
return true;
}
}

View File

@@ -144,7 +144,7 @@ class XMLBulkExport extends BulkExport
while ($aObjects = $oSet->FetchAssoc())
{
set_time_limit($iLoopTimeLimit);
set_time_limit(intval($iLoopTimeLimit));
if (count($aAuthorizedClasses) > 1)
{
$sData .= "<Row>\n";
@@ -182,7 +182,7 @@ class XMLBulkExport extends BulkExport
$iCount++;
}
set_time_limit($iPreviousTimeLimit);
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;
if ($this->aStatusInfo['total'] == 0)
{

5
css/README.md Normal file
View File

@@ -0,0 +1,5 @@
In order to better separate our developments from the embedded third-party libs, files should be moved in either the /backoffice, /lib or something new folders.
IMPORTANT: Before moving a file, we should first consider if:
- The file is used in an iTop extension that should be compatible with the iTop version currently under developments
- The file is still used in iTop itself (or if we can consider removing it for clean up)

71
css/backoffice/README.md Normal file
View File

@@ -0,0 +1,71 @@
## Description
This is a brief description of the SASS 7-1 system and how to use it.
- [File structure](#file-structure)
- [Usage](#usage)
## File structure
SCSS files are structured following the [7-1 pattern](https://sass-guidelin.es/#the-7-1-pattern). \
@rveitch made a great summary with the following, which can also be found [here](https://gist.github.com/rveitch/84cea9650092119527bc).
```
css/backoffice/
|
| utils/
| | _variables.scss # Sass Variables
| | _functions.scss # Sass Functions
| | _mixins.scss # Sass Mixins
| | _helpers.scss # Class & placeholders helpers
|
| vendors/
| | _bootstrap.scss # Bootstrap
| | _jquery-ui.scss # jQuery UI
| ... # Etc…
|
| base/
| | _reset.scss # Reset/normalize
| | _typography.scss # Typography rules
| ... # Etc…
|
| components/
| | _buttons.scss # Buttons
| | _carousel.scss # Carousel
| | _cover.scss # Cover
| | _dropdown.scss # Dropdown
| ... # Etc…
|
| layout/
| | _navigation.scss # Navigation
| | _grid.scss # Grid system
| | _header.scss # Header
| | _footer.scss # Footer
| | _sidebar.scss # Sidebar
| | _forms.scss # Forms
| ... # Etc…
|
| pages/
| | _home.scss # Home specific styles
| | _contact.scss # Contact specific styles
| ... # Etc…
|
| themes/
| | _theme.scss # Default theme
| | _admin.scss # Admin theme
| ... # Etc…
|
|
` main.scss # Main Sass file
```
## Usage
To avoid common errors, files should be imported in the final file in the following order. Again those are just following the SASS guidelines:
- Utils
- Variables
- Functions
- Mixins
- Helpers
- Vendors
- Base
- Components
- Layout
- Pages
- Themes

View File

@@ -0,0 +1,20 @@
/*!
* Copyright (C) 2013-2020 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
*/
@import "base";
@import "typography";

View File

@@ -0,0 +1,67 @@
/*!
* Copyright (C) 2013-2020 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
*/
$ibo-hyperlink-color: $ibo-color-primary-500 !default;
$ibo-hyperlink-color--on-hover: $ibo-color-primary-600 !default;
$ibo-hyperlink-color--on-active: $ibo-color-primary-700 !default;
$ibo-svg-illustration--fill: $ibo-color-primary-500 !default;
$ibo-content-block--background-color: $ibo-color-white-100 !default;
$ibo-content-block--border: 1px solid $ibo-color-grey-400 !default;
/* CSS variables */
:root{
--ibo-hyperlink-color: #{$ibo-hyperlink-color};
--ibo-hyperlink-color--on-hover: #{$ibo-hyperlink-color--on-hover};
--ibo-hyperlink-color--on-active: #{$ibo-hyperlink-color--on-active};
}
/* Box sizing reset */
*,
*::before,
*::after{
box-sizing: border-box;
}
/* Base font size (used by all typographies) */
html{
font-size: 12px;
}
/* Hyperlinks reset, ensure that they are of the right color and without decoration everywhere (of course this can be overloaded in some components) */
a{
color: var(--ibo-hyperlink-color);
text-decoration: none;
&:hover,
&:active,
&:visited{
text-decoration: none;
}
&:hover{
color: var(--ibo-hyperlink-color--on-hover);
}
&:active{
color: var(--ibo-hyperlink-color--on-active);
}
}
.ibo-svg-illustration--container>svg *[fill="#6c63ff"]{
fill: $ibo-svg-illustration--fill;
}

View File

@@ -0,0 +1,163 @@
/*!
* Copyright (C) 2013-2020 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
*/
/* This is an overload of the default lib. stylesheet to use local fonts instead of the CDN */
@font-face {
font-family: Monorale;
font-weight: 100;
font-style: normal;
src: local('Raleway-v4020 Thin'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Thin.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Thin.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 100;
font-style: italic;
src: local('Raleway-v4020 Thin'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-ThinItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-ThinItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 200;
font-style: normal;
src: local('Raleway-v4020 ExtraLight'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-ExtraLight.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-ExtraLight.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 200;
font-style: italic;
src: local('Raleway-v4020 ExtraLight'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-ExtraLightItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-ExtraLightItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 300;
font-style: normal;
src: local('Raleway-v4020 Light'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Light.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Light.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 300;
font-style: italic;
src: local('Raleway-v4020 Light'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-LightItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-LightItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 400;
font-style: normal;
src: local('Raleway-v4020'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Regular.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Regular.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 400;
font-style: italic;
src: local('Raleway-v4020'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Italic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Italic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 500;
font-style: normal;
src: local('Raleway-v4020 Medium'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Medium.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Medium.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 500;
font-style: italic;
src: local('Raleway-v4020 Medium'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-MediumItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-MediumItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 600;
font-style: normal;
src: local('Raleway-v4020 SemiBold'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-SemiBold.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-SemiBold.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 600;
font-style: italic;
src: local('Raleway-v4020 SemiBold'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-SemiBoldItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-SemiBoldItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 700;
font-style: normal;
src: local('Raleway-v4020'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Bold.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Bold.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 700;
font-style: italic;
src: local('Raleway-v4020'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-BoldItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-BoldItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 800;
font-style: normal;
src: local('Raleway-v4020 ExtraBold'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-ExtraBold.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-ExtraBold.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 800;
font-style: italic;
src: local('Raleway-v4020 ExtraBold'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-ExtraBoldItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-ExtraBoldItalic.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 900;
font-style: normal;
src: local('Raleway-v4020 Black'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-Black.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-Black.woff') format('woff');
}
@font-face {
font-family: Monorale;
font-weight: 900;
font-style: italic;
src: local('Raleway-v4020 Black'),
url('../../lib/samvk/monorale-raleway-sober/otf/Monorale-BlackItalic.otf') format('otf'),
url('../../lib/samvk/monorale-raleway-sober/woff/Monorale-BlackItalic.woff') format('woff');
}

View File

@@ -0,0 +1,98 @@
/*!
* Copyright (C) 2013-2020 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
*/
/* SCSS variables */
$ibo-alert--spacing-top--with-same-block: 6px !default;
$ibo-alert--spacing-top--with-other-blocks: 16px !default;
$ibo-alert--padding-y: 18px !default;
$ibo-alert--padding-x: 20px !default;
$ibo-alert--border-radius: $ibo-border-radius-300 !default;
$ibo-alert--title--padding-bottom: 4px !default;
$ibo-alert--title--highlight--width: 4px !default;
$ibo-alert--title--highlight--height: 100% !default;
$ibo-alert-colors: (
'primary': ($ibo-color-primary-200, $ibo-color-primary-700, $ibo-color-primary-900),
'secondary': ($ibo-color-secondary-200, $ibo-color-secondary-700, $ibo-color-secondary-900),
'neutral': ($ibo-color-secondary-200, $ibo-color-secondary-700, $ibo-color-secondary-900),
'information': ($ibo-color-information-200, $ibo-color-information-700, $ibo-color-information-900),
'success': ($ibo-color-success-100, $ibo-color-success-700, $ibo-color-success-900),
'failure': ($ibo-color-danger-200, $ibo-color-danger-700, $ibo-color-danger-900),
'warning': ($ibo-color-warning-200, $ibo-color-warning-700, $ibo-color-warning-900),
'danger': ($ibo-color-danger-200, $ibo-color-danger-700, $ibo-color-danger-900),
'grey' : ($ibo-color-grey-200, $ibo-color-grey-700, $ibo-color-grey-900),
'blue-grey': ($ibo-color-blue-grey-200, $ibo-color-blue-grey-700, $ibo-color-blue-grey-900),
'blue': ($ibo-color-blue-200, $ibo-color-blue-700, $ibo-color-blue-900),
'cyan': ($ibo-color-cyan-100, $ibo-color-blue-700, $ibo-color-cyan-900),
'green': ($ibo-color-green-100, $ibo-color-green-700, $ibo-color-green-900),
'orange' : ($ibo-color-orange-200, $ibo-color-orange-700, $ibo-color-orange-900),
'red': ($ibo-color-red-200, $ibo-color-red-700, $ibo-color-red-900),
'pink': ($ibo-color-pink-200, $ibo-color-pink-700, $ibo-color-pink-900),
) !default;
@each $sColor, $aColorValues in $ibo-alert-colors {
$bg-color: nth($aColorValues, 1);
$highlight-color: nth($aColorValues, 2);
$text-color: nth($aColorValues, 3);
.ibo-alert.ibo-is-#{$sColor} {
background-color: $bg-color;
color: $text-color;
&::before {
background-color: $highlight-color;
}
}
}
/* Rules */
.ibo-alert {
position: relative;
padding: $ibo-alert--padding-y $ibo-alert--padding-x;
border-radius: $ibo-alert--border-radius;
overflow: hidden; /* To force highlight color to be cropped by the border radius */
@extend %ibo-font-ral-med-150;
&::before {
display: block;
position: absolute;
top: 0;
left: 0;
content: '';
width: $ibo-alert--title--highlight--width;
height: $ibo-alert--title--highlight--height;
}
.ibo-alert--title {
padding-bottom: $ibo-alert--title--padding-bottom;
@extend %ibo-font-ral-bol-150;
}
}
/* Spacing between alert blocks */
.ibo-alert + .ibo-alert {
margin-top: $ibo-alert--spacing-top--with-same-block;
}
/* Spacing between an alert block and something else */
.ibo-alert + :not(.ibo-alert) {
margin-top: $ibo-alert--spacing-top--with-other-blocks;
}

View File

@@ -0,0 +1,29 @@
/*!
* copyright Copyright (C) 2010-2020 Combodo SARL
* license http://opensource.org/licenses/AGPL-3.0
*/
@import "alert";
@import "badge";
@import "button";
@import "breadcrumbs";
@import "quick-create";
@import "global-search";
@import "popover-menu/popover-menu";
@import "popover-menu/popover-menu-item";
@import "newsroom-menu";
@import "panel";
@import "modal";
@import "dashlet/all";
@import "input/all";
@import "title";
@import "datatable";
@import "form";
@import "fieldset";
@import "field";
@import "toolbar";
@import "richtext";
@import "formtable";
@import "formtablerow";
@import "search-form";

View File

@@ -0,0 +1,99 @@
/*!
* Copyright (C) 2013-2020 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
*/
/* SCSS variables */
$ibo-badge--margin: 4px 8px !default;
$ibo-badge--padding: 6px 10px !default;
$ibo-badge--border-radius: $ibo-border-radius-300 !default;
$ibo-badge-new-state-primary-color: $ibo-color-blue-100 !default;
$ibo-badge-new-state-secondary-color: $ibo-color-blue-900 !default;
$ibo-badge-neutral-state-primary-color: $ibo-color-blue-100 !default;
$ibo-badge-neutral-state-secondary-color: $ibo-color-blue-900 !default;
$ibo-badge-waiting-state-primary-color: $ibo-color-orange-100 !default;
$ibo-badge-waiting-state-secondary-color: $ibo-color-orange-800 !default;
$ibo-badge-success-state-primary-color: $ibo-color-green-100 !default;
$ibo-badge-success-state-secondary-color: $ibo-color-green-900 !default;
$ibo-badge-failure-state-primary-color: $ibo-color-red-100 !default;
$ibo-badge-failure-state-secondary-color: $ibo-color-red-800 !default;
$ibo-badge-frozen-state-primary-color: $ibo-color-grey-100 !default;
$ibo-badge-frozen-state-secondary-color: $ibo-color-grey-700 !default;
/* - For basic badge */
$ibo-badge-active-state-primary-color: $ibo-color-green-100 !default;
$ibo-badge-active-state-secondary-color: $ibo-color-green-900 !default;
$ibo-badge-inactive-state-primary-color: $ibo-color-orange-100 !default;
$ibo-badge-inactive-state-secondary-color: $ibo-color-orange-800 !default;
$ibo-badge-states-colors: (
'new': (
'primary-color': $ibo-badge-new-state-primary-color,
'secondary-color': $ibo-badge-new-state-secondary-color,
),
'neutral': (
'primary-color': $ibo-badge-neutral-state-primary-color,
'secondary-color': $ibo-badge-neutral-state-secondary-color,
),
'waiting': (
'primary-color': $ibo-badge-waiting-state-primary-color,
'secondary-color': $ibo-badge-waiting-state-secondary-color,
),
'success': (
'primary-color': $ibo-badge-success-state-primary-color,
'secondary-color': $ibo-badge-success-state-secondary-color,
),
'failure': (
'primary-color': $ibo-badge-failure-state-primary-color,
'secondary-color': $ibo-badge-failure-state-secondary-color,
),
'frozen': (
'primary-color': $ibo-badge-frozen-state-primary-color,
'secondary-color': $ibo-badge-frozen-state-secondary-color,
),
'active': (
'primary-color': $ibo-badge-active-state-primary-color,
'secondary-color': $ibo-badge-active-state-secondary-color,
),
'inactive': (
'primary-color': $ibo-badge-inactive-state-primary-color,
'secondary-color': $ibo-badge-inactive-state-secondary-color,
),
);
@each $sType, $aColors in $ibo-badge-states-colors {
.ibo-badge-is-#{$sType} {
color: map-get($aColors, 'secondary-color');
background-color: map-get($aColors, 'primary-color');
}
}
/* Rules */
.ibo-badge {
display: flex;
flex-direction: row;
align-items: center;
margin: $ibo-badge--margin;
padding: $ibo-badge--padding;
border-radius: $ibo-badge--border-radius;
}

View File

@@ -0,0 +1,80 @@
/*!
* Copyright (C) 2013-2020 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
*/
$ibo-breadcrumbs--item--text-color: $ibo-color-grey-800 !default;
$ibo-breadcrumbs--item-icon--margin-x: 8px !default;
$ibo-breadcrumbs--item-icon--max-width: 16px !default;
$ibo-breadcrumbs--item-icon--text-color: $ibo-color-grey-600 !default;
$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{
@extend %ibo-full-height-content;
* {
display: flex;
align-items: center;
}
}
.ibo-breadcrumbs--item{
color: $ibo-breadcrumbs--item--text-color;
@extend %ibo-font-ral-nor-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{
> *{
opacity: 1;
filter: none;
}
}
}
}
}
.ibo-breadcrumbs--item-icon{
margin-right: $ibo-breadcrumbs--item-icon--margin-x;
@extend %ibo-font-ral-nor-150;
transition: all 0.1s linear;
> span{
color: $ibo-breadcrumbs--item-icon--text-color;
opacity: 0.6;
}
> img{
height: auto;
max-width: $ibo-breadcrumbs--item-icon--max-width;
opacity: 0.3;
filter: grayscale(100%);
}
}
.ibo-breadcrumbs--item-label{
display: inline; /* Otherwise the "truncate" doesn't work with th default display: flex */
max-width: $ibo-breadcrumbs--item-label--max-width;
@extend %ibo-text-truncated-with-ellipsis;
}

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